3

I have a function organized like so:

create function everything(waypoints waypoint)
  returns table(node int, xy text array) as $$
BEGIN
    create view results as ...

    return query (select * from results);
END;
$$ LANGUAGE plpgsql;

And I have a table that has arguments organized the way the waypoint data type is structured. This table is not explicitly of type waypoint itself.

The function gets created as it should, however, I am unable to call it by passing in my table like so:

select everything(waypoints);

Or select everything(select * from temp);

But it says syntax error at or near select for the latter and column waypoints does not exist for the former.

How do I proceed?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Louis93
  • 3,843
  • 8
  • 48
  • 94
  • Why not make `everything` accept `temp` right away? `CREATE FUNCTION everything (waypoints temp) ...` and so on. – Quassnoi Jun 30 '15 at 21:11
  • Please *always* provide your version of Postgres. Your question is a bit unclear. Do I understand correctly that you want to cast whole table rows to a pre-defined composite row type? – Erwin Brandstetter Jul 01 '15 at 17:46

2 Answers2

6

Everything tested in Postgres 9.4.

Postgres has some weak spots in the syntax for handling ROW types. You cannot cast from a table (alias) directly:

SELECT w::waypoint FROM waypoints w;
ERROR:  cannot cast type waypoints to waypoint

The solution is only one step away: decompose the row in a subquery, then the cast works. This way, column values are decomposed and wrapped into the new type directly, without casting to text and back. No need to list all columns individually and you don't need to create a custom cast, either:

SELECT (w.*)::waypoint FROM (SELECT * FROM waypoints) w;

Or shorter:

SELECT w.*::waypoint FROM (TABLE waypoints) w;

Or shorter, yet:

SELECT w::waypoint FROM (TABLE waypoints) w;

SQL Fiddle

That's shorter and faster, in a quick test with 30k rows and simple types 10x faster than casting to text and back. If you have (big) jsonb columns or any complex type (expensive conversion to/from text), the difference will be much bigger, yet.

More importantly, you don't need another custom composite (ROW) type. Every table already has its row defined as type automatically. Just use the existing type waypoints instead of waypoint (if at all possible). Then all you need is:

SELECT w FROM waypoints w;

Or, for your example:

SELECT everything(t) FROM temp t;  -- using type waypoints
SELECT everything(t::waypoint) FROM (TABLE temp) t;  -- using type waypoint

Asides:

  • A table does not have "arguments" but columns.
  • You are not passing a table parameter to this function, but rather a row value. That's how you pass a table by name:

    You can't "pass a whole table" as parameter directly in Postgres, there are not table variables. You would use a cursor or a temp table for that.

Function

Your function has an invalid type declaration and is needlessly complex. I seriously doubt you want to create a view:

CREATE FUNCTION everything(_wp waypoint)  -- or use type waypoints
  RETURNS TABLE(node int, xy text[]) AS
$func$
BEGIN
   RETURN QUERY
   SELECT ...
END
$func$ LANGUAGE plpgsql;

text array is not valid syntax, using text[] instead to declare an array of text.

Rather not use the table / type name waypoints as function parameter name, that opens you up to confusing errors.

Or just use a simple SQL function if your case is as simple as demonstrated:

CREATE FUNCTION everything(_wp waypoint)  -- or use type waypoints
  RETURNS TABLE(node int, xy text[]) AS
$func$
   SELECT ...
$func$ LANGUAGE sql;

Don't quote the language name. It's an identifier.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • Hey this was an excellent write-up, much appreciated. However when I run this: `select everything(t.*::waypoint) from (table temp) t; I get: `relation "_wp" does not exist`... Why is this occuring? – Louis93 Jul 02 '15 at 14:58
  • @Louis93: `_wp` is the function parameter name in my example. Looks like dynamic SQL gone wrong. Since the function body is your secret, I can only guess. I suggest you post a *new question* with the necessary details. – Erwin Brandstetter Jul 02 '15 at 15:02
  • I've awarded you the best answer because it is the most informative. Here is a new question with the necessary details: http://stackoverflow.com/questions/31188822/unable-to-drop-temp-table-is-postgres-function-because-it-is-being-used-by-act Looking forward to your input. – Louis93 Jul 02 '15 at 15:33
1

If all types in waypoint and temp are convertible to text, you can serialize to and deserialize from text:

SELECT  everything(temp::text::waypoint)
FROM    temp

However, an explicit construction would be cleaner:

SELECT  everything((col1, col2, col3, ...)::waypoint)
FROM    temp

or creating a CAST:

CREATE FUNCTION temp_to_waypoint (temp)
RETURNS waypoint
AS
$$
        SELECT  (col1, col2, col3, ...)::waypoint
$$
LANGUAGE 'sql';

CREATE CAST (temp AS waypoint) WITH FUNCTION temp_to_waypoint (test) AS IMPLICIT;

SELECT  everything(temp)
FROM    temp;
Quassnoi
  • 413,100
  • 91
  • 616
  • 614
  • You mean temp::text::waypoint correct? Since waypoint is the datatype? – Louis93 Jun 30 '15 at 21:04
  • Another question, unrelated but I keep seeing this: cannot DROP TABLE "graph" because it is being used by active queries in this session While this table is used to generate the results, it is created right after I attempt to drop it. How can I workaround this? – Louis93 Jun 30 '15 at 21:22
  • @Louis93: please post it as a separate question. – Quassnoi Jun 30 '15 at 21:32