*** ./doc/src/sgml/ref/allfiles.sgml.orig 2011-11-25 12:04:21.509588672 +0100 --- ./doc/src/sgml/ref/allfiles.sgml 2011-11-25 12:05:31.844784492 +0100 *************** *** 40,45 **** --- 40,46 ---- + *** ./doc/src/sgml/ref/create_language.sgml.orig 2011-11-24 12:51:44.663317228 +0100 --- ./doc/src/sgml/ref/create_language.sgml 2011-11-25 12:05:31.845784481 +0100 *************** *** 23,29 **** CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE name CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE name ! HANDLER call_handler [ INLINE inline_handler ] [ VALIDATOR valfunction ] --- 23,29 ---- CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE name CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE name ! HANDLER call_handler [ INLINE inline_handler ] [ VALIDATOR valfunction ] [ CHECK checkfunction ] *************** *** 217,222 **** --- 217,236 ---- + + + CHECK checkfunction + + + checkfunction 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 CHECK FUNCTION or + CHECK TRIGGER. + + + + *** ./doc/src/sgml/reference.sgml.orig 2011-11-25 12:04:21.530588430 +0100 --- ./doc/src/sgml/reference.sgml 2011-11-25 12:05:31.849784436 +0100 *************** *** 68,73 **** --- 68,74 ---- &alterView; &analyze; &begin; + &checkFunction; &checkpoint; &close; &cluster; *** ./doc/src/sgml/ref/check_function.sgml.orig 2011-11-25 12:05:11.416017756 +0100 --- ./doc/src/sgml/ref/check_function.sgml 2011-11-25 12:05:31.849784436 +0100 *************** *** 0 **** --- 1,37 ---- + + + + + CHECK FUNCTION + 7 + SQL - Language Statements + + + + CHECK FUNCTION + ensure a deep checking of existing function + + + + CHECK FUNCTION + + + + + CREATE FUNCTION name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) + | CREATE TRIGGER name ON tablename + + + + + Description + + + CHECK FUNCTION check a existing function. + CHECK TRIGGER check a trigger function. + + + + *** ./src/backend/catalog/pg_proc.c.orig 2011-11-24 12:51:44.745316295 +0100 --- ./src/backend/catalog/pg_proc.c 2011-11-25 12:05:31.850784424 +0100 *************** *** 1101,1103 **** --- 1101,1104 ---- *newcursorpos = newcp; return false; } + *** ./src/backend/commands/functioncmds.c.orig 2011-11-24 12:51:44.754316192 +0100 --- ./src/backend/commands/functioncmds.c 2011-11-25 20:01:45.521469608 +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-24 12:51:44.756316170 +0100 --- ./src/backend/commands/proclang.c 2011-11-25 12:17:46.031559228 +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/parser/gram.y.orig 2011-11-24 12:51:44.810315553 +0100 --- ./src/backend/parser/gram.y 2011-11-25 12:05:31.859784321 +0100 *************** *** 227,232 **** --- 227,233 ---- DeallocateStmt PrepareStmt ExecuteStmt DropOwnedStmt ReassignOwnedStmt AlterTSConfigurationStmt AlterTSDictionaryStmt + CheckFunctionStmt %type select_no_parens select_with_parens select_clause simple_select values_clause *************** *** 276,282 **** %type func_name handler_name qual_Op qual_all_Op subquery_Op opt_class opt_inline_handler opt_validator validator_clause ! opt_collate %type qualified_name OptConstrFromTable --- 277,283 ---- %type 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 qualified_name OptConstrFromTable *************** *** 701,706 **** --- 702,708 ---- | AlterUserSetStmt | AlterUserStmt | AnalyzeStmt + | CheckFunctionStmt | CheckPointStmt | ClosePortalStmt | ClusterStmt *************** *** 3206,3216 **** 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; --- 3208,3219 ---- 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; *************** *** 3218,3223 **** --- 3221,3227 ---- n->plhandler = $8; n->plinline = $9; n->plvalidator = $10; + n->plchecker = $11; n->pltrusted = $3; $$ = (Node *)n; } *************** *** 3252,3257 **** --- 3256,3266 ---- | /*EMPTY*/ { $$ = NIL; } ; + opt_checker: + CHECK handler_name { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + DropPLangStmt: DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior { *************** *** 6282,6287 **** --- 6291,6326 ---- /***************************************************************************** * + * 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 [ LANGUAGE language ] * * We use a DefElem list for future extensibility, and to allow flexibility *** ./src/backend/tcop/utility.c.orig 2011-11-24 12:51:44.929314198 +0100 --- ./src/backend/tcop/utility.c 2011-11-25 12:05:31.880784082 +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/psql/tab-complete.c.orig 2011-11-24 12:51:45.147311716 +0100 --- ./src/bin/psql/tab-complete.c 2011-11-25 12:05:31.882784060 +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-25 12:04:21.610587514 +0100 --- ./src/include/catalog/pg_language.h 2011-11-25 12:05:31.883784048 +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-25 12:04:21.612587492 +0100 --- ./src/include/catalog/pg_pltemplate.h 2011-11-25 12:05:31.884784036 +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-24 12:51:45.172311430 +0100 --- ./src/include/commands/defrem.h 2011-11-25 12:05:31.885784024 +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-24 12:51:45.000000000 +0100 --- ./src/include/nodes/nodes.h 2011-11-25 12:22:37.306374249 +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-24 12:51:45.194311178 +0100 --- ./src/include/nodes/parsenodes.h 2011-11-25 12:05:31.888783991 +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/Makefile.orig 2011-11-24 12:51:45.000000000 +0100 --- ./src/pl/plpgsql/src/Makefile 2011-11-25 13:23:57.605922840 +0100 *************** *** 17,23 **** SHLIB_LINK = $(filter -lintl, $(LIBS)) rpath = ! OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql --- 17,23 ---- SHLIB_LINK = $(filter -lintl, $(LIBS)) rpath = ! OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o pl_check.o DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql *************** *** 44,50 **** # Force these dependencies to be known even without dependency info built: ! pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o: plpgsql.h pl_gram.h plerrcodes.h # See notes in src/backend/parser/Makefile about the following two rules --- 44,50 ---- # Force these dependencies to be known even without dependency info built: ! pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o pl_check.o: plpgsql.h pl_gram.h plerrcodes.h # See notes in src/backend/parser/Makefile about the following two rules *** ./src/pl/plpgsql/src/pl_exec.c.orig 2011-11-24 12:51:45.000000000 +0100 --- ./src/pl/plpgsql/src/pl_exec.c 2011-11-25 22:19:41.398342164 +0100 *************** *** 80,86 **** * Local function forward declarations ************************************************************/ static void plpgsql_exec_error_callback(void *arg); - static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum); static int exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block); --- 80,85 ---- *************** *** 133,141 **** static int exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt); - static void plpgsql_estate_setup(PLpgSQL_execstate *estate, - PLpgSQL_function *func, - ReturnSetInfo *rsi); static void exec_eval_cleanup(PLpgSQL_execstate *estate); static void exec_prepare_plan(PLpgSQL_execstate *estate, --- 132,137 ---- *************** *** 181,190 **** static ParamListInfo setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr); static void plpgsql_param_fetch(ParamListInfo params, int paramid); - static void exec_move_row(PLpgSQL_execstate *estate, - PLpgSQL_rec *rec, - PLpgSQL_row *row, - HeapTuple tup, TupleDesc tupdesc); static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); --- 177,182 ---- *************** *** 201,207 **** static void exec_init_tuple_store(PLpgSQL_execstate *estate); static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void plpgsql_create_econtext(PLpgSQL_execstate *estate); - static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate); static void free_var(PLpgSQL_var *var); static void assign_text_var(PLpgSQL_var *var, const char *str); static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate, --- 193,198 ---- *************** *** 229,235 **** * Setup the execution state */ plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo); - /* * Setup error traceback support for ereport() */ --- 220,225 ---- *************** *** 243,249 **** */ estate.err_text = gettext_noop("during initialization of execution state"); 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 --- 233,239 ---- */ estate.err_text = gettext_noop("during initialization of execution state"); for (i = 0; i < estate.ndatums; i++) ! estate.datums[i] = plpgsql_copy_datum(func->datums[i]); /* * Store the actual call argument values into the appropriate variables *************** *** 287,299 **** ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = td; ! exec_move_row(&estate, NULL, row, &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } else { /* If arg is null, treat it as an empty row */ ! exec_move_row(&estate, NULL, row, NULL, NULL); } } break; --- 277,289 ---- ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = td; ! plpgsql_exec_move_row(&estate, NULL, row, &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } else { /* If arg is null, treat it as an empty row */ ! plpgsql_exec_move_row(&estate, NULL, row, NULL, NULL); } } break; *************** *** 514,520 **** */ estate.err_text = gettext_noop("during initialization of execution state"); 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 --- 504,510 ---- */ estate.err_text = gettext_noop("during initialization of execution state"); for (i = 0; i < estate.ndatums; i++) ! estate.datums[i] = plpgsql_copy_datum(func->datums[i]); /* * Put the OLD and NEW tuples into record variables *************** *** 832,839 **** * Support function for initializing local execution variables * ---------- */ ! static PLpgSQL_datum * ! copy_plpgsql_datum(PLpgSQL_datum *datum) { PLpgSQL_datum *result; --- 822,829 ---- * Support function for initializing local execution variables * ---------- */ ! PLpgSQL_datum * ! plpgsql_copy_datum(PLpgSQL_datum *datum) { PLpgSQL_datum *result; *************** *** 2854,2860 **** * Initialize a mostly empty execution state * ---------- */ ! static void plpgsql_estate_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, ReturnSetInfo *rsi) --- 2844,2850 ---- * Initialize a mostly empty execution state * ---------- */ ! void plpgsql_estate_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, ReturnSetInfo *rsi) *************** *** 3170,3176 **** (errcode(ERRCODE_NO_DATA_FOUND), errmsg("query returned no rows"))); /* set the target to NULL(s) */ ! exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); } else { --- 3160,3166 ---- (errcode(ERRCODE_NO_DATA_FOUND), errmsg("query returned no rows"))); /* set the target to NULL(s) */ ! plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); } else { *************** *** 3179,3185 **** (errcode(ERRCODE_TOO_MANY_ROWS), errmsg("query returned more than one row"))); /* Put the first result row into the target */ ! exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); } /* Clean up */ --- 3169,3175 ---- (errcode(ERRCODE_TOO_MANY_ROWS), errmsg("query returned more than one row"))); /* Put the first result row into the target */ ! plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); } /* Clean up */ *************** *** 3350,3356 **** (errcode(ERRCODE_NO_DATA_FOUND), errmsg("query returned no rows"))); /* set the target to NULL(s) */ ! exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); } else { --- 3340,3346 ---- (errcode(ERRCODE_NO_DATA_FOUND), errmsg("query returned no rows"))); /* set the target to NULL(s) */ ! plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); } else { *************** *** 3359,3365 **** (errcode(ERRCODE_TOO_MANY_ROWS), errmsg("query returned more than one row"))); /* Put the first result row into the target */ ! exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); } } else --- 3349,3355 ---- (errcode(ERRCODE_TOO_MANY_ROWS), errmsg("query returned more than one row"))); /* Put the first result row into the target */ ! plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); } } else *************** *** 3630,3638 **** * ---------- */ if (n == 0) ! exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); else ! exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); SPI_freetuptable(tuptab); } --- 3620,3628 ---- * ---------- */ if (n == 0) ! plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); else ! plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); SPI_freetuptable(tuptab); } *************** *** 3806,3812 **** if (*isNull) { /* If source is null, just assign nulls to the row */ ! exec_move_row(estate, NULL, row, NULL, NULL); } else { --- 3796,3802 ---- if (*isNull) { /* If source is null, just assign nulls to the row */ ! plpgsql_exec_move_row(estate, NULL, row, NULL, NULL); } else { *************** *** 3832,3838 **** ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = td; ! exec_move_row(estate, NULL, row, &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } break; --- 3822,3828 ---- ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = td; ! plpgsql_exec_move_row(estate, NULL, row, &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } break; *************** *** 3848,3854 **** if (*isNull) { /* If source is null, just assign nulls to the record */ ! exec_move_row(estate, rec, NULL, NULL, NULL); } else { --- 3838,3844 ---- if (*isNull) { /* If source is null, just assign nulls to the record */ ! plpgsql_exec_move_row(estate, rec, NULL, NULL, NULL); } else { *************** *** 3875,3881 **** ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = td; ! exec_move_row(estate, rec, NULL, &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } break; --- 3865,3871 ---- ItemPointerSetInvalid(&(tmptup.t_self)); tmptup.t_tableOid = InvalidOid; tmptup.t_data = td; ! plpgsql_exec_move_row(estate, rec, NULL, &tmptup, tupdesc); ReleaseTupleDesc(tupdesc); } break; *************** *** 4730,4736 **** * through with found = false. */ if (n <= 0) ! exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); else found = true; /* processed at least one tuple */ --- 4720,4726 ---- * through with found = false. */ if (n <= 0) ! plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); else found = true; /* processed at least one tuple */ *************** *** 4746,4752 **** /* * Assign the tuple to the target */ ! exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc); /* * Execute the statements --- 4736,4742 ---- /* * Assign the tuple to the target */ ! plpgsql_exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc); /* * Execute the statements *************** *** 5137,5147 **** /* ---------- ! * exec_move_row Move one tuple's values into a record or row * ---------- */ ! static void ! exec_move_row(PLpgSQL_execstate *estate, PLpgSQL_rec *rec, PLpgSQL_row *row, HeapTuple tup, TupleDesc tupdesc) --- 5127,5137 ---- /* ---------- ! * plpgsql_exec_move_row Move one tuple's values into a record or row * ---------- */ ! void ! plpgsql_exec_move_row(PLpgSQL_execstate *estate, PLpgSQL_rec *rec, PLpgSQL_row *row, HeapTuple tup, TupleDesc tupdesc) *************** *** 5916,5922 **** * We check that it matches the top stack entry, and destroy the stack * entry along with the context. */ ! static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate) { SimpleEcontextStackEntry *next; --- 5906,5912 ---- * We check that it matches the top stack entry, and destroy the stack * entry along with the context. */ ! void plpgsql_destroy_econtext(PLpgSQL_execstate *estate) { SimpleEcontextStackEntry *next; *** ./src/pl/plpgsql/src/pl_handler.c.orig 2011-11-25 12:04:21.661586925 +0100 --- ./src/pl/plpgsql/src/pl_handler.c 2011-11-26 08:54:10.153356791 +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/pl_check.c.orig 2011-11-25 13:07:08.000000000 +0100 --- ./src/pl/plpgsql/src/pl_check.c 2011-11-26 09:15:17.010967364 +0100 *************** *** 0 **** --- 1,1091 ---- + /*------------------------------------------------------------------------- + * + * pl_check.c - Checker part of the PL/pgSQL + * procedural language + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/pl/plpgsql/src/pl_check.c + * + *------------------------------------------------------------------------- + */ + + #include "plpgsql.h" + + #include "funcapi.h" + #include "catalog/pg_type.h" + #include "executor/spi_priv.h" + #include "utils/builtins.h" + #include "utils/lsyscache.h" + #include "utils/rel.h" + #include "utils/typcache.h" + + static void check_target(PLpgSQL_execstate *estate, int varno); + static void check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec); + static void prepare_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, int cursorOptions); + 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 void assign_tupdesc_dno(PLpgSQL_execstate *estate, int varno, 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 check_stmts(PLpgSQL_execstate *estate, List *stmts); + static void check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); + + static void var_init_to_null(PLpgSQL_execstate *estate, int varno); + + + /* + * 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] = plpgsql_copy_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]; + + plpgsql_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] = plpgsql_copy_datum(func->datums[i]); + + /* + * Put the OLD and NEW tuples into record variables + * + * We make the tupdescs available in both records even though only one may + * have a value. This allows parsing of record references to succeed in + * functions that are used for multiple trigger types. For example, we + * might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')", + * which should parse regardless of the current trigger type. + */ + rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]); + rec_new->freetup = false; + rec_new->freetupdesc = false; + assign_tupdesc_row_or_rec(&estate, NULL, rec_new, trigdata->tg_relation->rd_att); + + rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]); + rec_old->freetup = false; + rec_old->freetupdesc = false; + assign_tupdesc_row_or_rec(&estate, NULL, rec_old, trigdata->tg_relation->rd_att); + + /* + * Assign the special tg_ variables + */ + var_init_to_null(&estate, func->tg_op_varno); + var_init_to_null(&estate, func->tg_name_varno); + var_init_to_null(&estate, func->tg_when_varno); + var_init_to_null(&estate, func->tg_level_varno); + var_init_to_null(&estate, func->tg_relid_varno); + var_init_to_null(&estate, func->tg_relname_varno); + var_init_to_null(&estate, func->tg_table_name_varno); + var_init_to_null(&estate, func->tg_table_schema_varno); + var_init_to_null(&estate, func->tg_nargs_varno); + var_init_to_null(&estate, func->tg_argv_varno); + + /* + * Now check the toplevel block of statements + */ + check_stmt(&estate, (PLpgSQL_stmt *) func->action); + + /* Cleanup temporary memory */ + plpgsql_destroy_econtext(&estate); + + /* Pop the error context stack */ + error_context_stack = plerrcontext.previous; + } + + /* + * Verify lvalue + * It doesn't repeat a checks that are done. + * Checks a subscript expressions, verify a validity of record's fields + */ + static void + check_target(PLpgSQL_execstate *estate, int varno) + { + PLpgSQL_datum *target = estate->datums[varno]; + + switch (target->dtype) + { + case PLPGSQL_DTYPE_VAR: + case PLPGSQL_DTYPE_REC: + break; + + case PLPGSQL_DTYPE_ROW: + check_row_or_rec(estate, (PLpgSQL_row *) target, NULL); + break; + + case PLPGSQL_DTYPE_RECFIELD: + { + PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target; + PLpgSQL_rec *rec; + int fno; + + rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); + + /* + * Check that there is already a tuple in the record. We need + * that because records don't have any predefined field + * structure. + */ + if (!HeapTupleIsValid(rec->tup)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("record \"%s\" is not assigned to tuple structure", + rec->refname))); + + /* + * Get the number of the records field to change and the + * number of attributes in the tuple. Note: disallow system + * column names because the code below won't cope. + */ + fno = SPI_fnumber(rec->tupdesc, recfield->fieldname); + if (fno <= 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("record \"%s\" has no field \"%s\"", + rec->refname, recfield->fieldname))); + } + break; + + case PLPGSQL_DTYPE_ARRAYELEM: + { + /* + * Target is an element of an array + */ + int nsubscripts; + Oid arrayelemtypeid; + Oid arraytypeid; + + /* + * To handle constructs like x[1][2] := something, we have to + * be prepared to deal with a chain of arrayelem datums. Chase + * back to find the base array datum, and save the subscript + * expressions as we go. (We are scanning right to left here, + * but want to evaluate the subscripts left-to-right to + * minimize surprises.) + */ + nsubscripts = 0; + do + { + PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target; + + if (nsubscripts++ >= MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + nsubscripts + 1, MAXDIM))); + + check_expr(estate, arrayelem->subscript); + + target = estate->datums[arrayelem->arrayparentno]; + } while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM); + + /* If target is domain over array, reduce to base type */ + arraytypeid = exec_get_datum_type(estate, target); + arraytypeid = getBaseType(arraytypeid); + + arrayelemtypeid = get_element_type(arraytypeid); + + if (!OidIsValid(arrayelemtypeid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("subscripted object is not an array"))); + } + break; + } + } + + /* + * Check composed lvalue + * There is nothing to check on rec variables + */ + static void + check_row_or_rec(PLpgSQL_execstate *estate, PLpgSQL_row *row, PLpgSQL_rec *rec) + { + int fnum; + + /* there are nothing to check on rec now */ + if (row != NULL) + { + for (fnum = 0; fnum < row->nfields; fnum++) + { + /* skip dropped columns */ + if (row->varnos[fnum] < 0) + continue; + + check_target(estate, row->varnos[fnum]); + } + } + } + + /* + * Generate a prepared plan - this is simplyfied copy from pl_exec.c + * Is not necessary to check simple plan + */ + static void + prepare_expr(PLpgSQL_execstate *estate, + PLpgSQL_expr *expr, int cursorOptions) + { + SPIPlanPtr plan; + + /* leave when there are not expression */ + if (expr == NULL) + return; + + /* leave when plan is created */ + if (expr->plan != NULL) + return; + + /* + * The grammar can't conveniently set expr->func while building the parse + * tree, so make sure it's set before parser hooks need it. + */ + expr->func = estate->func; + + /* + * Generate and save the plan + */ + plan = SPI_prepare_params(expr->query, + (ParserSetupHook) plpgsql_parser_setup, + (void *) expr, + cursorOptions); + if (plan == NULL) + { + /* Some SPI errors deserve specific error messages */ + switch (SPI_result) + { + case SPI_ERROR_COPY: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot COPY to/from client in PL/pgSQL"))); + case SPI_ERROR_TRANSACTION: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot begin/end transactions in PL/pgSQL"), + errhint("Use a BEGIN block with an EXCEPTION clause instead."))); + default: + elog(ERROR, "SPI_prepare_params failed for \"%s\": %s", + expr->query, SPI_result_code_string(SPI_result)); + } + } + + expr->plan = SPI_saveplan(plan); + SPI_freeplan(plan); + } + + /* + * Verify a expression + */ + static void + check_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) + { + TupleDesc tupdesc; + + if (expr != NULL) + { + prepare_expr(estate, expr, 0); + tupdesc = expr_get_desc(estate, expr, false, false, true); + ReleaseTupleDesc(tupdesc); + } + } + + /* + * We have to assign TupleDesc to all used record variables step by step. + * We would to use a exec routines for query preprocessing, so we must + * to create a typed NULL value, and this value is assigned to record + * variable. + */ + static void + assign_tupdesc_row_or_rec(PLpgSQL_execstate *estate, + PLpgSQL_row *row, PLpgSQL_rec *rec, + TupleDesc tupdesc) + { + bool *nulls; + HeapTuple tup; + + if (tupdesc == NULL) + elog(ERROR, "tuple descriptor is empty"); + + /* + * row variable has assigned TupleDesc already, so don't be processed + * here + */ + if (rec != NULL) + { + PLpgSQL_rec *target = (PLpgSQL_rec *)(estate->datums[rec->dno]); + + if (target->freetup) + heap_freetuple(target->tup); + + if (rec->freetupdesc) + FreeTupleDesc(target->tupdesc); + + /* initialize rec by NULLs */ + nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); + memset(nulls, true, tupdesc->natts * sizeof(bool)); + + target->tupdesc = CreateTupleDescCopy(tupdesc); + target->freetupdesc = true; + + tup = heap_form_tuple(tupdesc, NULL, nulls); + if (HeapTupleIsValid(tup)) + { + target->tup = tup; + target->freetup = true; + } + else + elog(ERROR, "cannot to build valid composite value"); + } + } + + /* + * Assign a tuple descriptor to variable specified by dno + */ + static void + assign_tupdesc_dno(PLpgSQL_execstate *estate, int varno, TupleDesc tupdesc) + { + PLpgSQL_datum *target = estate->datums[varno]; + + if (target->dtype == PLPGSQL_DTYPE_REC) + assign_tupdesc_row_or_rec(estate, NULL, (PLpgSQL_rec *) target, tupdesc); + } + + /* + * Returns a tuple descriptor based on existing plan + */ + static TupleDesc + expr_get_desc(PLpgSQL_execstate *estate, + PLpgSQL_expr *query, + bool use_element_type, + bool expand_record, + bool is_expression) + { + TupleDesc tupdesc = NULL; + CachedPlanSource *plansource = NULL; + + if (query->plan != NULL) + { + SPIPlanPtr plan = query->plan; + + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) + elog(ERROR, "cached plan is not valid plan"); + + if (list_length(plan->plancache_list) != 1) + elog(ERROR, "plan is not single execution plan"); + + plansource = (CachedPlanSource *) linitial(plan->plancache_list); + + tupdesc = CreateTupleDescCopy(plansource->resultDesc); + } + else + elog(ERROR, "there are no plan for query: \"%s\"", + query->query); + + /* + * try to get a element type, when result is a array (used with FOREACH ARRAY stmt) + */ + if (use_element_type) + { + Oid elemtype; + TupleDesc elemtupdesc; + + /* result should be a array */ + if (tupdesc->natts != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("query \"%s\" returned %d column", + "query \"%s\" returned %d columns", + tupdesc->natts, + query->query, + tupdesc->natts))); + + /* check the type of the expression - must be an array */ + elemtype = get_element_type(tupdesc->attrs[0]->atttypid); + if (!OidIsValid(elemtype)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("FOREACH expression must yield an array, not type %s", + format_type_be(tupdesc->attrs[0]->atttypid)))); + + /* we can't know typmod now */ + elemtupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true); + if (elemtupdesc != NULL) + { + FreeTupleDesc(tupdesc); + tupdesc = CreateTupleDescCopy(elemtupdesc); + ReleaseTupleDesc(elemtupdesc); + } + else + elog(ERROR, "cannot to identify real type for record type variable"); + } + + if (is_expression && tupdesc->natts != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("query \"%s\" returned %d column", + "query \"%s\" returned %d columns", + tupdesc->natts, + query->query, + tupdesc->natts))); + + /* + * One spacial case is when record is assigned to composite type, then + * we should to unpack composite type. + */ + if (tupdesc->tdtypeid == RECORDOID && + tupdesc->tdtypmod == -1 && + tupdesc->natts == 1 && expand_record) + { + TupleDesc unpack_tupdesc; + + unpack_tupdesc = lookup_rowtype_tupdesc_noerror(tupdesc->attrs[0]->atttypid, + tupdesc->attrs[0]->atttypmod, + true); + if (unpack_tupdesc != NULL) + { + FreeTupleDesc(tupdesc); + tupdesc = CreateTupleDescCopy(unpack_tupdesc); + ReleaseTupleDesc(unpack_tupdesc); + } + } + + /* + * There is special case, when returned tupdesc contains only + * unpined record: rec := func_with_out_parameters(). IN this case + * we must to dig more deep - we have to find oid of function and + * get their parameters, + * + * This is support for assign statement + * recvar := func_with_out_parameters(..) + */ + if (tupdesc->tdtypeid == RECORDOID && + tupdesc->tdtypmod == -1 && + tupdesc->natts == 1 && + tupdesc->attrs[0]->atttypid == RECORDOID && + tupdesc->attrs[0]->atttypmod == -1 && + expand_record) + { + PlannedStmt *_stmt; + Plan *_plan; + TargetEntry *tle; + CachedPlan *cplan; + + /* + * When tupdesc is related to unpined record, we will try + * to check plan if it is just function call and if it is + * then we can try to derive a tupledes from function's + * description. + */ + cplan = GetCachedPlan(plansource, NULL, true); + _stmt = (PlannedStmt *) linitial(cplan->stmt_list); + + if (IsA(_stmt, PlannedStmt) && _stmt->commandType == CMD_SELECT) + { + _plan = _stmt->planTree; + if (IsA(_plan, Result) && list_length(_plan->targetlist) == 1) + { + tle = (TargetEntry *) linitial(_plan->targetlist); + if (((Node *) tle->expr)->type == T_FuncExpr) + { + FuncExpr *fn = (FuncExpr *) tle->expr; + FmgrInfo flinfo; + FunctionCallInfoData fcinfo; + TupleDesc rd; + Oid rt; + + fmgr_info(fn->funcid, &flinfo); + flinfo.fn_expr = (Node *) fn; + fcinfo.flinfo = &flinfo; + + get_call_result_type(&fcinfo, &rt, &rd); + if (rd == NULL) + elog(ERROR, "function does not return composite type is not possible to identify composite type"); + + FreeTupleDesc(tupdesc); + BlessTupleDesc(rd); + + tupdesc = rd; + } + } + } + + ReleaseCachedPlan(cplan, true); + } + + return tupdesc; + } + + /* + * Ensure check for all statements in list + */ + static void + check_stmts(PLpgSQL_execstate *estate, List *stmts) + { + ListCell *lc; + + foreach(lc, stmts) + { + check_stmt(estate, (PLpgSQL_stmt *) lfirst(lc)); + } + } + + /* + * walk over all statements + */ + static void + check_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) + { + TupleDesc tupdesc = NULL; + PLpgSQL_function *func; + ListCell *l; + + if (stmt == NULL) + return; + + estate->err_stmt = stmt; + func = estate->func; + + switch ((enum PLpgSQL_stmt_types) stmt->cmd_type) + { + case PLPGSQL_STMT_BLOCK: + { + PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt; + int i; + PLpgSQL_datum *d; + + for (i = 0; i < stmt_block->n_initvars; i++) + { + d = func->datums[stmt_block->initvarnos[i]]; + + if (d->dtype == PLPGSQL_DTYPE_VAR) + { + PLpgSQL_var *var = (PLpgSQL_var *) d; + + check_expr(estate, var->default_val); + } + } + + check_stmts(estate, stmt_block->body); + + if (stmt_block->exceptions) + { + foreach(l, stmt_block->exceptions->exc_list) + { + check_stmts(estate, ((PLpgSQL_exception *) lfirst(l))->action); + } + } + } + break; + + case PLPGSQL_STMT_ASSIGN: + { + PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt; + + /* prepare plan if desn't exist yet */ + prepare_expr(estate, stmt_assign->expr, 0); + + tupdesc = expr_get_desc(estate, + stmt_assign->expr, + false, /* no element type */ + true, /* expand record */ + true); /* is expression */ + + /* check target, ensure target can get a result */ + check_target(estate, stmt_assign->varno); + + /* assign a tupdesc to record variable */ + assign_tupdesc_dno(estate, stmt_assign->varno, tupdesc); + ReleaseTupleDesc(tupdesc); + } + break; + + case PLPGSQL_STMT_IF: + { + PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; + ListCell *l; + + check_expr(estate, stmt_if->cond); + + check_stmts(estate, stmt_if->then_body); + + foreach(l, stmt_if->elsif_list) + { + PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l); + + check_expr(estate, elif->cond); + check_stmts(estate, elif->stmts); + } + + check_stmts(estate, stmt_if->else_body); + } + break; + + case PLPGSQL_STMT_CASE: + { + PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; + Oid result_oid; + + if (stmt_case->t_expr != NULL) + { + PLpgSQL_var *t_var = (PLpgSQL_var *) estate->datums[stmt_case->t_varno]; + + /* we need to set hidden variable type */ + prepare_expr(estate, stmt_case->t_expr, 0); + + tupdesc = expr_get_desc(estate, + stmt_case->t_expr, + false, /* no element type */ + false, /* expand record */ + true); /* is expression */ + + result_oid = tupdesc->attrs[0]->atttypid; + + /* + * When expected datatype is different from real, change it. Note that + * what we're modifying here is an execution copy of the datum, so + * this doesn't affect the originally stored function parse tree. + */ + + if (t_var->datatype->typoid != result_oid) + t_var->datatype = plpgsql_build_datatype(result_oid, + -1, + estate->func->fn_input_collation); + + ReleaseTupleDesc(tupdesc); + } + + foreach(l, stmt_case->case_when_list) + { + PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); + + check_expr(estate, cwt->expr); + check_stmts(estate, cwt->stmts); + } + + check_stmts(estate, stmt_case->else_stmts); + } + break; + + case PLPGSQL_STMT_LOOP: + check_stmts(estate, ((PLpgSQL_stmt_loop *) stmt)->body); + break; + + case PLPGSQL_STMT_WHILE: + { + PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt; + + check_expr(estate, stmt_while->cond); + check_stmts(estate, stmt_while->body); + } + break; + + case PLPGSQL_STMT_FORI: + { + PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt; + + check_expr(estate, stmt_fori->lower); + check_expr(estate, stmt_fori->upper); + check_expr(estate, stmt_fori->step); + + check_stmts(estate, stmt_fori->body); + } + break; + + case PLPGSQL_STMT_FORS: + { + PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt; + + /* we need to set hidden variable type */ + prepare_expr(estate, stmt_fors->query, 0); + + tupdesc = expr_get_desc(estate, + stmt_fors->query, + false, /* no element type */ + false, /* expand record */ + false); /* is expression */ + + check_row_or_rec(estate, stmt_fors->row, stmt_fors->rec); + assign_tupdesc_row_or_rec(estate, stmt_fors->row, stmt_fors->rec, tupdesc); + + check_stmts(estate, stmt_fors->body); + ReleaseTupleDesc(tupdesc); + } + break; + + case PLPGSQL_STMT_FORC: + { + PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt; + PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar]; + + prepare_expr(estate, stmt_forc->argquery, 0); + + if (var->cursor_explicit_expr != NULL) + { + prepare_expr(estate, var->cursor_explicit_expr, + var->cursor_options); + + tupdesc = expr_get_desc(estate, + var->cursor_explicit_expr, + false, /* no element type */ + false, /* expand record */ + false); /* is expression */ + + check_row_or_rec(estate, stmt_forc->row, stmt_forc->rec); + assign_tupdesc_row_or_rec(estate, stmt_forc->row, stmt_forc->rec, tupdesc); + } + + check_stmts(estate, stmt_forc->body); + if (tupdesc != NULL) + ReleaseTupleDesc(tupdesc); + } + break; + + case PLPGSQL_STMT_DYNFORS: + { + PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt; + + if (stmt_dynfors->rec != NULL) + elog(ERROR, "cannot determinate a result of dynamic SQL"); + + check_expr(estate, stmt_dynfors->query); + + foreach(l, stmt_dynfors->params) + { + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); + } + + check_stmts(estate, stmt_dynfors->body); + } + break; + + case PLPGSQL_STMT_FOREACH_A: + { + PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt; + + prepare_expr(estate, stmt_foreach_a->expr, 0); + + tupdesc = expr_get_desc(estate, + stmt_foreach_a->expr, + true, /* no element type */ + false, /* expand record */ + true); /* is expression */ + + check_target(estate, stmt_foreach_a->varno); + assign_tupdesc_dno(estate, stmt_foreach_a->varno, tupdesc); + ReleaseTupleDesc(tupdesc); + + check_stmts(estate, stmt_foreach_a->body); + } + break; + + case PLPGSQL_STMT_EXIT: + check_expr(estate, ((PLpgSQL_stmt_exit *) stmt)->cond); + break; + + case PLPGSQL_STMT_PERFORM: + prepare_expr(estate, ((PLpgSQL_stmt_perform *) stmt)->expr, 0); + break; + + case PLPGSQL_STMT_RETURN: + check_expr(estate, ((PLpgSQL_stmt_return *) stmt)->expr); + break; + + case PLPGSQL_STMT_RETURN_NEXT: + check_expr(estate, ((PLpgSQL_stmt_return_next *) stmt)->expr); + break; + + case PLPGSQL_STMT_RETURN_QUERY: + { + PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt; + + check_expr(estate, stmt_rq->dynquery); + prepare_expr(estate, stmt_rq->query, 0); + + foreach(l, stmt_rq->params) + { + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); + } + } + break; + + case PLPGSQL_STMT_RAISE: + { + PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt; + ListCell *current_param; + char *cp; + + foreach(l, stmt_raise->params) + { + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); + } + + foreach(l, stmt_raise->options) + { + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); + } + + current_param = list_head(stmt_raise->params); + + /* ensure any single % has a own parameter */ + if (stmt_raise->message != NULL) + { + for (cp = stmt_raise->message; *cp; cp++) + { + if (cp[0] == '%') + { + if (cp[1] == '%') + { + cp++; + continue; + } + + if (current_param == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too few parameters specified for RAISE"))); + + current_param = lnext(current_param); + } + } + } + + if (current_param != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too many parameters specified for RAISE"))); + } + break; + + case PLPGSQL_STMT_EXECSQL: + { + PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt; + + prepare_expr(estate, stmt_execsql->sqlstmt, 0); + if (stmt_execsql->into) + { + tupdesc = expr_get_desc(estate, + stmt_execsql->sqlstmt, + false, /* no element type */ + false, /* expand record */ + false); /* is expression */ + + /* check target, ensure target can get a result */ + check_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec); + assign_tupdesc_row_or_rec(estate, stmt_execsql->row, stmt_execsql->rec, tupdesc); + ReleaseTupleDesc(tupdesc); + } + } + break; + + case PLPGSQL_STMT_DYNEXECUTE: + { + PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt; + + check_expr(estate, stmt_dynexecute->query); + + foreach(l, stmt_dynexecute->params) + { + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); + } + + if (stmt_dynexecute->into) + { + if (stmt_dynexecute->rec != NULL) + elog(ERROR, "cannot determinate a result of dynamic SQL"); + + check_row_or_rec(estate, stmt_dynexecute->row, stmt_dynexecute->rec); + } + } + break; + + case PLPGSQL_STMT_OPEN: + { + PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt; + PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_open->curvar]; + + if (var->cursor_explicit_expr) + prepare_expr(estate, var->cursor_explicit_expr, + var->cursor_options); + + prepare_expr(estate, stmt_open->query, 0); + prepare_expr(estate, stmt_open->argquery, 0); + check_expr(estate, stmt_open->dynquery); + + foreach(l, stmt_open->params) + { + check_expr(estate, (PLpgSQL_expr *) lfirst(l)); + } + } + break; + + case PLPGSQL_STMT_GETDIAG: + { + PLpgSQL_stmt_getdiag *stmt_getdiag = (PLpgSQL_stmt_getdiag *) stmt; + ListCell *lc; + + foreach(lc, stmt_getdiag->diag_items) + { + PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); + + check_target(estate, diag_item->target); + } + } + break; + + case PLPGSQL_STMT_FETCH: + { + PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt; + PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[stmt_fetch->curvar]); + + if (var != NULL && var->cursor_explicit_expr != NULL) + { + prepare_expr(estate, var->cursor_explicit_expr, + var->cursor_options); + tupdesc = expr_get_desc(estate, + var->cursor_explicit_expr, + false, /* no element type */ + false, /* expand record */ + false); /* is expression */ + check_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec); + assign_tupdesc_row_or_rec(estate, stmt_fetch->row, stmt_fetch->rec, tupdesc); + ReleaseTupleDesc(tupdesc); + } + } + break; + + case PLPGSQL_STMT_CLOSE: + break; + + default: + elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); + return; /* be compiler quite */ + } + } + + /* + * Initialize variable to NULL + */ + static void + var_init_to_null(PLpgSQL_execstate *estate, int varno) + { + PLpgSQL_var *var = (PLpgSQL_var *) estate->datums[varno]; + var->value = (Datum) 0; + var->isnull = true; + var->freeval = false; + } *** ./src/pl/plpgsql/src/plpgsql.h.orig 2011-11-24 12:51:45.000000000 +0100 --- ./src/pl/plpgsql/src/plpgsql.h 2011-11-25 22:45:14.167881352 +0100 *************** *** 901,906 **** --- 901,907 ---- extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname); extern void plpgsql_adddatum(PLpgSQL_datum *new); extern int plpgsql_add_initdatums(int **varnos); + extern void plpgsql_delete_function(PLpgSQL_function *func); extern void plpgsql_HashTableInit(void); /* ---------- *************** *** 911,921 **** --- 912,931 ---- 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_check.c + * ---------- + */ + extern void plpgsql_check_function(PLpgSQL_function *func, FunctionCallInfo fcinfo); + extern void plpgsql_check_trigger(PLpgSQL_function *func, TriggerData *trigdata); /* ---------- * Functions in pl_exec.c * ---------- */ + extern PLpgSQL_datum *plpgsql_copy_datum(PLpgSQL_datum *datum); extern Datum plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo); extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func, *************** *** 928,933 **** --- 938,952 ---- extern void exec_get_datum_type_info(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typmod, Oid *collation); + extern bool plpgsql_expr_prepare_plan(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context); + extern void plpgsql_estate_setup(PLpgSQL_execstate *estate, + PLpgSQL_function *func, + ReturnSetInfo *rsi); + extern void plpgsql_exec_move_row(PLpgSQL_execstate *estate, + PLpgSQL_rec *rec, + PLpgSQL_row *row, + HeapTuple tup, TupleDesc tupdesc); + extern void plpgsql_destroy_econtext(PLpgSQL_execstate *estate); /* ---------- * Functions for namespace handling in pl_funcs.c *** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig 2011-11-25 12:04:21.675586770 +0100 --- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql 2011-11-25 12:05:31.895783910 +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-24 12:51:45.000000000 +0100 --- ./src/test/regress/expected/plpgsql.out 2011-11-26 09:15:55.000000000 +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-24 12:51:45.000000000 +0100 --- ./src/test/regress/sql/plpgsql.sql 2011-11-26 09:01:01.346078538 +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(); +