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