st 17. 3. 2021 v 9:20 odesÃlatel Michael Paquier <[email protected]>
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;