Create a validator for plpgsql, so that some minimal syntax checking
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 19 Mar 2004 18:58:07 +0000 (18:58 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 19 Mar 2004 18:58:07 +0000 (18:58 +0000)
is done at creation time for plpgsql functions.  Improve createlang and
droplang to support adding/dropping validators for PLs.  Initial steps
towards producing a syntax error position from plpgsql syntax errors
(this part is a work in progress, and will change depending on outcome
of current discussions).

src/bin/scripts/createlang.c
src/bin/scripts/droplang.c
src/include/catalog/pg_type.h
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_handler.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/scan.l

index d81f42075590b1ff2d9b49b3b6cfc505414bbdad..64e257bc2f05d66aeff9b71c42e7c4568e16c07a 100644 (file)
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/scripts/createlang.c,v 1.7 2003/11/29 19:52:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/bin/scripts/createlang.c,v 1.8 2004/03/19 18:58:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,8 +49,10 @@ main(int argc, char *argv[])
 
        char       *p;
        bool            handlerexists;
+       bool            validatorexists;
        bool            trusted;
        char       *handler;
+       char       *validator = NULL;
        char       *object;
 
        PQExpBufferData sql;
@@ -169,6 +171,7 @@ main(int argc, char *argv[])
        {
                trusted = true;
                handler = "plpgsql_call_handler";
+               validator = "plpgsql_validator";
                object = "plpgsql";
        }
        else if (strcmp(langname, "pltcl") == 0)
@@ -229,13 +232,26 @@ main(int argc, char *argv[])
        /*
         * Check whether the call handler exists
         */
-       printfPQExpBuffer(&sql, "SELECT oid FROM pg_proc WHERE proname = '%s' AND prorettype = (SELECT oid FROM pg_type WHERE typname = 'language_handler') AND pronargs = 0;", handler);
+       printfPQExpBuffer(&sql, "SELECT oid FROM pg_proc WHERE proname = '%s' AND prorettype = 'pg_catalog.language_handler'::regtype AND pronargs = 0;", handler);
        result = executeQuery(conn, sql.data, progname, echo);
        handlerexists = (PQntuples(result) > 0);
        PQclear(result);
 
        /*
-        * Create the call handler and the language
+        * Check whether the validator exists
+        */
+       if (validator)
+       {
+               printfPQExpBuffer(&sql, "SELECT oid FROM pg_proc WHERE proname = '%s' AND proargtypes[0] = 'pg_catalog.oid'::regtype AND pronargs = 1;", validator);
+               result = executeQuery(conn, sql.data, progname, echo);
+               validatorexists = (PQntuples(result) > 0);
+               PQclear(result);
+       }
+       else
+               validatorexists = true;                 /* don't try to create it */
+
+       /*
+        * Create the function(s) and the language
         */
        resetPQExpBuffer(&sql);
 
@@ -244,10 +260,20 @@ main(int argc, char *argv[])
                                                  "CREATE FUNCTION \"%s\" () RETURNS language_handler AS '%s/%s' LANGUAGE C;\n",
                                                  handler, pglib, object);
 
+       if (!validatorexists)
+               appendPQExpBuffer(&sql,
+                                                 "CREATE FUNCTION \"%s\" (oid) RETURNS void AS '%s/%s' LANGUAGE C;\n",
+                                                 validator, pglib, object);
+
        appendPQExpBuffer(&sql,
-                                         "CREATE %sLANGUAGE \"%s\" HANDLER \"%s\";\n",
+                                         "CREATE %sLANGUAGE \"%s\" HANDLER \"%s\"",
                                          (trusted ? "TRUSTED " : ""), langname, handler);
 
+       if (validator)
+               appendPQExpBuffer(&sql, " VALIDATOR \"%s\"", validator);
+
+       appendPQExpBuffer(&sql, ";\n");
+
        if (echo)
                printf("%s", sql.data);
        result = PQexec(conn, sql.data);
index 93c6816199507558bfdf2821da147efd3c2967fc..43f57115f53d47c781f759bcfc35f10f99679510 100644 (file)
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/scripts/droplang.c,v 1.6 2003/11/29 19:52:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/bin/scripts/droplang.c,v 1.7 2004/03/19 18:58:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -14,6 +14,8 @@
 #include "common.h"
 #include "print.h"
 
+#define atooid(x)  ((Oid) strtoul((x), NULL, 10))
+
 
 static void help(const char *progname);
 
