st 17. 3. 2021 v 9:20 odesÃlatel Michael Paquier <mich...@paquier.xyz> napsal:
> On Wed, Mar 17, 2021 at 02:06:57PM +0800, Julien Rouhaud wrote: > > I also think that there should be a single usable top label, otherwise > it will > > lead to confusing code and it can be a source of bug. > > Okay, that's fine by me. Could it be possible to come up with an > approach that does not hijack the namespace list contruction and the > lookup logic as much as it does now? I get it that the patch is done > this way because of the ordering of operations done for the initial ns > list creation and the grammar parsing that adds the routine label on > top of the root one, but I'd like to believe that there are more solid > ways to achieve that, like for example something that decouples the > root label and its alias, or something that associates an alias > directly with its PLpgSQL_nsitem entry? > I am checking it now, and I don't see any easy solution. The namespace is a one direction tree - only backward iteration from leafs to roots is supported. At the moment, when I try to replace the name of root ns_item, this root ns_item is referenced by the function's arguments (NS_TYPE_VAR type). So anytime I need some helper ns_item node, that can be a persistent target for these nodes. In this case is a memory overhead of just one ns_item. orig_label <- argument1 <- argument2 The patch has to save the original orig_label because it can be referenced from argument1 or by "FOUND" variable. New graphs looks like new_label <- invalidated orig_label <- argument1 <- ... This tree has a different direction than is usual, and then replacing the root node is not simple. Second solution (significantly more simple) is an additional pointer in ns_item structure. In almost all cases this pointer will not be used. Because ns_item uses a flexible array, then union cannot be used. I implemented this in a patch marked as "alias-implementation". What do you think about it? Pavel > -- > Michael >
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 9242c54329..ed8e774899 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -292,7 +292,9 @@ $$ LANGUAGE plpgsql; special variables such as <literal>FOUND</literal> (see <xref linkend="plpgsql-statements-diagnostics"/>). The outer block is labeled with the function's name, meaning that parameters and special - variables can be qualified with the function's name. + variables can be qualified with the function's name. The name of this outer + block can be changed by inserting special command + <literal>#routine_label new_name</literal> at the start of the function. </para> </note> @@ -435,6 +437,31 @@ $$ LANGUAGE plpgsql; </para> </note> + <para> + The function's argument can be qualified with the function name: +<programlisting> +CREATE FUNCTION sales_tax(subtotal real) RETURNS real AS $$ +BEGIN + RETURN sales_tax.subtotal * 0.06; +END; +$$ LANGUAGE plpgsql; +</programlisting> + </para> + + <para> + Sometimes the function name is too long and it can be more practical to use + some shorter label. The top namespace label can be changed (along with + the functions' arguments) using the option <literal>routine_label</literal>: +<programlisting> +CREATE FUNCTION sales_tax(subtotal real) RETURNS real AS $$ +#routine_label s +BEGIN + RETURN s.subtotal * 0.06; +END; +$$ LANGUAGE plpgsql; +</programlisting> + </para> + <para> Some more examples: <programlisting> diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index ce8d97447d..d32e050c32 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -378,6 +378,10 @@ do_compile(FunctionCallInfo fcinfo, */ plpgsql_ns_init(); plpgsql_ns_push(NameStr(procStruct->proname), PLPGSQL_LABEL_BLOCK); + + /* save top ns for possibility to alter top label */ + function->root_ns = plpgsql_ns_top(); + plpgsql_DumpExecTree = false; plpgsql_start_datums(); diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 919b968826..7132da35d1 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -101,6 +101,7 @@ plpgsql_ns_additem(PLpgSQL_nsitem_type itemtype, int itemno, const char *name) nse->itemtype = itemtype; nse->itemno = itemno; nse->prev = ns_top; + nse->alias = NULL; strcpy(nse->name, name); ns_top = nse; } @@ -141,7 +142,7 @@ plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode, nsitem->itemtype != PLPGSQL_NSTYPE_LABEL; nsitem = nsitem->prev) { - if (strcmp(nsitem->name, name1) == 0) + if (strcmp(nsitem->alias ? nsitem->alias : nsitem->name, name1) == 0) { if (name2 == NULL || nsitem->itemtype != PLPGSQL_NSTYPE_VAR) @@ -155,13 +156,13 @@ plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode, /* Check this level for qualified match to variable name */ if (name2 != NULL && - strcmp(nsitem->name, name1) == 0) + strcmp(nsitem->alias ? nsitem->alias : nsitem->name, name1) == 0) { for (nsitem = ns_cur; nsitem->itemtype != PLPGSQL_NSTYPE_LABEL; nsitem = nsitem->prev) { - if (strcmp(nsitem->name, name2) == 0) + if (strcmp(nsitem->alias ? nsitem->alias : nsitem->name, name2) == 0) { if (name3 == NULL || nsitem->itemtype != PLPGSQL_NSTYPE_VAR) @@ -197,7 +198,7 @@ plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur, const char *name) while (ns_cur != NULL) { if (ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL && - strcmp(ns_cur->name, name) == 0) + strcmp(ns_cur->alias ? ns_cur->alias : ns_cur->name, name) == 0) return ns_cur; ns_cur = ns_cur->prev; } diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 34e0520719..f3536d75ae 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -333,6 +333,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token <keyword> K_RETURNED_SQLSTATE %token <keyword> K_REVERSE %token <keyword> K_ROLLBACK +%token <keyword> K_ROUTINE_LABEL %token <keyword> K_ROW_COUNT %token <keyword> K_ROWTYPE %token <keyword> K_SCHEMA @@ -393,6 +394,24 @@ comp_option : '#' K_OPTION K_DUMP { plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN; } + | '#' K_ROUTINE_LABEL any_identifier + { + if (!plpgsql_curr_compile->root_ns) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("The command \"routine_label\" cannot be used in inline block"), + parser_errposition(@1))); + + /* Don't allow more top labels in routine */ + if (plpgsql_curr_compile->root_ns->alias) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("redundant option"), + errhint("The command \"routine_label\" can be used only once in rutine."), + parser_errposition(@1))); + + plpgsql_curr_compile->root_ns->alias = pstrdup($3); + } ; option_value : T_WORD diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h index 44c8b68bfb..d09d4729b7 100644 --- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h +++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h @@ -94,6 +94,7 @@ PG_KEYWORD("return", K_RETURN) PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE) PG_KEYWORD("reverse", K_REVERSE) PG_KEYWORD("rollback", K_ROLLBACK) +PG_KEYWORD("routine_label", K_ROUTINE_LABEL) PG_KEYWORD("row_count", K_ROW_COUNT) PG_KEYWORD("rowtype", K_ROWTYPE) PG_KEYWORD("schema", K_SCHEMA) diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index d5010862a5..d024dae3fa 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -444,6 +444,7 @@ typedef struct PLpgSQL_nsitem */ int itemno; struct PLpgSQL_nsitem *prev; + char *alias; /* used when original name should not be used */ char name[FLEXIBLE_ARRAY_MEMBER]; /* nul-terminated string */ } PLpgSQL_nsitem; @@ -1024,6 +1025,9 @@ typedef struct PLpgSQL_function /* these fields change when the function is used */ struct PLpgSQL_execstate *cur_estate; unsigned long use_count; + + /* routine level namespace entry */ + struct PLpgSQL_nsitem *root_ns; } PLpgSQL_function; /* diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 6ea169d9ad..04176fa40a 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -5701,3 +5701,48 @@ END; $$ LANGUAGE plpgsql; ERROR: "x" is not a scalar variable LINE 3: GET DIAGNOSTICS x = ROW_COUNT; ^ +-- +-- Check root namespace renaming (routine_label option) +-- +CREATE OR REPLACE FUNCTION test_root_namespace_rename(arg1 int) +RETURNS void AS $$ +#ROUTINE_LABEL argsns +BEGIN + RAISE NOTICE '% %', arg1, argsns.arg1; +END; +$$ LANGUAGE plpgsql; +SELECT test_root_namespace_rename(10); +NOTICE: 10 10 + test_root_namespace_rename +---------------------------- + +(1 row) + +CREATE OR REPLACE FUNCTION test_root_namespace_rename(arg1 int) +RETURNS void AS $$ +#ROUTINE_LABEL argsns +BEGIN + -- should to fail, original name is overwritten + RAISE NOTICE '% %', arg1, test_root_namespace_rename.arg1; +END; +$$ LANGUAGE plpgsql; +SELECT test_root_namespace_rename(10); +ERROR: missing FROM-clause entry for table "test_root_namespace_rename" +LINE 1: test_root_namespace_rename.arg1 + ^ +QUERY: test_root_namespace_rename.arg1 +CONTEXT: PL/pgSQL function test_root_namespace_rename(integer) line 5 at RAISE +-- should fail, syntax error - redundant option +CREATE OR REPLACE FUNCTION test_root_namespace_rename(arg1 int) +RETURNS void AS $$ +#ROUTINE_LABEL argsns +#ROUTINE_LABEL argsns +BEGIN + -- should to fail, original name is overwritten + RAISE NOTICE '% %', arg1, test_root_namespace_rename.arg1; +END; +$$ LANGUAGE plpgsql; +ERROR: redundant option +LINE 4: #ROUTINE_LABEL argsns + ^ +HINT: The command "routine_label" can be used only once in rutine. diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 781666a83a..da58e46182 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -4645,3 +4645,38 @@ BEGIN GET DIAGNOSTICS x = ROW_COUNT; RETURN; END; $$ LANGUAGE plpgsql; + +-- +-- Check root namespace renaming (routine_label option) +-- +CREATE OR REPLACE FUNCTION test_root_namespace_rename(arg1 int) +RETURNS void AS $$ +#ROUTINE_LABEL argsns +BEGIN + RAISE NOTICE '% %', arg1, argsns.arg1; +END; +$$ LANGUAGE plpgsql; + +SELECT test_root_namespace_rename(10); + +CREATE OR REPLACE FUNCTION test_root_namespace_rename(arg1 int) +RETURNS void AS $$ +#ROUTINE_LABEL argsns +BEGIN + -- should to fail, original name is overwritten + RAISE NOTICE '% %', arg1, test_root_namespace_rename.arg1; +END; +$$ LANGUAGE plpgsql; + +SELECT test_root_namespace_rename(10); + +-- should fail, syntax error - redundant option +CREATE OR REPLACE FUNCTION test_root_namespace_rename(arg1 int) +RETURNS void AS $$ +#ROUTINE_LABEL argsns +#ROUTINE_LABEL argsns +BEGIN + -- should to fail, original name is overwritten + RAISE NOTICE '% %', arg1, test_root_namespace_rename.arg1; +END; +$$ LANGUAGE plpgsql;