diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 180554b..7ec5b59 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -3663,6 +3663,18 @@ + lanchecker + oid + pg_proc.oid + + This references a correctness check function that is used by + CHECK FUNCTION and CHECK TRIGGER for in-depth + checks of function bodies. + Zero if no such function is provided. + + + + lanacl aclitem[] @@ -4273,6 +4285,12 @@ + tmplchecker + text + Name of correctness check function, or null if none + + + tmpllibrary text Path of shared library that implements language diff --git a/doc/src/sgml/plhandler.sgml b/doc/src/sgml/plhandler.sgml index 54b88ef..d460683 100644 --- a/doc/src/sgml/plhandler.sgml +++ b/doc/src/sgml/plhandler.sgml @@ -159,12 +159,15 @@ CREATE LANGUAGE plsample Although providing a call handler is sufficient to create a minimal - procedural language, there are two other functions that can optionally + procedural language, there are three other functions that can optionally be provided to make the language more convenient to use. These - are a validator and an - inline handler. A validator can be provided - to allow language-specific checking to be done during - . + are a validator, a checker + and an inline handler. + A validator can be provided to allow language-specific checking to be + done during . + A checker performs in-depth checks of function bodies via the + commands and + . An inline handler can be provided to allow the language to support anonymous code blocks executed via the command. @@ -203,6 +206,39 @@ CREATE LANGUAGE plsample + If a checker is provided by a procedural language, it must be declared + as a function taking four arguments, one of type oid second of + type regclass third is text array (used for options) and fourth + of type boolean. + The checker's result is a table which consists of + functionid of type oid (oid of a checked function), lineno + of type integer (representing line number of the function + body source), statement of type text (statement type), sqlstate + of type text, message of type text (error message), + detail of type text (detail of the error mesaage if any), + hint of type text (hint for the error message if any), + level of type text (error level), position of type + integer (possition of the error in a query if there is a query) + and query of type text (showing SQL statement in which + the error occured). + The checker will be called whenever a function is checked with + CHECK FUNCTION or CHECK TRIGGER. + The passed-in OID is the OID of the function's pg_proc + row. The passed-in regclass is the OID of the + pg_class row of the table on which the trigger is defined, + or InvalidOid in the case of CHECK FUNCTION. + The last parameter passed as boolean defines if checker should + stop checking on first error. + The checker must fetch these rows in the usual way, and do + whatever checking is appropriate. + Typical checks include verifying that all referenced database objects + actually exist or style checks like verifying that all declared + variables are actually used. If the checker finds the function to be okay, + it should just return. If it finds a problem, it should return a row + describing error. + + + If an inline handler is provided by a procedural language, it must be declared as a function taking a single parameter of type internal. The inline handler's result is ignored, so it is diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 382d297..ef75780 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -40,6 +40,8 @@ Complete list of usable sgml source files in this directory. + + diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml index 0995b13..b6f6dfa3 100644 --- a/doc/src/sgml/ref/create_language.sgml +++ b/doc/src/sgml/ref/create_language.sgml @@ -23,7 +23,7 @@ PostgreSQL documentation CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE name CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE name - HANDLER call_handler [ INLINE inline_handler ] [ VALIDATOR valfunction ] + HANDLER call_handler [ INLINE inline_handler ] [ VALIDATOR valfunction ] [ CHECK checkfunction ] @@ -217,6 +217,45 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE checkfunction + + + checkfunction is the + name of a previously registered function that will be called + by CHECK FUNCTION and CHECK TRIGGER + to check the bodies of functions defined in the language. + If no check function is specified, this kind of check is not available. + The check function must take four arguments, first of type oid + (the OID of the function to be checked), second of type regclass + (the table with the trigger for CHECK TRIGGER), + third is text array (options) and fourth is boolean (defines if check + function should stop on first error found). + The check function will typically return set of rows with ten fields: + functionid of type oid (oid of a checked function), lineno + of type integer (representing line number of the function + body source), statement of type text (statement type), sqlstate + of type text, message of type text (error message), + detail of type text (detail of the error mesaage if any), + hint of type text (hint for the error message if any), + level of type text (error level), position of type + integer (possition of the error in a query if there is a query) + and query of type text (showing SQL statement in which + the error occured). + + + + A check function would typically perform an in-depth check of the function + body, for example that all referenced database objects exist, but it could + also check for stylistic problems like defined but unreferenced variables. + To signal a problem found during the check, the function should return row + for each error found, if fourth input argument is True, it should return + immediately after first error. + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 7326519..460794e 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -68,6 +68,8 @@ &alterView; &analyze; &begin; + &checkFunction; + &checkTrigger; &checkpoint; &close; &cluster; diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index ce866a2..5140ead 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -48,6 +48,9 @@ #include "catalog/pg_type_fn.h" #include "commands/defrem.h" #include "commands/proclang.h" +#include "commands/trigger.h" +#include "executor/executor.h" +#include "executor/spi.h" #include "miscadmin.h" #include "optimizer/var.h" #include "parser/parse_coerce.h" @@ -791,7 +794,19 @@ interpret_AS_clause(Oid languageOid, const char *languageName, } } - +/* + * Emit an appropriate error message that a language was searched for and + * not found. + */ +static void +report_missing_language(const char *language) +{ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("language \"%s\" does not exist", language), + (PLTemplateExists(language) ? + errhint("Use CREATE LANGUAGE to load the language into the database.") : 0))); +} /* * CreateFunction @@ -858,11 +873,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString) /* Look up the language and validate permissions */ languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); if (!HeapTupleIsValid(languageTuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("language \"%s\" does not exist", language), - (PLTemplateExists(language) ? - errhint("Use CREATE LANGUAGE to load the language into the database.") : 0))); + report_missing_language(language); languageOid = HeapTupleGetOid(languageTuple); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); @@ -1048,6 +1059,533 @@ RemoveFunctionById(Oid funcOid) } } +/* + * Search and execute the checker function. + * + * returns true, when checked function is valid + */ +static bool +CheckFunctionById(Oid funcOid, Oid relid, ArrayType *options, + bool fatal_errors, TupOutputState *tstate) +{ + HeapTuple tup; + Form_pg_proc proc; + HeapTuple languageTuple; + Form_pg_language lanForm; + Oid languageChecker; + char *funcname; + StringInfoData sinfo; + Form_pg_proc checker_fce; + HeapTuple checker_fce_tup; + bool result = true; + + 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)); + + lanForm = (Form_pg_language) GETSTRUCT(languageTuple); + languageChecker = lanForm->lanchecker; + + funcname = format_procedure(funcOid); + + /* Check a function body */ + if (OidIsValid(languageChecker)) + { + Oid argtypes[4] = + { REGPROCEDUREOID, REGCLASSOID, TEXTARRAYOID, BOOLOID }; + bool nulls[4] = { false, false, false, false }; + Datum values[4]; + int i; + + /* call a checker function and read result */ + checker_fce_tup = SearchSysCache1(PROCOID, + ObjectIdGetDatum(languageChecker)); + if (!HeapTupleIsValid(checker_fce_tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for function %u", funcOid); + checker_fce = (Form_pg_proc) GETSTRUCT(checker_fce_tup); + + initStringInfo(&sinfo); + appendStringInfo(&sinfo, "SELECT * FROM %s($1, $2, $3, $4)", + quote_identifier(NameStr(checker_fce->proname))); + ReleaseSysCache(checker_fce_tup); + + /* + * Connect to SPI manager + */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + values[0] = ObjectIdGetDatum(funcOid); + values[1] = ObjectIdGetDatum(relid); + values[2] = PointerGetDatum(options); + values[3] = BoolGetDatum(fatal_errors); + + SPI_execute_with_args(sinfo.data, + 4, argtypes, + values, nulls, + true, 0); + + result = SPI_processed == 0; + + if (result) + { + resetStringInfo(&sinfo); + appendStringInfo(&sinfo, "function is valid: %s", funcname); + do_text_output_oneline(tstate, sinfo.data); + } + else + { + resetStringInfo(&sinfo); + appendStringInfo(&sinfo, "in function: %s", funcname); + do_text_output_oneline(tstate, sinfo.data); + + for (i = 0; i < SPI_processed; i++) + { + char *query; + + resetStringInfo(&sinfo); + appendStringInfo(&sinfo, "%s:%s:%s:%s:%s", + SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 8), + SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 4), + SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 2), + SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 3), + SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 5)); + + do_text_output_oneline(tstate, sinfo.data); + resetStringInfo(&sinfo); + + query = SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 10); + if (query != NULL) + { + int position; + Datum value; + bool isnull; + + appendStringInfo(&sinfo, "query:%s", query); + do_text_output_oneline(tstate, sinfo.data); + + value = SPI_getbinval(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 9, &isnull); + + if (!isnull) + { + position = DatumGetInt32( value); + if (position > 0) + { + resetStringInfo(&sinfo); + + appendStringInfo(&sinfo, " %*s", + position, "^"); + do_text_output_oneline(tstate, sinfo.data); + } + } + } + } + } + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + } + + pfree(funcname); + + ReleaseSysCache(languageTuple); + ReleaseSysCache(tup); + + return result; +} + +TupleDesc +CheckFunctionResultDesc(CheckFunctionStmt *stmt) +{ + TupleDesc tupdesc; + + tupdesc = CreateTemplateTupleDesc(1, false); + + TupleDescInitEntry(tupdesc, (AttrNumber) 1, + stmt->is_function ? "CHECK FUNCTION" : "CHECK TRIGGER", + TEXTOID, -1, 0); + + return tupdesc; +} + +/* + * CollectCheckedFunctions + * Collect functions to check according to options + */ +static List * +CollectCheckedFunctions(List *options) +{ + ScanKeyData key[5]; + Relation rel; + HeapScanDesc scan; + bool language_opt = false; + bool schema_opt = false; + bool owner_opt = false; + ListCell *option; + Oid infoschema_oid = InvalidOid; + int nkeys = 0; + HeapTuple tup; + List *objects = NIL; + + foreach(option, options) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "language") == 0) + { + char *language; + HeapTuple langtup; + Form_pg_language lanForm; + Oid languageId; + Oid lanchecker; + + if (language_opt) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("multiple %s clauses not allowed", + "IN LANGUAGE"))); + language_opt = true; + + language = defGetString(defel); + langtup = SearchSysCache1(LANGNAME, PointerGetDatum(language)); + + if (!HeapTupleIsValid(langtup)) + report_missing_language(language); + + languageId = HeapTupleGetOid(langtup); + + if (languageId == INTERNALlanguageId) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot check functions in language \"%s\"", + "internal"))); + if (languageId == ClanguageId) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot check functions in language \"%s\"", + "C"))); + if (languageId == SQLlanguageId) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot check functions in language \"%s\"", + "SQL"))); + + lanForm = (Form_pg_language) GETSTRUCT(langtup); + lanchecker = lanForm->lanchecker; + if (!OidIsValid(lanchecker)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("language \"%s\" does not have a checker function", + language))); + + ScanKeyInit(&key[nkeys++], + Anum_pg_proc_prolang, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(languageId)); + + ReleaseSysCache(langtup); + } + else if (strcmp(defel->defname, "schema") == 0) + { + Oid namespaceId; + + if (schema_opt) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("multiple %s clauses not allowed", + "IN SCHEMA"))); + schema_opt = true; + namespaceId = LookupExplicitNamespace(defGetString(defel)); + ScanKeyInit(&key[nkeys++], + Anum_pg_proc_pronamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + + } + else if (strcmp(defel->defname, "owner") == 0) + { + Oid ownerId; + + if (owner_opt) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("multiple %s clauses not allowed", + "FOR ROLE"))); + owner_opt = true; + + ownerId = get_role_oid(defGetString(defel), false); + ScanKeyInit(&key[nkeys++], + Anum_pg_proc_proowner, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ownerId)); + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("option \"%s\" not recognized", defel->defname))); + } + + if (!schema_opt) + infoschema_oid = LookupExplicitNamespace("information_schema"); + + rel = heap_open(ProcedureRelationId, AccessShareLock); + scan = heap_beginscan(rel, SnapshotNow, nkeys, key); + + while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection))) + { + Form_pg_proc proc = (Form_pg_proc) GETSTRUCT(tup); + Oid funcOid; + char *procname; + + /* + * ignore pg_catalog and information_schema, unless explicitely + * requested. + */ + if (schema_opt) + { + if (proc->pronamespace != namespaceId) + continue; + } + else if (proc->pronamespace == PG_CATALOG_NAMESPACE || + proc->pronamespace == infoschema_oid) + continue; + + funcOid = HeapTupleGetOid(tup); + procname = format_procedure(funcOid); + + /* some languages are not supported */ + if (!language_opt) + { + Oid prolang = proc->prolang; + HeapTuple langtup; + + /* skip C, internal or SQL */ + if (prolang == INTERNALlanguageId) + { + ereport(DEBUG1, + (errmsg("skipping function %s -- uses language %s", + procname, "internal"))); + pfree(procname); + continue; + } + else if (prolang == ClanguageId) + { + ereport(DEBUG1, + (errmsg("skipping function %s -- uses language %s", + procname, "C"))); + pfree(procname); + continue; + } + else if (prolang == SQLlanguageId) + { + ereport(DEBUG1, + (errmsg("skipping function %s -- uses language %s", + procname, "SQL"))); + pfree(procname); + continue; + } + + langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(prolang)); + lanForm = (Form_pg_language) GETSTRUCT(langtup); + lanchecker = lanForm->lanchecker; + if (!OidIsValid(lanchecker)) + { + ereport(DEBUG1, + (errmsg("skipping function %s -- language %s does not have a checker", + procname, language))); + pfree(procname); + continue; + } + } + + + if (proc->prorettype == TRIGGEROID) + { + /* XXX why? */ + ereport(NOTICE, + (errmsg("skipping function %s", procname), + errdetail("This function is a trigger function."))); + pfree(procname); + continue; + } + + pfree(procname); + objects = lappend_oid(objects, funcOid); + } + + heap_endscan(scan); + heap_close(rel, AccessShareLock); + + return objects; +} + +/* + * Transform a CHECK FUNCTION option list into a 2-D text array, to pass to the + * checker function. If there are no options, return an empty array; this is + * necessary because the checker function might be strict. + * + * stop_on_first_error corresponds to the "fatal_errors" option; that one + * is passed as a separate argument to the checker function instead of + * being part of the options array. + */ +static ArrayType * +process_options(List *options, bool *stop_on_first_error) +{ + ArrayType *optarray; + ArrayBuildState *astate = NULL; + int dims[2] = {0, 2}; + int lbs[2] = {1, 1}; + + if (options != NIL) + { + ListCell *option; + + /* complete dimensions */ + dims[0] = list_length(options); + + foreach(option, options) + { + DefElem *defel = (DefElem *) lfirst(option); + char *option_name = defel->defname; + Datum value; + bool isnull; + + /* this option is treated specially */ + if (strcmp(option_name, "fatal_errors") == 0) + { + *stop_on_first_error = defGetBoolean(defel); + continue; + } + + astate = accumArrayResult(astate, + CStringGetTextDatum(option_name), false, + TEXTOID, + CurrentMemoryContext); + + if (defel->arg != NULL) + { + value = CStringGetTextDatum(defGetString(defel)); + isnull = false; + } + else + { + value = (Datum) 0; + isnull = true; + } + + astate = accumArrayResult(astate, + value, isnull, + TEXTOID, CurrentMemoryContext); + } + } + + if (astate != NULL) + optarray = DatumGetArrayTypeP(makeMdArrayResult(astate, 2, dims, lbs, + CurrentMemoryContext, + true)); + else + optarray = construct_empty_array(TEXTOID); + + return optarray; +} + +/* + * CheckFunction + * Call a PL checker function, if it exists. + */ +void +CheckFunction(CheckFunctionStmt *stmt, DestReceiver *dest) +{ + List *functionName = stmt->funcname; + List *argTypes = stmt->args; /* list of TypeName nodes */ + ArrayType *options; + bool stop_on_first_error = false; + TupOutputState *tstate; + + tstate = begin_tup_output_tupdesc(dest, CheckFunctionResultDesc(stmt)); + options = process_options(stmt->check_options, &stop_on_first_error); + + if (stmt->funcname != NULL) + { + Oid funcOid; + + /* find a function */ + funcOid = LookupFuncNameTypeNames(functionName, argTypes, false); + + CheckFunctionById(funcOid, InvalidOid, options, + stop_on_first_error, tstate); + } + else if (stmt->trgname != NULL) + { + Oid relid; + Oid funcOid; + + /* find a trigger function */ + relid = RangeVarGetRelid(stmt->relation, AccessShareLock, false); + funcOid = get_trigger_funcid(relid, stmt->trgname, false); + + CheckFunctionById(funcOid, relid, options, + stop_on_first_error, tstate); + } + else + { + List *objects; + Oid funcOid; + + objects = CollectCheckedFunctions(stmt->options); + if (objects != NIL) + { + ListCell *object; + bool isfirst = true; + bool prev_error = false; + + foreach(object, objects) + { + CHECK_FOR_INTERRUPTS(); + + /* + * append an empty line when the previous one was an error, for + * better readability. + */ + if (!isfirst && prev_error) + do_text_output_oneline(tstate, ""); + + funcOid = lfirst_oid(object); + if (!CheckFunctionById(funcOid, InvalidOid, options, + stop_on_first_error, tstate)) + { + prev_error = true; + if (stop_on_first_error) + break; + } + else + prev_error = false; + + isfirst = false; + } + } + else + ereport(NOTICE, + (errmsg("nothing to check"))); + } + + end_tup_output(tstate); +} /* * Rename function @@ -1955,11 +2493,7 @@ ExecuteDoStmt(DoStmt *stmt) /* Look up the language and validate permissions */ languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); if (!HeapTupleIsValid(languageTuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("language \"%s\" does not exist", language), - (PLTemplateExists(language) ? - errhint("Use CREATE LANGUAGE to load the language into the database.") : 0))); + report_missing_language(language); codeblock->langOid = HeapTupleGetOid(languageTuple); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c index 8d6a041..86054f1 100644 --- a/src/backend/commands/proclang.c +++ b/src/backend/commands/proclang.c @@ -46,12 +46,13 @@ typedef struct 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, bool trusted); + 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,9 +68,10 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) PLTemplate *pltemplate; Oid handlerOid, inlineOid, - valOid; + valOid, + checkerOid; Oid funcrettype; - Oid funcargtypes[1]; + Oid funcargtypes[4]; /* * If we have template information for the language, ignore the supplied @@ -222,10 +224,90 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) 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] = REGPROCEDUREOID; + funcargtypes[1] = REGCLASSOID; + funcargtypes[2] = TEXTARRAYOID; + funcargtypes[3] = BOOLOID; + + checkerOid = LookupFuncName(funcname, 4, funcargtypes, true); + if (!OidIsValid(checkerOid)) + { + Datum allTypes[14]; + Datum paramModes[14]; + Datum paramNames[14]; + ArrayType *allParameterTypes; + ArrayType *parameterModes; + ArrayType *parameterNames; + + Oid Types[14] = { REGPROCEDUREOID, REGCLASSOID, TEXTARRAYOID, BOOLOID, + OIDOID, INT4OID, TEXTOID, TEXTOID, + TEXTOID, TEXTOID, TEXTOID, TEXTOID, + INT4OID, TEXTOID }; + + char Modes[14] = { FUNC_PARAM_IN, FUNC_PARAM_IN, FUNC_PARAM_IN, FUNC_PARAM_IN, + FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, + FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, FUNC_PARAM_OUT, + FUNC_PARAM_OUT, FUNC_PARAM_OUT }; + + char *Names[14] = { "functionid", "tableid", "options", "fatal_errors", + "functionid", "lineno", "statement", "sqlstate", + "message", "detail", "hint", "level", + "position", "query" }; + int i; + + for (i = 0; i < 14; i++) + { + allTypes[i] = ObjectIdGetDatum(Types[i]); + paramModes[i] = CharGetDatum(Modes[i]); + paramNames[i] = CStringGetTextDatum(Names[i]); + } + + allParameterTypes = construct_array(allTypes, 14, OIDOID, + sizeof(Oid), true, 'i'); + parameterModes = construct_array(paramModes, 14, CHAROID, + 1, true, 'c'); + parameterNames = construct_array(paramNames, 14, TEXTOID, + -1, false, 'i'); + + checkerOid = ProcedureCreate(pltemplate->tmplchecker, + PG_CATALOG_NAMESPACE, + false, /* replace */ + true, /* returnsSet */ + RECORDOID, + ClanguageId, + F_FMGR_C_VALIDATOR, + pltemplate->tmplchecker, + pltemplate->tmpllibrary, + false, /* isAgg */ + false, /* isWindowFunc */ + false, /* security_definer */ + false, /* isLeakProof */ + true, /* isStrict */ + PROVOLATILE_VOLATILE, + buildoidvector(funcargtypes, 4), /* parameterTypes */ + PointerGetDatum(allParameterTypes), + PointerGetDatum(parameterModes), + PointerGetDatum(parameterNames), + NIL, + PointerGetDatum(NULL), + 1, + 100); + } + } + else + checkerOid = InvalidOid; + /* ok, create it */ create_proc_lang(stmt->plname, stmt->replace, GetUserId(), handlerOid, inlineOid, - valOid, pltemplate->tmpltrusted); + valOid, checkerOid, pltemplate->tmpltrusted); } else { @@ -297,10 +379,22 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) else valOid = InvalidOid; + /* validate the checker function */ + if (stmt->plchecker) + { + funcargtypes[0] = OIDOID; + funcargtypes[1] = REGCLASSOID; + funcargtypes[2] = TEXTARRAYOID; + checkerOid = LookupFuncName(stmt->plchecker, 3, 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, stmt->pltrusted); + valOid, checkerOid, stmt->pltrusted); } } @@ -310,7 +404,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt) static void create_proc_lang(const char *languageName, bool replace, Oid languageOwner, Oid handlerOid, Oid inlineOid, - Oid valOid, bool trusted) + Oid valOid, Oid checkerOid, bool trusted) { Relation rel; TupleDesc tupDesc; @@ -340,6 +434,7 @@ create_proc_lang(const char *languageName, bool replace, 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 */ @@ -426,6 +521,15 @@ create_proc_lang(const char *languageName, bool replace, 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); @@ -481,6 +585,11 @@ find_language_template(const char *languageName) 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) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index caae2da..fdd4539 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -4658,3 +4658,50 @@ pg_trigger_depth(PG_FUNCTION_ARGS) { PG_RETURN_INT32(MyTriggerDepth); } + +/* + * get_trigger_procoid - returns a oid of trigger functions + */ +Oid +get_trigger_funcid(Oid relid, const char *tgname, bool missing_ok) +{ + Oid funcOid; + HeapTuple ht_trig; + Form_pg_trigger trigrec; + ScanKeyData skey[1]; + Relation tgrel; + SysScanDesc tgscan; + Oid trgOid; + + trgOid = get_trigger_oid(relid, tgname, missing_ok); + + /* + * 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; + + /* Clean up */ + systable_endscan(tgscan); + + heap_close(tgrel, AccessShareLock); + + return funcOid; +} diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index e755e7c..c1caf2e 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -1135,7 +1135,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) * Functions for sending tuples to the frontend (or other specified destination) * as though it is a SELECT result. These are used by utility commands that * need to project directly to the destination and don't need or want full - * table function capability. Currently used by EXPLAIN and SHOW ALL. + * table function capability. Currently used by EXPLAIN, SHOW ALL, CHECK FUNCTION. */ TupOutputState * begin_tup_output_tupdesc(DestReceiver *dest, TupleDesc tupdesc) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 7fec4db..f71b2fd 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2884,6 +2884,22 @@ _copyAlterFunctionStmt(const AlterFunctionStmt *from) return newnode; } +static CheckFunctionStmt * +_copyCheckFunctionStmt(const CheckFunctionStmt *from) +{ + CheckFunctionStmt *newnode = makeNode(CheckFunctionStmt); + + COPY_NODE_FIELD(funcname); + COPY_NODE_FIELD(args); + COPY_STRING_FIELD(trgname); + COPY_NODE_FIELD(relation); + COPY_SCALAR_FIELD(is_function); + COPY_NODE_FIELD(options); + COPY_NODE_FIELD(check_options); + + return newnode; +} + static DoStmt * _copyDoStmt(const DoStmt *from) { @@ -4172,6 +4188,9 @@ copyObject(const void *from) case T_AlterFunctionStmt: retval = _copyAlterFunctionStmt(from); break; + case T_CheckFunctionStmt: + retval = _copyCheckFunctionStmt(from); + break; case T_DoStmt: retval = _copyDoStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index d2a79eb..11553f3 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1294,6 +1294,20 @@ _equalAlterFunctionStmt(const AlterFunctionStmt *a, const AlterFunctionStmt *b) } static bool +_equalCheckFunctionStmt(const CheckFunctionStmt *a, const CheckFunctionStmt *b) +{ + COMPARE_NODE_FIELD(funcname); + COMPARE_NODE_FIELD(args); + COMPARE_STRING_FIELD(trgname); + COMPARE_NODE_FIELD(relation); + COMPARE_SCALAR_FIELD(is_function); + COMPARE_NODE_FIELD(options); + COMPARE_NODE_FIELD(check_options); + + return true; +} + +static bool _equalDoStmt(const DoStmt *a, const DoStmt *b) { COMPARE_NODE_FIELD(args); @@ -2715,6 +2729,9 @@ equal(const void *a, const void *b) case T_AlterFunctionStmt: retval = _equalAlterFunctionStmt(a, b); break; + case T_CheckFunctionStmt: + retval = _equalCheckFunctionStmt(a, b); + break; case T_DoStmt: retval = _equalDoStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index d1ce2ab..f84876d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -227,6 +227,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, DeallocateStmt PrepareStmt ExecuteStmt DropOwnedStmt ReassignOwnedStmt AlterTSConfigurationStmt AlterTSDictionaryStmt + CheckFunctionStmt %type select_no_parens select_with_parens select_clause simple_select values_clause @@ -276,7 +277,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, %type func_name handler_name qual_Op qual_all_Op subquery_Op opt_class opt_inline_handler opt_validator validator_clause - opt_collate + opt_collate opt_checker %type qualified_name OptConstrFromTable @@ -463,6 +464,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType, %type window_definition over_clause window_specification opt_frame_clause frame_extent frame_bound %type opt_existing_window_name +%type CheckFunctionOptElem +%type CheckFunctionOpts OptCheckFunctionOpts opt_check_options /* @@ -700,6 +703,7 @@ stmt : | AlterUserSetStmt | AlterUserStmt | AnalyzeStmt + | CheckFunctionStmt | CheckPointStmt | ClosePortalStmt | ClusterStmt @@ -3226,11 +3230,12 @@ CreatePLangStmt: 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 + HANDLER handler_name opt_inline_handler opt_validator opt_checker { CreatePLangStmt *n = makeNode(CreatePLangStmt); n->replace = $2; @@ -3238,6 +3243,7 @@ CreatePLangStmt: n->plhandler = $8; n->plinline = $9; n->plvalidator = $10; + n->plchecker = $11; n->pltrusted = $3; $$ = (Node *)n; } @@ -3272,6 +3278,11 @@ opt_validator: | /*EMPTY*/ { $$ = NIL; } ; +opt_checker: + CHECK handler_name { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + DropPLangStmt: DROP opt_procedural LANGUAGE ColId_or_Sconst opt_drop_behavior { @@ -6337,6 +6348,99 @@ any_operator: /***************************************************************************** * + * CHECK FUNCTION funcname(args) + * CHECK TRIGGER triggername ON table + * + * + *****************************************************************************/ + + +CheckFunctionStmt: + CHECK FUNCTION func_name func_args opt_check_options + { + CheckFunctionStmt *n = makeNode(CheckFunctionStmt); + n->funcname = $3; + n->args = extractArgTypes($4); + n->trgname = NULL; + n->relation = NULL; + n->is_function = true; + n->options = NIL; + n->check_options = $5; + $$ = (Node *) n; + } + | CHECK TRIGGER name ON qualified_name opt_check_options + { + CheckFunctionStmt *n = makeNode(CheckFunctionStmt); + n->funcname = NULL; + n->args = NIL; + n->trgname = $3; + n->relation = $5; + n->is_function = false; + n->options = NIL; + n->check_options = $6; + $$ = (Node *) n; + } + | CHECK FUNCTION ALL OptCheckFunctionOpts opt_check_options + { + CheckFunctionStmt *n = makeNode(CheckFunctionStmt); + n->funcname = NULL; + n->args = NIL; + n->trgname = NULL; + n->relation = NULL; + n->is_function = true; + n->options = $4; + n->check_options = $5; + $$ = (Node *) n; + } + ; + +OptCheckFunctionOpts: + CheckFunctionOpts + { + $$ = $1; + } + | /* EMPTY */ + { + $$ = NIL; + } + ; + +CheckFunctionOpts: + CheckFunctionOptElem { $$ = list_make1($1); } + | CheckFunctionOpts CheckFunctionOptElem { $$ = lappend($1, $2); } + ; + +CheckFunctionOptElem: + IN_P LANGUAGE ColId + { + $$ = makeDefElem("language", + (Node *) makeString($3)); + } + | IN_P SCHEMA ColId + { + $$ = makeDefElem("schema", + (Node *) makeString($3)); + } + | FOR ROLE ColId + { + $$ = makeDefElem("owner", + (Node *) makeString($3)); + } + ; + +opt_check_options: + WITH '(' explain_option_list ')' + { + $$ = $3; + } + | /* EMPTY */ + { + $$ = NIL; + } + ; + +/***************************************************************************** + * * DO [ LANGUAGE language ] * * We use a DefElem list for future extensibility, and to allow flexibility diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 5b81c0b..37f0709 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -901,6 +901,10 @@ standard_ProcessUtility(Node *parsetree, AlterFunction((AlterFunctionStmt *) parsetree); break; + case T_CheckFunctionStmt: + CheckFunction((CheckFunctionStmt *) parsetree, dest); + break; + case T_IndexStmt: /* CREATE INDEX */ { IndexStmt *stmt = (IndexStmt *) parsetree; @@ -1243,6 +1247,9 @@ UtilityReturnsTuples(Node *parsetree) case T_ExplainStmt: return true; + case T_CheckFunctionStmt: + return true; + case T_VariableShowStmt: return true; @@ -1293,6 +1300,9 @@ UtilityTupleDescriptor(Node *parsetree) case T_ExplainStmt: return ExplainResultDesc((ExplainStmt *) parsetree); + case T_CheckFunctionStmt: + return CheckFunctionResultDesc((CheckFunctionStmt *) parsetree); + case T_VariableShowStmt: { VariableShowStmt *n = (VariableShowStmt *) parsetree; @@ -2144,6 +2154,13 @@ CreateCommandTag(Node *parsetree) } break; + case T_CheckFunctionStmt: + if (((CheckFunctionStmt *) parsetree)->is_function) + tag = "CHECK FUNCTION"; + else + tag = "CHECK TRIGGER"; + break; + default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); @@ -2584,6 +2601,10 @@ GetCommandLogLevel(Node *parsetree) } break; + case T_CheckFunctionStmt: + lev = LOGSTMT_ALL; + break; + default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d845c90..3e2b7bf 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5306,13 +5306,26 @@ getProcLangs(Archive *fout, int *numProcLangs) int i_lanplcallfoid; int i_laninline; int i_lanvalidator; + int i_lanchecker; int i_lanacl; int i_lanowner; /* Make sure we are in proper schema */ selectSourceSchema(fout, "pg_catalog"); - if (fout->remoteVersion >= 90000) + if (fout->remoteVersion >= 90200) + { + /* pg_language has a lanchecker column */ + appendPQExpBuffer(query, "SELECT tableoid, oid, " + "lanname, lanpltrusted, lanplcallfoid, " + "laninline, lanvalidator, lanchecker, lanacl, " + "(%s lanowner) AS lanowner " + "FROM pg_language " + "WHERE lanispl " + "ORDER BY oid", + username_subquery); + } + else if (fout->remoteVersion >= 90000) { /* pg_language has a laninline column */ appendPQExpBuffer(query, "SELECT tableoid, oid, " @@ -5388,6 +5401,7 @@ getProcLangs(Archive *fout, int *numProcLangs) /* these may fail and return -1: */ i_laninline = PQfnumber(res, "laninline"); i_lanvalidator = PQfnumber(res, "lanvalidator"); + i_lanchecker = PQfnumber(res, "lanchecker"); i_lanacl = PQfnumber(res, "lanacl"); i_lanowner = PQfnumber(res, "lanowner"); @@ -5401,6 +5415,10 @@ getProcLangs(Archive *fout, int *numProcLangs) planginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_lanname)); planginfo[i].lanpltrusted = *(PQgetvalue(res, i, i_lanpltrusted)) == 't'; planginfo[i].lanplcallfoid = atooid(PQgetvalue(res, i, i_lanplcallfoid)); + if (i_lanchecker >= 0) + planginfo[i].lanchecker = atooid(PQgetvalue(res, i, i_lanchecker)); + else + planginfo[i].lanchecker = InvalidOid; if (i_laninline >= 0) planginfo[i].laninline = atooid(PQgetvalue(res, i, i_laninline)); else @@ -8621,6 +8639,7 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) char *qlanname; char *lanschema; FuncInfo *funcInfo; + FuncInfo *checkerInfo = NULL; FuncInfo *inlineInfo = NULL; FuncInfo *validatorInfo = NULL; @@ -8640,6 +8659,13 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) if (funcInfo != NULL && !funcInfo->dobj.dump) funcInfo = NULL; /* treat not-dumped same as not-found */ + if (OidIsValid(plang->lanchecker)) + { + checkerInfo = findFuncByOid(plang->lanchecker); + if (checkerInfo != NULL && !checkerInfo->dobj.dump) + checkerInfo = NULL; + } + if (OidIsValid(plang->laninline)) { inlineInfo = findFuncByOid(plang->laninline); @@ -8666,6 +8692,7 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) * don't, this might not work terribly nicely. */ useParams = (funcInfo != NULL && + (checkerInfo != NULL || !OidIsValid(plang->lanchecker)) && (inlineInfo != NULL || !OidIsValid(plang->laninline)) && (validatorInfo != NULL || !OidIsValid(plang->lanvalidator))); @@ -8721,6 +8748,16 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) appendPQExpBuffer(defqry, "%s", fmtId(validatorInfo->dobj.name)); } + if (OidIsValid(plang->lanchecker)) + { + appendPQExpBuffer(defqry, " CHECK "); + /* Cope with possibility that checker is in different schema */ + if (checkerInfo->dobj.namespace != funcInfo->dobj.namespace) + appendPQExpBuffer(defqry, "%s.", + fmtId(checkerInfo->dobj.namespace->dobj.name)); + appendPQExpBuffer(defqry, "%s", + fmtId(checkerInfo->dobj.name)); + } } else { diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 28a7fe4..d95c4d9 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -381,6 +381,7 @@ typedef struct _procLangInfo Oid lanplcallfoid; Oid laninline; Oid lanvalidator; + Oid lanchecker; char *lanacl; char *lanowner; /* name of owner, or empty string */ } ProcLangInfo; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 6f481bb..c2758a7 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1,4 +1,5 @@ /* + * * psql - the PostgreSQL interactive terminal * * Copyright (c) 2000-2012, PostgreSQL Global Development Group @@ -739,7 +740,7 @@ psql_completion(char *text, int start, int end) #define prev6_wd (previous_words[5]) static const char *const sql_commands[] = { - "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", + "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", @@ -1536,6 +1537,28 @@ psql_completion(char *text, int start, int end) 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 */ /* diff --git a/src/include/catalog/pg_language.h b/src/include/catalog/pg_language.h index eb4ae5a..ac2faa7 100644 --- a/src/include/catalog/pg_language.h +++ b/src/include/catalog/pg_language.h @@ -37,6 +37,7 @@ CATALOG(pg_language,2612) Oid lanplcallfoid; /* Call handler for PL */ Oid laninline; /* Optional anonymous-block handler function */ Oid lanvalidator; /* Optional validation function */ + Oid lanchecker; /* Optional checker function */ #ifdef CATALOG_VARLEN /* variable-length fields start here */ aclitem lanacl[1]; /* Access privileges */ #endif @@ -53,7 +54,7 @@ typedef FormData_pg_language *Form_pg_language; * compiler constants for pg_language * ---------------- */ -#define Natts_pg_language 8 +#define Natts_pg_language 9 #define Anum_pg_language_lanname 1 #define Anum_pg_language_lanowner 2 #define Anum_pg_language_lanispl 3 @@ -61,20 +62,21 @@ typedef FormData_pg_language *Form_pg_language; #define Anum_pg_language_lanplcallfoid 5 #define Anum_pg_language_laninline 6 #define Anum_pg_language_lanvalidator 7 -#define Anum_pg_language_lanacl 8 +#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 _null_ )); +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 _null_ )); +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 _null_ )); +DATA(insert OID = 14 ( "sql" PGUID f t 0 0 2248 0 _null_ )); DESCR("SQL-language functions"); #define SQLlanguageId 14 diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h index 00abd53..62ce2de 100644 --- a/src/include/catalog/pg_pltemplate.h +++ b/src/include/catalog/pg_pltemplate.h @@ -37,6 +37,7 @@ CATALOG(pg_pltemplate,1136) BKI_SHARED_RELATION BKI_WITHOUT_OIDS 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 */ #endif @@ -53,15 +54,16 @@ typedef FormData_pg_pltemplate *Form_pg_pltemplate; * compiler constants for pg_pltemplate * ---------------- */ -#define Natts_pg_pltemplate 8 +#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_tmpllibrary 7 -#define Anum_pg_pltemplate_tmplacl 8 +#define Anum_pg_pltemplate_tmplchecker 7 +#define Anum_pg_pltemplate_tmpllibrary 8 +#define Anum_pg_pltemplate_tmplacl 9 /* ---------------- @@ -69,13 +71,13 @@ typedef FormData_pg_pltemplate *Form_pg_pltemplate; * ---------------- */ -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_ )); +DATA(insert ( "plpgsql" t t "plpgsql_call_handler" "plpgsql_inline_handler" "plpgsql_validator" _null_ "$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 */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 163b2ea..4ba192c 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -15,6 +15,7 @@ #define DEFREM_H #include "nodes/parsenodes.h" +#include "tcop/dest.h" /* commands/dropcmds.c */ extern void RemoveObjects(DropStmt *stmt); @@ -62,6 +63,8 @@ extern Oid GetDefaultOpClass(Oid type_id, Oid am_id); /* commands/functioncmds.c */ extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString); extern void RemoveFunctionById(Oid funcOid); +extern void CheckFunction(CheckFunctionStmt *stmt, DestReceiver *dest); +extern TupleDesc CheckFunctionResultDesc(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); diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 9303341..10d89d0 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -207,4 +207,6 @@ extern int RI_FKey_trigger_type(Oid tgfoid); extern Datum pg_trigger_depth(PG_FUNCTION_ARGS); +extern Oid get_trigger_funcid(Oid relid, const char *tgname, bool missing_ok); + #endif /* TRIGGER_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 0e7d184..cbafc8e 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -290,6 +290,7 @@ typedef enum NodeTag T_IndexStmt, T_CreateFunctionStmt, T_AlterFunctionStmt, + T_CheckFunctionStmt, T_DoStmt, T_RenameStmt, T_RuleStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ab55639..4c295a0 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1741,6 +1741,7 @@ typedef struct CreatePLangStmt 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; @@ -2085,6 +2086,22 @@ typedef struct AlterFunctionStmt } 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 */ + bool is_function; /* true for CHECK FUNCTION statement */ + List *options; /* other DefElem options */ + List *check_options; /* options for check function */ +} CheckFunctionStmt; + +/* ---------------------- * DO Statement * * DoStmt is the raw parser output, InlineCodeBlock is the execution-time API