Re: review: CHECK FUNCTION statement - Mailing list pgsql-hackers
From | Bruce Momjian |
---|---|
Subject | Re: review: CHECK FUNCTION statement |
Date | |
Msg-id | 20120817010800.GM30286@momjian.us Whole thread Raw |
In response to | Re: review: CHECK FUNCTION statement (Pavel Stehule <pavel.stehule@gmail.com>) |
Responses |
Re: review: CHECK FUNCTION statement
|
List | pgsql-hackers |
What happened to this feature patch? A TODO? --------------------------------------------------------------------------- On Tue, Nov 29, 2011 at 08:37:15PM +0100, Pavel Stehule wrote: > Hello > > updated patch: > > * recheck compilation and initdb > * working routines moved to pl_exec.c > * add entry to catalog.sgml about lanchecker field > * add node's utils > > Regards > > Pavel Stehule > > 2011/11/29 Albe Laurenz <laurenz.albe@wien.gv.at>: > > Pavel Stehule wrote: > >> I am sending updated patch, that implements a CHECK FUNCTION and CHECK > >> TRIGGER statements. > >> > >> This patch is significantly redesigned to previous version (PL/pgSQL > >> part) - it is more readable, more accurate. There are new regress > >> tests. > >> > >> Please, can some English native speaker fix doc and comments? > > > >> ToDo: > >> > >> CHECK FUNCTION search function according to function signature - it > >> should be changes for using a actual types - it can be solution for > >> polymorphic types and useful tool for work with overloaded functions - > >> when is not clean, that function was executed. > >> > >> check function foo(int, int); > >> NOTICE: checking function foo(variadic anyarray) > >> ... > >> > >> and maybe some support for named parameters > >> check function foo(name text, surname text); > >> NOTICE: checking function foo(text, text, text, text) > >> ... > > > > I think that CHECK FUNCTION should work exactly like DROP FUNCTION > > in these respects. > > > > Submission review: > > ------------------ > > > > The patch is context diff, applies with some offsets, contains > > regression tests and documentation. > > > > The documentation should be expanded, the doc for CHECK FUNCTION > > is only a stub. It should describe the procedure and what is checked. > > That would also make reviewing easier. > > I think that some documentation should be added to plhandler.sgml. > > There is a spelling error (statemnt) in the docs. > > > > Usability review: > > ----------------- > > > > If I understand right, the goal of CHECK FUNCTION is to find errors in > > the function definition without actually having to execute it. > > The patch tries to provide this for PL/pgSQL. > > > > There hasn't been any discussion on the list, the patch was just posted, > > so I can't say that we want that. Tom added it to the commitfest page, > > so there's one important voice against dismissing it right away :^) > > > > I don't understand the functional difference between a "validator function" > > and a "check function" as proposed by this patch. I am probably missing > > something, but why couldn't these checks be added to function validation > > when check_function_bodies is set? > > A new "CHECK FUNCTION" statement could simply call the validator function. > > > > I don't see any pg_dump support in this patch, and PL/pgSQL probably doesn't > > need that, but I think pg_dump support for CREATE LANGUAGE would have to > > be added for other PLs. > > > > I can't test if the functionality is complete because I can't get it to > > run (see below). > > > > Feature test: > > ------------- > > > > I can't really test the patch because initdb fails: > > > > $ initdb -E UTF8 --locale=de_DE.UTF-8 --lc-messages=en_US.UTF-8 -U postgres /postgres/cvs/dbhome > > The files belonging to this database system will be owned by user "laurenz". > > This user must also own the server process. > > > > The database cluster will be initialized with locales > > COLLATE: de_DE.UTF-8 > > CTYPE: de_DE.UTF-8 > > MESSAGES: en_US.UTF-8 > > MONETARY: de_DE.UTF-8 > > NUMERIC: de_DE.UTF-8 > > TIME: de_DE.UTF-8 > > The default text search configuration will be set to "german". > > > > creating directory /postgres/cvs/dbhome ... ok > > creating subdirectories ... ok > > selecting default max_connections ... 100 > > selecting default shared_buffers ... 32MB > > creating configuration files ... ok > > creating template1 database in /postgres/cvs/dbhome/base/1 ... ok > > initializing pg_authid ... ok > > initializing dependencies ... ok > > creating system views ... ok > > loading system objects' descriptions ... ok > > creating collations ... ok > > creating conversions ... ok > > creating dictionaries ... ok > > setting privileges on built-in objects ... ok > > creating information schema ... ok > > loading PL/pgSQL server-side language ... FATAL: could not load library "/postgres/cvs/pg92/lib/plpgsql.so": /postgres/cvs/pg92/lib/plpgsql.so:undefined symbol: plpgsql_delete_function > > STATEMENT: CREATE EXTENSION plpgsql; > > > > child process exited with exit code 1 > > initdb: removing data directory "/postgres/cvs/dbhome" > > > > Coding review: > > -------------- > > > > The patch compiles without warnings. > > The comments in the code should be revised, they are bad English. > > I can't say if there should be more of them -- I don't know this part of > > the code well enough to have a well-founded opinion. > > > > I don't think there are any portability issues, but I could not test it. > > > > There are a lot of small changes to pl/plpgsql/src/pl_exec.c, are they all > > necessary? For example, why was copy_plpgsql_datum renamed to > > plpgsql_copy_datum? > > > > I'll mark the patch as "Waiting on Author". > > > > Yours, > > Laurenz Albe > > > *** ./doc/src/sgml/catalogs.sgml.orig 2011-11-29 19:09:02.000000000 +0100 > --- ./doc/src/sgml/catalogs.sgml 2011-11-29 20:28:00.571246006 +0100 > *************** > *** 3652,3657 **** > --- 3652,3668 ---- > </row> > > <row> > + <entry><structfield>lanchecker</structfield></entry> > + <entry><type>oid</type></entry> > + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> > + <entry> > + This references a language checker function that is responsible > + for checking a embedded SQL and can provide detailed checking. > + Zero if no checker is provided. > + </entry> > + </row> > + > + <row> > <entry><structfield>lanacl</structfield></entry> > <entry><type>aclitem[]</type></entry> > <entry></entry> > *** ./doc/src/sgml/ref/allfiles.sgml.orig 2011-11-29 19:20:59.468117093 +0100 > --- ./doc/src/sgml/ref/allfiles.sgml 2011-11-29 19:21:24.487804955 +0100 > *************** > *** 40,45 **** > --- 40,46 ---- > <!ENTITY alterView SYSTEM "alter_view.sgml"> > <!ENTITY analyze SYSTEM "analyze.sgml"> > <!ENTITY begin SYSTEM "begin.sgml"> > + <!ENTITY checkFunction SYSTEM "check_function.sgml"> > <!ENTITY checkpoint SYSTEM "checkpoint.sgml"> > <!ENTITY close SYSTEM "close.sgml"> > <!ENTITY cluster SYSTEM "cluster.sgml"> > *** ./doc/src/sgml/ref/create_language.sgml.orig 2011-11-29 19:20:59.470117069 +0100 > --- ./doc/src/sgml/ref/create_language.sgml 2011-11-29 19:21:24.488804943 +0100 > *************** > *** 23,29 **** > <synopsis> > CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable> > CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable> > ! HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] > </synopsis> > </refsynopsisdiv> > > --- 23,29 ---- > <synopsis> > CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable> > CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable> > ! HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] [ CHECK <replaceable>checkfunction</replaceable>] > </synopsis> > </refsynopsisdiv> > > *************** > *** 217,222 **** > --- 217,236 ---- > </para> > </listitem> > </varlistentry> > + > + <varlistentry> > + <term><literal>CHECK</literal> <replaceable class="parameter">checkfunction</replaceable></term> > + > + <listitem> > + <para><replaceable class="parameter">checkfunction</replaceable> is the > + name of a previously registered function that will be called > + when a new function in the language is created, to check the > + function by statemnt <command>CHECK FUNCTION</command> or > + <command>CHECK TRIGGER</command>. > + </para> > + </listitem> > + </varlistentry> > + > </variablelist> > > <para> > *** ./doc/src/sgml/reference.sgml.orig 2011-11-29 19:20:59.471117057 +0100 > --- ./doc/src/sgml/reference.sgml 2011-11-29 19:21:24.492804895 +0100 > *************** > *** 68,73 **** > --- 68,74 ---- > &alterView; > &analyze; > &begin; > + &checkFunction; > &checkpoint; > &close; > &cluster; > *** ./src/backend/catalog/pg_proc.c.orig 2011-11-29 19:20:59.474117021 +0100 > --- ./src/backend/catalog/pg_proc.c 2011-11-29 19:21:24.494804869 +0100 > *************** > *** 1101,1103 **** > --- 1101,1104 ---- > *newcursorpos = newcp; > return false; > } > + > *** ./src/backend/commands/functioncmds.c.orig 2011-11-29 19:20:59.475117009 +0100 > --- ./src/backend/commands/functioncmds.c 2011-11-29 19:21:24.496804843 +0100 > *************** > *** 44,53 **** > --- 44,55 ---- > #include "catalog/pg_namespace.h" > #include "catalog/pg_proc.h" > #include "catalog/pg_proc_fn.h" > + #include "catalog/pg_trigger.h" > #include "catalog/pg_type.h" > #include "catalog/pg_type_fn.h" > #include "commands/defrem.h" > #include "commands/proclang.h" > + #include "commands/trigger.h" > #include "miscadmin.h" > #include "optimizer/var.h" > #include "parser/parse_coerce.h" > *************** > *** 60,65 **** > --- 62,68 ---- > #include "utils/fmgroids.h" > #include "utils/guc.h" > #include "utils/lsyscache.h" > + #include "utils/memutils.h" > #include "utils/rel.h" > #include "utils/syscache.h" > #include "utils/tqual.h" > *************** > *** 1009,1014 **** > --- 1012,1152 ---- > } > } > > + /* > + * CheckFunction > + * call a PL checker function when this function exists. > + */ > + void > + CheckFunction(CheckFunctionStmt *stmt) > + { > + List *functionName = stmt->funcname; > + List *argTypes = stmt->args; /* list of TypeName nodes */ > + Oid funcOid; > + > + HeapTuple tup; > + Form_pg_proc proc; > + > + HeapTuple languageTuple; > + Form_pg_language languageStruct; > + Oid languageChecker; > + Oid trgOid = InvalidOid; > + Oid relid = InvalidOid; > + > + /* when we should to check trigger, then we should to find a trigger handler */ > + if (functionName == NULL) > + { > + HeapTuple ht_trig; > + Form_pg_trigger trigrec; > + ScanKeyData skey[1]; > + Relation tgrel; > + SysScanDesc tgscan; > + char *fname; > + > + relid = RangeVarGetRelid(stmt->relation, ShareLock, false, false); > + trgOid = get_trigger_oid(relid, stmt->trgname, false); > + > + /* > + * Fetch the pg_trigger tuple by the Oid of the trigger > + */ > + tgrel = heap_open(TriggerRelationId, AccessShareLock); > + > + ScanKeyInit(&skey[0], > + ObjectIdAttributeNumber, > + BTEqualStrategyNumber, F_OIDEQ, > + ObjectIdGetDatum(trgOid)); > + > + tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true, > + SnapshotNow, 1, skey); > + > + ht_trig = systable_getnext(tgscan); > + > + if (!HeapTupleIsValid(ht_trig)) > + elog(ERROR, "could not find tuple for trigger %u", trgOid); > + > + trigrec = (Form_pg_trigger) GETSTRUCT(ht_trig); > + > + /* we need to know trigger function to get PL checker function */ > + funcOid = trigrec->tgfoid; > + fname = format_procedure(funcOid); > + /* Clean up */ > + systable_endscan(tgscan); > + > + elog(NOTICE, "checking function \"%s\"", fname); > + pfree(fname); > + > + heap_close(tgrel, AccessShareLock); > + } > + else > + { > + /* > + * Find the function, > + */ > + funcOid = LookupFuncNameTypeNames(functionName, argTypes, false); > + } > + > + tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid)); > + if (!HeapTupleIsValid(tup)) /* should not happen */ > + elog(ERROR, "cache lookup failed for function %u", funcOid); > + > + proc = (Form_pg_proc) GETSTRUCT(tup); > + > + languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang)); > + Assert(HeapTupleIsValid(languageTuple)); > + > + languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); > + languageChecker = languageStruct->lanchecker; > + > + /* Check a function body */ > + if (OidIsValid(languageChecker)) > + { > + ArrayType *set_items = NULL; > + int save_nestlevel; > + Datum datum; > + bool isnull; > + MemoryContext oldCxt; > + MemoryContext checkCxt; > + > + datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull); > + > + if (!isnull) > + { > + /* Set per-function configuration parameters */ > + set_items = (ArrayType *) DatumGetPointer(datum); > + if (set_items) /* Need a new GUC nesting level */ > + { > + save_nestlevel = NewGUCNestLevel(); > + ProcessGUCArray(set_items, > + (superuser() ? PGC_SUSET : PGC_USERSET), > + PGC_S_SESSION, > + GUC_ACTION_SAVE); > + } > + else > + save_nestlevel = 0; /* keep compiler quiet */ > + } > + > + checkCxt = AllocSetContextCreate(CurrentMemoryContext, > + "Check temporary context", > + ALLOCSET_DEFAULT_MINSIZE, > + ALLOCSET_DEFAULT_INITSIZE, > + ALLOCSET_DEFAULT_MAXSIZE); > + > + oldCxt = MemoryContextSwitchTo(checkCxt); > + > + OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), > + ObjectIdGetDatum(relid)); > + > + MemoryContextSwitchTo(oldCxt); > + > + if (set_items) > + AtEOXact_GUC(true, save_nestlevel); > + } > + else > + elog(WARNING, "language \"%s\" has no defined checker function", > + NameStr(languageStruct->lanname)); > + > + ReleaseSysCache(languageTuple); > + ReleaseSysCache(tup); > + } > > /* > * Rename function > *** ./src/backend/commands/proclang.c.orig 2011-11-29 19:20:59.477116983 +0100 > --- ./src/backend/commands/proclang.c 2011-11-29 19:21:24.497804830 +0100 > *************** > *** 46,57 **** > char *tmplhandler; /* name of handler function */ > char *tmplinline; /* name of anonymous-block handler, or NULL */ > char *tmplvalidator; /* name of validator function, or NULL */ > char *tmpllibrary; /* path of shared library */ > } PLTemplate; > > static void create_proc_lang(const char *languageName, bool replace, > Oid languageOwner, Oid handlerOid, Oid inlineOid, > ! Oid valOid, bool trusted); > static PLTemplate *find_language_template(const char *languageName); > static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel, > Oid newOwnerId); > --- 46,58 ---- > char *tmplhandler; /* name of handler function */ > char *tmplinline; /* name of anonymous-block handler, or NULL */ > char *tmplvalidator; /* name of validator function, or NULL */ > + char *tmplchecker; /* name of checker function, or NULL */ > char *tmpllibrary; /* path of shared library */ > } PLTemplate; > > static void create_proc_lang(const char *languageName, bool replace, > Oid languageOwner, Oid handlerOid, Oid inlineOid, > ! Oid valOid, Oid checkerOid, bool trusted); > static PLTemplate *find_language_template(const char *languageName); > static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel, > Oid newOwnerId); > *************** > *** 67,75 **** > PLTemplate *pltemplate; > Oid handlerOid, > inlineOid, > ! valOid; > Oid funcrettype; > ! Oid funcargtypes[1]; > > /* > * If we have template information for the language, ignore the supplied > --- 68,77 ---- > PLTemplate *pltemplate; > Oid handlerOid, > inlineOid, > ! valOid, > ! checkerOid; > Oid funcrettype; > ! Oid funcargtypes[2]; > > /* > * If we have template information for the language, ignore the supplied > *************** > *** 219,228 **** > else > valOid = InvalidOid; > > /* ok, create it */ > create_proc_lang(stmt->plname, stmt->replace, GetUserId(), > handlerOid, inlineOid, > ! valOid, pltemplate->tmpltrusted); > } > else > { > --- 221,269 ---- > else > valOid = InvalidOid; > > + /* > + * Likewise for the checker, if required; but we don't care about > + * its return type. > + */ > + if (pltemplate->tmplchecker) > + { > + funcname = SystemFuncName(pltemplate->tmplchecker); > + funcargtypes[0] = OIDOID; > + funcargtypes[1] = REGCLASSOID; > + checkerOid = LookupFuncName(funcname, 2, funcargtypes, true); > + if (!OidIsValid(checkerOid)) > + { > + checkerOid = ProcedureCreate(pltemplate->tmplchecker, > + PG_CATALOG_NAMESPACE, > + false, /* replace */ > + false, /* returnsSet */ > + VOIDOID, > + ClanguageId, > + F_FMGR_C_VALIDATOR, > + pltemplate->tmplchecker, > + pltemplate->tmpllibrary, > + false, /* isAgg */ > + false, /* isWindowFunc */ > + false, /* security_definer */ > + true, /* isStrict */ > + PROVOLATILE_VOLATILE, > + buildoidvector(funcargtypes, 2), > + PointerGetDatum(NULL), > + PointerGetDatum(NULL), > + PointerGetDatum(NULL), > + NIL, > + PointerGetDatum(NULL), > + 1, > + 0); > + } > + } > + else > + checkerOid = InvalidOid; > + > /* ok, create it */ > create_proc_lang(stmt->plname, stmt->replace, GetUserId(), > handlerOid, inlineOid, > ! valOid, checkerOid, pltemplate->tmpltrusted); > } > else > { > *************** > *** 294,303 **** > else > valOid = InvalidOid; > > /* ok, create it */ > create_proc_lang(stmt->plname, stmt->replace, GetUserId(), > handlerOid, inlineOid, > ! valOid, stmt->pltrusted); > } > } > > --- 335,355 ---- > else > valOid = InvalidOid; > > + /* validate the checker function */ > + if (stmt->plchecker) > + { > + funcargtypes[0] = OIDOID; > + funcargtypes[1] = REGCLASSOID; > + checkerOid = LookupFuncName(stmt->plchecker, 2, funcargtypes, false); > + /* return value is ignored, so we don't check the type */ > + } > + else > + checkerOid = InvalidOid; > + > /* ok, create it */ > create_proc_lang(stmt->plname, stmt->replace, GetUserId(), > handlerOid, inlineOid, > ! valOid, checkerOid, stmt->pltrusted); > } > } > > *************** > *** 307,313 **** > static void > create_proc_lang(const char *languageName, bool replace, > Oid languageOwner, Oid handlerOid, Oid inlineOid, > ! Oid valOid, bool trusted) > { > Relation rel; > TupleDesc tupDesc; > --- 359,365 ---- > static void > create_proc_lang(const char *languageName, bool replace, > Oid languageOwner, Oid handlerOid, Oid inlineOid, > ! Oid valOid, Oid checkerOid, bool trusted) > { > Relation rel; > TupleDesc tupDesc; > *************** > *** 337,342 **** > --- 389,395 ---- > values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid); > values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid); > values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid); > + values[Anum_pg_language_lanchecker - 1] = ObjectIdGetDatum(checkerOid); > nulls[Anum_pg_language_lanacl - 1] = true; > > /* Check for pre-existing definition */ > *************** > *** 423,428 **** > --- 476,490 ---- > recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); > } > > + /* dependency on the checker function, if any */ > + if (OidIsValid(checkerOid)) > + { > + referenced.classId = ProcedureRelationId; > + referenced.objectId = checkerOid; > + referenced.objectSubId = 0; > + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); > + } > + > /* Post creation hook for new procedural language */ > InvokeObjectAccessHook(OAT_POST_CREATE, > LanguageRelationId, myself.objectId, 0); > *************** > *** 478,483 **** > --- 540,550 ---- > if (!isnull) > result->tmplvalidator = TextDatumGetCString(datum); > > + datum = heap_getattr(tup, Anum_pg_pltemplate_tmplchecker, > + RelationGetDescr(rel), &isnull); > + if (!isnull) > + result->tmplchecker = TextDatumGetCString(datum); > + > datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary, > RelationGetDescr(rel), &isnull); > if (!isnull) > *** ./src/backend/nodes/copyfuncs.c.orig 2011-11-29 19:09:02.000000000 +0100 > --- ./src/backend/nodes/copyfuncs.c 2011-11-29 20:17:01.339172458 +0100 > *************** > *** 2880,2885 **** > --- 2880,2898 ---- > return newnode; > } > > + static CheckFunctionStmt * > + _copyCheckFunctionStmt(CheckFunctionStmt *from) > + { > + CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt); > + > + COPY_NODE_FIELD(funcname); > + COPY_NODE_FIELD(args); > + COPY_STRING_FIELD(trgname); > + COPY_NODE_FIELD(relation); > + > + return newnode; > + } > + > static DoStmt * > _copyDoStmt(DoStmt *from) > { > *************** > *** 4165,4170 **** > --- 4178,4186 ---- > case T_AlterFunctionStmt: > retval = _copyAlterFunctionStmt(from); > break; > + case T_CheckFunctionStmt: > + retval = _copyCheckFunctionStmt(from); > + break; > case T_DoStmt: > retval = _copyDoStmt(from); > break; > *** ./src/backend/nodes/equalfuncs.c.orig 2011-11-29 20:19:55.045587471 +0100 > --- ./src/backend/nodes/equalfuncs.c 2011-11-29 20:19:21.850082357 +0100 > *************** > *** 1292,1297 **** > --- 1292,1308 ---- > } > > static bool > + _equalCheckFunctionStmt(CheckFunctionStmt *a, CheckFunctionStmt *b) > + { > + COMPARE_NODE_FIELD(funcname); > + COMPARE_NODE_FIELD(args); > + COMPARE_STRING_FIELD(trgname); > + COMPARE_NODE_FIELD(relation); > + > + return true; > + } > + > + static bool > _equalDoStmt(DoStmt *a, DoStmt *b) > { > COMPARE_NODE_FIELD(args); > *************** > *** 2708,2713 **** > --- 2719,2727 ---- > case T_AlterFunctionStmt: > retval = _equalAlterFunctionStmt(a, b); > break; > + case T_CheckFunctionStmt: > + retval = _equalCheckFunctionStmt(a, b); > + break; > case T_DoStmt: > retval = _equalDoStmt(a, b); > break; > *** ./src/backend/parser/gram.y.orig 2011-11-29 19:09:02.876463248 +0100 > --- ./src/backend/parser/gram.y 2011-11-29 19:21:24.502804769 +0100 > *************** > *** 227,232 **** > --- 227,233 ---- > DeallocateStmt PrepareStmt ExecuteStmt > DropOwnedStmt ReassignOwnedStmt > AlterTSConfigurationStmt AlterTSDictionaryStmt > + CheckFunctionStmt > > %type <node> select_no_parens select_with_parens select_clause > simple_select values_clause > *************** > *** 276,282 **** > > %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op > opt_class opt_inline_handler opt_validator validator_clause > ! opt_collate > > %type <range> qualified_name OptConstrFromTable > > --- 277,283 ---- > > %type <list> func_name handler_name qual_Op qual_all_Op subquery_Op > opt_class opt_inline_handler opt_validator validator_clause > ! opt_collate opt_checker > > %type <range> qualified_name OptConstrFromTable > > *************** > *** 700,705 **** > --- 701,707 ---- > | AlterUserSetStmt > | AlterUserStmt > | AnalyzeStmt > + | CheckFunctionStmt > | CheckPointStmt > | ClosePortalStmt > | ClusterStmt > *************** > *** 3174,3184 **** > n->plhandler = NIL; > n->plinline = NIL; > n->plvalidator = NIL; > n->pltrusted = false; > $$ = (Node *)n; > } > | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst > ! HANDLER handler_name opt_inline_handler opt_validator > { > CreatePLangStmt *n = makeNode(CreatePLangStmt); > n->replace = $2; > --- 3176,3187 ---- > n->plhandler = NIL; > n->plinline = NIL; > n->plvalidator = NIL; > + n->plchecker = NIL; > n->pltrusted = false; > $$ = (Node *)n; > } > | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE ColId_or_Sconst > ! HANDLER handler_name opt_inline_handler opt_validator opt_checker > { > CreatePLangStmt *n = makeNode(CreatePLangStmt); > n->replace = $2; > *************** > *** 3186,3191 **** > --- 3189,3195 ---- > n->plhandler = $8; > n->plinline = $9; > n->plvalidator = $10; > + n->plchecker = $11; > n->pltrusted = $3; > $$ = (Node *)n; > } > *************** > *** 3220,3225 **** > --- 3224,3234 ---- > | /*EMPTY*/ { $$ = NIL; } > ; > > + opt_checker: > + CHECK handler_name { $$ = $2; } > + | /*EMPTY*/ { $$ = NIL; } > + ; > + > DropPLangStmt: > DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior > { > *************** > *** 6250,6255 **** > --- 6259,6294 ---- > > /***************************************************************************** > * > + * CHECK FUNCTION funcname(args) > + * CHECK TRIGGER triggername ON table > + * > + * > + *****************************************************************************/ > + > + > + CheckFunctionStmt: > + CHECK FUNCTION func_name func_args > + { > + CheckFunctionStmt *n = makeNode(CheckFunctionStmt); > + n->funcname = $3; > + n->args = extractArgTypes($4); > + n->trgname = NULL; > + n->relation = NULL; > + $$ = (Node *) n; > + } > + | CHECK TRIGGER name ON qualified_name > + { > + CheckFunctionStmt *n = makeNode(CheckFunctionStmt); > + n->funcname = NULL; > + n->args = NIL; > + n->trgname = $3; > + n->relation = $5; > + $$ = (Node *) n; > + } > + ; > + > + /***************************************************************************** > + * > * DO <anonymous code block> [ LANGUAGE language ] > * > * We use a DefElem list for future extensibility, and to allow flexibility > *** ./src/backend/tcop/utility.c.orig 2011-11-29 19:20:59.480116945 +0100 > --- ./src/backend/tcop/utility.c 2011-11-29 19:21:24.513804628 +0100 > *************** > *** 882,887 **** > --- 882,891 ---- > AlterFunction((AlterFunctionStmt *) parsetree); > break; > > + case T_CheckFunctionStmt: > + CheckFunction((CheckFunctionStmt *) parsetree); > + break; > + > case T_IndexStmt: /* CREATE INDEX */ > { > IndexStmt *stmt = (IndexStmt *) parsetree; > *************** > *** 2125,2130 **** > --- 2129,2141 ---- > } > break; > > + case T_CheckFunctionStmt: > + if (((CheckFunctionStmt *) parsetree)->funcname != NULL) > + tag = "CHECK FUNCTION"; > + else > + tag = "CHECK TRIGGER"; > + break; > + > default: > elog(WARNING, "unrecognized node type: %d", > (int) nodeTag(parsetree)); > *************** > *** 2565,2570 **** > --- 2576,2585 ---- > } > break; > > + case T_CheckFunctionStmt: > + lev = LOGSTMT_ALL; > + break; > + > default: > elog(WARNING, "unrecognized node type: %d", > (int) nodeTag(parsetree)); > *** ./src/bin/pg_dump/pg_dump.c.orig 2011-11-29 19:09:03.000000000 +0100 > --- ./src/bin/pg_dump/pg_dump.c 2011-11-29 20:04:31.094156626 +0100 > *************** > *** 5326,5338 **** > int i_lanplcallfoid; > int i_laninline; > int i_lanvalidator; > int i_lanacl; > int i_lanowner; > > /* Make sure we are in proper schema */ > selectSourceSchema("pg_catalog"); > > ! if (g_fout->remoteVersion >= 90000) > { > /* pg_language has a laninline column */ > appendPQExpBuffer(query, "SELECT tableoid, oid, " > --- 5326,5351 ---- > int i_lanplcallfoid; > int i_laninline; > int i_lanvalidator; > + int i_lanchecker; > int i_lanacl; > int i_lanowner; > > /* Make sure we are in proper schema */ > selectSourceSchema("pg_catalog"); > > ! if (g_fout->remoteVersion >= 90200) > ! { > ! /* pg_language has a lanchecker column */ > ! appendPQExpBuffer(query, "SELECT tableoid, oid, " > ! "lanname, lanpltrusted, lanplcallfoid, " > ! "laninline, lanvalidator, lanchecker, lanacl, " > ! "(%s lanowner) AS lanowner " > ! "FROM pg_language " > ! "WHERE lanispl " > ! "ORDER BY oid", > ! username_subquery); > ! } > ! else if (g_fout->remoteVersion >= 90000) > { > /* pg_language has a laninline column */ > appendPQExpBuffer(query, "SELECT tableoid, oid, " > *************** > *** 5409,5414 **** > --- 5422,5428 ---- > /* these may fail and return -1: */ > i_laninline = PQfnumber(res, "laninline"); > i_lanvalidator = PQfnumber(res, "lanvalidator"); > + i_lanchecker = PQfnumber(res, "lanchecker"); > i_lanacl = PQfnumber(res, "lanacl"); > i_lanowner = PQfnumber(res, "lanowner"); > > *************** > *** 5422,5427 **** > --- 5436,5445 ---- > planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname)); > planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't'; > planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid)); > + if (i_lanchecker >= 0) > + planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker)); > + else > + planginfo[i].lanchecker = InvalidOid; > if (i_laninline >= 0) > planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline)); > else > *************** > *** 8597,8602 **** > --- 8615,8621 ---- > char *qlanname; > char *lanschema; > FuncInfo *funcInfo; > + FuncInfo *checkerInfo = NULL; > FuncInfo *inlineInfo = NULL; > FuncInfo *validatorInfo = NULL; > > *************** > *** 8616,8621 **** > --- 8635,8647 ---- > if (funcInfo != NULL && !funcInfo->dobj.dump) > funcInfo = NULL; /* treat not-dumped same as not-found */ > > + if (OidIsValid(plang->lanchecker)) > + { > + checkerInfo = findFuncByOid(plang->lanchecker); > + if (checkerInfo != NULL && !checkerInfo->dobj.dump) > + checkerInfo = NULL; > + } > + > if (OidIsValid(plang->laninline)) > { > inlineInfo = findFuncByOid(plang->laninline); > *************** > *** 8642,8647 **** > --- 8668,8674 ---- > * don't, this might not work terribly nicely. > */ > useParams = (funcInfo != NULL && > + (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) && > (inlineInfo != NULL || !OidIsValid(plang->laninline)) && > (validatorInfo != NULL || !OidIsValid(plang->lanvalidator))); > > *************** > *** 8697,8702 **** > --- 8724,8739 ---- > appendPQExpBuffer(defqry, "%s", > fmtId(validatorInfo->dobj.name)); > } > + if (OidIsValid(plang->lanchecker)) > + { > + appendPQExpBuffer(defqry, " CHECK "); > + /* Cope with possibility that checker is in different schema */ > + if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace) > + appendPQExpBuffer(defqry, "%s.", > + fmtId(checkerInfo->dobj.namespace->dobj.name)); > + appendPQExpBuffer(defqry, "%s", > + fmtId(checkerInfo->dobj.name)); > + } > } > else > { > *** ./src/bin/pg_dump/pg_dump.h.orig 2011-11-29 20:05:48.255044631 +0100 > --- ./src/bin/pg_dump/pg_dump.h 2011-11-29 20:05:08.766614345 +0100 > *************** > *** 387,392 **** > --- 387,393 ---- > Oid lanplcallfoid; > Oid laninline; > Oid lanvalidator; > + Oid lanchecker; > char *lanacl; > char *lanowner; /* name of owner, or empty string */ > } ProcLangInfo; > *** ./src/bin/psql/tab-complete.c.orig 2011-11-29 19:20:59.482116921 +0100 > --- ./src/bin/psql/tab-complete.c 2011-11-29 19:21:24.516804592 +0100 > *************** > *** 1,4 **** > --- 1,5 ---- > /* > + * > * psql - the PostgreSQL interactive terminal > * > * Copyright (c) 2000-2011, PostgreSQL Global Development Group > *************** > *** 727,733 **** > #define prev6_wd (previous_words[5]) > > static const char *const sql_commands[] = { > ! "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", > "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", > "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH", > "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE", > --- 728,734 ---- > #define prev6_wd (previous_words[5]) > > static const char *const sql_commands[] = { > ! "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECK", "CHECKPOINT", "CLOSE", "CLUSTER", > "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", > "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH", > "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE", > *************** > *** 1524,1529 **** > --- 1525,1552 ---- > > COMPLETE_WITH_LIST(list_TRANS); > } > + > + /* CHECK */ > + else if (pg_strcasecmp(prev_wd, "CHECK") == 0) > + { > + static const char *const list_CHECK[] = > + {"FUNCTION", "TRIGGER", NULL}; > + > + COMPLETE_WITH_LIST(list_CHECK); > + } > + else if (pg_strcasecmp(prev3_wd, "CHECK") == 0 && > + pg_strcasecmp(prev2_wd, "TRIGGER") == 0) > + { > + COMPLETE_WITH_CONST("ON"); > + } > + else if (pg_strcasecmp(prev4_wd, "CHECK") == 0 && > + pg_strcasecmp(prev3_wd, "TRIGGER") == 0 && > + pg_strcasecmp(prev_wd, "ON") == 0) > + { > + completion_info_charp = prev2_wd; > + COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger); > + } > + > /* CLUSTER */ > > /* > *** ./src/include/catalog/pg_language.h.orig 2011-11-29 19:20:59.483116909 +0100 > --- ./src/include/catalog/pg_language.h 2011-11-29 19:21:24.518804568 +0100 > *************** > *** 37,42 **** > --- 37,43 ---- > Oid lanplcallfoid; /* Call handler for PL */ > Oid laninline; /* Optional anonymous-block handler function */ > Oid lanvalidator; /* Optional validation function */ > + Oid lanchecker; /* Optional checker function */ > aclitem lanacl[1]; /* Access privileges */ > } FormData_pg_language; > > *************** > *** 51,57 **** > * compiler constants for pg_language > * ---------------- > */ > ! #define Natts_pg_language 8 > #define Anum_pg_language_lanname 1 > #define Anum_pg_language_lanowner 2 > #define Anum_pg_language_lanispl 3 > --- 52,58 ---- > * compiler constants for pg_language > * ---------------- > */ > ! #define Natts_pg_language 9 > #define Anum_pg_language_lanname 1 > #define Anum_pg_language_lanowner 2 > #define Anum_pg_language_lanispl 3 > *************** > *** 59,78 **** > #define Anum_pg_language_lanplcallfoid 5 > #define Anum_pg_language_laninline 6 > #define Anum_pg_language_lanvalidator 7 > ! #define Anum_pg_language_lanacl 8 > > /* ---------------- > * initial contents of pg_language > * ---------------- > */ > > ! DATA(insert OID = 12 ( "internal" PGUID f f 0 0 2246 _null_ )); > DESCR("built-in functions"); > #define INTERNALlanguageId 12 > ! DATA(insert OID = 13 ( "c" PGUID f f 0 0 2247 _null_ )); > DESCR("dynamically-loaded C functions"); > #define ClanguageId 13 > ! DATA(insert OID = 14 ( "sql" PGUID f t 0 0 2248 _null_ )); > DESCR("SQL-language functions"); > #define SQLlanguageId 14 > > --- 60,80 ---- > #define Anum_pg_language_lanplcallfoid 5 > #define Anum_pg_language_laninline 6 > #define Anum_pg_language_lanvalidator 7 > ! #define Anum_pg_language_lanchecker 8 > ! #define Anum_pg_language_lanacl 9 > > /* ---------------- > * initial contents of pg_language > * ---------------- > */ > > ! DATA(insert OID = 12 ( "internal" PGUID f f 0 0 2246 0 _null_ )); > DESCR("built-in functions"); > #define INTERNALlanguageId 12 > ! DATA(insert OID = 13 ( "c" PGUID f f 0 0 2247 0 _null_ )); > DESCR("dynamically-loaded C functions"); > #define ClanguageId 13 > ! DATA(insert OID = 14 ( "sql" PGUID f t 0 0 2248 0 _null_ )); > DESCR("SQL-language functions"); > #define SQLlanguageId 14 > > *** ./src/include/catalog/pg_pltemplate.h.orig 2011-11-29 19:20:59.484116897 +0100 > --- ./src/include/catalog/pg_pltemplate.h 2011-11-29 19:21:24.518804568 +0100 > *************** > *** 36,41 **** > --- 36,42 ---- > text tmplhandler; /* name of call handler function */ > text tmplinline; /* name of anonymous-block handler, or NULL */ > text tmplvalidator; /* name of validator function, or NULL */ > + text tmplchecker; /* name of checker function, or NULL */ > text tmpllibrary; /* path of shared library */ > aclitem tmplacl[1]; /* access privileges for template */ > } FormData_pg_pltemplate; > *************** > *** 51,65 **** > * compiler constants for pg_pltemplate > * ---------------- > */ > ! #define Natts_pg_pltemplate 8 > #define Anum_pg_pltemplate_tmplname 1 > #define Anum_pg_pltemplate_tmpltrusted 2 > #define Anum_pg_pltemplate_tmpldbacreate 3 > #define Anum_pg_pltemplate_tmplhandler 4 > #define Anum_pg_pltemplate_tmplinline 5 > #define Anum_pg_pltemplate_tmplvalidator 6 > ! #define Anum_pg_pltemplate_tmpllibrary 7 > ! #define Anum_pg_pltemplate_tmplacl 8 > > > /* ---------------- > --- 52,67 ---- > * compiler constants for pg_pltemplate > * ---------------- > */ > ! #define Natts_pg_pltemplate 9 > #define Anum_pg_pltemplate_tmplname 1 > #define Anum_pg_pltemplate_tmpltrusted 2 > #define Anum_pg_pltemplate_tmpldbacreate 3 > #define Anum_pg_pltemplate_tmplhandler 4 > #define Anum_pg_pltemplate_tmplinline 5 > #define Anum_pg_pltemplate_tmplvalidator 6 > ! #define Anum_pg_pltemplate_tmplchecker 7 > ! #define Anum_pg_pltemplate_tmpllibrary 8 > ! #define Anum_pg_pltemplate_tmplacl 9 > > > /* ---------------- > *************** > *** 67,79 **** > * ---------------- > */ > > ! DATA(insert ( "plpgsql" t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "$libdir/plpgsql"_null_ )); > ! DATA(insert ( "pltcl" t t "pltcl_call_handler" _null_ _null_ "$libdir/pltcl" _null_ )); > ! DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" _null_ )); > ! DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_)); > ! DATA(insert ( "plperlu" f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" "$libdir/plperl"_null_ )); > ! DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" "$libdir/plpython2"_null_ )); > ! DATA(insert ( "plpython2u" f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" "$libdir/plpython2"_null_ )); > ! DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" "$libdir/plpython3"_null_ )); > > #endif /* PG_PLTEMPLATE_H */ > --- 69,81 ---- > * ---------------- > */ > > ! DATA(insert ( "plpgsql" t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" "plpgsql_checker""$libdir/plpgsql" _null_ )); > ! DATA(insert ( "pltcl" t t "pltcl_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ )); > ! DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ _null_ "$libdir/pltcl" _null_ )); > ! DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" _null_ "$libdir/plperl"_null_ )); > ! DATA(insert ( "plperlu" f f "plperlu_call_handler" "plperlu_inline_handler" "plperlu_validator" _null_ "$libdir/plperl"_null_ )); > ! DATA(insert ( "plpythonu" f f "plpython_call_handler" "plpython_inline_handler" "plpython_validator" _null_ "$libdir/plpython2"_null_ )); > ! DATA(insert ( "plpython2u" f f "plpython2_call_handler" "plpython2_inline_handler" "plpython2_validator" _null_ "$libdir/plpython2"_null_ )); > ! DATA(insert ( "plpython3u" f f "plpython3_call_handler" "plpython3_inline_handler" "plpython3_validator" _null_ "$libdir/plpython3"_null_ )); > > #endif /* PG_PLTEMPLATE_H */ > *** ./src/include/commands/defrem.h.orig 2011-11-29 19:20:59.486116871 +0100 > --- ./src/include/commands/defrem.h 2011-11-29 19:21:24.519804556 +0100 > *************** > *** 62,67 **** > --- 62,68 ---- > /* commands/functioncmds.c */ > extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString); > extern void RemoveFunctionById(Oid funcOid); > + extern void CheckFunction(CheckFunctionStmt *stmt); > extern void SetFunctionReturnType(Oid funcOid, Oid newRetType); > extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType); > extern void RenameFunction(List *name, List *argtypes, const char *newname); > *** ./src/include/nodes/nodes.h.orig 2011-11-29 19:20:59.487116858 +0100 > --- ./src/include/nodes/nodes.h 2011-11-29 19:21:24.521804532 +0100 > *************** > *** 291,296 **** > --- 291,297 ---- > T_IndexStmt, > T_CreateFunctionStmt, > T_AlterFunctionStmt, > + T_CheckFunctionStmt, > T_DoStmt, > T_RenameStmt, > T_RuleStmt, > *** ./src/include/nodes/parsenodes.h.orig 2011-11-29 19:20:59.489116833 +0100 > --- ./src/include/nodes/parsenodes.h 2011-11-29 19:21:24.523804506 +0100 > *************** > *** 1734,1739 **** > --- 1734,1740 ---- > List *plhandler; /* PL call handler function (qual. name) */ > List *plinline; /* optional inline function (qual. name) */ > List *plvalidator; /* optional validator function (qual. name) */ > + List *plchecker; /* optional checker function (qual. name) */ > bool pltrusted; /* PL is trusted */ > } CreatePLangStmt; > > *************** > *** 2077,2082 **** > --- 2078,2096 ---- > } AlterFunctionStmt; > > /* ---------------------- > + * Check {Function|Trigger} Statement > + * ---------------------- > + */ > + typedef struct CheckFunctionStmt > + { > + NodeTag type; > + List *funcname; /* qualified name of checked object */ > + List *args; /* types of the arguments */ > + char *trgname; /* trigger's name */ > + RangeVar *relation; /* trigger's relation */ > + } CheckFunctionStmt; > + > + /* ---------------------- > * DO Statement > * > * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API > *** ./src/pl/plpgsql/src/pl_comp.c.orig 2011-11-29 19:09:03.000000000 +0100 > --- ./src/pl/plpgsql/src/pl_comp.c 2011-11-29 19:42:43.058753779 +0100 > *************** > *** 115,121 **** > static void plpgsql_HashTableInsert(PLpgSQL_function *function, > PLpgSQL_func_hashkey *func_key); > static void plpgsql_HashTableDelete(PLpgSQL_function *function); > - static void delete_function(PLpgSQL_function *func); > > /* ---------- > * plpgsql_compile Make an execution tree for a PL/pgSQL function. > --- 115,120 ---- > *************** > *** 175,181 **** > * Nope, so remove it from hashtable and try to drop associated > * storage (if not done already). > */ > ! delete_function(function); > > /* > * If the function isn't in active use then we can overwrite the > --- 174,180 ---- > * Nope, so remove it from hashtable and try to drop associated > * storage (if not done already). > */ > ! plpgsql_delete_function(function); > > /* > * If the function isn't in active use then we can overwrite the > *************** > *** 2426,2432 **** > } > > /* > ! * delete_function - clean up as much as possible of a stale function cache > * > * We can't release the PLpgSQL_function struct itself, because of the > * possibility that there are fn_extra pointers to it. We can release > --- 2425,2431 ---- > } > > /* > ! * plpgsql_delete_function - clean up as much as possible of a stale function cache > * > * We can't release the PLpgSQL_function struct itself, because of the > * possibility that there are fn_extra pointers to it. We can release > *************** > *** 2439,2446 **** > * pointers to the same function cache. Hence be careful not to do things > * twice. > */ > ! static void > ! delete_function(PLpgSQL_function *func) > { > /* remove function from hash table (might be done already) */ > plpgsql_HashTableDelete(func); > --- 2438,2445 ---- > * pointers to the same function cache. Hence be careful not to do things > * twice. > */ > ! void > ! plpgsql_delete_function(PLpgSQL_function *func) > { > /* remove function from hash table (might be done already) */ > plpgsql_HashTableDelete(func); > *** ./src/pl/plpgsql/src/pl_exec.c.orig 2011-11-29 19:09:03.316459122 +0100 > --- ./src/pl/plpgsql/src/pl_exec.c 2011-11-29 19:37:19.000000000 +0100 > *************** > *** 210,216 **** > static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate, > PLpgSQL_expr *dynquery, List *params, > const char *portalname, int cursorOptions); > ! > > /* ---------- > * plpgsql_exec_function Called by the call handler for > --- 210,228 ---- > static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate, > PLpgSQL_expr *dynquery, List *params, > const char *portalname, int cursorOptions); > ! static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec); > ! static void check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr); > ! static void assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate, > ! PLpgSQL_row *row, PLpgSQL_rec *rec, > ! TupleDesc tupdesc); > ! static TupleDesc expr_get_desc(PLpgSQL_execstate *estate, > ! PLpgSQL_expr *query, > ! bool use_element_type, > ! bool expand_record, > ! bool is_expression); > ! static void var_init_to_null(PLpgSQL_execstate *estate, int varno); > ! static void check_stmts(PLpgSQL_execstate *estate, List *stmts); > ! static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); > > /* ---------- > * plpgsql_exec_function Called by the call handler for > *************** > *** 6176,6178 **** > --- 6188,7242 ---- > > return portal; > } > + > + /* > + * Following code ensures a CHECK FUNCTION and CHECK TRIGGER statements for PL/pgSQL > + * > + */ > + > + /* > + * append a CONTEXT to error message > + */ > + static void > + check_error_callback(void *arg) > + { > + PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg; > + > + if (estate->err_stmt != NULL) > + { > + /* translator: last %s is a plpgsql statement type name */ > + errcontext("checking of PL/pgSQL function \"%s\" line %d at %s", > + estate->func->fn_name, > + estate->err_stmt->lineno, > + plpgsql_stmt_typename(estate->err_stmt)); > + } > + else > + errcontext("checking of PL/pgSQL function \"%s\"", > + estate->func->fn_name); > + } > + > + /* > + * Check function - it prepare variables and starts a prepare plan walker > + * called by function checker > + */ > + void > + plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo) > + { > + PLpgSQL_execstate estate; > + ErrorContextCallback plerrcontext; > + int i; > + > + /* Setup error callback for ereport */ > + plerrcontext.callback = check_error_callback; > + plerrcontext.arg = &estate; > + plerrcontext.previous = error_context_stack; > + error_context_stack = &plerrcontext; > + > + /* > + * Setup the execution state - we would to reuse some exec routines > + * so we need a estate > + */ > + plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo); > + > + /* > + * Make local execution copies of all the datums > + */ > + for (i = 0; i < estate.ndatums; i++) > + estate.datums[i] = copy_plpgsql_datum(func->datums[i]); > + > + /* > + * Store the actual call argument values into the appropriate variables > + */ > + for (i = 0; i < func->fn_nargs; i++) > + { > + int n = func->fn_argvarnos[i]; > + > + switch (estate.datums[n]->dtype) > + { > + case PLPGSQL_DTYPE_VAR: > + { > + var_init_to_null(&estate, n); > + } > + break; > + > + case PLPGSQL_DTYPE_ROW: > + { > + PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n]; > + > + exec_move_row(&estate, NULL, row, NULL, NULL); > + } > + break; > + > + default: > + elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype); > + } > + } > + > + /* > + * Now check the toplevel block of statements > + */ > + check_stmt(&estate, (PLpgSQL_stmt *) func->action); > + > + /* Cleanup temporary memory */ > + plpgsql_destroy_econtext(&estate); > + > + /* Pop the error context stack */ > + error_context_stack = plerrcontext.previous; > + } > + > + /* > + * Check trigger - prepare fake environments for testing trigger > + * > + */ > + void > + plpgsql_check_trigger(PLpgSQL_function *func, > + TriggerData *trigdata) > + { > + PLpgSQL_execstate estate; > + ErrorContextCallback plerrcontext; > + PLpgSQL_rec *rec_new, > + *rec_old; > + int i; > + > + /* Setup error callback for ereport */ > + plerrcontext.callback = check_error_callback; > + plerrcontext.arg = &estate; > + plerrcontext.previous = error_context_stack; > + error_context_stack = &plerrcontext; > + > + /* > + * Setup the execution state - we would to reuse some exec routines > + * so we need a estate > + */ > + plpgsql_estate_setup(&estate, func, NULL); > + > + /* > + * Make local execution copies of all the datums > + */ > + for (i = 0; i < estate.ndatums; i++) > + estate.datums[i] = copy_plpgsql_datum(func->datums[i]); > + > + /* > + * Put the OLD and NEW tuples into record variables > + * > + * We make the tupdescs available in both records even though only one may > + * have a value. This allows parsing of record references to succeed in > + * functions that are used for multiple trigger types. For example, we > + * might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')", > + * which should parse regardless of the current trigger type. > + */ > + rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]); > + rec_new->freetup = false; > + rec_new->freetupdesc = false; > + assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att); > + > + rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]); > + rec_old->freetup = false; > + rec_old->freetupdesc = false; > + assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att); > + > + /* > + * Assign the special tg_ variables > + */ > + var_init_to_null(&estate, func->tg_op_varno); > + var_init_to_null(&estate, func->tg_name_varno); > + var_init_to_null(&estate, func->tg_when_varno); > + var_init_to_null(&estate, func->tg_level_varno); > + var_init_to_null(&estate, func->tg_relid_varno); > + var_init_to_null(&estate, func->tg_relname_varno); > + var_init_to_null(&estate, func->tg_table_name_varno); > + var_init_to_null(&estate, func->tg_table_schema_varno); > + var_init_to_null(&estate, func->tg_nargs_varno); > + var_init_to_null(&estate, func->tg_argv_varno); > + > + /* > + * Now check the toplevel block of statements > + */ > + check_stmt(&estate, (PLpgSQL_stmt *) func->action); > + > + /* Cleanup temporary memory */ > + plpgsql_destroy_econtext(&estate); > + > + /* Pop the error context stack */ > + error_context_stack = plerrcontext.previous; > + } > + > + /* > + * Verify lvalue > + * It doesn't repeat a checks that are done. > + * Checks a subscript expressions, verify a validity of record's fields > + */ > + static void > + check_target(PLpgSQL_execstate *estate, int varno) > + { > + PLpgSQL_datum *target = estate->datums[varno]; > + > + switch (target->dtype) > + { > + case PLPGSQL_DTYPE_VAR: > + case PLPGSQL_DTYPE_REC: > + break; > + > + case PLPGSQL_DTYPE_ROW: > + check_row_or_rec(estate, (PLpgSQL_row *) target, NULL); > + break; > + > + case PLPGSQL_DTYPE_RECFIELD: > + { > + PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target; > + PLpgSQL_rec *rec; > + int fno; > + > + rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); > + > + /* > + * Check that there is already a tuple in the record. We need > + * that because records don't have any predefined field > + * structure. > + */ > + if (!HeapTupleIsValid(rec->tup)) > + ereport(ERROR, > + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), > + errmsg("record \"%s\" is not assigned to tuple structure", > + rec->refname))); > + > + /* > + * Get the number of the records field to change and the > + * number of attributes in the tuple. Note: disallow system > + * column names because the code below won't cope. > + */ > + fno = SPI_fnumber(rec->tupdesc, recfield->fieldname); > + if (fno <= 0) > + ereport(ERROR, > + (errcode(ERRCODE_UNDEFINED_COLUMN), > + errmsg("record \"%s\" has no field \"%s\"", > + rec->refname, recfield->fieldname))); > + } > + break; > + > + case PLPGSQL_DTYPE_ARRAYELEM: > + { > + /* > + * Target is an element of an array > + */ > + int nsubscripts; > + Oid arrayelemtypeid; > + Oid arraytypeid; > + > + /* > + * To handle constructs like x[1][2] := something, we have to > + * be prepared to deal with a chain of arrayelem datums. Chase > + * back to find the base array datum, and save the subscript > + * expressions as we go. (We are scanning right to left here, > + * but want to evaluate the subscripts left-to-right to > + * minimize surprises.) > + */ > + nsubscripts = 0; > + do > + { > + PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target; > + > + if (nsubscripts++ >= MAXDIM) > + ereport(ERROR, > + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), > + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", > + nsubscripts + 1, MAXDIM))); > + > + check_expr(estate, arrayelem->subscript); > + > + target = estate->datums[arrayelem->arrayparentno]; > + } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM); > + > + /* If target is domain over array, reduce to base type */ > + arraytypeid = exec_get_datum_type(estate, target); > + arraytypeid = getBaseType(arraytypeid); > + > + arrayelemtypeid = get_element_type(arraytypeid); > + > + if (!OidIsValid(arrayelemtypeid)) > + ereport(ERROR, > + (errcode(ERRCODE_DATATYPE_MISMATCH), > + errmsg("subscripted object is not an array"))); > + } > + break; > + } > + } > + > + /* > + * Check composed lvalue > + * There is nothing to check on rec variables > + */ > + static void > + check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec) > + { > + int fnum; > + > + /* there are nothing to check on rec now */ > + if (row != NULL) > + { > + for (fnum = 0; fnum < row->nfields; fnum++) > + { > + /* skip dropped columns */ > + if (row->varnos[fnum] < 0) > + continue; > + > + check_target(estate, row->varnos[fnum]); > + } > + } > + } > + > + /* > + * Generate a prepared plan - this is simplyfied copy from pl_exec.c > + * Is not necessary to check simple plan > + */ > + static void > + prepare_expr(PLpgSQL_execstate *estate, > + PLpgSQL_expr *expr, int cursorOptions) > + { > + SPIPlanPtr plan; > + > + /* leave when there are not expression */ > + if (expr == NULL) > + return; > + > + /* leave when plan is created */ > + if (expr->plan != NULL) > + return; > + > + /* > + * The grammar can't conveniently set expr->func while building the parse > + * tree, so make sure it's set before parser hooks need it. > + */ > + expr->func = estate->func; > + > + /* > + * Generate and save the plan > + */ > + plan = SPI_prepare_params(expr->query, > + (ParserSetupHook) plpgsql_parser_setup, > + (void *) expr, > + cursorOptions); > + if (plan == NULL) > + { > + /* Some SPI errors deserve specific error messages */ > + switch (SPI_result) > + { > + case SPI_ERROR_COPY: > + ereport(ERROR, > + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > + errmsg("cannot COPY to/from client in PL/pgSQL"))); > + case SPI_ERROR_TRANSACTION: > + ereport(ERROR, > + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > + errmsg("cannot begin/end transactions in PL/pgSQL"), > + errhint("Use a BEGIN block with an EXCEPTION clause instead."))); > + default: > + elog(ERROR, "SPI_prepare_params failed for \"%s\": %s", > + expr->query, SPI_result_code_string(SPI_result)); > + } > + } > + > + expr->plan = SPI_saveplan(plan); > + SPI_freeplan(plan); > + } > + > + /* > + * Verify a expression > + */ > + static void > + check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) > + { > + TupleDesc tupdesc; > + > + if (expr != NULL) > + { > + prepare_expr(estate, expr, 0); > + tupdesc = expr_get_desc(estate, expr, false, false, true); > + ReleaseTupleDesc(tupdesc); > + } > + } > + > + /* > + * We have to assign TupleDesc to all used record variables step by step. > + * We would to use a exec routines for query preprocessing, so we must > + * to create a typed NULL value, and this value is assigned to record > + * variable. > + */ > + static void > + assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate, > + PLpgSQL_row *row, PLpgSQL_rec *rec, > + TupleDesc tupdesc) > + { > + bool *nulls; > + HeapTuple tup; > + > + if (tupdesc == NULL) > + elog(ERROR, "tuple descriptor is empty"); > + > + /* > + * row variable has assigned TupleDesc already, so don't be processed > + * here > + */ > + if (rec != NULL) > + { > + PLpgSQL_rec *target = (PLpgSQL_rec *)(estate->datums[rec->dno]); > + > + if (target->freetup) > + heap_freetuple(target->tup); > + > + if (rec->freetupdesc) > + FreeTupleDesc(target->tupdesc); > + > + /* initialize rec by NULLs */ > + nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); > + memset(nulls, true, tupdesc->natts * sizeof(bool)); > + > + target->tupdesc = CreateTupleDescCopy(tupdesc); > + target->freetupdesc = true; > + > + tup = heap_form_tuple(tupdesc, NULL, nulls); > + if (HeapTupleIsValid(tup)) > + { > + target->tup = tup; > + target->freetup = true; > + } > + else > + elog(ERROR, "cannot to build valid composite value"); > + } > + } > + > + /* > + * Assign a tuple descriptor to variable specified by dno > + */ > + static void > + assign_tupdesc_dno(PLpgSQL_execstate *estate, int varno, TupleDesc tupdesc) > + { > + PLpgSQL_datum *target = estate->datums[varno]; > + > + if (target->dtype == PLPGSQL_DTYPE_REC) > + assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc); > + } > + > + /* > + * Returns a tuple descriptor based on existing plan > + */ > + static TupleDesc > + expr_get_desc(PLpgSQL_execstate *estate, > + PLpgSQL_expr *query, > + bool use_element_type, > + bool expand_record, > + bool is_expression) > + { > + TupleDesc tupdesc = NULL; > + CachedPlanSource *plansource = NULL; > + > + if (query->plan != NULL) > + { > + SPIPlanPtr plan = query->plan; > + > + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) > + elog(ERROR, "cached plan is not valid plan"); > + > + if (list_length(plan->plancache_list) != 1) > + elog(ERROR, "plan is not single execution plan"); > + > + plansource = (CachedPlanSource *) linitial(plan->plancache_list); > + > + tupdesc = CreateTupleDescCopy(plansource->resultDesc); > + } > + else > + elog(ERROR, "there are no plan for query: \"%s\"", > + query->query); > + > + /* > + * try to get a element type, when result is a array (used with FOREACH ARRAY stmt) > + */ > + if (use_element_type) > + { > + Oid elemtype; > + TupleDesc elemtupdesc; > + > + /* result should be a array */ > + if (tupdesc->natts != 1) > + ereport(ERROR, > + (errcode(ERRCODE_SYNTAX_ERROR), > + errmsg_plural("query \"%s\" returned %d column", > + "query \"%s\" returned %d columns", > + tupdesc->natts, > + query->query, > + tupdesc->natts))); > + > + /* check the type of the expression - must be an array */ > + elemtype = get_element_type(tupdesc->attrs[0]->atttypid); > + if (!OidIsValid(elemtype)) > + ereport(ERROR, > + (errcode(ERRCODE_DATATYPE_MISMATCH), > + errmsg("FOREACH expression must yield an array, not type %s", > + format_type_be(tupdesc->attrs[0]->atttypid)))); > + > + /* we can't know typmod now */ > + elemtupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true); > + if (elemtupdesc != NULL) > + { > + FreeTupleDesc(tupdesc); > + tupdesc = CreateTupleDescCopy(elemtupdesc); > + ReleaseTupleDesc(elemtupdesc); > + } > + else > + elog(ERROR, "cannot to identify real type for record type variable"); > + } > + > + if (is_expression && tupdesc->natts != 1) > + ereport(ERROR, > + (errcode(ERRCODE_SYNTAX_ERROR), > + errmsg_plural("query \"%s\" returned %d column", > + "query \"%s\" returned %d columns", > + tupdesc->natts, > + query->query, > + tupdesc->natts))); > + > + /* > + * One spacial case is when record is assigned to composite type, then > + * we should to unpack composite type. > + */ > + if (tupdesc->tdtypeid == RECORDOID && > + tupdesc->tdtypmod == -1 && > + tupdesc->natts == 1 && expand_record) > + { > + TupleDesc unpack_tupdesc; > + > + unpack_tupdesc = lookup_rowtype_tupdesc_noerror(tupdesc->attrs[0]->atttypid, > + tupdesc->attrs[0]->atttypmod, > + true); > + if (unpack_tupdesc != NULL) > + { > + FreeTupleDesc(tupdesc); > + tupdesc = CreateTupleDescCopy(unpack_tupdesc); > + ReleaseTupleDesc(unpack_tupdesc); > + } > + } > + > + /* > + * There is special case, when returned tupdesc contains only > + * unpined record: rec := func_with_out_parameters(). IN this case > + * we must to dig more deep - we have to find oid of function and > + * get their parameters, > + * > + * This is support for assign statement > + * recvar := func_with_out_parameters(..) > + */ > + if (tupdesc->tdtypeid == RECORDOID && > + tupdesc->tdtypmod == -1 && > + tupdesc->natts == 1 && > + tupdesc->attrs[0]->atttypid == RECORDOID && > + tupdesc->attrs[0]->atttypmod == -1 && > + expand_record) > + { > + PlannedStmt *_stmt; > + Plan *_plan; > + TargetEntry *tle; > + CachedPlan *cplan; > + > + /* > + * When tupdesc is related to unpined record, we will try > + * to check plan if it is just function call and if it is > + * then we can try to derive a tupledes from function's > + * description. > + */ > + cplan = GetCachedPlan(plansource, NULL, true); > + _stmt = (PlannedStmt *) linitial(cplan->stmt_list); > + > + if (IsA(_stmt, PlannedStmt) && _stmt->commandType == CMD_SELECT) > + { > + _plan = _stmt->planTree; > + if (IsA(_plan, Result) && list_length(_plan->targetlist) == 1) > + { > + tle = (TargetEntry *) linitial(_plan->targetlist); > + if (((Node *) tle->expr)->type == T_FuncExpr) > + { > + FuncExpr *fn = (FuncExpr *) tle->expr; > + FmgrInfo flinfo; > + FunctionCallInfoData fcinfo; > + TupleDesc rd; > + Oid rt; > + > + fmgr_info(fn->funcid, &flinfo); > + flinfo.fn_expr = (Node *) fn; > + fcinfo.flinfo = &flinfo; > + > + get_call_result_type(&fcinfo, &rt, &rd); > + if (rd == NULL) > + elog(ERROR, "function does not return composite type is not possible to identify composite type"); > + > + FreeTupleDesc(tupdesc); > + BlessTupleDesc(rd); > + > + tupdesc = rd; > + } > + } > + } > + > + ReleaseCachedPlan(cplan, true); > + } > + > + return tupdesc; > + } > + > + /* > + * Ensure check for all statements in list > + */ > + static void > + check_stmts(PLpgSQL_execstate *estate, List *stmts) > + { > + ListCell *lc; > + > + foreach(lc, stmts) > + { > + check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc)); > + } > + } > + > + /* > + * walk over all statements > + */ > + static void > + check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) > + { > + TupleDesc tupdesc = NULL; > + PLpgSQL_function *func; > + ListCell *l; > + > + if (stmt == NULL) > + return; > + > + estate->err_stmt = stmt; > + func = estate->func; > + > + switch ((enum PLpgSQL_stmt_types) stmt->cmd_type) > + { > + case PLPGSQL_STMT_BLOCK: > + { > + PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt; > + int i; > + PLpgSQL_datum *d; > + > + for (i = 0; i < stmt_block->n_initvars; i++) > + { > + d = func->datums[stmt_block->initvarnos[i]]; > + > + if (d->dtype == PLPGSQL_DTYPE_VAR) > + { > + PLpgSQL_var *var = (PLpgSQL_var *) d; > + > + check_expr(estate, var->default_val); > + } > + } > + > + check_stmts(estate, stmt_block->body); > + > + if (stmt_block->exceptions) > + { > + foreach(l, stmt_block->exceptions->exc_list) > + { > + check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action); > + } > + } > + } > + break; > + > + case PLPGSQL_STMT_ASSIGN: > + { > + PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt; > + > + /* prepare plan if desn't exist yet */ > + prepare_expr(estate, stmt_assign->expr, 0); > + > + tupdesc = expr_get_desc(estate, > + stmt_assign->expr, > + false, /* no element type */ > + true, /* expand record */ > + true); /* is expression */ > + > + /* check target, ensure target can get a result */ > + check_target(estate, stmt_assign->varno); > + > + /* assign a tupdesc to record variable */ > + assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc); > + ReleaseTupleDesc(tupdesc); > + } > + break; > + > + case PLPGSQL_STMT_IF: > + { > + PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; > + ListCell *l; > + > + check_expr(estate, stmt_if->cond); > + > + check_stmts(estate, stmt_if->then_body); > + > + foreach(l, stmt_if->elsif_list) > + { > + PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l); > + > + check_expr(estate, elif->cond); > + check_stmts(estate, elif->stmts); > + } > + > + check_stmts(estate, stmt_if->else_body); > + } > + break; > + > + case PLPGSQL_STMT_CASE: > + { > + PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; > + Oid result_oid; > + > + if (stmt_case->t_expr != NULL) > + { > + PLpgSQL_var *t_var = (PLpgSQL_var *) estate->datums[stmt_case->t_varno]; > + > + /* we need to set hidden variable type */ > + prepare_expr(estate, stmt_case->t_expr, 0); > + > + tupdesc = expr_get_desc(estate, > + stmt_case->t_expr, > + false, /* no element type */ > + false, /* expand record */ > + true); /* is expression */ > + > + result_oid = tupdesc->attrs[0]->atttypid; > + > + /* > + * When expected datatype is different from real, change it. Note that > + * what we're modifying here is an execution copy of the datum, so > + * this doesn't affect the originally stored function parse tree. > + */ > + > + if (t_var->datatype->typoid != result_oid) > + t_var->datatype = plpgsql_build_datatype(result_oid, > + -1, > + estate->func->fn_input_collation); > + > + ReleaseTupleDesc(tupdesc); > + } > + > + foreach(l, stmt_case->case_when_list) > + { > + PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); > + > + check_expr(estate, cwt->expr); > + check_stmts(estate, cwt->stmts); > + } > + > + check_stmts(estate, stmt_case->else_stmts); > + } > + break; > + > + case PLPGSQL_STMT_LOOP: > + check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body); > + break; > + > + case PLPGSQL_STMT_WHILE: > + { > + PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt; > + > + check_expr(estate, stmt_while->cond); > + check_stmts(estate, stmt_while->body); > + } > + break; > + > + case PLPGSQL_STMT_FORI: > + { > + PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt; > + > + check_expr(estate, stmt_fori->lower); > + check_expr(estate, stmt_fori->upper); > + check_expr(estate, stmt_fori->step); > + > + check_stmts(estate, stmt_fori->body); > + } > + break; > + > + case PLPGSQL_STMT_FORS: > + { > + PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt; > + > + /* we need to set hidden variable type */ > + prepare_expr(estate, stmt_fors->query, 0); > + > + tupdesc = expr_get_desc(estate, > + stmt_fors->query, > + false, /* no element type */ > + false, /* expand record */ > + false); /* is expression */ > + > + check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec); > + assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc); > + > + check_stmts(estate, stmt_fors->body); > + ReleaseTupleDesc(tupdesc); > + } > + break; > + > + case PLPGSQL_STMT_FORC: > + { > + PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt; > + PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar]; > + > + prepare_expr(estate, stmt_forc->argquery, 0); > + > + if (var->cursor_explicit_expr != NULL) > + { > + prepare_expr(estate, var->cursor_explicit_expr, > + var->cursor_options); > + > + tupdesc = expr_get_desc(estate, > + var->cursor_explicit_expr, > + false, /* no element type */ > + false, /* expand record */ > + false); /* is expression */ > + > + check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec); > + assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc); > + } > + > + check_stmts(estate, stmt_forc->body); > + if (tupdesc != NULL) > + ReleaseTupleDesc(tupdesc); > + } > + break; > + > + case PLPGSQL_STMT_DYNFORS: > + { > + PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt; > + > + if (stmt_dynfors->rec != NULL) > + elog(ERROR, "cannot determinate a result of dynamic SQL"); > + > + check_expr(estate, stmt_dynfors->query); > + > + foreach(l, stmt_dynfors->params) > + { > + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); > + } > + > + check_stmts(estate, stmt_dynfors->body); > + } > + break; > + > + case PLPGSQL_STMT_FOREACH_A: > + { > + PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt; > + > + prepare_expr(estate, stmt_foreach_a->expr, 0); > + > + tupdesc = expr_get_desc(estate, > + stmt_foreach_a->expr, > + true, /* no element type */ > + false, /* expand record */ > + true); /* is expression */ > + > + check_target(estate, stmt_foreach_a->varno); > + assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc); > + ReleaseTupleDesc(tupdesc); > + > + check_stmts(estate, stmt_foreach_a->body); > + } > + break; > + > + case PLPGSQL_STMT_EXIT: > + check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond); > + break; > + > + case PLPGSQL_STMT_PERFORM: > + prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0); > + break; > + > + case PLPGSQL_STMT_RETURN: > + check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr); > + break; > + > + case PLPGSQL_STMT_RETURN_NEXT: > + check_expr(estate, ((PLpgSQL_stmt_return_next *) stmt)->expr); > + break; > + > + case PLPGSQL_STMT_RETURN_QUERY: > + { > + PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt; > + > + check_expr(estate, stmt_rq->dynquery); > + prepare_expr(estate, stmt_rq->query, 0); > + > + foreach(l, stmt_rq->params) > + { > + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); > + } > + } > + break; > + > + case PLPGSQL_STMT_RAISE: > + { > + PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt; > + ListCell *current_param; > + char *cp; > + > + foreach(l, stmt_raise->params) > + { > + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); > + } > + > + foreach(l, stmt_raise->options) > + { > + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); > + } > + > + current_param = list_head(stmt_raise->params); > + > + /* ensure any single % has a own parameter */ > + if (stmt_raise->message != NULL) > + { > + for (cp = stmt_raise->message; *cp; cp++) > + { > + if (cp[0] == '%') > + { > + if (cp[1] == '%') > + { > + cp++; > + continue; > + } > + > + if (current_param == NULL) > + ereport(ERROR, > + (errcode(ERRCODE_SYNTAX_ERROR), > + errmsg("too few parameters specified for RAISE"))); > + > + current_param = lnext(current_param); > + } > + } > + } > + > + if (current_param != NULL) > + ereport(ERROR, > + (errcode(ERRCODE_SYNTAX_ERROR), > + errmsg("too many parameters specified for RAISE"))); > + } > + break; > + > + case PLPGSQL_STMT_EXECSQL: > + { > + PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt; > + > + prepare_expr(estate, stmt_execsql->sqlstmt, 0); > + if (stmt_execsql->into) > + { > + tupdesc = expr_get_desc(estate, > + stmt_execsql->sqlstmt, > + false, /* no element type */ > + false, /* expand record */ > + false); /* is expression */ > + > + /* check target, ensure target can get a result */ > + check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec); > + assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc); > + ReleaseTupleDesc(tupdesc); > + } > + } > + break; > + > + case PLPGSQL_STMT_DYNEXECUTE: > + { > + PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt; > + > + check_expr(estate, stmt_dynexecute->query); > + > + foreach(l, stmt_dynexecute->params) > + { > + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); > + } > + > + if (stmt_dynexecute->into) > + { > + if (stmt_dynexecute->rec != NULL) > + elog(ERROR, "cannot determinate a result of dynamic SQL"); > + > + check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec); > + } > + } > + break; > + > + case PLPGSQL_STMT_OPEN: > + { > + PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt; > + PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_open->curvar]; > + > + if (var->cursor_explicit_expr) > + prepare_expr(estate, var->cursor_explicit_expr, > + var->cursor_options); > + > + prepare_expr(estate, stmt_open->query, 0); > + prepare_expr(estate, stmt_open->argquery, 0); > + check_expr(estate, stmt_open->dynquery); > + > + foreach(l, stmt_open->params) > + { > + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); > + } > + } > + break; > + > + case PLPGSQL_STMT_GETDIAG: > + { > + PLpgSQL_stmt_getdiag *stmt_getdiag = (PLpgSQL_stmt_getdiag *) stmt; > + ListCell *lc; > + > + foreach(lc, stmt_getdiag->diag_items) > + { > + PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); > + > + check_target(estate, diag_item->target); > + } > + } > + break; > + > + case PLPGSQL_STMT_FETCH: > + { > + PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt; > + PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]); > + > + if (var != NULL && var->cursor_explicit_expr != NULL) > + { > + prepare_expr(estate, var->cursor_explicit_expr, > + var->cursor_options); > + tupdesc = expr_get_desc(estate, > + var->cursor_explicit_expr, > + false, /* no element type */ > + false, /* expand record */ > + false); /* is expression */ > + check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec); > + assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc); > + ReleaseTupleDesc(tupdesc); > + } > + } > + break; > + > + case PLPGSQL_STMT_CLOSE: > + break; > + > + default: > + elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); > + return; /* be compiler quite */ > + } > + } > + > + /* > + * Initialize variable to NULL > + */ > + static void > + var_init_to_null(PLpgSQL_execstate *estate, int varno) > + { > + PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno]; > + var->value = (Datum) 0; > + var->isnull = true; > + var->freeval = false; > + } > *** ./src/pl/plpgsql/src/pl_handler.c.orig 2011-11-29 19:20:59.494116771 +0100 > --- ./src/pl/plpgsql/src/pl_handler.c 2011-11-29 19:21:24.529804431 +0100 > *************** > *** 312,314 **** > --- 312,452 ---- > > PG_RETURN_VOID(); > } > + > + /* ---------- > + * plpgsql_checker > + * > + * This function attempts to check a embeded SQL inside a PL/pgSQL function at > + * CHECK FUNCTION time. It should to have one or two parameters. Second > + * parameter is a relation (used when function is trigger). > + * ---------- > + */ > + PG_FUNCTION_INFO_V1(plpgsql_checker); > + > + Datum > + plpgsql_checker(PG_FUNCTION_ARGS) > + { > + Oid funcoid = PG_GETARG_OID(0); > + Oid relid = PG_GETARG_OID(1); > + HeapTuple tuple; > + FunctionCallInfoData fake_fcinfo; > + FmgrInfo flinfo; > + TriggerData trigdata; > + int rc; > + PLpgSQL_function *function; > + PLpgSQL_execstate *cur_estate; > + > + Form_pg_proc proc; > + char functyptype; > + bool istrigger = false; > + > + /* we don't need to repair a check done by validator */ > + > + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); > + if (!HeapTupleIsValid(tuple)) > + elog(ERROR, "cache lookup failed for function %u", funcoid); > + proc = (Form_pg_proc) GETSTRUCT(tuple); > + > + functyptype = get_typtype(proc->prorettype); > + > + if (functyptype == TYPTYPE_PSEUDO) > + { > + /* we assume OPAQUE with no arguments means a trigger */ > + if (proc->prorettype == TRIGGEROID || > + (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) > + { > + istrigger = true; > + if (!OidIsValid(relid)) > + ereport(ERROR, > + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > + errmsg("PL/pgSQL trigger functions cannot be checked directly"), > + errhint("use CHECK TRIGGER statement instead"))); > + } > + } > + > + /* > + * Connect to SPI manager > + */ > + if ((rc = SPI_connect()) != SPI_OK_CONNECT) > + elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); > + > + /* > + * Set up a fake fcinfo with just enough info to satisfy > + * plpgsql_compile(). > + * > + * there should be a different real argtypes for polymorphic params > + */ > + MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); > + MemSet(&flinfo, 0, sizeof(flinfo)); > + fake_fcinfo.flinfo = &flinfo; > + flinfo.fn_oid = funcoid; > + flinfo.fn_mcxt = CurrentMemoryContext; > + > + if (istrigger) > + { > + MemSet(&trigdata, 0, sizeof(trigdata)); > + trigdata.type = T_TriggerData; > + trigdata.tg_relation = relation_open(relid, AccessShareLock); > + fake_fcinfo.context = (Node *) &trigdata; > + } > + > + /* Get a compiled function */ > + function = plpgsql_compile(&fake_fcinfo, false); > + > + /* Must save and restore prior value of cur_estate */ > + cur_estate = function->cur_estate; > + > + /* Mark the function as busy, so it can't be deleted from under us */ > + function->use_count++; > + > + > + /* Create a fake runtime environment and prepare plans */ > + PG_TRY(); > + { > + if (!istrigger) > + plpgsql_check_function(function, &fake_fcinfo); > + else > + plpgsql_check_trigger(function, &trigdata); > + } > + PG_CATCH(); > + { > + if (istrigger) > + relation_close(trigdata.tg_relation, AccessShareLock); > + > + function->cur_estate = cur_estate; > + function->use_count--; > + > + /* > + * We cannot to preserve instance of this function, because > + * expressions are not consistent - a tests on simple expression > + * was be processed newer. > + */ > + plpgsql_delete_function(function); > + > + PG_RE_THROW(); > + } > + PG_END_TRY(); > + > + if (istrigger) > + relation_close(trigdata.tg_relation, AccessShareLock); > + > + function->cur_estate = cur_estate; > + function->use_count--; > + > + /* > + * We cannot to preserve instance of this function, because > + * expressions are not consistent - a tests on simple expression > + * was be processed newer. > + */ > + plpgsql_delete_function(function); > + > + /* > + * Disconnect from SPI manager > + */ > + if ((rc = SPI_finish()) != SPI_OK_FINISH) > + elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); > + > + ReleaseSysCache(tuple); > + > + PG_RETURN_VOID(); > + } > *** ./src/pl/plpgsql/src/plpgsql.h.orig 2011-11-29 19:20:59.500116698 +0100 > --- ./src/pl/plpgsql/src/plpgsql.h 2011-11-29 20:22:19.423516596 +0100 > *************** > *** 902,907 **** > --- 902,908 ---- > extern void plpgsql_adddatum(PLpgSQL_datum *new); > extern int plpgsql_add_initdatums(int **varnos); > extern void plpgsql_HashTableInit(void); > + extern void plpgsql_delete_function(PLpgSQL_function *func); > > /* ---------- > * Functions in pl_handler.c > *************** > *** 911,916 **** > --- 912,918 ---- > extern Datum plpgsql_call_handler(PG_FUNCTION_ARGS); > extern Datum plpgsql_inline_handler(PG_FUNCTION_ARGS); > extern Datum plpgsql_validator(PG_FUNCTION_ARGS); > + extern Datum plpgsql_checker(PG_FUNCTION_ARGS); > > /* ---------- > * Functions in pl_exec.c > *************** > *** 928,933 **** > --- 930,939 ---- > extern void exec_get_datum_type_info(PLpgSQL_execstate *estate, > PLpgSQL_datum *datum, > Oid *typeid, int32 *typmod, Oid *collation); > + extern void plpgsql_check_function(PLpgSQL_function *func, > + FunctionCallInfo fcinfo); > + extern void plpgsql_check_trigger(PLpgSQL_function *func, > + TriggerData *trigdata); > > /* ---------- > * Functions for namespace handling in pl_funcs.c > *** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig 2011-11-29 19:20:59.502116672 +0100 > --- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql 2011-11-29 19:21:24.533804381 +0100 > *************** > *** 5,7 **** > --- 5,8 ---- > ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler(); > ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal); > ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid); > + ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_checker(oid, regclass); > *** ./src/test/regress/expected/plpgsql.out.orig 2011-11-29 19:20:59.505116634 +0100 > --- ./src/test/regress/expected/plpgsql.out 2011-11-29 19:21:24.536804342 +0100 > *************** > *** 302,307 **** > --- 302,310 ---- > ' language plpgsql; > create trigger tg_hslot_biu before insert or update > on HSlot for each row execute procedure tg_hslot_biu(); > + -- check trigger should not fail > + check trigger tg_hslot_biu on HSlot; > + NOTICE: checking function "tg_hslot_biu()" > -- ************************************************************ > -- * BEFORE DELETE on HSlot > -- * - prevent from manual manipulation > *************** > *** 635,640 **** > --- 638,645 ---- > raise exception ''illegal backlink beginning with %'', mytype; > end; > ' language plpgsql; > + -- check function should not fail > + check function tg_backlink_set(bpchar, bpchar); > -- ************************************************************ > -- * Support function to clear out the backlink field if > -- * it still points to specific slot > *************** > *** 2802,2807 **** > --- 2807,2840 ---- > > (1 row) > > + -- check function should not fail > + check function for_vect(); > + -- recheck after check function > + select for_vect(); > + NOTICE: 1 > + NOTICE: 2 > + NOTICE: 3 > + NOTICE: 1 BB CC > + NOTICE: 2 BB CC > + NOTICE: 3 BB CC > + NOTICE: 4 BB CC > + NOTICE: 1 > + NOTICE: 2 > + NOTICE: 3 > + NOTICE: 4 > + NOTICE: 1 BB CC > + NOTICE: 2 BB CC > + NOTICE: 3 BB CC > + NOTICE: 4 BB CC > + NOTICE: 1 bb cc > + NOTICE: 2 bb cc > + NOTICE: 3 bb cc > + NOTICE: 4 bb cc > + for_vect > + ---------- > + > + (1 row) > + > -- regression test: verify that multiple uses of same plpgsql datum within > -- a SQL command all get mapped to the same $n parameter. The return value > -- of the SELECT is not important, we only care that it doesn't fail with > *************** > *** 3283,3288 **** > --- 3316,3323 ---- > return; > end; > $$ language plpgsql; > + -- check function should not fail > + check function forc01(); > select forc01(); > NOTICE: 5 from c > NOTICE: 6 from c > *************** > *** 3716,3721 **** > --- 3751,3758 ---- > end case; > end; > $$ language plpgsql immutable; > + -- check function should not fail > + check function case_test(bigint); > select case_test(1); > case_test > ----------- > *************** > *** 4571,4573 **** > --- 4608,4942 ---- > CONTEXT: PL/pgSQL function "testoa" line 5 at assignment > drop function arrayassign1(); > drop function testoa(x1 int, x2 int, x3 int); > + -- > + -- check function statement tests > + -- > + create table t1(a int, b int); > + create function f1() > + returns void as $$ > + begin > + if false then > + update t1 set c = 30; > + end if; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: column "c" of relation "t1" does not exist > + LINE 1: update t1 set c = 30 > + ^ > + QUERY: update t1 set c = 30 > + CONTEXT: checking of PL/pgSQL function "f1" line 4 at SQL statement > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + create function g1(out a int, out b int) > + as $$ > + select 10,20; > + $$ language sql; > + create function f1() > + returns void as $$ > + declare r record; > + begin > + r := g1(); > + if false then > + raise notice '%', r.c; > + end if; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: record "r" has no field "c" > + CONTEXT: SQL statement "SELECT r.c" > + checking of PL/pgSQL function "f1" line 6 at RAISE > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + drop function g1(); > + create function g1(out a int, out b int) > + returns setof record as $$ > + select * from t1; > + $$ language sql; > + create function f1() > + returns void as $$ > + declare r record; > + begin > + for r in select * from g1() > + loop > + raise notice '%', r.c; > + end loop; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: record "r" has no field "c" > + CONTEXT: SQL statement "SELECT r.c" > + checking of PL/pgSQL function "f1" line 6 at RAISE > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + create or replace function f1() > + returns void as $$ > + declare r record; > + begin > + for r in select * from g1() > + loop > + r.c := 20; > + end loop; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: record "r" has no field "c" > + CONTEXT: checking of PL/pgSQL function "f1" line 6 at assignment > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + drop function g1(); > + create function f1() > + returns int as $$ > + declare r int; > + begin > + if false then > + r := a + b; > + end if; > + return r; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: column "a" does not exist > + LINE 1: SELECT a + b > + ^ > + QUERY: SELECT a + b > + CONTEXT: checking of PL/pgSQL function "f1" line 5 at assignment > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + create or replace function f1() > + returns void as $$ > + begin > + if false then > + raise notice '%', 1, 2; > + end if; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: too many parameters specified for RAISE > + CONTEXT: checking of PL/pgSQL function "f1" line 4 at RAISE > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + create or replace function f1() > + returns void as $$ > + begin > + if false then > + raise notice '% %'; > + end if; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: too few parameters specified for RAISE > + CONTEXT: checking of PL/pgSQL function "f1" line 4 at RAISE > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + create or replace function f1() > + returns void as $$ > + declare r int[]; > + begin > + if false then > + r[c+10] := 20; > + end if; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: column "c" does not exist > + LINE 1: SELECT c+10 > + ^ > + QUERY: SELECT c+10 > + CONTEXT: checking of PL/pgSQL function "f1" line 5 at assignment > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + create or replace function f1() > + returns void as $$ > + declare r int; > + begin > + if false then > + r[10] := 20; > + end if; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: subscripted object is not an array > + CONTEXT: checking of PL/pgSQL function "f1" line 5 at assignment > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + drop function f1(); > + create type _exception_type as ( > + state text, > + message text, > + detail text); > + create or replace function f1() > + returns void as $$ > + declare > + _exception record; > + begin > + _exception := NULL::_exception_type; > + exception when others then > + get stacked diagnostics > + _exception.state = RETURNED_SQLSTATE, > + _exception.message = MESSAGE_TEXT, > + _exception.detail = PG_EXCEPTION_DETAIL, > + _exception.hint = PG_EXCEPTION_HINT; > + end; > + $$ language plpgsql; > + select f1(); > + f1 > + ---- > + > + (1 row) > + > + check function f1(); > + ERROR: record "_exception" has no field "hint" > + CONTEXT: checking of PL/pgSQL function "f1" line 7 at GET DIAGNOSTICS > + drop function f1(); > + create or replace function f1_trg() > + returns trigger as $$ > + begin > + if new.a > 10 then > + raise notice '%', new.b; > + raise notice '%', new.c; > + end if; > + return new; > + end; > + $$ language plpgsql; > + create trigger t1_f1 before insert on t1 > + for each row > + execute procedure f1_trg(); > + insert into t1 values(6,30); > + check trigger t1_f1 on t1; > + NOTICE: checking function "f1_trg()" > + ERROR: record "new" has no field "c" > + CONTEXT: SQL statement "SELECT new.c" > + checking of PL/pgSQL function "f1_trg" line 5 at RAISE > + insert into t1 values(6,30); > + create or replace function f1_trg() > + returns trigger as $$ > + begin > + new.a := new.a + 10; > + new.b := new.b + 10; > + new.c := 30; > + return new; > + end; > + $$ language plpgsql; > + -- should to fail > + check trigger t1_f1 on t1; > + NOTICE: checking function "f1_trg()" > + ERROR: record "new" has no field "c" > + CONTEXT: checking of PL/pgSQL function "f1_trg" line 5 at assignment > + -- should to fail but not crash > + insert into t1 values(6,30); > + ERROR: record "new" has no field "c" > + CONTEXT: PL/pgSQL function "f1_trg" line 5 at assignment > + create or replace function f1_trg() > + returns trigger as $$ > + begin > + new.a := new.a + 10; > + new.b := new.b + 10; > + return new; > + end; > + $$ language plpgsql; > + -- ok > + check trigger t1_f1 on t1; > + NOTICE: checking function "f1_trg()" > + -- ok > + insert into t1 values(6,30); > + drop table t1; > + drop type _exception_type; > + drop function f1_trg(); > *** ./src/test/regress/sql/plpgsql.sql.orig 2011-11-29 19:20:59.508116598 +0100 > --- ./src/test/regress/sql/plpgsql.sql 2011-11-29 19:21:24.538804318 +0100 > *************** > *** 366,371 **** > --- 366,373 ---- > create trigger tg_hslot_biu before insert or update > on HSlot for each row execute procedure tg_hslot_biu(); > > + -- check trigger should not fail > + check trigger tg_hslot_biu on HSlot; > > -- ************************************************************ > -- * BEFORE DELETE on HSlot > *************** > *** 747,752 **** > --- 749,757 ---- > end; > ' language plpgsql; > > + -- check function should not fail > + check function tg_backlink_set(bpchar, bpchar); > + > > -- ************************************************************ > -- * Support function to clear out the backlink field if > *************** > *** 2335,2340 **** > --- 2340,2352 ---- > > select for_vect(); > > + -- check function should not fail > + check function for_vect(); > + > + -- recheck after check function > + select for_vect(); > + > + > -- regression test: verify that multiple uses of same plpgsql datum within > -- a SQL command all get mapped to the same $n parameter. The return value > -- of the SELECT is not important, we only care that it doesn't fail with > *************** > *** 2714,2719 **** > --- 2726,2734 ---- > end; > $$ language plpgsql; > > + -- check function should not fail > + check function forc01(); > + > select forc01(); > > -- try updating the cursor's current row > *************** > *** 3048,3053 **** > --- 3063,3071 ---- > end; > $$ language plpgsql immutable; > > + -- check function should not fail > + check function case_test(bigint); > + > select case_test(1); > select case_test(2); > select case_test(3); > *************** > *** 3600,3602 **** > --- 3618,3862 ---- > > drop function arrayassign1(); > drop function testoa(x1 int, x2 int, x3 int); > + > + -- > + -- check function statement tests > + -- > + > + create table t1(a int, b int); > + > + create function f1() > + returns void as $$ > + begin > + if false then > + update t1 set c = 30; > + end if; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + > + create function g1(out a int, out b int) > + as $$ > + select 10,20; > + $$ language sql; > + > + create function f1() > + returns void as $$ > + declare r record; > + begin > + r := g1(); > + if false then > + raise notice '%', r.c; > + end if; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + drop function g1(); > + > + create function g1(out a int, out b int) > + returns setof record as $$ > + select * from t1; > + $$ language sql; > + > + create function f1() > + returns void as $$ > + declare r record; > + begin > + for r in select * from g1() > + loop > + raise notice '%', r.c; > + end loop; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + create or replace function f1() > + returns void as $$ > + declare r record; > + begin > + for r in select * from g1() > + loop > + r.c := 20; > + end loop; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + drop function g1(); > + > + create function f1() > + returns int as $$ > + declare r int; > + begin > + if false then > + r := a + b; > + end if; > + return r; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + > + create or replace function f1() > + returns void as $$ > + begin > + if false then > + raise notice '%', 1, 2; > + end if; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + > + create or replace function f1() > + returns void as $$ > + begin > + if false then > + raise notice '% %'; > + end if; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + > + create or replace function f1() > + returns void as $$ > + declare r int[]; > + begin > + if false then > + r[c+10] := 20; > + end if; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + > + create or replace function f1() > + returns void as $$ > + declare r int; > + begin > + if false then > + r[10] := 20; > + end if; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + select f1(); > + > + drop function f1(); > + > + create type _exception_type as ( > + state text, > + message text, > + detail text); > + > + create or replace function f1() > + returns void as $$ > + declare > + _exception record; > + begin > + _exception := NULL::_exception_type; > + exception when others then > + get stacked diagnostics > + _exception.state = RETURNED_SQLSTATE, > + _exception.message = MESSAGE_TEXT, > + _exception.detail = PG_EXCEPTION_DETAIL, > + _exception.hint = PG_EXCEPTION_HINT; > + end; > + $$ language plpgsql; > + > + select f1(); > + check function f1(); > + > + drop function f1(); > + > + create or replace function f1_trg() > + returns trigger as $$ > + begin > + if new.a > 10 then > + raise notice '%', new.b; > + raise notice '%', new.c; > + end if; > + return new; > + end; > + $$ language plpgsql; > + > + create trigger t1_f1 before insert on t1 > + for each row > + execute procedure f1_trg(); > + > + insert into t1 values(6,30); > + check trigger t1_f1 on t1; > + insert into t1 values(6,30); > + > + create or replace function f1_trg() > + returns trigger as $$ > + begin > + new.a := new.a + 10; > + new.b := new.b + 10; > + new.c := 30; > + return new; > + end; > + $$ language plpgsql; > + > + -- should to fail > + check trigger t1_f1 on t1; > + > + -- should to fail but not crash > + insert into t1 values(6,30); > + > + create or replace function f1_trg() > + returns trigger as $$ > + begin > + new.a := new.a + 10; > + new.b := new.b + 10; > + return new; > + end; > + $$ language plpgsql; > + > + -- ok > + check trigger t1_f1 on t1; > + > + -- ok > + insert into t1 values(6,30); > + > + drop table t1; > + drop type _exception_type; > + > + drop function f1_trg(); > + > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers -- Bruce Momjian <bruce@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + It's impossible for everything to be true. +
pgsql-hackers by date: