Hi 2016-02-24 22:18 GMT+01:00 Peter Eisentraut <pete...@gmx.net>:
> On 1/18/16 4:21 PM, Robert Haas wrote: > > One idea that occurs to me is: If you can DECLARE BAR FOO%TYPE, but > > then you want to make BAR an array of that type rather than a scalar, > > why not write that as DECLARE BAR FOO%TYPE[]? That seems quite > > natural to me. > > Right, and it's arguably dubious that that doesn't already work. > Unfortunately, these % things are just random plpgsql parser hacks, not > real types. Maybe this should be done in the main PostgreSQL parser > with parameter hooks, if we wanted this feature to be available outside > plpgsql as well. > > > I think the part of this patch that makes %TYPE work for more kinds of > > types is probably a good idea, although I haven't carefully studied > > exactly what it does. > > I agree that this should be more general. For instance, this patch > would allow you to get the element type of an array-typed variable, but > there is no way to get the element type of just another type. If we > could do something like > > DECLARE > var ELEMENT OF point; > > (not necessary that syntax) > > then > > DECLARE > var ELEMENT OF othervar%TYPE; > > should just fall into place. > > I am sending update of this patch. The basic concept is same, syntax was changed per your and Robert requirement. Regards Pavel
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml new file mode 100644 index 9786242..5587839 *** a/doc/src/sgml/plpgsql.sgml --- b/doc/src/sgml/plpgsql.sgml *************** url varchar; *** 322,334 **** myrow tablename%ROWTYPE; myfield tablename.columnname%TYPE; arow RECORD; </programlisting> </para> <para> The general syntax of a variable declaration is: <synopsis> ! <replaceable>name</replaceable> <optional> CONSTANT </optional> <replaceable>type</replaceable> <optional> COLLATE <replaceable>collation_name</replaceable> </optional> <optional> NOT NULL </optional> <optional> { DEFAULT | := | = } <replaceable>expression</replaceable> </optional>; </synopsis> The <literal>DEFAULT</> clause, if given, specifies the initial value assigned to the variable when the block is entered. If the <literal>DEFAULT</> clause --- 322,336 ---- myrow tablename%ROWTYPE; myfield tablename.columnname%TYPE; arow RECORD; + myarray tablename.columnname%TYPE[]; + myelement ELEMENT OF arrayparam%TYPE; </programlisting> </para> <para> The general syntax of a variable declaration is: <synopsis> ! <replaceable>name</replaceable> <optional> CONSTANT </optional> <optional> ELEMENT OF </optional> <replaceable>type</replaceable> <optional> COLLATE <replaceable>collation_name</replaceable> </optional> <optional> NOT NULL </optional> <optional> { DEFAULT | := | = } <replaceable>expression</replaceable> </optional>; </synopsis> The <literal>DEFAULT</> clause, if given, specifies the initial value assigned to the variable when the block is entered. If the <literal>DEFAULT</> clause *************** arow RECORD; *** 337,342 **** --- 339,347 ---- The <literal>CONSTANT</> option prevents the variable from being assigned to after initialization, so that its value will remain constant for the duration of the block. + The <literal>ELEMENT OF</> ensure using the element type of a given array type. + This construct is valuable in polymorphic functions, since the data types needed + for internal variables can change from one call to the next call. The <literal>COLLATE</> option specifies a collation to use for the variable (see <xref linkend="plpgsql-declaration-collation">). If <literal>NOT NULL</> *************** user_id users.user_id%TYPE; *** 611,616 **** --- 616,666 ---- change from one call to the next. Appropriate variables can be created by applying <literal>%TYPE</literal> to the function's arguments or result placeholders. + <programlisting> + CREATE OR REPLACE FUNCTION array_init(v anyelement, size integer) + RETURNS anyarray AS $$ + DECLARE + result v%TYPE[] DEFAULT '{}'; + BEGIN + -- prefer builtin function array_fill + FOR i IN 1 .. size + LOOP + result := result || v; + END LOOP; + RETURN result; + END; + $$ LANGUAGE plpgsql; + + SELECT array_init(0::numeric, 10); + SELECT array_init(''::varchar, 10); + + + CREATE OR REPLACE FUNCTION bubble_sort(a anyarray) + RETURNS anyarray AS $$ + DECLARE + aux ELEMENT OF a%TYPE; + repeat_again boolean DEFAULT true; + BEGIN + -- Don't use this code for large arrays! + -- use builtin sort + WHILE repeat_again + LOOP + repeat_again := false; + FOR i IN array_lower(a, 1) .. array_upper(a, 1) + LOOP + IF a[i] > a[i+1] THEN + aux := a[i+1]; + a[i+1] := a[i]; a[i] := aux; + repeat_again := true; + END IF; + END LOOP; + END LOOP; + RETURN a; + END; + $$ LANGUAGE plpgsql; + + SELECT bubble_sort(ARRAY[3,2,4,6,1]); + </programlisting> </para> </sect2> diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c new file mode 100644 index 2aeab96..b77117e *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** plpgsql_parse_wordtype(char *ident) *** 1646,1653 **** case PLPGSQL_NSTYPE_VAR: return ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; ! /* XXX perhaps allow REC/ROW here? */ default: return NULL; } --- 1646,1660 ---- case PLPGSQL_NSTYPE_VAR: return ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; ! case PLPGSQL_NSTYPE_ROW: ! { ! return ((PLpgSQL_row *) (plpgsql_Datums[nse->itemno]))->datatype; ! } + /* + * XXX perhaps allow REC here? Currently PLpgSQL doesn't allow + * REC parameters, so REC support is not required. + */ default: return NULL; } *************** plpgsql_parse_cwordtype(List *idents) *** 1718,1727 **** NULL, NULL); ! if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR) { ! dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; ! goto done; } /* --- 1725,1742 ---- NULL, NULL); ! if (nse != NULL) { ! if (nse->itemtype == PLPGSQL_NSTYPE_VAR) ! { ! dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; ! goto done; ! } ! else if (nse->itemtype == PLPGSQL_NSTYPE_ROW) ! { ! dtype = ((PLpgSQL_row *) (plpgsql_Datums[nse->itemno]))->datatype; ! goto done; ! } } /* *************** plpgsql_parse_cwordrowtype(List *idents) *** 1852,1857 **** --- 1867,1925 ---- } /* + * This routine is used for generating element or array type from base type. + * The options to_element_type and to_array_type can be used together, when + * we would to ensure valid result. The array array type is original type, so + * this direction is safe. The element of scalar type is not allowed, but if + * we do "to array" transformation first, then this direction should be safe + * too. This design is tolerant, because we should to support a design of + * polymorphic parameters, where a array value can be passed as anyelement + * or anyarray parameter. + */ + PLpgSQL_type * + plpgsql_derive_type(PLpgSQL_type *base_type, + bool to_element_type, bool to_array_type) + { + Oid typid = base_type->typoid; + + if (to_array_type) + { + /* do nothing if base_type is a array already */ + if (!OidIsValid(get_element_type(typid))) + { + Oid array_typid = get_array_type(typid); + + if (!OidIsValid(array_typid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not find array type for data type %s", + format_type_be(typid)))); + typid = array_typid; + } + } + + if (to_element_type) + { + Oid element_typid = get_element_type(typid); + + if (!OidIsValid(element_typid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("referenced variable should be an array, not type %s", + format_type_be(typid)))); + typid = element_typid; + } + + /* when type is changed, construct new datatype */ + if (typid != base_type->typoid) + return plpgsql_build_datatype(typid, -1, + plpgsql_curr_compile->fn_input_collation); + + /* return original base_type, when any change is not required */ + return base_type; + } + + /* * plpgsql_build_variable - build a datum-array entry of a given * datatype * *************** plpgsql_build_variable(const char *refna *** 1903,1908 **** --- 1971,1977 ---- row->dtype = PLPGSQL_DTYPE_ROW; row->refname = pstrdup(refname); row->lineno = lineno; + row->datatype = dtype; plpgsql_adddatum((PLpgSQL_datum *) row); if (add2namespace) diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y new file mode 100644 index df09575..5a05fed *** a/src/pl/plpgsql/src/pl_gram.y --- b/src/pl/plpgsql/src/pl_gram.y *************** static void check_raise_parameters(PLp *** 270,275 **** --- 270,276 ---- %token <keyword> K_DETAIL %token <keyword> K_DIAGNOSTICS %token <keyword> K_DUMP + %token <keyword> K_ELEMENT %token <keyword> K_ELSE %token <keyword> K_ELSIF %token <keyword> K_END *************** static void check_raise_parameters(PLp *** 303,308 **** --- 304,310 ---- %token <keyword> K_NOT %token <keyword> K_NOTICE %token <keyword> K_NULL + %token <keyword> K_OF %token <keyword> K_OPEN %token <keyword> K_OPTION %token <keyword> K_OR *************** unreserved_keyword : *** 2408,2413 **** --- 2410,2416 ---- | K_DETAIL | K_DIAGNOSTICS | K_DUMP + | K_ELEMENT | K_ELSIF | K_ERRCODE | K_ERROR *************** unreserved_keyword : *** 2429,2434 **** --- 2432,2438 ---- | K_NEXT | K_NO | K_NOTICE + | K_OF | K_OPEN | K_OPTION | K_PERFORM *************** read_sql_construct(int until, *** 2684,2697 **** return expr; } static PLpgSQL_type * read_datatype(int tok) { StringInfoData ds; char *type_name; int startlocation; ! PLpgSQL_type *result; int parenlevel = 0; /* Should only be called while parsing DECLARE sections */ Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE); --- 2688,2745 ---- return expr; } + /* + * Returns true when following two tokens after %TYPE/%ROWTYPE are [] + */ + static bool + array_type_is_required(void) + { + int tok = yylex(); + + if (tok == '[') + { + tok = yylex(); + if (tok != ']') + yyerror("syntax error, expected \"]\""); + + return true; + } + else + plpgsql_push_back_token(tok); + + return false; + } + + /* + * Returns true when type is introducted by ELEMENT OF tokens + */ + static bool + element_type_is_required(int tok) + { + if (tok_is_keyword(tok, &yylval, + K_ELEMENT, "element")) + { + tok = yylex(); + if (!tok_is_keyword(tok, &yylval, K_OF, "of")) + yyerror("syntax error, expected \"OF\""); + + tok = yylex(); + return true; + } + + return false; + } + static PLpgSQL_type * read_datatype(int tok) { StringInfoData ds; char *type_name; int startlocation; ! PLpgSQL_type *result = NULL; int parenlevel = 0; + bool to_element_type = false; + bool to_array_type = false; /* Should only be called while parsing DECLARE sections */ Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE); *************** read_datatype(int tok) *** 2700,2705 **** --- 2748,2758 ---- if (tok == YYEMPTY) tok = yylex(); + /* + * The request of element type can be first. + */ + to_element_type = element_type_is_required(tok); + startlocation = yylloc; /* *************** read_datatype(int tok) *** 2718,2739 **** K_TYPE, "type")) { result = plpgsql_parse_wordtype(dtname); - if (result) - return result; } else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) { result = plpgsql_parse_wordrowtype(dtname); ! if (result) ! return result; } } } else if (plpgsql_token_is_unreserved_keyword(tok)) { char *dtname = pstrdup(yylval.keyword); - tok = yylex(); if (tok == '%') { --- 2771,2794 ---- K_TYPE, "type")) { result = plpgsql_parse_wordtype(dtname); } else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) { result = plpgsql_parse_wordrowtype(dtname); ! } ! ! if (result != NULL) ! { ! to_array_type = array_type_is_required(); ! return plpgsql_derive_type(result, ! to_element_type, to_array_type); } } } else if (plpgsql_token_is_unreserved_keyword(tok)) { char *dtname = pstrdup(yylval.keyword); tok = yylex(); if (tok == '%') { *************** read_datatype(int tok) *** 2742,2763 **** K_TYPE, "type")) { result = plpgsql_parse_wordtype(dtname); - if (result) - return result; } else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) { result = plpgsql_parse_wordrowtype(dtname); ! if (result) ! return result; } } } else if (tok == T_CWORD) { List *dtnames = yylval.cword.idents; - tok = yylex(); if (tok == '%') { --- 2797,2820 ---- K_TYPE, "type")) { result = plpgsql_parse_wordtype(dtname); } else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) { result = plpgsql_parse_wordrowtype(dtname); ! } ! ! if (result != NULL) ! { ! to_array_type = array_type_is_required(); ! return plpgsql_derive_type(result, ! to_element_type, to_array_type); } } } else if (tok == T_CWORD) { List *dtnames = yylval.cword.idents; tok = yylex(); if (tok == '%') { *************** read_datatype(int tok) *** 2766,2780 **** K_TYPE, "type")) { result = plpgsql_parse_cwordtype(dtnames); - if (result) - return result; } else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) { result = plpgsql_parse_cwordrowtype(dtnames); ! if (result) ! return result; } } } --- 2823,2840 ---- K_TYPE, "type")) { result = plpgsql_parse_cwordtype(dtnames); } else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) { result = plpgsql_parse_cwordrowtype(dtnames); ! } ! ! if (result != NULL) ! { ! to_array_type = array_type_is_required(); ! return plpgsql_derive_type(result, ! to_element_type, to_array_type); } } } *************** read_datatype(int tok) *** 2817,2823 **** plpgsql_push_back_token(tok); ! return result; } static PLpgSQL_stmt * --- 2877,2884 ---- plpgsql_push_back_token(tok); ! return plpgsql_derive_type(result, ! to_element_type, to_array_type); } static PLpgSQL_stmt * diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c new file mode 100644 index bb0f25b..82959a9 *** a/src/pl/plpgsql/src/pl_scanner.c --- b/src/pl/plpgsql/src/pl_scanner.c *************** static const ScanKeyword unreserved_keyw *** 116,121 **** --- 116,122 ---- PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD) PG_KEYWORD("diagnostics", K_DIAGNOSTICS, UNRESERVED_KEYWORD) PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD) + PG_KEYWORD("element", K_ELEMENT, UNRESERVED_KEYWORD) PG_KEYWORD("elseif", K_ELSIF, UNRESERVED_KEYWORD) PG_KEYWORD("elsif", K_ELSIF, UNRESERVED_KEYWORD) PG_KEYWORD("errcode", K_ERRCODE, UNRESERVED_KEYWORD) *************** static const ScanKeyword unreserved_keyw *** 138,143 **** --- 139,145 ---- PG_KEYWORD("next", K_NEXT, UNRESERVED_KEYWORD) PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD) PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD) + PG_KEYWORD("of", K_OF, UNRESERVED_KEYWORD) PG_KEYWORD("open", K_OPEN, UNRESERVED_KEYWORD) PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD) PG_KEYWORD("perform", K_PERFORM, UNRESERVED_KEYWORD) diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h new file mode 100644 index a1e900d..f3c563c *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** typedef struct *** 281,286 **** --- 281,287 ---- char *refname; int lineno; + PLpgSQL_type *datatype; TupleDesc rowtupdesc; /* *************** extern PLpgSQL_type *plpgsql_parse_wordt *** 965,970 **** --- 966,973 ---- extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents); extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident); extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents); + extern PLpgSQL_type *plpgsql_derive_type(PLpgSQL_type *base_type, + bool to_element_type, bool to_array_type); extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod, Oid collation); extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno, diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out new file mode 100644 index e30c579..2cb207e *** a/src/test/regress/expected/plpgsql.out --- b/src/test/regress/expected/plpgsql.out *************** end; *** 5573,5575 **** --- 5573,5729 ---- $$; ERROR: unhandled assertion CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT + -- test referenced types + create type test_composite_type as (x int, y int); + create domain array_domain as int[]; + create domain int_domain as int; + create or replace function test_simple(src anyelement) + returns anyelement as $$ + declare dest src%type; + begin + dest := src; + return dest; + end; + $$ language plpgsql; + select test_simple(10); + test_simple + ------------- + 10 + (1 row) + + select test_simple('hoj'::text); + test_simple + ------------- + hoj + (1 row) + + select test_simple((10,20)::test_composite_type); + test_simple + ------------- + (10,20) + (1 row) + + create or replace function test_poly_element(x anyelement) + returns anyarray as $$ + declare result x%type[]; + begin + result := ARRAY[x]; + raise notice '% %', pg_typeof(result), result; + return result; + end; + $$ language plpgsql; + create or replace function test_array_init(v anyelement, size integer) + returns anyarray as $$ + declare result v%type[] default '{}'; + begin + -- prefer builtin function array_fill + for i in 1 .. size + loop + result := result || v; + end loop; + return result; + end; + $$ language plpgsql; + select test_poly_element(1); + NOTICE: integer[] {1} + test_poly_element + ------------------- + {1} + (1 row) + + select test_poly_element('hoj'::text); + NOTICE: text[] {hoj} + test_poly_element + ------------------- + {hoj} + (1 row) + + select test_poly_element((10,20)::test_composite_type); + NOTICE: test_composite_type[] {"(10,20)"} + test_poly_element + ------------------- + {"(10,20)"} + (1 row) + + select test_array_init(1.0::numeric, 10); + test_array_init + ------------------------------------------- + {1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0} + (1 row) + + select test_array_init(1::int, 10); + test_array_init + ----------------------- + {1,1,1,1,1,1,1,1,1,1} + (1 row) + + -- should fail, there are no array type for scalar domain + select test_poly_element(1::int_domain); + ERROR: could not find array type for data type int_domain + select test_array_init(1::int_domain, 10); + ERROR: could not find array type for data type int_domain + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result element of x%type; + begin + result := x[1]; + raise notice '% %', pg_typeof(result), result; + return result; + end; + $$ language plpgsql; + select test_poly_array(ARRAY[1]); + NOTICE: integer 1 + test_poly_array + ----------------- + 1 + (1 row) + + select test_poly_array(ARRAY['hoj'::text]); + NOTICE: text hoj + test_poly_array + ----------------- + hoj + (1 row) + + select test_poly_array(ARRAY[(10,20)::test_composite_type]); + NOTICE: test_composite_type (10,20) + test_poly_array + ----------------- + (10,20) + (1 row) + + select test_poly_array(ARRAY[1,2,3,4]::array_domain); + NOTICE: integer 1 + test_poly_array + ----------------- + 1 + (1 row) + + drop function test_simple(anyelement); + drop type test_composite_type; + drop domain array_domain; + drop domain int_domain; + drop function test_poly_element(anyelement); + drop function test_array_init(anyelement, int); + drop function test_poly_array(anyarray); + -- should fail, syntax errors + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result element x%type; + begin + return result; + end; + $$ language plpgsql; + ERROR: syntax error, expected "OF" at or near "x" + LINE 3: declare result element x%type; + ^ + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result x%type[; + begin + return result; + end; + $$ language plpgsql; + ERROR: syntax error, expected "]" at or near ";" + LINE 3: declare result x%type[; + ^ diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql new file mode 100644 index 7ffef89..0e16260 *** a/src/test/regress/sql/plpgsql.sql --- b/src/test/regress/sql/plpgsql.sql *************** exception when others then *** 4386,4388 **** --- 4386,4482 ---- null; -- do nothing end; $$; + + + -- test referenced types + create type test_composite_type as (x int, y int); + create domain array_domain as int[]; + create domain int_domain as int; + + create or replace function test_simple(src anyelement) + returns anyelement as $$ + declare dest src%type; + begin + dest := src; + return dest; + end; + $$ language plpgsql; + + select test_simple(10); + select test_simple('hoj'::text); + select test_simple((10,20)::test_composite_type); + + create or replace function test_poly_element(x anyelement) + returns anyarray as $$ + declare result x%type[]; + begin + result := ARRAY[x]; + raise notice '% %', pg_typeof(result), result; + return result; + end; + $$ language plpgsql; + + create or replace function test_array_init(v anyelement, size integer) + returns anyarray as $$ + declare result v%type[] default '{}'; + begin + -- prefer builtin function array_fill + for i in 1 .. size + loop + result := result || v; + end loop; + return result; + end; + $$ language plpgsql; + + select test_poly_element(1); + select test_poly_element('hoj'::text); + select test_poly_element((10,20)::test_composite_type); + + select test_array_init(1.0::numeric, 10); + select test_array_init(1::int, 10); + + -- should fail, there are no array type for scalar domain + select test_poly_element(1::int_domain); + select test_array_init(1::int_domain, 10); + + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result element of x%type; + begin + result := x[1]; + raise notice '% %', pg_typeof(result), result; + return result; + end; + $$ language plpgsql; + + select test_poly_array(ARRAY[1]); + select test_poly_array(ARRAY['hoj'::text]); + select test_poly_array(ARRAY[(10,20)::test_composite_type]); + select test_poly_array(ARRAY[1,2,3,4]::array_domain); + + drop function test_simple(anyelement); + drop type test_composite_type; + drop domain array_domain; + drop domain int_domain; + + drop function test_poly_element(anyelement); + drop function test_array_init(anyelement, int); + drop function test_poly_array(anyarray); + + -- should fail, syntax errors + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result element x%type; + begin + return result; + end; + $$ language plpgsql; + + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result x%type[; + begin + return result; + end; + $$ language plpgsql;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers