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);
 }