diff options
-rw-r--r-- | doc/src/sgml/catalogs.sgml | 16 | ||||
-rw-r--r-- | src/backend/catalog/pg_proc.c | 31 | ||||
-rw-r--r-- | src/backend/commands/functioncmds.c | 11 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 2 | ||||
-rw-r--r-- | src/backend/executor/functions.c | 20 | ||||
-rw-r--r-- | src/backend/optimizer/util/clauses.c | 66 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dump.c | 2 | ||||
-rw-r--r-- | src/bin/psql/describe.c | 2 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/catalog/pg_proc.h | 2 | ||||
-rw-r--r-- | src/include/executor/functions.h | 5 | ||||
-rw-r--r-- | src/test/regress/expected/create_function_3.out | 6 | ||||
-rw-r--r-- | src/test/regress/expected/opr_sanity.out | 11 | ||||
-rw-r--r-- | src/test/regress/sql/create_function_3.sql | 5 | ||||
-rw-r--r-- | src/test/regress/sql/opr_sanity.sql | 7 |
15 files changed, 109 insertions, 79 deletions
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 2656786d1e6..1345791e963 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -6007,8 +6007,9 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l <structfield>prosqlbody</structfield> <type>pg_node_tree</type> </para> <para> - Pre-parsed SQL function body. This will be used for language SQL - functions if the body is not specified as a string constant. + Pre-parsed SQL function body. This is used for SQL-language + functions when the body is given in SQL-standard notation + rather than as a string literal. It's null in other cases. </para></entry> </row> @@ -6036,9 +6037,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l <para> For compiled functions, both built-in and dynamically loaded, <structfield>prosrc</structfield> contains the function's C-language - name (link symbol). For all other currently-known language types, + name (link symbol). + For SQL-language functions, <structfield>prosrc</structfield> contains + the function's source text if that is specified as a string literal; + but if the function body is specified in SQL-standard style, + <structfield>prosrc</structfield> is unused (typically it's an empty + string) and <structfield>prosqlbody</structfield> contains the + pre-parsed definition. + For all other currently-known language types, <structfield>prosrc</structfield> contains the function's source - text. <structfield>probin</structfield> is unused except for + text. <structfield>probin</structfield> is null except for dynamically-loaded C functions, for which it gives the name of the shared library file containing the function. </para> diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index cd13e63852a..478dbde3fe6 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -121,7 +121,7 @@ ProcedureCreate(const char *procedureName, /* * sanity checks */ - Assert(PointerIsValid(prosrc) || PointerIsValid(prosqlbody)); + Assert(PointerIsValid(prosrc)); parameterCount = parameterTypes->dim1; if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS) @@ -336,10 +336,7 @@ ProcedureCreate(const char *procedureName, values[Anum_pg_proc_protrftypes - 1] = trftypes; else nulls[Anum_pg_proc_protrftypes - 1] = true; - if (prosrc) - values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc); - else - nulls[Anum_pg_proc_prosrc - 1] = true; + values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc); if (probin) values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin); else @@ -874,26 +871,29 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) /* Postpone body checks if !check_function_bodies */ if (check_function_bodies) { + tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); + if (isnull) + elog(ERROR, "null prosrc"); + + prosrc = TextDatumGetCString(tmp); + /* * Setup error traceback support for ereport(). */ callback_arg.proname = NameStr(proc->proname); - callback_arg.prosrc = NULL; + callback_arg.prosrc = prosrc; sqlerrcontext.callback = sql_function_parse_error_callback; sqlerrcontext.arg = (void *) &callback_arg; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; - tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull); - if (isnull) + /* If we have prosqlbody, pay attention to that not prosrc */ + tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull); + if (!isnull) { Node *n; - tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull); - if (isnull) - elog(ERROR, "null prosrc and prosqlbody"); - n = stringToNode(TextDatumGetCString(tmp)); if (IsA(n, List)) querytree_list = castNode(List, n); @@ -902,10 +902,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) } else { - prosrc = TextDatumGetCString(tmp); - - callback_arg.prosrc = prosrc; - /* * We can't do full prechecking of the function definition if there * are any polymorphic input types, because actual datatypes of @@ -1001,9 +997,6 @@ function_parse_error_transpose(const char *prosrc) int newerrposition; const char *queryText; - if (!prosrc) - return false; - /* * Nothing to do unless we are dealing with a syntax error that has a * cursor position. diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 199029b7a85..dc317c83afc 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -958,8 +958,17 @@ interpret_AS_clause(Oid languageOid, const char *languageName, *sql_body_out = (Node *) q; } + /* + * We must put something in prosrc. For the moment, just record an + * empty string. It might be useful to store the original text of the + * CREATE FUNCTION statement --- but to make actual use of that in + * error reports, we'd also have to adjust readfuncs.c to not throw + * away node location fields when reading prosqlbody. + */ + *prosrc_str_p = pstrdup(""); + + /* But we definitely don't need probin. */ *probin_str_p = NULL; - *prosrc_str_p = NULL; } else { diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index b2e2df87733..2cf6dad7685 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -195,6 +195,8 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) palloc0(nParamExec * sizeof(ParamExecData)); } + /* We now require all callers to provide sourceText */ + Assert(queryDesc->sourceText != NULL); estate->es_sourceText = queryDesc->sourceText; /* diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 642683843ed..39580f7d577 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -667,6 +667,15 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) procedureTuple, Anum_pg_proc_prosrc, &isNull); + if (isNull) + elog(ERROR, "null prosrc for function %u", foid); + fcache->src = TextDatumGetCString(tmp); + + /* If we have prosqlbody, pay attention to that not prosrc. */ + tmp = SysCacheGetAttr(PROCOID, + procedureTuple, + Anum_pg_proc_prosqlbody, + &isNull); /* * Parse and rewrite the queries in the function text. Use sublists to @@ -678,18 +687,11 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) * plancache.c. */ queryTree_list = NIL; - if (isNull) + if (!isNull) { Node *n; List *stored_query_list; - tmp = SysCacheGetAttr(PROCOID, - procedureTuple, - Anum_pg_proc_prosqlbody, - &isNull); - if (isNull) - elog(ERROR, "null prosrc and prosqlbody for function %u", foid); - n = stringToNode(TextDatumGetCString(tmp)); if (IsA(n, List)) stored_query_list = linitial_node(List, castNode(List, n)); @@ -710,8 +712,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK) { List *raw_parsetree_list; - fcache->src = TextDatumGetCString(tmp); - raw_parsetree_list = pg_parse_query(fcache->src); foreach(lc, raw_parsetree_list) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 526997327c6..d9ad4efc5ea 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4317,32 +4317,37 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(mycxt); + /* Fetch the function body */ + tmp = SysCacheGetAttr(PROCOID, + func_tuple, + Anum_pg_proc_prosrc, + &isNull); + if (isNull) + elog(ERROR, "null prosrc for function %u", funcid); + src = TextDatumGetCString(tmp); + /* * Setup error traceback support for ereport(). This is so that we can * finger the function that bad information came from. */ callback_arg.proname = NameStr(funcform->proname); - callback_arg.prosrc = NULL; + callback_arg.prosrc = src; sqlerrcontext.callback = sql_inline_error_callback; sqlerrcontext.arg = (void *) &callback_arg; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; - /* Fetch the function body */ + /* If we have prosqlbody, pay attention to that not prosrc */ tmp = SysCacheGetAttr(PROCOID, func_tuple, - Anum_pg_proc_prosrc, + Anum_pg_proc_prosqlbody, &isNull); - if (isNull) + if (!isNull) { Node *n; List *querytree_list; - tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull); - if (isNull) - elog(ERROR, "null prosrc and prosqlbody for function %u", funcid); - n = stringToNode(TextDatumGetCString(tmp)); if (IsA(n, List)) querytree_list = linitial_node(List, castNode(List, n)); @@ -4354,10 +4359,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, } else { - src = TextDatumGetCString(tmp); - - callback_arg.prosrc = src; - /* * Set up to handle parameters while parsing the function body. We need a * dummy FuncExpr node containing the already-simplified arguments to pass @@ -4658,15 +4659,12 @@ sql_inline_error_callback(void *arg) int syntaxerrposition; /* If it's a syntax error, convert to internal syntax error report */ - if (callback_arg->prosrc) + syntaxerrposition = geterrposition(); + if (syntaxerrposition > 0) { - syntaxerrposition = geterrposition(); - if (syntaxerrposition > 0) - { - errposition(0); - internalerrposition(syntaxerrposition); - internalerrquery(callback_arg->prosrc); - } + errposition(0); + internalerrposition(syntaxerrposition); + internalerrquery(callback_arg->prosrc); } errcontext("SQL function \"%s\" during inlining", callback_arg->proname); @@ -4778,6 +4776,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) Oid func_oid; HeapTuple func_tuple; Form_pg_proc funcform; + char *src; Datum tmp; bool isNull; MemoryContext oldcxt; @@ -4886,31 +4885,36 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) ALLOCSET_DEFAULT_SIZES); oldcxt = MemoryContextSwitchTo(mycxt); + /* Fetch the function body */ + tmp = SysCacheGetAttr(PROCOID, + func_tuple, + Anum_pg_proc_prosrc, + &isNull); + if (isNull) + elog(ERROR, "null prosrc for function %u", func_oid); + src = TextDatumGetCString(tmp); + /* * Setup error traceback support for ereport(). This is so that we can * finger the function that bad information came from. */ callback_arg.proname = NameStr(funcform->proname); - callback_arg.prosrc = NULL; + callback_arg.prosrc = src; sqlerrcontext.callback = sql_inline_error_callback; sqlerrcontext.arg = (void *) &callback_arg; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; - /* Fetch the function body */ + /* If we have prosqlbody, pay attention to that not prosrc */ tmp = SysCacheGetAttr(PROCOID, func_tuple, - Anum_pg_proc_prosrc, + Anum_pg_proc_prosqlbody, &isNull); - if (isNull) + if (!isNull) { Node *n; - tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull); - if (isNull) - elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid); - n = stringToNode(TextDatumGetCString(tmp)); if (IsA(n, List)) querytree_list = linitial_node(List, castNode(List, n)); @@ -4927,12 +4931,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) } else { - char *src; - - src = TextDatumGetCString(tmp); - - callback_arg.prosrc = src; - /* * Set up to handle parameters while parsing the function body. We can * use the FuncExpr just created as the input for diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 391947340f4..e397b763568 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -12247,7 +12247,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo) if (fout->remoteVersion >= 140000) appendPQExpBufferStr(query, - "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n"); + "pg_get_function_sqlbody(p.oid) AS prosqlbody\n"); else appendPQExpBufferStr(query, "NULL AS prosqlbody\n"); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index fdc2a89085a..400b683859c 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -512,7 +512,7 @@ describeFunctions(const char *functypes, const char *func_pattern, gettext_noop("Language")); if (pset.sversion >= 140000) appendPQExpBuffer(&buf, - ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"", + ",\n COALESCE(pg_catalog.pg_get_function_sqlbody(p.oid), p.prosrc) as \"%s\"", gettext_noop("Source code")); else appendPQExpBuffer(&buf, diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 45722fdbb13..87e9596da56 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202104081 +#define CATALOG_VERSION_NO 202104151 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 8d58067d034..448d9898cb3 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -112,7 +112,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce Oid protrftypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type); /* procedure source text */ - text prosrc; + text prosrc BKI_FORCE_NOT_NULL; /* secondary procedure info (can be NULL) */ text probin BKI_DEFAULT(_null_); diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h index dcb8e18437f..b56ce26da07 100644 --- a/src/include/executor/functions.h +++ b/src/include/executor/functions.h @@ -17,9 +17,6 @@ #include "nodes/execnodes.h" #include "tcop/dest.h" -/* This struct is known only within executor/functions.c */ -typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr; - /* * Data structure needed by the parser callback hooks to resolve parameter * references during parsing of a SQL function's body. This is separate from @@ -35,6 +32,8 @@ typedef struct SQLFunctionParseInfo Oid collation; /* function's input collation, if known */ } SQLFunctionParseInfo; +typedef SQLFunctionParseInfo *SQLFunctionParseInfoPtr; + extern Datum fmgr_sql(PG_FUNCTION_ARGS); extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple, diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out index 94ff7095e7d..ce480890127 100644 --- a/src/test/regress/expected/create_function_3.out +++ b/src/test/regress/expected/create_function_3.out @@ -290,6 +290,12 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement LANGUAGE SQL RETURN x[1]; ERROR: SQL function with unquoted function body cannot have polymorphic arguments +-- check reporting of parse-analysis errors +CREATE FUNCTION functest_S_xx(x date) RETURNS boolean + LANGUAGE SQL + RETURN x > 1; +ERROR: operator does not exist: date > integer +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. -- tricky parsing CREATE FUNCTION functest_S_15(x int) RETURNS boolean LANGUAGE SQL diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index fa26bf76104..7a0d345b608 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -91,10 +91,17 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR -----+--------- (0 rows) --- prosrc should never be null or empty +-- prosrc should never be null; it can be empty only if prosqlbody isn't null SELECT p1.oid, p1.proname FROM pg_proc as p1 -WHERE prosrc IS NULL OR prosrc = '' OR prosrc = '-'; +WHERE prosrc IS NULL; + oid | proname +-----+--------- +(0 rows) + +SELECT p1.oid, p1.proname +FROM pg_proc as p1 +WHERE (prosrc = '' OR prosrc = '-') AND prosqlbody IS NULL; oid | proname -----+--------- (0 rows) diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql index 592a43b5ed2..4b778999ed7 100644 --- a/src/test/regress/sql/create_function_3.sql +++ b/src/test/regress/sql/create_function_3.sql @@ -191,6 +191,11 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement LANGUAGE SQL RETURN x[1]; +-- check reporting of parse-analysis errors +CREATE FUNCTION functest_S_xx(x date) RETURNS boolean + LANGUAGE SQL + RETURN x > 1; + -- tricky parsing CREATE FUNCTION functest_S_15(x int) RETURNS boolean LANGUAGE SQL diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 04691745981..393acdf8c3c 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -96,10 +96,13 @@ WHERE p1.prolang = 0 OR p1.prorettype = 0 OR provolatile NOT IN ('i', 's', 'v') OR proparallel NOT IN ('s', 'r', 'u'); --- prosrc should never be null or empty +-- prosrc should never be null; it can be empty only if prosqlbody isn't null SELECT p1.oid, p1.proname FROM pg_proc as p1 -WHERE prosrc IS NULL OR prosrc = '' OR prosrc = '-'; +WHERE prosrc IS NULL; +SELECT p1.oid, p1.proname +FROM pg_proc as p1 +WHERE (prosrc = '' OR prosrc = '-') AND prosqlbody IS NULL; -- proretset should only be set for normal functions SELECT p1.oid, p1.proname |