@@ -46,9 +48,12 @@ main(int argc, char *argv[])
        char       *langname = NULL;
 
        char       *p;
-       char       *lanplcallfoid;
+       Oid                     lanplcallfoid;
+       Oid                     lanvalidator;
        char       *handler;
+       char       *validator;
        bool            keephandler;
+       bool            keepvalidator;
 
        PQExpBufferData sql;
 
@@ -159,10 +164,10 @@ main(int argc, char *argv[])
        conn = connectDatabase(dbname, host, port, username, password, progname);
 
        /*
-        * Make sure the language is installed and find the OID of the handler
-        * function
+        * Make sure the language is installed and find the OIDs of the handler
+        * and validator functions
         */
-       printfPQExpBuffer(&sql, "SELECT lanplcallfoid FROM pg_language WHERE lanname = '%s' AND lanispl;", langname);
+       printfPQExpBuffer(&sql, "SELECT lanplcallfoid, lanvalidator FROM pg_language WHERE lanname = '%s' AND lanispl;", langname);
        result = executeQuery(conn, sql.data, progname, echo);
        if (PQntuples(result) == 0)
        {
@@ -171,8 +176,9 @@ main(int argc, char *argv[])
                                progname, langname, dbname);
                exit(1);
        }
-       lanplcallfoid = PQgetvalue(result, 0, 0);
-       /* result not cleared! */
+       lanplcallfoid = atooid(PQgetvalue(result, 0, 0));
+       lanvalidator = atooid(PQgetvalue(result, 0, 1));
+       PQclear(result);
 
        /*
         * Check that there are no functions left defined in that language
@@ -192,7 +198,7 @@ main(int argc, char *argv[])
        /*
         * Check that the handler function isn't used by some other language
         */
-       printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language WHERE lanplcallfoid = %s AND lanname <> '%s';", lanplcallfoid, langname);
+       printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language WHERE lanplcallfoid = %u AND lanname <> '%s';", lanplcallfoid, langname);
        result = executeQuery(conn, sql.data, progname, echo);
        if (strcmp(PQgetvalue(result, 0, 0), "0") == 0)
                keephandler = false;
@@ -205,20 +211,51 @@ main(int argc, char *argv[])
         */
        if (!keephandler)
        {
-               printfPQExpBuffer(&sql, "SELECT proname FROM pg_proc WHERE oid = %s;", lanplcallfoid);
+               printfPQExpBuffer(&sql, "SELECT proname FROM pg_proc WHERE oid = %u;", lanplcallfoid);
                result = executeQuery(conn, sql.data, progname, echo);
-               handler = PQgetvalue(result, 0, 0);
-               /* result not cleared! */
+               handler = strdup(PQgetvalue(result, 0, 0));
+               PQclear(result);
        }
        else
                handler = NULL;
 
        /*
-        * Drop the language
+        * Check that the validator function isn't used by some other language
+        */
+       if (OidIsValid(lanvalidator))
+       {
+               printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language WHERE lanvalidator = %u AND lanname <> '%s';", lanvalidator, langname);
+               result = executeQuery(conn, sql.data, progname, echo);
+               if (strcmp(PQgetvalue(result, 0, 0), "0") == 0)
+                       keepvalidator = false;
+               else
+                       keepvalidator = true;
+               PQclear(result);
+       }
+       else
+               keepvalidator = true;   /* don't try to delete it */
+
+       /*
+        * Find the validator name
+        */
+       if (!keepvalidator)
+       {
+               printfPQExpBuffer(&sql, "SELECT proname FROM pg_proc WHERE oid = %u;", lanvalidator);
+               result = executeQuery(conn, sql.data, progname, echo);
+               validator = strdup(PQgetvalue(result, 0, 0));
+               PQclear(result);
+       }
+       else
+               validator = NULL;
+
+       /*
+        * Drop the language and the functions
         */
        printfPQExpBuffer(&sql, "DROP LANGUAGE \"%s\";\n", langname);
        if (!keephandler)
                appendPQExpBuffer(&sql, "DROP FUNCTION \"%s\" ();\n", handler);
+       if (!keepvalidator)
+               appendPQExpBuffer(&sql, "DROP FUNCTION \"%s\" (oid);\n", validator);
        if (echo)
                printf("%s", sql.data);
        result = PQexec(conn, sql.data);
index 8f18d50cefc7de68545af718b0298b811bfb59bd..8cbc0aa8e67a5ddb1a00e6d645ddb678d684c253 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.150 2004/02/24 22:59:10 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.151 2004/03/19 18:58:07 tgl Exp $
  *
  * NOTES
  *       the genbki.sh script reads this file and generates .bki
@@ -402,6 +402,7 @@ DATA(insert OID = 1003 (  _name              PGNSP PGUID -1 f b t \054 0    19 array_in array_
 DATA(insert OID = 1005 (  _int2                 PGNSP PGUID -1 f b t \054 0    21 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
 DATA(insert OID = 1006 (  _int2vector PGNSP PGUID -1 f b t \054 0      22 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
 DATA(insert OID = 1007 (  _int4                 PGNSP PGUID -1 f b t \054 0    23 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
+#define INT4ARRAYOID           1007
 DATA(insert OID = 1008 (  _regproc      PGNSP PGUID -1 f b t \054 0    24 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
 DATA(insert OID = 1009 (  _text                 PGNSP PGUID -1 f b t \054 0    25 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
 DATA(insert OID = 1028 (  _oid          PGNSP PGUID -1 f b t \054 0    26 array_in array_out array_recv array_send - i x f 0 -1 0 _null_ _null_ ));
index a00d5586246d495839a43740758e8b7c6ec4c7ef..46e9ca925b72eaaba30fa85b5c37c475e8e2cac9 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.73 2004/01/07 18:56:30 neilc Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.74 2004/03/19 18:58:07 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -101,13 +101,15 @@ typedef struct plpgsql_hashent
  */
 static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
                   HeapTuple procTup,
-                  PLpgSQL_func_hashkey *hashkey);
+                  PLpgSQL_func_hashkey *hashkey,
+                  bool forValidator);
 static void plpgsql_compile_error_callback(void *arg);
 static char **fetchArgNames(HeapTuple procTup, int nargs);
 static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod);
 static void compute_function_hashkey(FunctionCallInfo fcinfo,
                                                 Form_pg_proc procStruct,
-                                                PLpgSQL_func_hashkey *hashkey);
+                                                PLpgSQL_func_hashkey *hashkey,
+                                                bool forValidator);
 static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
 static void plpgsql_HashTableInsert(PLpgSQL_function *function,
                                                PLpgSQL_func_hashkey *func_key);
@@ -134,12 +136,15 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
 /* ----------
  * plpgsql_compile             Make an execution tree for a PL/pgSQL function.
  *
+ * If forValidator is true, we're only compiling for validation purposes,
+ * and so some checks are skipped.
+ *
  * Note: it's important for this to fall through quickly if the function
  * has already been compiled.
  * ----------
  */
 PLpgSQL_function *
-plpgsql_compile(FunctionCallInfo fcinfo)
+plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
 {
        Oid                     funcOid = fcinfo->flinfo->fn_oid;
        HeapTuple       procTup;
@@ -171,7 +176,7 @@ plpgsql_compile(FunctionCallInfo fcinfo)
                        plpgsql_HashTableInit();
 
                /* Compute hashkey using function signature and actual arg types */
-               compute_function_hashkey(fcinfo, procStruct, &hashkey);
+               compute_function_hashkey(fcinfo, procStruct, &hashkey, forValidator);
                hashkey_valid = true;
 
                /* And do the lookup */
@@ -205,12 +210,13 @@ plpgsql_compile(FunctionCallInfo fcinfo)
                 * the completed function.
                 */
                if (!hashkey_valid)
-                       compute_function_hashkey(fcinfo, procStruct, &hashkey);
+                       compute_function_hashkey(fcinfo, procStruct, &hashkey,
+                                                                        forValidator);
 
                /*
                 * Do the hard part.
                 */
-               function = do_compile(fcinfo, procTup, &hashkey);
+               function = do_compile(fcinfo, procTup, &hashkey, forValidator);
        }
 
        ReleaseSysCache(procTup);
@@ -232,7 +238,8 @@ plpgsql_compile(FunctionCallInfo fcinfo)
 static PLpgSQL_function *
 do_compile(FunctionCallInfo fcinfo,
                   HeapTuple procTup,
-                  PLpgSQL_func_hashkey *hashkey)
+                  PLpgSQL_func_hashkey *hashkey,
+                  bool forValidator)
 {
        Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
        int                     functype = CALLED_AS_TRIGGER(fcinfo) ? T_TRIGGER : T_FUNCTION;
@@ -308,7 +315,8 @@ do_compile(FunctionCallInfo fcinfo,
                        /*
                         * Check for a polymorphic returntype. If found, use the
                         * actual returntype type from the caller's FuncExpr node, if
-                        * we have one.
+                        * we have one.  (In validation mode we arbitrarily assume we
+                        * are dealing with integers.)
                         *
                         * Note: errcode is FEATURE_NOT_SUPPORTED because it should
                         * always work; if it doesn't we're in some context that fails
@@ -317,7 +325,15 @@ do_compile(FunctionCallInfo fcinfo,
                        rettypeid = procStruct->prorettype;
                        if (rettypeid == ANYARRAYOID || rettypeid == ANYELEMENTOID)
                        {
-                               rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
+                               if (forValidator)
+                               {
+                                       if (rettypeid == ANYARRAYOID)
+                                               rettypeid = INT4ARRAYOID;
+                                       else
+                                               rettypeid = INT4OID;
+                               }
+                               else
+                                       rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
                                if (!OidIsValid(rettypeid))
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1758,22 +1774,6 @@ plpgsql_add_initdatums(int **varnos)
 }
 
 
-/* ---------
- * plpgsql_yyerror                     Handle parser error
- * ---------
- */
-
-void
-plpgsql_yyerror(const char *s)
-{
-       plpgsql_error_lineno = plpgsql_scanner_lineno();
-       ereport(ERROR,
-                       (errcode(ERRCODE_SYNTAX_ERROR),
-       /* translator: first %s is a phrase like "syntax error" */
-                        errmsg("%s at or near \"%s\"", s, plpgsql_yytext)));
-}
-
-
 /*
  * Compute the hashkey for a given function invocation
  *
@@ -1782,7 +1782,8 @@ plpgsql_yyerror(const char *s)
 static void
 compute_function_hashkey(FunctionCallInfo fcinfo,
                                                 Form_pg_proc procStruct,
-                                                PLpgSQL_func_hashkey *hashkey)
+                                                PLpgSQL_func_hashkey *hashkey,
+                                                bool forValidator)
 {
        int                     i;
 
@@ -1792,8 +1793,12 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
        /* get function OID */
        hashkey->funcOid = fcinfo->flinfo->fn_oid;
 
-       /* if trigger, get relation OID */
-       if (CALLED_AS_TRIGGER(fcinfo))
+       /*
+        * if trigger, get relation OID.  In validation mode we do not know what
+        * relation is intended to be used, so we leave trigrelOid zero; the
+        * hash entry built in this case will never really be used.
+        */
+       if (CALLED_AS_TRIGGER(fcinfo) && !forValidator)
        {
                TriggerData *trigdata = (TriggerData *) fcinfo->context;
 
@@ -1808,6 +1813,9 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
                /*
                 * Check for polymorphic arguments. If found, use the actual
                 * parameter type from the caller's FuncExpr node, if we have one.
+                * (In validation mode we arbitrarily assume we are dealing with
+                * integers.  This lets us build a valid, if possibly useless,
+                * function hashtable entry.)
                 *
                 * We can support arguments of type ANY the same way as normal
                 * polymorphic arguments.
@@ -1815,7 +1823,15 @@ compute_function_hashkey(FunctionCallInfo fcinfo,
                if (argtypeid == ANYARRAYOID || argtypeid == ANYELEMENTOID ||
                        argtypeid == ANYOID)
                {
-                       argtypeid = get_fn_expr_argtype(fcinfo->flinfo, i);
+                       if (forValidator)
+                       {
+                               if (argtypeid == ANYARRAYOID)
+                                       argtypeid = INT4ARRAYOID;
+                               else
+                                       argtypeid = INT4OID;
+                       }
+                       else
+                               argtypeid = get_fn_expr_argtype(fcinfo->flinfo, i);
                        if (!OidIsValid(argtypeid))
                                ereport(ERROR,
                                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
index 3f9998f490008f7b2c4ea9526471895ba45e2cfa..5feeb55cce75dd84474d426ac81ed3523659e87b 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.19 2003/11/29 19:52:12 pgsql Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.20 2004/03/19 18:58:07 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+extern bool check_function_bodies;
+
 static int     plpgsql_firstcall = 1;
 
 static void plpgsql_init_all(void);
@@ -88,7 +91,6 @@ plpgsql_init_all(void)
 /* ----------
  * plpgsql_call_handler
  *
- * This is the only visible function of the PL interpreter.
  * The PostgreSQL function manager and trigger manager
  * call this function for execution of PL/pgSQL procedures.
  * ----------
@@ -111,7 +113,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
                elog(ERROR, "SPI_connect failed");
 
        /* Find or compile the function */
-       func = plpgsql_compile(fcinfo);
+       func = plpgsql_compile(fcinfo, false);
 
        /*
         * Determine if called as function or trigger and call appropriate
@@ -131,3 +133,118 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
 
        return retval;
 }
+
+/* ----------
+ * plpgsql_validator
+ *
+ * This function attempts to validate a PL/pgSQL function at
+ * CREATE FUNCTION time.
+ * ----------
+ */
+PG_FUNCTION_INFO_V1(plpgsql_validator);
+
+Datum
+plpgsql_validator(PG_FUNCTION_ARGS)
+{
+       Oid                     funcoid = PG_GETARG_OID(0);
+       HeapTuple       tuple;
+       Form_pg_proc proc;
+       char            functyptype;
+       bool            istrigger = false;
+       bool            haspolyresult;
+       bool            haspolyarg;
+       int                     i;
+
+       /* perform initialization */
+       plpgsql_init_all();
+
+       /* Get the new function's pg_proc entry */
+       tuple = SearchSysCache(PROCOID,
+                                                  ObjectIdGetDatum(funcoid),
+                                                  0, 0, 0);
+       if (!HeapTupleIsValid(tuple))
+               elog(ERROR, "cache lookup failed for function %u", funcoid);
+       proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+       functyptype = get_typtype(proc->prorettype);
+
+       /* Disallow pseudotype result */
+       /* except for TRIGGER, RECORD, VOID, ANYARRAY, or ANYELEMENT */
+       if (functyptype == 'p')
+       {
+               /* we assume OPAQUE with no arguments means a trigger */
+               if (proc->prorettype == TRIGGEROID ||
+                       (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+                       istrigger = true;
+               else if (proc->prorettype == ANYARRAYOID ||
+                                proc->prorettype == ANYELEMENTOID)
+                       haspolyresult = true;
+               else if (proc->prorettype != RECORDOID &&
+                                proc->prorettype != VOIDOID)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("plpgsql functions cannot return type %s",
+                                                       format_type_be(proc->prorettype))));
+       }
+
+       /* Disallow pseudotypes in arguments */
+       /* except for ANYARRAY or ANYELEMENT */
+       haspolyarg = false;
+       for (i = 0; i < proc->pronargs; i++)
+       {
+               if (get_typtype(proc->proargtypes[i]) == 'p')
+               {
+                       if (proc->proargtypes[i] == ANYARRAYOID ||
+                               proc->proargtypes[i] == ANYELEMENTOID)
+                               haspolyarg = true;
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("plpgsql functions cannot take type %s",
+                                                               format_type_be(proc->proargtypes[i]))));
+               }
+       }
+
+       /* Postpone body checks if !check_function_bodies */
+       if (check_function_bodies)
+       {
+               FunctionCallInfoData fake_fcinfo;
+               FmgrInfo        flinfo;
+               TriggerData trigdata;
+
+               /*
+                * Connect to SPI manager (is this needed for compilation?)
+                */
+               if (SPI_connect() != SPI_OK_CONNECT)
+                       elog(ERROR, "SPI_connect failed");
+
+               /*
+                * Set up a fake fcinfo with just enough info to satisfy
+                * plpgsql_compile().
+                */
+               MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
+               MemSet(&flinfo, 0, sizeof(flinfo));
+               fake_fcinfo.flinfo = &flinfo;
+               flinfo.fn_oid = funcoid;
+               flinfo.fn_mcxt = CurrentMemoryContext;
+               if (istrigger)
+               {
+                       MemSet(&trigdata, 0, sizeof(trigdata));
+                       trigdata.type = T_TriggerData;
+                       fake_fcinfo.context = (Node *) &trigdata;
+               }
+
+               /* Test-compile the function */
+               plpgsql_compile(&fake_fcinfo, true);
+
+               /*
+                * Disconnect from SPI manager
+                */
+               if (SPI_finish() != SPI_OK_FINISH)
+                       elog(ERROR, "SPI_finish failed");
+       }
+
+       ReleaseSysCache(tuple);
+
+       PG_RETURN_VOID();
+}
index 911e331adf2175c9538f061ae277a04b752613f3..cb3c4c2944feb013785fa1b72079e8fb2b4757f6 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.44 2004/02/25 18:10:51 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.45 2004/03/19 18:58:07 tgl Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -620,7 +620,8 @@ extern PLpgSQL_function *plpgsql_curr_compile;
  * Functions in pl_comp.c
  * ----------
  */
-extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo);
+extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo,
+                                                                                bool forValidator);
 extern int     plpgsql_parse_word(char *word);
 extern int     plpgsql_parse_dblword(char *word);
 extern int     plpgsql_parse_tripword(char *word);
@@ -633,7 +634,6 @@ extern PLpgSQL_type *plpgsql_parse_datatype(char *string);
 extern PLpgSQL_row *plpgsql_build_rowtype(Oid classOid);
 extern void plpgsql_adddatum(PLpgSQL_datum * new);
 extern int     plpgsql_add_initdatums(int **varnos);
-extern void plpgsql_yyerror(const char *s);
 extern void plpgsql_HashTableInit(void);
 
 /* ----------
@@ -642,6 +642,7 @@ extern void plpgsql_HashTableInit(void);
  */
 extern void plpgsql_init(void);
 extern Datum plpgsql_call_handler(PG_FUNCTION_ARGS);
+extern Datum plpgsql_validator(PG_FUNCTION_ARGS);
 
 /* ----------
  * Functions in pl_exec.c
@@ -691,6 +692,7 @@ extern int  plpgsql_yyparse(void);
 extern int     plpgsql_base_yylex(void);
 extern int     plpgsql_yylex(void);
 extern void plpgsql_push_back_token(int token);
+extern void plpgsql_yyerror(const char *message);
 extern int     plpgsql_scanner_lineno(void);
 extern void plpgsql_scanner_init(const char *str, int functype);
 extern void plpgsql_scanner_finish(void);
index de447e09f1b711f89a0b7ae26f525a69784ad4cd..3e6ee8fd187be273d1b497f7f130645cf617b3d5 100644 (file)
@@ -4,7 +4,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.32 2004/02/25 18:10:51 tgl Exp $
+ *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.33 2004/03/19 18:58:07 tgl Exp $
  *
  *    This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -38,6 +38,8 @@
 
 #include "plpgsql.h"
 
+#include "mb/pg_wchar.h"
+
 
 /* No reason to constrain amount of data slurped */
 #define YY_READ_BUF_SIZE 16777216
@@ -409,6 +411,38 @@ plpgsql_push_back_token(int token)
        have_pushback_token = true;
 }
 
+/*
+ * Report a syntax error.
+ */
+void
+plpgsql_yyerror(const char *message)
+{
+       const char *loc = yytext;
+       int                     cursorpos;
+
+       plpgsql_error_lineno = plpgsql_scanner_lineno();
+
+       /* in multibyte encodings, return index in characters not bytes */
+       cursorpos = pg_mbstrlen_with_len(scanbuf, loc - scanbuf) + 1;
+
+       if (*loc == YY_END_OF_BUFFER_CHAR)
+       {
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                /* translator: %s is typically "syntax error" */
+                                errmsg("%s at end of input", message),
+                                errposition(cursorpos)));
+       }
+       else
+       {
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                /* translator: first %s is typically "syntax error" */
+                                errmsg("%s at or near \"%s\"", message, loc),
+                                errposition(cursorpos)));
+       }
+}
+
 /*
  * Get the line number at which the current token ends.  This substitutes
  * for flex's very poorly implemented yylineno facility.
@@ -439,19 +473,6 @@ plpgsql_scanner_init(const char *str, int functype)
 {
        Size    slen;
 
-       /*----------
-        * Hack: skip any initial newline, so that in the common coding layout
-        *              CREATE FUNCTION ... AS '
-        *                      code body
-        *              ' LANGUAGE 'plpgsql';
-        * we will think "line 1" is what the programmer thinks of as line 1.
-        *----------
-        */
-    if (*str == '\r')
-        str++;
-    if (*str == '\n')
-        str++;
-
        slen = strlen(str);
 
        /*
@@ -478,6 +499,19 @@ plpgsql_scanner_init(const char *str, int functype)
        cur_line_start = scanbuf;
        cur_line_num = 1;
 
+       /*----------
+        * Hack: skip any initial newline, so that in the common coding layout
+        *              CREATE FUNCTION ... AS '
+        *                      code body
+        *              ' LANGUAGE 'plpgsql';
+        * we will think "line 1" is what the programmer thinks of as line 1.
+        *----------
+        */
+    if (*cur_line_start == '\r')
+        cur_line_start++;
+    if (*cur_line_start == '\n')
+        cur_line_start++;
+
        BEGIN(INITIAL);
 }