*** ./doc/src/sgml/ref/allfiles.sgml.orig 2011-10-04 13:56:40.000000000 +0200 --- ./doc/src/sgml/ref/allfiles.sgml 2011-10-05 11:44:22.667571959 +0200 *************** *** 40,45 **** --- 40,46 ---- + *** ./doc/src/sgml/ref/create_language.sgml.orig 2011-10-04 13:58:46.580303751 +0200 --- ./doc/src/sgml/ref/create_language.sgml 2011-10-04 13:59:14.301344571 +0200 *************** *** 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 ] *************** *** 218,223 **** --- 218,237 ---- + + + 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-10-04 13:56:40.000000000 +0200 --- ./doc/src/sgml/reference.sgml 2011-10-05 14:18:39.597956163 +0200 *************** *** 68,73 **** --- 68,74 ---- &alterView; &analyze; &begin; + &checkFunction; &checkpoint; &close; &cluster; *** ./doc/src/sgml/ref/check_function.sgml.orig 2011-10-05 11:42:53.365345351 +0200 --- ./doc/src/sgml/ref/check_function.sgml 2011-10-06 13:33:07.781530754 +0200 *************** *** 0 **** --- 1,76 ---- + + + + + 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. + + + + postgres=# \d foo + Table "public.foo" + Column | Type | Modifiers + --------+---------+----------- + id | integer | not null + b | integer | + c | integer | + d | integer | + Indexes: + "foo_pkey" PRIMARY KEY, btree (id) + "foo_b_c_key" UNIQUE CONSTRAINT, btree (b, c) + "foo_b_key" UNIQUE CONSTRAINT, btree (b) + "foo_d_idx" btree (d) + Referenced by: + TABLE "omega" CONSTRAINT "omega_foo_id_fkey" FOREIGN KEY (foo_id) REFERENCES foo(id) + + postgres=# \sf fx + CREATE OR REPLACE FUNCTION public.fx(b integer) + RETURNS integer + LANGUAGE plpgsql + AS $function$ + DECLARE r record; + s int := 0; + BEGIN + FOR r IN SELECT * FROM foo WHERE foo.b = fx.b + LOOP + s := s + r.x; + END LOOP; + RETURN s; + END; + $function$ + postgres=# CHECK FUNCTION fx(int); + ERROR: record "r" has no field "x" + CONTEXT: SQL statement "SELECT s + r.x" + PL/pgSQL function "fx" line 7 at assignment + + + + + *** ./src/backend/catalog/pg_proc.c.orig 2011-10-04 13:56:40.742123353 +0200 --- ./src/backend/catalog/pg_proc.c 2011-10-04 13:59:14.302344572 +0200 *************** *** 1075,1077 **** --- 1075,1078 ---- *newcursorpos = newcp; return false; } + *** ./src/backend/commands/functioncmds.c.orig 2011-10-04 13:58:46.587303763 +0200 --- ./src/backend/commands/functioncmds.c 2011-10-06 13:13:53.211900081 +0200 *************** *** 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" *************** *** 1079,1084 **** --- 1081,1209 ---- } } + /* + * 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; + + 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 */ + } + + OidFunctionCall2(languageChecker, ObjectIdGetDatum(funcOid), + ObjectIdGetDatum(relid)); + + 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-10-04 13:58:46.590303767 +0200 --- ./src/backend/commands/proclang.c 2011-10-05 07:25:52.104765099 +0200 *************** *** 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); *************** *** 68,76 **** PLTemplate *pltemplate; Oid handlerOid, inlineOid, ! valOid; Oid funcrettype; ! Oid funcargtypes[1]; /* * Translate the language name to lower case --- 69,78 ---- PLTemplate *pltemplate; Oid handlerOid, inlineOid, ! valOid, ! checkerOid; Oid funcrettype; ! Oid funcargtypes[2]; /* * Translate the language name to lower case *************** *** 225,234 **** else valOid = InvalidOid; /* ok, create it */ create_proc_lang(languageName, stmt->replace, GetUserId(), handlerOid, inlineOid, ! valOid, pltemplate->tmpltrusted); } else { --- 227,275 ---- 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(languageName, stmt->replace, GetUserId(), handlerOid, inlineOid, ! valOid, checkerOid, pltemplate->tmpltrusted); } else { *************** *** 300,309 **** else valOid = InvalidOid; /* ok, create it */ create_proc_lang(languageName, stmt->replace, GetUserId(), handlerOid, inlineOid, ! valOid, stmt->pltrusted); } } --- 341,362 ---- else valOid = InvalidOid; + /* validate the validator 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(languageName, stmt->replace, GetUserId(), handlerOid, inlineOid, ! valOid, checkerOid, stmt->pltrusted); } } *************** *** 313,319 **** static void create_proc_lang(const char *languageName, bool replace, Oid languageOwner, Oid handlerOid, Oid inlineOid, ! Oid valOid, bool trusted) { Relation rel; TupleDesc tupDesc; --- 366,372 ---- 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; *************** *** 343,348 **** --- 396,402 ---- 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 */ *************** *** 429,434 **** --- 483,497 ---- 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); *************** *** 484,489 **** --- 547,557 ---- 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-10-04 13:56:40.000000000 +0200 --- ./src/backend/nodes/copyfuncs.c 2011-10-06 13:06:19.787276391 +0200 *************** *** 2867,2872 **** --- 2867,2885 ---- 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) { *************** *** 4230,4235 **** --- 4243,4251 ---- case T_RemoveFuncStmt: retval = _copyRemoveFuncStmt(from); break; + case T_CheckFunctionStmt: + retval = _copyCheckFunctionStmt(from); + break; case T_DoStmt: retval = _copyDoStmt(from); break; *** ./src/backend/nodes/equalfuncs.c.orig 2011-10-04 13:56:40.000000000 +0200 --- ./src/backend/nodes/equalfuncs.c 2011-10-06 13:08:28.417452554 +0200 *************** *** 1302,1307 **** --- 1302,1318 ---- } 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); *************** *** 2787,2792 **** --- 2798,2806 ---- case T_RemoveFuncStmt: retval = _equalRemoveFuncStmt(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-10-04 13:58:46.594303771 +0200 --- ./src/backend/parser/gram.y 2011-10-04 13:59:14.314344592 +0200 *************** *** 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 *************** *** 3197,3207 **** 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; --- 3199,3210 ---- 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; *************** *** 3209,3214 **** --- 3212,3218 ---- n->plhandler = $8; n->plinline = $9; n->plvalidator = $10; + n->plchecker = $11; n->pltrusted = $3; $$ = (Node *)n; } *************** *** 3243,3248 **** --- 3247,3257 ---- | /*EMPTY*/ { $$ = NIL; } ; + opt_checker: + CHECK handler_name { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + DropPLangStmt: DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior { *************** *** 6249,6254 **** --- 6258,6293 ---- /***************************************************************************** * + * 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-10-04 13:58:46.609303793 +0200 --- ./src/backend/tcop/utility.c 2011-10-05 08:11:46.129451749 +0200 *************** *** 936,941 **** --- 936,945 ---- AlterFunction((AlterFunctionStmt *) parsetree); break; + case T_CheckFunctionStmt: + CheckFunction((CheckFunctionStmt *) parsetree); + break; + case T_IndexStmt: /* CREATE INDEX */ { IndexStmt *stmt = (IndexStmt *) parsetree; *************** *** 2260,2265 **** --- 2264,2276 ---- } 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)); *************** *** 2722,2727 **** --- 2733,2742 ---- } 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-10-04 13:56:40.000000000 +0200 --- ./src/bin/psql/tab-complete.c 2011-10-05 08:51:23.603634388 +0200 *************** *** 1,4 **** --- 1,5 ---- /* + * * psql - the PostgreSQL interactive terminal * * Copyright (c) 2000-2011, PostgreSQL Global Development Group *************** *** 719,725 **** *prev6_wd; 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", --- 720,726 ---- *prev6_wd; 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", *************** *** 1521,1526 **** --- 1522,1549 ---- 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-10-04 13:58:46.610303794 +0200 --- ./src/include/catalog/pg_language.h 2011-10-04 13:59:14.317344596 +0200 *************** *** 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-10-04 13:58:46.612303798 +0200 --- ./src/include/catalog/pg_pltemplate.h 2011-10-04 13:59:14.318344598 +0200 *************** *** 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-10-04 13:58:46.614303802 +0200 --- ./src/include/commands/defrem.h 2011-10-04 13:59:14.318344598 +0200 *************** *** 61,66 **** --- 61,67 ---- extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString); extern void RemoveFunction(RemoveFuncStmt *stmt); 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-10-04 13:58:46.616303805 +0200 --- ./src/include/nodes/nodes.h 2011-10-04 13:59:14.319344599 +0200 *************** *** 290,295 **** --- 290,296 ---- T_CreateFunctionStmt, T_AlterFunctionStmt, T_RemoveFuncStmt, + T_CheckFunctionStmt, T_DoStmt, T_RenameStmt, T_RuleStmt, *** ./src/include/nodes/parsenodes.h.orig 2011-10-04 13:56:40.994123707 +0200 --- ./src/include/nodes/parsenodes.h 2011-10-04 13:59:14.320344601 +0200 *************** *** 1750,1755 **** --- 1750,1756 ---- 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; *************** *** 2132,2137 **** --- 2133,2151 ---- } RemoveFuncStmt; /* ---------------------- + * 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_exec.c.orig 2011-10-04 13:56:41.051123786 +0200 --- ./src/pl/plpgsql/src/pl_exec.c 2011-10-05 07:45:35.615349404 +0200 *************** *** 211,216 **** --- 211,220 ---- PLpgSQL_expr *dynquery, List *params, const char *portalname, int cursorOptions); + static void + set_rectype_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt, + PLpgSQL_rec *rec,PLpgSQL_expr *query, + bool use_element_type); /* ---------- * plpgsql_exec_function Called by the call handler for *************** *** 6175,6177 **** --- 6179,6837 ---- return portal; } + + /* + * Sometime we must initialize a unknown record variable with NULL + * of type that is derived from some plan. This is necessary for later + * using a rec variable. Last parameter 'use_element_type' is true, when + * we would to assign a element type of result array. + * + */ + static void + set_rectype_for_query(PLpgSQL_execstate *estate, + PLpgSQL_stmt *stmt, + PLpgSQL_rec *rec, + PLpgSQL_expr *query, + bool use_element_type) + { + bool *nulls; + HeapTuple tup; + CachedPlanSource *plansource = NULL; + const char *err_text = estate->err_text; + + estate->err_text = NULL; + estate->err_stmt = stmt; + + if (rec->freetup) + heap_freetuple(rec->tup); + + if (rec->freetupdesc) + FreeTupleDesc(rec->tupdesc); + + 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); + + rec->tupdesc = CreateTupleDescCopy(plansource->resultDesc); + rec->freetupdesc = true; + } + 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 tupdesc; + + /* result should be a array */ + if (rec->tupdesc->natts != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("query \"%s\" returned %d column", + "query \"%s\" returned %d columns", + rec->tupdesc->natts, + query->query, + rec->tupdesc->natts))); + + /* check the type of the expression - must be an array */ + elemtype = get_element_type(rec->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(rec->tupdesc->attrs[0]->atttypid)))); + + /* we can't know typmod now */ + tupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true); + if (tupdesc != NULL) + { + if (rec->freetupdesc) + FreeTupleDesc(rec->tupdesc); + rec->tupdesc = CreateTupleDescCopy(tupdesc); + rec->freetupdesc = true; + ReleaseTupleDesc(tupdesc); + } + else + elog(ERROR, "cannot to identify real type for record type variable"); + } + + /* + * When record is assigned to composite type, then + * we should to unpack composite type. + */ + if (rec->tupdesc->tdtypeid == RECORDOID && + rec->tupdesc->tdtypmod == -1 && + rec->tupdesc->natts == 1 && + stmt->cmd_type == PLPGSQL_STMT_ASSIGN) + { + TupleDesc tupdesc; + + tupdesc = lookup_rowtype_tupdesc_noerror(rec->tupdesc->attrs[0]->atttypid, + rec->tupdesc->attrs[0]->atttypmod, + true); + if (tupdesc != NULL) + { + if (rec->freetupdesc) + FreeTupleDesc(rec->tupdesc); + rec->tupdesc = CreateTupleDescCopy(tupdesc); + rec->freetupdesc = true; + ReleaseTupleDesc(tupdesc); + } + } + + /* + * 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 (rec->tupdesc->tdtypeid == RECORDOID && + rec->tupdesc->tdtypmod == -1 && + rec->tupdesc->natts == 1 && + rec->tupdesc->attrs[0]->atttypid == RECORDOID && + rec->tupdesc->attrs[0]->atttypmod == -1) + { + 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(rec->tupdesc); + BlessTupleDesc(rd); + + rec->tupdesc = rd; + } + } + } + + ReleaseCachedPlan(cplan, true); + } + + /* last recheck */ + if (rec->tupdesc->tdtypeid == RECORDOID && + rec->tupdesc->tdtypmod == -1 && + rec->tupdesc->natts == 1 && + rec->tupdesc->attrs[0]->atttypid == RECORDOID && + rec->tupdesc->attrs[0]->atttypmod == -1) + elog(ERROR, "cannot to identify real type for record type variable"); + + /* initialize rec by NULLs - variable should be valid */ + nulls = (bool *) palloc(rec->tupdesc->natts * sizeof(bool)); + memset(nulls, true, rec->tupdesc->natts * sizeof(bool)); + + tup = heap_form_tuple(rec->tupdesc, NULL, nulls); + if (HeapTupleIsValid(tup)) + { + rec->tup = tup; + rec->freetup = true; + } + else + { + rec->tup = NULL; + rec->freetup = false; + } + + estate->err_text = err_text; + } + + /* + * Prepare plans walker - this can be used for checking + * + */ + bool + plpgsql_expr_prepare_plan(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context) + { + PLpgSQL_execstate *estate = (PLpgSQL_execstate *) context; + int cursorOptions = 0; + const char *err_text = estate->err_text; + + /* overwrite a estate variables */ + estate->err_text = NULL; + estate->err_stmt = stmt; + + if (expr == NULL) + return false; + + switch (stmt->cmd_type) + { + case PLPGSQL_STMT_OPEN: + { + PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt; + PLpgSQL_var *curvar = (PLpgSQL_var *) estate->datums[stmt_open->curvar]; + + cursorOptions = curvar->cursor_options; + } + break; + + case PLPGSQL_STMT_FORC: + { + PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt; + PLpgSQL_var *curvar = (PLpgSQL_var *) estate->datums[stmt_forc->curvar]; + + /* + * change a cursorOption only whenn this call is related to + * curvar->cursor_explicit_expr + */ + if (curvar->cursor_explicit_expr == expr) + cursorOptions = curvar->cursor_options; + } + break; + } + + /* + * If first time through, create a plan for this expression. + */ + if (expr->plan == NULL) + { + exec_prepare_plan(estate, expr, cursorOptions); + } + + /* + * very common practic in PLpgSQL is is using a record type. But any using of + * untyped record breaks a check. A solution is an prediction of record type based + * on plans - a following switch covers all PLpgSQL statements where a record + * variable can be assigned. + * + * when record is target of dynamic SQL statement, then raise exception + * + */ + switch (stmt->cmd_type) + { + case PLPGSQL_STMT_ASSIGN: + { + PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt; + PLpgSQL_datum *target = (estate->datums[stmt_assign->varno]); + + if (target->dtype == PLPGSQL_DTYPE_REC) + { + set_rectype_for_query(estate, stmt, + (PLpgSQL_rec *) target, + stmt_assign->expr, + false); + } + } + break; + + case PLPGSQL_STMT_EXECSQL: + { + PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt; + ListCell *l; + + stmt_execsql->mod_stmt = false; + foreach(l, expr->plan->plancache_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l); + CachedPlan *cplan = GetCachedPlan(plansource, NULL, true); + ListCell *l2; + + foreach(l2, cplan->stmt_list) + { + PlannedStmt *p = (PlannedStmt *) lfirst(l2); + + if (IsA(p, PlannedStmt) && + p->canSetTag) + { + if (p->commandType == CMD_INSERT || + p->commandType == CMD_UPDATE || + p->commandType == CMD_DELETE) + stmt_execsql->mod_stmt = true; + } + } + + ReleaseCachedPlan(cplan, true); + } + + if (stmt_execsql->rec != NULL) + { + set_rectype_for_query(estate, stmt, + (PLpgSQL_rec *) (estate->datums[stmt_execsql->rec->dno]), + stmt_execsql->sqlstmt, + false); + } + } + break; + + case PLPGSQL_STMT_FETCH: + { + PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt; + + /* fetch can not determinate a record datatype for refcursors */ + if (stmt_fetch->rec != NULL) + { + PLpgSQL_var *curvar = (PLpgSQL_var *)( estate->datums[stmt_fetch->curvar]); + PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt_fetch->rec->dno]); + + if (curvar->cursor_explicit_expr == NULL) + elog(ERROR, "cannot to determinate record type for refcursor"); + + set_rectype_for_query(estate, stmt, + rec, + curvar->cursor_explicit_expr, + false); + } + } + break; + + case PLPGSQL_STMT_FORS: + { + PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt; + + if (stmt_fors->rec != NULL) + { + set_rectype_for_query(estate, stmt, + (PLpgSQL_rec *) (estate->datums[stmt_fors->rec->dno]), + stmt_fors->query, + false); + } + } + break; + + case PLPGSQL_STMT_FORC: + { + PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt; + PLpgSQL_var *curvar = (PLpgSQL_var *) (estate->datums[stmt_forc->curvar]); + + if (stmt_forc->rec != NULL && curvar->cursor_explicit_expr == expr) + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt_forc->rec->dno]); + + set_rectype_for_query(estate, stmt, + rec, + curvar->cursor_explicit_expr, + false); + } + } + break; + + case PLPGSQL_STMT_FOREACH_A: + { + PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt; + PLpgSQL_datum *loop_var = estate->datums[stmt_foreach_a->varno]; + + if (loop_var->dtype == PLPGSQL_DTYPE_REC) + { + set_rectype_for_query(estate, stmt, + (PLpgSQL_rec *) loop_var, + stmt_foreach_a->expr, + true); + } + } + break; + + case PLPGSQL_STMT_CASE: + { + PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; + TupleDesc tupdesc; + Oid result_oid; + + /* + * this is special case - a result type of expression should to + * overwrite a expected int datatype. + */ + if (stmt_case->t_expr == expr) + { + CachedPlanSource *plansource = NULL; + const char *err_text = estate->err_text; + + estate->err_text = NULL; + estate->err_stmt = stmt; + + + if (expr->plan != NULL) + { + SPIPlanPtr plan = expr->plan; + PLpgSQL_var *t_var = (PLpgSQL_var *) estate->datums[stmt_case->t_varno]; + + 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); + + if (tupdesc->natts != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg_plural("query \"%s\" returned %d column", + "query \"%s\" returned %d columns", + tupdesc->natts, + expr->query, + tupdesc->natts))); + + 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); + FreeTupleDesc(tupdesc); + } + else + elog(ERROR, "there are no plan for query: \"%s\"", + expr->query); + + estate->err_text = err_text; + } + } + break; + + case PLPGSQL_STMT_DYNEXECUTE: + { + PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt; + + if (stmt_dynexecute->into && stmt_dynexecute->rec != NULL) + elog(ERROR, "cannot to determine a result of dynamic SQL"); + } + break; + + case PLPGSQL_STMT_DYNFORS: + { + PLpgSQL_stmt_dynfors *stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt; + + if (stmt_dynfors->rec != NULL) + elog(ERROR, "cannot to determinate a result of dynamic SQL"); + } + break; + } + + estate->err_text = err_text; + + return false; + } + + + /* + * 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; + } + + /* + * 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 the execution state + */ + plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo); + + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpgsql_exec_error_callback; + plerrcontext.arg = &estate; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + /* + * 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 call the toplevel block of statements + */ + estate.err_text = NULL; + estate.err_stmt = (PLpgSQL_stmt *) (func->action); + + plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action, + plpgsql_expr_prepare_plan, + &estate); + + estate.err_stmt = NULL; + estate.err_text = gettext_noop("while casting return value to function's return type"); + + /* Clean up any leftover temporary memory */ + plpgsql_destroy_econtext(&estate); + exec_eval_cleanup(&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; + int i; + PLpgSQL_rec *rec_new, + *rec_old; + + /* + * Setup the execution state + */ + plpgsql_estate_setup(&estate, func, NULL); + + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpgsql_exec_error_callback; + plerrcontext.arg = &estate; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + /* + * Make local execution copies of all the datums + */ + 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 + * + * 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->tupdesc = trigdata->tg_relation->rd_att; + rec_new->freetupdesc = false; + rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]); + rec_old->freetup = false; + rec_old->tupdesc = trigdata->tg_relation->rd_att; + rec_old->freetupdesc = false; + + /* + * 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 call the toplevel block of statements + */ + estate.err_text = NULL; + estate.err_stmt = (PLpgSQL_stmt *) (func->action); + + plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action, + plpgsql_expr_prepare_plan, + &estate); + + estate.err_stmt = NULL; + estate.err_text = gettext_noop("while casting return value to function's return type"); + + /* Clean up any leftover temporary memory */ + plpgsql_destroy_econtext(&estate); + exec_eval_cleanup(&estate); + + /* + * Pop the error context stack + */ + error_context_stack = plerrcontext.previous; + } *** ./src/pl/plpgsql/src/pl_funcs.c.orig 2011-10-04 13:58:46.624303816 +0200 --- ./src/pl/plpgsql/src/pl_funcs.c 2011-10-04 13:59:14.325344608 +0200 *************** *** 293,681 **** } ! /********************************************************************** ! * Release memory when a PL/pgSQL function is no longer needed * ! * The code for recursing through the function tree is really only ! * needed to locate PLpgSQL_expr nodes, which may contain references ! * to saved SPI Plans that must be freed. The function tree itself, ! * along with subsidiary data, is freed in one swoop by freeing the ! * function's permanent memory context. ! **********************************************************************/ ! static void free_stmt(PLpgSQL_stmt *stmt); ! static void free_block(PLpgSQL_stmt_block *block); ! static void free_assign(PLpgSQL_stmt_assign *stmt); ! static void free_if(PLpgSQL_stmt_if *stmt); ! static void free_case(PLpgSQL_stmt_case *stmt); ! static void free_loop(PLpgSQL_stmt_loop *stmt); ! static void free_while(PLpgSQL_stmt_while *stmt); ! static void free_fori(PLpgSQL_stmt_fori *stmt); ! static void free_fors(PLpgSQL_stmt_fors *stmt); ! static void free_forc(PLpgSQL_stmt_forc *stmt); ! static void free_foreach_a(PLpgSQL_stmt_foreach_a *stmt); ! static void free_exit(PLpgSQL_stmt_exit *stmt); ! static void free_return(PLpgSQL_stmt_return *stmt); ! static void free_return_next(PLpgSQL_stmt_return_next *stmt); ! static void free_return_query(PLpgSQL_stmt_return_query *stmt); ! static void free_raise(PLpgSQL_stmt_raise *stmt); ! static void free_execsql(PLpgSQL_stmt_execsql *stmt); ! static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt); ! static void free_dynfors(PLpgSQL_stmt_dynfors *stmt); ! static void free_getdiag(PLpgSQL_stmt_getdiag *stmt); ! static void free_open(PLpgSQL_stmt_open *stmt); ! static void free_fetch(PLpgSQL_stmt_fetch *stmt); ! static void free_close(PLpgSQL_stmt_close *stmt); ! static void free_perform(PLpgSQL_stmt_perform *stmt); ! static void free_expr(PLpgSQL_expr *expr); ! static void ! free_stmt(PLpgSQL_stmt *stmt) { switch ((enum PLpgSQL_stmt_types) stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: ! free_block((PLpgSQL_stmt_block *) stmt); ! break; case PLPGSQL_STMT_ASSIGN: ! free_assign((PLpgSQL_stmt_assign *) stmt); ! break; case PLPGSQL_STMT_IF: ! free_if((PLpgSQL_stmt_if *) stmt); ! break; case PLPGSQL_STMT_CASE: ! free_case((PLpgSQL_stmt_case *) stmt); ! break; case PLPGSQL_STMT_LOOP: ! free_loop((PLpgSQL_stmt_loop *) stmt); ! break; case PLPGSQL_STMT_WHILE: ! free_while((PLpgSQL_stmt_while *) stmt); ! break; case PLPGSQL_STMT_FORI: ! free_fori((PLpgSQL_stmt_fori *) stmt); ! break; case PLPGSQL_STMT_FORS: ! free_fors((PLpgSQL_stmt_fors *) stmt); ! break; case PLPGSQL_STMT_FORC: ! free_forc((PLpgSQL_stmt_forc *) stmt); ! break; ! case PLPGSQL_STMT_FOREACH_A: ! free_foreach_a((PLpgSQL_stmt_foreach_a *) stmt); ! break; ! case PLPGSQL_STMT_EXIT: ! free_exit((PLpgSQL_stmt_exit *) stmt); ! break; ! case PLPGSQL_STMT_RETURN: ! free_return((PLpgSQL_stmt_return *) stmt); ! break; ! case PLPGSQL_STMT_RETURN_NEXT: ! free_return_next((PLpgSQL_stmt_return_next *) stmt); ! break; ! case PLPGSQL_STMT_RETURN_QUERY: ! free_return_query((PLpgSQL_stmt_return_query *) stmt); ! break; ! case PLPGSQL_STMT_RAISE: ! free_raise((PLpgSQL_stmt_raise *) stmt); ! break; ! case PLPGSQL_STMT_EXECSQL: ! free_execsql((PLpgSQL_stmt_execsql *) stmt); ! break; ! case PLPGSQL_STMT_DYNEXECUTE: ! free_dynexecute((PLpgSQL_stmt_dynexecute *) stmt); ! break; case PLPGSQL_STMT_DYNFORS: ! free_dynfors((PLpgSQL_stmt_dynfors *) stmt); ! break; ! case PLPGSQL_STMT_GETDIAG: ! free_getdiag((PLpgSQL_stmt_getdiag *) stmt); ! break; ! case PLPGSQL_STMT_OPEN: ! free_open((PLpgSQL_stmt_open *) stmt); ! break; ! case PLPGSQL_STMT_FETCH: ! free_fetch((PLpgSQL_stmt_fetch *) stmt); ! break; ! case PLPGSQL_STMT_CLOSE: ! free_close((PLpgSQL_stmt_close *) stmt); ! break; ! case PLPGSQL_STMT_PERFORM: ! free_perform((PLpgSQL_stmt_perform *) stmt); ! break; ! default: ! elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); ! break; ! } ! } ! static void ! free_stmts(List *stmts) ! { ! ListCell *s; ! foreach(s, stmts) ! { ! free_stmt((PLpgSQL_stmt *) lfirst(s)); ! } ! } ! static void ! free_block(PLpgSQL_stmt_block *block) ! { ! free_stmts(block->body); ! if (block->exceptions) ! { ! ListCell *e; ! foreach(e, block->exceptions->exc_list) ! { ! PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e); ! free_stmts(exc->action); ! } ! } ! } ! static void ! free_assign(PLpgSQL_stmt_assign *stmt) ! { ! free_expr(stmt->expr); ! } ! static void ! free_if(PLpgSQL_stmt_if *stmt) ! { ! free_expr(stmt->cond); ! free_stmts(stmt->true_body); ! free_stmts(stmt->false_body); ! } ! static void ! free_case(PLpgSQL_stmt_case *stmt) ! { ! ListCell *l; ! free_expr(stmt->t_expr); ! foreach(l, stmt->case_when_list) ! { ! PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); ! free_expr(cwt->expr); ! free_stmts(cwt->stmts); ! } ! free_stmts(stmt->else_stmts); ! } ! static void ! free_loop(PLpgSQL_stmt_loop *stmt) ! { ! free_stmts(stmt->body); ! } ! static void ! free_while(PLpgSQL_stmt_while *stmt) ! { ! free_expr(stmt->cond); ! free_stmts(stmt->body); ! } ! static void ! free_fori(PLpgSQL_stmt_fori *stmt) ! { ! free_expr(stmt->lower); ! free_expr(stmt->upper); ! free_expr(stmt->step); ! free_stmts(stmt->body); ! } ! static void ! free_fors(PLpgSQL_stmt_fors *stmt) ! { ! free_stmts(stmt->body); ! free_expr(stmt->query); ! } ! static void ! free_forc(PLpgSQL_stmt_forc *stmt) ! { ! free_stmts(stmt->body); ! free_expr(stmt->argquery); ! } ! static void ! free_foreach_a(PLpgSQL_stmt_foreach_a *stmt) ! { ! free_expr(stmt->expr); ! free_stmts(stmt->body); ! } ! static void ! free_open(PLpgSQL_stmt_open *stmt) ! { ! ListCell *lc; ! free_expr(stmt->argquery); ! free_expr(stmt->query); ! free_expr(stmt->dynquery); ! foreach(lc, stmt->params) ! { ! free_expr((PLpgSQL_expr *) lfirst(lc)); ! } ! } ! static void ! free_fetch(PLpgSQL_stmt_fetch *stmt) ! { ! free_expr(stmt->expr); ! } ! static void ! free_close(PLpgSQL_stmt_close *stmt) ! { ! } ! static void ! free_perform(PLpgSQL_stmt_perform *stmt) ! { ! free_expr(stmt->expr); ! } ! static void ! free_exit(PLpgSQL_stmt_exit *stmt) ! { ! free_expr(stmt->cond); ! } ! static void ! free_return(PLpgSQL_stmt_return *stmt) ! { ! free_expr(stmt->expr); ! } ! static void ! free_return_next(PLpgSQL_stmt_return_next *stmt) ! { ! free_expr(stmt->expr); ! } ! static void ! free_return_query(PLpgSQL_stmt_return_query *stmt) ! { ! ListCell *lc; ! free_expr(stmt->query); ! free_expr(stmt->dynquery); ! foreach(lc, stmt->params) ! { ! free_expr((PLpgSQL_expr *) lfirst(lc)); ! } ! } ! static void ! free_raise(PLpgSQL_stmt_raise *stmt) ! { ! ListCell *lc; ! foreach(lc, stmt->params) ! { ! free_expr((PLpgSQL_expr *) lfirst(lc)); ! } ! foreach(lc, stmt->options) ! { ! PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc); ! free_expr(opt->expr); ! } ! } ! static void ! free_execsql(PLpgSQL_stmt_execsql *stmt) ! { ! free_expr(stmt->sqlstmt); ! } ! static void ! free_dynexecute(PLpgSQL_stmt_dynexecute *stmt) ! { ! ListCell *lc; ! free_expr(stmt->query); ! foreach(lc, stmt->params) ! { ! free_expr((PLpgSQL_expr *) lfirst(lc)); ! } ! } ! static void ! free_dynfors(PLpgSQL_stmt_dynfors *stmt) ! { ! ListCell *lc; ! free_stmts(stmt->body); ! free_expr(stmt->query); ! foreach(lc, stmt->params) ! { ! free_expr((PLpgSQL_expr *) lfirst(lc)); } } ! static void ! free_getdiag(PLpgSQL_stmt_getdiag *stmt) ! { ! } ! static void ! free_expr(PLpgSQL_expr *expr) { if (expr && expr->plan) { SPI_freeplan(expr->plan); expr->plan = NULL; } } void plpgsql_free_function_memory(PLpgSQL_function *func) { - int i; - /* Better not call this on an in-use function */ Assert(func->use_count == 0); - /* Release plans associated with variable declarations */ - for (i = 0; i < func->ndatums; i++) - { - PLpgSQL_datum *d = func->datums[i]; - - switch (d->dtype) - { - case PLPGSQL_DTYPE_VAR: - { - PLpgSQL_var *var = (PLpgSQL_var *) d; - - free_expr(var->default_val); - free_expr(var->cursor_explicit_expr); - } - break; - case PLPGSQL_DTYPE_ROW: - break; - case PLPGSQL_DTYPE_REC: - break; - case PLPGSQL_DTYPE_RECFIELD: - break; - case PLPGSQL_DTYPE_ARRAYELEM: - free_expr(((PLpgSQL_arrayelem *) d)->subscript); - break; - default: - elog(ERROR, "unrecognized data type: %d", d->dtype); - } - } - func->ndatums = 0; - /* Release plans in statement tree */ ! if (func->action) ! free_block(func->action); func->action = NULL; /* --- 293,653 ---- } ! /* ! * call a plpgsql_expr_walker for any statement in list * ! */ ! static bool ! plpgsql_expr_walker_list(PLpgSQL_function *func, List *stmts, ! bool (*expr_walker)(), ! void *context) ! { ! ListCell *lc; + foreach(lc, stmts) + { + PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(lc); ! if (plpgsql_expr_walker(func, stmt, expr_walker, context)) ! return true; ! } ! return false; ! } ! ! /* ! * walk over all expressions inside statements tree ! * ! * stmt_walker is function called for every stmt and should be NULL ! * ! */ ! bool ! plpgsql_expr_walker(PLpgSQL_function *func, ! PLpgSQL_stmt *stmt, ! bool (*expr_walker)(), ! void *context) { + ListCell *l; + + if (stmt == NULL) + return false; + 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]]; ! ! switch(d->dtype) ! { ! case PLPGSQL_DTYPE_VAR: ! { ! PLpgSQL_var *var = (PLpgSQL_var *) d; ! ! if (expr_walker(stmt, var->default_val, context)) ! return true; ! ! /* ! * theoretically there is place for walk over var->cursor_explicit_expr, ! * but we would not to call process too early. In this moment a some ! * record parameters should be unknown. So we will wait on better moment ! * ! * if (expr_walker(stmt, var->cursor_explicit_expr, context)) ! * return true; ! */ ! } ! break; ! case PLPGSQL_DTYPE_ROW: ! case PLPGSQL_DTYPE_REC: ! case PLPGSQL_DTYPE_RECFIELD: ! break; ! case PLPGSQL_DTYPE_ARRAYELEM: ! if (expr_walker(stmt, ((PLpgSQL_arrayelem *) d)->subscript, context)) ! return true; ! default: ! elog(ERROR, "unrecognized data type: %d", d->dtype); ! } ! } ! ! if (plpgsql_expr_walker_list(func, stmt_block->body, expr_walker, context)) ! return true; ! ! if (stmt_block->exceptions) ! { ! foreach(l, stmt_block->exceptions->exc_list) ! { ! if (plpgsql_expr_walker_list(func, ((PLpgSQL_exception *) lfirst(l))->action, ! expr_walker, ! context)) ! return true; ! } ! } ! ! return false; ! } ! case PLPGSQL_STMT_ASSIGN: ! return expr_walker(stmt, ((PLpgSQL_stmt_assign *) stmt)->expr, context); ! case PLPGSQL_STMT_IF: ! { ! PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt; ! ! if (expr_walker(stmt, stmt_if->cond, context)) ! return true; ! ! if (plpgsql_expr_walker_list(func, stmt_if->true_body, expr_walker, context)) ! return true; ! ! return plpgsql_expr_walker_list(func, stmt_if->false_body, expr_walker, context); ! } ! case PLPGSQL_STMT_CASE: ! { ! PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt; ! ! if (expr_walker(stmt, stmt_case->t_expr, context)) ! return true; ! ! foreach(l, stmt_case->case_when_list) ! { ! PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); ! ! if (expr_walker(stmt, cwt->expr, context)) ! return true; ! ! if (plpgsql_expr_walker_list(func, cwt->stmts, expr_walker, context)) ! return true; ! } ! ! return plpgsql_expr_walker_list(func, stmt_case->else_stmts, expr_walker, context); ! } ! case PLPGSQL_STMT_LOOP: ! return plpgsql_expr_walker_list(func, ((PLpgSQL_stmt_loop *) stmt)->body, expr_walker, context); ! case PLPGSQL_STMT_WHILE: ! { ! PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt; ! ! if (expr_walker(stmt, stmt_while->cond, context)) ! return true; ! ! return plpgsql_expr_walker_list(func, stmt_while->body, expr_walker, context); ! } ! case PLPGSQL_STMT_FORI: ! { ! PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt; ! ! if (expr_walker(stmt, stmt_fori->lower, context)) ! return true; ! ! if (expr_walker(stmt, stmt_fori->upper, context)) ! return true; ! ! if (expr_walker(stmt, stmt_fori->step, context)) ! return true; ! ! return plpgsql_expr_walker_list(func, stmt_fori->body, expr_walker, context); ! } ! case PLPGSQL_STMT_FORS: ! { ! PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt; ! ! if (expr_walker(stmt, stmt_fors->query, context)) ! return true; ! ! return plpgsql_expr_walker_list(func, stmt_fors->body, expr_walker, context); ! } ! case PLPGSQL_STMT_FORC: ! { ! PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt; ! PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar]; ! ! if (expr_walker(stmt, stmt_forc->argquery, context)) ! return true; ! ! if (expr_walker(stmt, var->cursor_explicit_expr, context)) ! return true; ! ! return plpgsql_expr_walker_list(func, stmt_forc->body, expr_walker, context); ! } ! case PLPGSQL_STMT_DYNFORS: ! { ! PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt; ! if (expr_walker(stmt, stmt_dynfors->query, context)) ! return true; ! foreach(l, stmt_dynfors->params) ! { ! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context)) ! return true; ! } ! return plpgsql_expr_walker_list(func, stmt_dynfors->body, expr_walker, context); ! } ! case PLPGSQL_STMT_FOREACH_A: ! { ! PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt; ! if (expr_walker(stmt, stmt_foreach_a->expr, context)) ! return true; ! return plpgsql_expr_walker_list(func, stmt_foreach_a->body, expr_walker, context); ! } ! case PLPGSQL_STMT_EXIT: ! return expr_walker(stmt, ((PLpgSQL_stmt_exit *) stmt)->cond, context); ! case PLPGSQL_STMT_PERFORM: ! return expr_walker(stmt, ((PLpgSQL_stmt_perform *) stmt)->expr, context); ! case PLPGSQL_STMT_RETURN: ! return expr_walker(stmt, ((PLpgSQL_stmt_return *) stmt)->expr, context); ! case PLPGSQL_STMT_RETURN_NEXT: ! return expr_walker(stmt, ((PLpgSQL_stmt_return_next *) stmt)->expr, context); ! case PLPGSQL_STMT_RETURN_QUERY: ! { ! PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt; ! if (expr_walker(stmt, stmt_rq->query, context)) ! return true; ! if (expr_walker(stmt, stmt_rq->dynquery, context)) ! return true; ! foreach(l, stmt_rq->params) ! { ! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context)) ! return true; ! } ! return false; ! } ! case PLPGSQL_STMT_RAISE: ! { ! PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt; ! foreach(l, stmt_raise->params) ! { ! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context)) ! return true; ! } ! foreach(l, stmt_raise->options) ! { ! if (expr_walker(stmt, ((PLpgSQL_raise_option *) lfirst(l))->expr, context)) ! return true; ! } ! return false; ! } ! case PLPGSQL_STMT_EXECSQL: ! return expr_walker(stmt, ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt, context); ! case PLPGSQL_STMT_DYNEXECUTE: ! { ! PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt; ! if (expr_walker(stmt, stmt_dynexecute->query, context)) ! return true; ! foreach(l, stmt_dynexecute->params) ! { ! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context)) ! return true; ! } ! return false; ! } ! break; ! case PLPGSQL_STMT_GETDIAG: ! return false; ! case PLPGSQL_STMT_OPEN: ! { ! PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt; ! PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_open->curvar]; ! if (expr_walker(stmt, var->cursor_explicit_expr, context)) ! return true; ! if (expr_walker(stmt, stmt_open->query, context)) ! return true; ! if (expr_walker(stmt, stmt_open->dynquery, context)) ! return true; ! if (expr_walker(stmt, stmt_open->argquery, context)) ! return true; ! foreach(l, stmt_open->params) ! { ! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context)) ! return true; ! } ! return false; ! } ! case PLPGSQL_STMT_FETCH: ! case PLPGSQL_STMT_CLOSE: ! return false; ! default: ! elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); ! return false; /* be compiler quite */ } } ! /********************************************************************** ! * Release memory when a PL/pgSQL function is no longer needed ! * ! * The code for recursing through the function tree is really only ! * needed to locate PLpgSQL_expr nodes, which may contain references ! * to saved SPI Plans that must be freed. The function tree itself, ! * along with subsidiary data, is freed in one swoop by freeing the ! * function's permanent memory context. ! **********************************************************************/ ! static bool ! free_expression(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context) { if (expr && expr->plan) { SPI_freeplan(expr->plan); expr->plan = NULL; } + return false; } void plpgsql_free_function_memory(PLpgSQL_function *func) { /* Better not call this on an in-use function */ Assert(func->use_count == 0); /* Release plans in statement tree */ ! plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action, ! free_expression, ! NULL); ! func->ndatums = 0; func->action = NULL; /* *** ./src/pl/plpgsql/src/pl_handler.c.orig 2011-10-04 13:58:46.626303819 +0200 --- ./src/pl/plpgsql/src/pl_handler.c 2011-10-06 13:11:21.515690600 +0200 *************** *** 312,314 **** --- 312,428 ---- 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; + + Form_pg_proc proc; + char functyptype; + bool istrigger = false; + PLpgSQL_execstate *save_cur_estate; + + /* 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, true); + + /* Must save and restore prior value of cur_estate */ + save_cur_estate = function->cur_estate; + + /* 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 = save_cur_estate; + PG_RE_THROW(); + } + PG_END_TRY(); + + if (istrigger) + relation_close(trigdata.tg_relation, AccessShareLock); + function->cur_estate = save_cur_estate; + + /* + * 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-10-04 13:56:41.052123789 +0200 --- ./src/pl/plpgsql/src/plpgsql.h 2011-10-05 07:43:53.217212322 +0200 *************** *** 903,908 **** --- 903,909 ---- 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 *************** *** 920,925 **** --- 921,929 ---- 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_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 *************** *** 944,949 **** --- 948,956 ---- extern const char *plpgsql_getdiag_kindname(int kind); extern void plpgsql_free_function_memory(PLpgSQL_function *func); extern void plpgsql_dumptree(PLpgSQL_function *func); + extern bool plpgsql_expr_walker(PLpgSQL_function *func, + PLpgSQL_stmt *stmt, + bool (*expr_walker)(), void *context); /* ---------- * Scanner functions in pl_scanner.c *** ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql.orig 2011-10-04 13:58:46.628303823 +0200 --- ./src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql 2011-10-04 13:59:14.326344609 +0200 *************** *** 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-10-04 13:56:41.000000000 +0200 --- ./src/test/regress/expected/plpgsql.out 2011-10-06 13:47:47.000000000 +0200 *************** *** 4571,4573 **** --- 4571,4626 ---- CONTEXT: PL/pgSQL function "testoa" line 5 at assignment drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int); + -- + -- check function test + -- + create table plpgsql_foo(a int, b int, c int); + create or replace function fx() + returns void as $$ + declare r record; + s int := 0; + begin + for r in select a from plpgsql_foo where a = 10 + loop + s := s + r.a; + end loop; + end; + $$ language plpgsql; + -- should be ok + check function fx(); + create or replace function fx() + returns void as $$ + declare r record; + s int := 0; + begin + for r in select a from plpgsql_foo where x = 10 + loop + s := s + r.a; + end loop; + end; + $$ language plpgsql; + -- should fail + check function fx(); + ERROR: column "x" does not exist + LINE 1: select a from plpgsql_foo where x = 10 + ^ + QUERY: select a from plpgsql_foo where x = 10 + CONTEXT: PL/pgSQL function "fx" line 5 at FOR over SELECT rows + create or replace function fx() + returns void as $$ + declare r record; + s int := 0; + begin + for r in select a from plpgsql_foo where a = 10 + loop + s := s + r.x; + end loop; + end; + $$ language plpgsql; + -- should fail + check function fx(); + ERROR: record "r" has no field "x" + CONTEXT: SQL statement "SELECT s + r.x" + PL/pgSQL function "fx" line 7 at assignment + drop function fx(); + drop table plpgsql_foo; *** ./src/test/regress/sql/plpgsql.sql.orig 2011-10-04 13:56:41.000000000 +0200 --- ./src/test/regress/sql/plpgsql.sql 2011-10-06 13:47:13.444820293 +0200 *************** *** 3600,3602 **** --- 3600,3655 ---- drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int); + + -- + -- check function test + -- + create table plpgsql_foo(a int, b int, c int); + + create or replace function fx() + returns void as $$ + declare r record; + s int := 0; + begin + for r in select a from plpgsql_foo where a = 10 + loop + s := s + r.a; + end loop; + end; + $$ language plpgsql; + + -- should be ok + check function fx(); + + create or replace function fx() + returns void as $$ + declare r record; + s int := 0; + begin + for r in select a from plpgsql_foo where x = 10 + loop + s := s + r.a; + end loop; + end; + $$ language plpgsql; + + -- should fail + check function fx(); + + create or replace function fx() + returns void as $$ + declare r record; + s int := 0; + begin + for r in select a from plpgsql_foo where a = 10 + loop + s := s + r.x; + end loop; + end; + $$ language plpgsql; + + -- should fail + check function fx(); + + drop function fx(); + drop table plpgsql_foo;