Re: review: CHECK FUNCTION statement - Mailing list pgsql-hackers
From | Pavel Stehule |
---|---|
Subject | Re: review: CHECK FUNCTION statement |
Date | |
Msg-id | CAFj8pRDtrsm2dgxtC=MvK6NSOGiQtzZpYQPr1-Z8mh0R+g9Trw@mail.gmail.com Whole thread Raw |
In response to | Re: review: CHECK FUNCTION statement (Bruce Momjian <bruce@momjian.us>) |
List | pgsql-hackers |
Hello it is in open commitfest http://archives.postgresql.org/message-id/CAFj8pRAYVTQYCL8_NF_hDQjc0m+JBvbwR6E_ZJ0SJfkKQ9m2kA@mail.gmail.com Regards Pavel 2012/8/17 Bruce Momjian <bruce@momjian.us>: > > 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 maximumallowed (%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 toidentify 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: