Hi 2015-12-21 16:21 GMT+01:00 Artur Zakirov <a.zaki...@postgrespro.ru>:
> Hi. > I have tried to do some review of this patch. Below are my comments. > > Introduction: > > This patch fixes and adds the following functionality: > - %TYPE - now can be used for composite types. > - %ARRAYTYPE - new functionality, provides the array type from a variable > or table column. > - %ELEMENTTYPE - new funcitonality, provides the element type of a given > array. > > New regression tests are included in the patch. Changes to the > documentation are not provided. > > Initial Run: > > The patch applies correctly to HEAD. Regression tests pass successfully, > without errors. It seems that the patch work as you expected. > > Performance: > > It seems patch have not possible performance issues for the existing > functionality. > > Coding: > > The style looks fine. I attached the patch that does some corrections in > code and documentation. I have corrected indentation in pl_comp.c and > "read_datatype" function in pl_gram.y. I think changes in "read_datatype" > function would be better to avoid a code duplication. But I could be wrong > of course. > I merged Artur's patch and appended examples to doc. > > Conclusion: > > The patch could be applied on master with documentation corrections. But > I'm not sure that your task could be resloved only by adding %ARRAYTYPE and > %ELEMENTTYPE. Maybe you will give some examples? > It fixes the most missed/known features related to this part of plpgsql, what I found. But any ideas for this or follofup patches are welcome. Regards Pavel > > -- > Artur Zakirov > Postgres Professional: http://www.postgrespro.com > Russian Postgres Company >
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml new file mode 100644 index 9786242..140c81f *** a/doc/src/sgml/plpgsql.sgml --- b/doc/src/sgml/plpgsql.sgml *************** SELECT merge_fields(t.*) FROM table1 t W *** 710,715 **** --- 710,792 ---- </para> </sect2> + <sect2 id="plpgsql-declaration-arraytype"> + <title>Array Types</title> + + <synopsis> + <replaceable>variable</replaceable>%ARRAYTYPE + </synopsis> + + <para> + <literal>%ARRAYTYPE</literal> provides the array type from a variable or + table column. <literal>%ARRAYTYPE</literal> is particularly valuable in + polymorphic functions, since the data types needed for internal variables can + change from one call to the next. Appropriate variables can be + created by applying <literal>%ARRAYTYPE</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%ARRAYTYPE 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); + </programlisting> + </para> + </sect2> + + <sect2 id="plpgsql-declaration-elementtype"> + <title>Array Element Types</title> + + <synopsis> + <replaceable>variable</replaceable>%ELEMENTTYPE + </synopsis> + + <para> + <literal>%ELEMENTTYPE</literal> provides the element type of a given + array. <literal>%ELEMENTTYPE</literal> is particularly valuable in polymorphic + functions, since the data types needed for internal variables can + change from one call to the next. Appropriate variables can be + created by applying <literal>%ELEMENTTYPE</literal> to the function's + arguments or result placeholders: + <programlisting> + CREATE OR REPLACE FUNCTION bubble_sort(a anyarray) + RETURNS anyarray AS $$ + DECLARE + aux a%ELEMENTTYPE; + 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; + </programlisting> + </para> + </sect2> + <sect2 id="plpgsql-declaration-collation"> <title>Collation of <application>PL/pgSQL</application> Variables</title> diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c new file mode 100644 index 1ae4bb7..c819517 *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** plpgsql_parse_tripword(char *word1, char *** 1617,1622 **** --- 1617,1677 ---- return false; } + /* + * Derive type from ny base type controlled by reftype_mode + */ + static PLpgSQL_type * + derive_type(PLpgSQL_type *base_type, int reftype_mode) + { + Oid typoid; + + switch (reftype_mode) + { + case PLPGSQL_REFTYPE_TYPE: + return base_type; + + case PLPGSQL_REFTYPE_ELEMENT: + { + typoid = get_element_type(base_type->typoid); + if (!OidIsValid(typoid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("referenced variable should be an array, not type %s", + format_type_be(base_type->typoid)))); + + return plpgsql_build_datatype(typoid, -1, + plpgsql_curr_compile->fn_input_collation); + } + + case PLPGSQL_REFTYPE_ARRAY: + { + /* + * Question: can we allow anyelement (array or nonarray) -> array direction. + * if yes, then probably we have to modify enforce_generic_type_consistency, + * parse_coerce.c where still is check on scalar type -> raise error + * ERROR: 42704: could not find array type for data type integer[] + * + if (OidIsValid(get_element_type(base_type->typoid))) + return base_type; + */ + + typoid = get_array_type(base_type->typoid); + if (!OidIsValid(typoid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("there are not array type for type %s", + format_type_be(base_type->typoid)))); + + return plpgsql_build_datatype(typoid, -1, + plpgsql_curr_compile->fn_input_collation); + } + + default: + return NULL; + } + } + + /* ---------- * plpgsql_parse_wordtype The scanner found word%TYPE. word can be *************** plpgsql_parse_tripword(char *word1, char *** 1626,1632 **** * ---------- */ PLpgSQL_type * ! plpgsql_parse_wordtype(char *ident) { PLpgSQL_type *dtype; PLpgSQL_nsitem *nse; --- 1681,1687 ---- * ---------- */ PLpgSQL_type * ! plpgsql_parse_wordtype(char *ident, int reftype_mode) { PLpgSQL_type *dtype; PLpgSQL_nsitem *nse; *************** plpgsql_parse_wordtype(char *ident) *** 1644,1653 **** switch (nse->itemtype) { case PLPGSQL_NSTYPE_VAR: ! return ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; ! /* XXX perhaps allow REC/ROW here? */ default: return NULL; } --- 1699,1721 ---- switch (nse->itemtype) { case PLPGSQL_NSTYPE_VAR: ! { ! dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; ! return derive_type(dtype, reftype_mode); ! } ! case PLPGSQL_NSTYPE_ROW: ! { ! dtype = ((PLpgSQL_row *) (plpgsql_Datums[nse->itemno]))->datatype; ! return derive_type(dtype, reftype_mode); ! } + /* + * XXX perhaps allow REC here? Probably it has not any sense, because + * in this moment, because PLpgSQL doesn't support rec parameters, so + * there should not be any rec polymorphic parameter, and any work can + * be done inside function. + */ default: return NULL; } *************** plpgsql_parse_wordtype(char *ident) *** 1689,1695 **** * ---------- */ PLpgSQL_type * ! plpgsql_parse_cwordtype(List *idents) { PLpgSQL_type *dtype = NULL; PLpgSQL_nsitem *nse; --- 1757,1763 ---- * ---------- */ PLpgSQL_type * ! plpgsql_parse_cwordtype(List *idents, int reftype_mode) { PLpgSQL_type *dtype = NULL; PLpgSQL_nsitem *nse; *************** 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; } /* --- 1786,1809 ---- NULL, NULL); ! if (nse != NULL) { ! PLpgSQL_type *ref_type; ! ! if (nse->itemtype == PLPGSQL_NSTYPE_VAR) ! { ! ref_type = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; ! dtype = derive_type(ref_type, reftype_mode); ! ! goto done; ! } ! else if (nse->itemtype == PLPGSQL_NSTYPE_ROW) ! { ! ref_type = ((PLpgSQL_row *) (plpgsql_Datums[nse->itemno]))->datatype; ! dtype = derive_type(ref_type, reftype_mode); ! ! goto done; ! } } /* *************** plpgsql_build_variable(const char *refna *** 1903,1908 **** --- 1985,1991 ---- 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 841a8d6..2a197a2 *** a/src/pl/plpgsql/src/pl_gram.y --- b/src/pl/plpgsql/src/pl_gram.y *************** static void check_raise_parameters(PLp *** 248,253 **** --- 248,254 ---- %token <keyword> K_ALIAS %token <keyword> K_ALL %token <keyword> K_ARRAY + %token <keyword> K_ARRAYTYPE %token <keyword> K_ASSERT %token <keyword> K_BACKWARD %token <keyword> K_BEGIN *************** static void check_raise_parameters(PLp *** 270,275 **** --- 271,277 ---- %token <keyword> K_DETAIL %token <keyword> K_DIAGNOSTICS %token <keyword> K_DUMP + %token <keyword> K_ELEMENTTYPE %token <keyword> K_ELSE %token <keyword> K_ELSIF %token <keyword> K_END *************** unreserved_keyword : *** 2390,2395 **** --- 2392,2398 ---- K_ABSOLUTE | K_ALIAS | K_ARRAY + | K_ARRAYTYPE | K_ASSERT | K_BACKWARD | K_CLOSE *************** unreserved_keyword : *** 2408,2413 **** --- 2411,2417 ---- | K_DETAIL | K_DIAGNOSTICS | K_DUMP + | K_ELEMENTTYPE | K_ELSIF | K_ERRCODE | K_ERROR *************** read_datatype(int tok) *** 2690,2696 **** StringInfoData ds; char *type_name; int startlocation; ! PLpgSQL_type *result; int parenlevel = 0; /* Should only be called while parsing DECLARE sections */ --- 2694,2700 ---- StringInfoData ds; char *type_name; int startlocation; ! PLpgSQL_type *result = 0; int parenlevel = 0; /* Should only be called while parsing DECLARE sections */ *************** read_datatype(int tok) *** 2703,2710 **** startlocation = yylloc; /* ! * If we have a simple or composite identifier, check for %TYPE ! * and %ROWTYPE constructs. */ if (tok == T_WORD) { --- 2707,2714 ---- startlocation = yylloc; /* ! * If we have a simple or composite identifier, check for %TYPE, ! * %ELEMENTTYPE, %ARRAYTYPE and %ROWTYPE constructs. */ if (tok == T_WORD) { *************** read_datatype(int tok) *** 2716,2733 **** tok = yylex(); if (tok_is_keyword(tok, &yylval, 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)) --- 2720,2737 ---- tok = yylex(); if (tok_is_keyword(tok, &yylval, K_TYPE, "type")) ! result = plpgsql_parse_wordtype(dtname, PLPGSQL_REFTYPE_TYPE); ! else if (tok_is_keyword(tok, &yylval, ! K_ELEMENTTYPE, "elementtype")) ! result = plpgsql_parse_wordtype(dtname, PLPGSQL_REFTYPE_ELEMENT); ! else if (tok_is_keyword(tok, &yylval, ! K_ARRAYTYPE, "arraytype")) ! result = plpgsql_parse_wordtype(dtname, PLPGSQL_REFTYPE_ARRAY); 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)) *************** read_datatype(int tok) *** 2740,2757 **** tok = yylex(); if (tok_is_keyword(tok, &yylval, 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) --- 2744,2761 ---- tok = yylex(); if (tok_is_keyword(tok, &yylval, K_TYPE, "type")) ! result = plpgsql_parse_wordtype(dtname, PLPGSQL_REFTYPE_TYPE); ! else if (tok_is_keyword(tok, &yylval, ! K_ELEMENTTYPE, "elementtype")) ! result = plpgsql_parse_wordtype(dtname, PLPGSQL_REFTYPE_ELEMENT); ! else if (tok_is_keyword(tok, &yylval, ! K_ARRAYTYPE, "arraytype")) ! result = plpgsql_parse_wordtype(dtname, PLPGSQL_REFTYPE_ARRAY); else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) result = plpgsql_parse_wordrowtype(dtname); ! if (result) ! return result; } } else if (tok == T_CWORD) *************** read_datatype(int tok) *** 2764,2781 **** tok = yylex(); if (tok_is_keyword(tok, &yylval, 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; ! } } } --- 2768,2785 ---- tok = yylex(); if (tok_is_keyword(tok, &yylval, K_TYPE, "type")) ! result = plpgsql_parse_cwordtype(dtnames, PLPGSQL_REFTYPE_TYPE); ! else if (tok_is_keyword(tok, &yylval, ! K_ELEMENTTYPE, "elementtype")) ! result = plpgsql_parse_cwordtype(dtnames, PLPGSQL_REFTYPE_ELEMENT); ! else if (tok_is_keyword(tok, &yylval, ! K_ARRAYTYPE, "arraytype")) ! result = plpgsql_parse_cwordtype(dtnames, PLPGSQL_REFTYPE_ARRAY); else if (tok_is_keyword(tok, &yylval, K_ROWTYPE, "rowtype")) result = plpgsql_parse_cwordrowtype(dtnames); ! if (result) ! return result; } } diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c new file mode 100644 index 683fdab..6e34605 *** a/src/pl/plpgsql/src/pl_scanner.c --- b/src/pl/plpgsql/src/pl_scanner.c *************** static const ScanKeyword unreserved_keyw *** 98,103 **** --- 98,104 ---- PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD) PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD) PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD) + PG_KEYWORD("arraytype", K_ARRAYTYPE, UNRESERVED_KEYWORD) PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD) PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD) PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD) *************** static const ScanKeyword unreserved_keyw *** 116,121 **** --- 117,123 ---- PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD) PG_KEYWORD("diagnostics", K_DIAGNOSTICS, UNRESERVED_KEYWORD) PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD) + PG_KEYWORD("elementtype", K_ELEMENTTYPE, UNRESERVED_KEYWORD) PG_KEYWORD("elseif", K_ELSIF, UNRESERVED_KEYWORD) PG_KEYWORD("elsif", K_ELSIF, UNRESERVED_KEYWORD) PG_KEYWORD("errcode", K_ERRCODE, UNRESERVED_KEYWORD) diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h new file mode 100644 index 696fb61..5b1a074 *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** enum *** 84,89 **** --- 84,100 ---- }; /* ---------- + * Possible modes for type references + * ---------- + */ + enum + { + PLPGSQL_REFTYPE_TYPE, /* use type of some variable */ + PLPGSQL_REFTYPE_ELEMENT, /* use a element type of referenced variable */ + PLPGSQL_REFTYPE_ARRAY /* use a array type of referenced variable */ + }; + + /* ---------- * Execution tree node types * ---------- */ *************** typedef struct *** 281,286 **** --- 292,298 ---- char *refname; int lineno; + PLpgSQL_type *datatype; TupleDesc rowtupdesc; /* *************** extern bool plpgsql_parse_dblword(char * *** 961,968 **** PLwdatum *wdatum, PLcword *cword); extern bool plpgsql_parse_tripword(char *word1, char *word2, char *word3, PLwdatum *wdatum, PLcword *cword); ! extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident); ! 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_build_datatype(Oid typeOid, int32 typmod, --- 973,980 ---- PLwdatum *wdatum, PLcword *cword); extern bool plpgsql_parse_tripword(char *word1, char *word2, char *word3, PLwdatum *wdatum, PLcword *cword); ! extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident, int reftype_mode); ! extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents, int reftype_mode); extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident); extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents); extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod, diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out new file mode 100644 index e30c579..b6e848b *** a/src/test/regress/expected/plpgsql.out --- b/src/test/regress/expected/plpgsql.out *************** end; *** 5573,5575 **** --- 5573,5667 ---- $$; 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 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%arraytype; + begin + result := ARRAY[x]; + raise notice '% %', pg_typeof(result), result; + 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) + + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result x%elementtype; + 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) + + drop function test_simple(anyelement); + drop type test_composite_type; + drop function test_poly_element(anyelement); + drop function test_poly_array(anyarray); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql new file mode 100644 index 7ffef89..46bad10 *** a/src/test/regress/sql/plpgsql.sql --- b/src/test/regress/sql/plpgsql.sql *************** exception when others then *** 4386,4388 **** --- 4386,4437 ---- null; -- do nothing end; $$; + + -- test referenced types + create type test_composite_type as (x int, y 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%arraytype; + begin + result := ARRAY[x]; + raise notice '% %', pg_typeof(result), result; + 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); + + create or replace function test_poly_array(x anyarray) + returns anyelement as $$ + declare result x%elementtype; + 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]); + + drop function test_simple(anyelement); + drop type test_composite_type; + drop function test_poly_element(anyelement); + drop function test_poly_array(anyarray);
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers