From 0fdc6c4cc0a1b84844caa29b5a8fb6cb5f28b752 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 19 Mar 2004 18:58:07 +0000 Subject: [PATCH] Create a validator for plpgsql, so that some minimal syntax checking 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 | 34 +++++++-- src/bin/scripts/droplang.c | 61 ++++++++++++---- src/include/catalog/pg_type.h | 3 +- src/pl/plpgsql/src/pl_comp.c | 76 ++++++++++++-------- src/pl/plpgsql/src/pl_handler.c | 123 +++++++++++++++++++++++++++++++- src/pl/plpgsql/src/plpgsql.h | 8 ++- src/pl/plpgsql/src/scan.l | 62 ++++++++++++---- 7 files changed, 300 insertions(+), 67 deletions(-) diff --git a/src/bin/scripts/createlang.c b/src/bin/scripts/createlang.c index d81f420755..64e257bc2f 100644 --- a/src/bin/scripts/createlang.c +++ b/src/bin/scripts/createlang.c @@ -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); diff --git a/src/bin/scripts/droplang.c b/src/bin/scripts/droplang.c index 93c6816199..43f57115f5 100644 --- a/src/bin/scripts/droplang.c +++ b/src/bin/scripts/droplang.c @@ -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); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 8f18d50cef..8cbc0aa8e6 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -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_ )); diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index a00d558624..46e9ca925b 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -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), diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 3f9998f490..5feeb55cce 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -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. * @@ -42,8 +42,11 @@ #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(); +} diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 911e331adf..cb3c4c2944 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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); diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index de447e09f1..3e6ee8fd18 100644 --- a/src/pl/plpgsql/src/scan.l +++ b/src/pl/plpgsql/src/scan.l @@ -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); }