From 5a9dd0dc4f84a0e847535086fbbf547084a8af1e Mon Sep 17 00:00:00 2001 From: Neil Conway Date: Tue, 22 Feb 2005 07:18:27 +0000 Subject: [PATCH] This patch changes makes some significant changes to how compilation and parsing work in PL/PgSQL: - memory management is now done via palloc(). The compiled representation of each function now has its own memory context. Therefore, the storage consumed by a function can be reclaimed via MemoryContextDelete(). During compilation, the CurrentMemoryContext is the function's memory context. This means that a palloc() is sufficient to allocate memory that will have the same lifetime as the function itself. As a result, code invoked during compilation should be careful to pfree() temporary allocations to avoid leaking memory. Since a lot of the code in the backend is not careful about releasing palloc'ed memory, that means we should switch into a temporary memory context before invoking backend functions. A temporary context appropriate for such allocations is `compile_tmp_cxt'. - The ability to use palloc() allows us to simply a lot of the code in the parser. Rather than representing lists of elements via ad hoc linked lists or arrays, we can use the List type. Rather than doing malloc followed by memset(0), we can just use palloc0(). - We now check that the user has supplied the right number of parameters to a RAISE statement. Supplying either too few or too many results in an error (at runtime). - PL/PgSQL's parser needs to accept arbitrary SQL statements. Since we do not want to duplicate the SQL grammar in the PL/PgSQL grammar, this means we need to be quite lax in what the PL/PgSQL grammar considers a "SQL statement". This can lead to misleading behavior if there is a syntax error in the function definition, since we assume a malformed PL/PgSQL construct is a SQL statement. Furthermore, these errors were only detected at runtime (when we tried to execute the alleged "SQL statement" via SPI). To rectify this, the patch changes the parser to invoke the main SQL parser when it sees a string it believes to be a SQL expression. This means that synctically-invalid SQL will be rejected during the compilation of the PL/PgSQL function. This is only done when compiling for "validation" purposes (i.e. at CREATE FUNCTION time), so it should not impose a runtime overhead. - Fixes for the various buffer overruns I've patched in stable branches in the past few weeks. I've rewritten code where I thought it was warranted (unlike the patches applied to older branches, which were minimally invasive). - Various other minor changes and cleanups. - Updates to the regression tests. --- src/pl/plpgsql/src/gram.y | 923 ++++++++++++-------------- src/pl/plpgsql/src/pl_comp.c | 471 ++++++------- src/pl/plpgsql/src/pl_exec.c | 178 +++-- src/pl/plpgsql/src/pl_funcs.c | 90 ++- src/pl/plpgsql/src/pl_handler.c | 12 +- src/pl/plpgsql/src/plpgsql.h | 65 +- src/pl/plpgsql/src/scan.l | 22 +- src/test/regress/expected/plpgsql.out | 69 ++ src/test/regress/sql/plpgsql.sql | 55 ++ 9 files changed, 962 insertions(+), 923 deletions(-) diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index 1c8fa43b21..210d144277 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -4,7 +4,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.65 2005/02/13 01:25:50 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.66 2005/02/22 07:18:24 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -38,18 +38,24 @@ #include "plpgsql.h" +#include "parser/parser.h" -static PLpgSQL_expr *read_sql_construct(int until, +static PLpgSQL_expr *read_sql_construct(int until, int until2, const char *expected, - bool isexpression, const char *sqlstart, + bool isexpression, + bool valid_sql, int *endtoken); static PLpgSQL_expr *read_sql_stmt(const char *sqlstart); static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_stmt *make_select_stmt(void); static PLpgSQL_stmt *make_fetch_stmt(void); -static void check_assignable(PLpgSQL_datum *datum); +static void check_assignable(PLpgSQL_datum *datum); +static PLpgSQL_row *read_into_scalar_list(const char *initial_name, + PLpgSQL_datum *initial_datum); +static void check_sql_expr(const char *stmt); +static void plpgsql_sql_error_callback(void *arg); %} @@ -62,23 +68,11 @@ static void check_assignable(PLpgSQL_datum *datum); int lineno; } varname; struct - { - int nalloc; - int nused; - int *nums; - } intlist; - struct - { - int nalloc; - int nused; - PLpgSQL_diag_item *dtitems; - } dtlist; - struct - { + { char *name; int lineno; - PLpgSQL_rec *rec; - PLpgSQL_row *row; + PLpgSQL_rec *rec; + PLpgSQL_row *row; } forvariable; struct { @@ -86,6 +80,7 @@ static void check_assignable(PLpgSQL_datum *datum); int n_initvars; int *initvarnos; } declhdr; + List *list; PLpgSQL_type *dtype; PLpgSQL_datum *scalar; /* a VAR, RECFIELD, or TRIGARG */ PLpgSQL_variable *variable; /* a VAR, REC, or ROW */ @@ -94,12 +89,11 @@ static void check_assignable(PLpgSQL_datum *datum); PLpgSQL_rec *rec; PLpgSQL_expr *expr; PLpgSQL_stmt *stmt; - PLpgSQL_stmts *stmts; PLpgSQL_stmt_block *program; PLpgSQL_condition *condition; PLpgSQL_exception *exception; - PLpgSQL_exceptions *exceptions; PLpgSQL_nsitem *nsitem; + PLpgSQL_diag_item *diagitem; } %type decl_sect @@ -108,7 +102,8 @@ static void check_assignable(PLpgSQL_datum *datum); %type decl_const decl_notnull %type decl_defval decl_cursor_query %type decl_datatype -%type decl_cursor_args decl_cursor_arglist +%type decl_cursor_args +%type decl_cursor_arglist %type decl_aliasitem %type decl_stmts decl_stmt @@ -126,7 +121,7 @@ static void check_assignable(PLpgSQL_datum *datum); %type opt_exitlabel %type execsql_start -%type proc_sect proc_stmts stmt_else loop_body +%type proc_sect proc_stmts stmt_else loop_body %type proc_stmt pl_block %type stmt_assign stmt_if stmt_loop stmt_while stmt_exit %type stmt_return stmt_return_next stmt_raise stmt_execsql @@ -134,16 +129,17 @@ static void check_assignable(PLpgSQL_datum *datum); %type stmt_dynexecute stmt_getdiag %type stmt_open stmt_fetch stmt_close stmt_null -%type exception_sect proc_exceptions +%type exception_sect proc_exceptions %type proc_exception %type proc_conditions -%type raise_params +%type raise_params %type raise_level raise_param %type raise_msg -%type getdiag_list -%type getdiag_item getdiag_target +%type getdiag_list +%type getdiag_list_item +%type getdiag_kind getdiag_target %type lno @@ -240,7 +236,7 @@ comp_options : comp_options comp_option comp_option : O_OPTION O_DUMP { - plpgsql_DumpExecTree = 1; + plpgsql_DumpExecTree = true; } ; @@ -252,8 +248,7 @@ pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END { PLpgSQL_stmt_block *new; - new = malloc(sizeof(PLpgSQL_stmt_block)); - memset(new, 0, sizeof(PLpgSQL_stmt_block)); + new = palloc0(sizeof(PLpgSQL_stmt_block)); new->cmd_type = PLPGSQL_STMT_BLOCK; new->lineno = $3; @@ -379,8 +374,7 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval -1), true); - curname_def = malloc(sizeof(PLpgSQL_expr)); - memset(curname_def, 0, sizeof(PLpgSQL_expr)); + curname_def = palloc0(sizeof(PLpgSQL_expr)); curname_def->dtype = PLPGSQL_DTYPE_EXPR; strcpy(buf, "SELECT '"); @@ -393,7 +387,7 @@ decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval *cp2++ = *cp1++; } strcpy(cp2, "'::refcursor"); - curname_def->query = strdup(buf); + curname_def->query = pstrdup(buf); new->default_val = curname_def; new->cursor_explicit_expr = $6; @@ -422,62 +416,40 @@ decl_cursor_args : } | '(' decl_cursor_arglist ')' { - /* Copy the temp arrays to malloc'd storage */ - int nfields = $2->nfields; - char **ftmp; - int *vtmp; + PLpgSQL_row *new; + int i; + ListCell *l; - ftmp = malloc(nfields * sizeof(char *)); - vtmp = malloc(nfields * sizeof(int)); - memcpy(ftmp, $2->fieldnames, nfields * sizeof(char *)); - memcpy(vtmp, $2->varnos, nfields * sizeof(int)); + new = palloc0(sizeof(PLpgSQL_row)); + new->dtype = PLPGSQL_DTYPE_ROW; + new->lineno = plpgsql_scanner_lineno(); + new->rowtupdesc = NULL; + new->nfields = list_length($2); + new->fieldnames = palloc(new->nfields * sizeof(char *)); + new->varnos = palloc(new->nfields * sizeof(int)); - pfree($2->fieldnames); - pfree($2->varnos); + i = 0; + foreach (l, $2) + { + PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l); + new->fieldnames[i] = arg->refname; + new->varnos[i] = arg->dno; + i++; + } + list_free($2); - $2->fieldnames = ftmp; - $2->varnos = vtmp; - - plpgsql_adddatum((PLpgSQL_datum *)$2); - - $$ = $2; + plpgsql_adddatum((PLpgSQL_datum *) new); + $$ = new; } ; -decl_cursor_arglist : decl_cursor_arg +decl_cursor_arglist : decl_cursor_arglist decl_cursor_arg { - PLpgSQL_row *new; - - new = malloc(sizeof(PLpgSQL_row)); - memset(new, 0, sizeof(PLpgSQL_row)); - - new->dtype = PLPGSQL_DTYPE_ROW; - new->refname = strdup("*internal*"); - new->lineno = plpgsql_scanner_lineno(); - new->rowtupdesc = NULL; - /* - * We make temporary fieldnames/varnos arrays that - * are much bigger than necessary. We will resize - * them to just the needed size in the - * decl_cursor_args production. - */ - new->fieldnames = palloc(1024 * sizeof(char *)); - new->varnos = palloc(1024 * sizeof(int)); - new->nfields = 1; - - new->fieldnames[0] = $1->refname; - new->varnos[0] = $1->dno; - - $$ = new; + $$ = lappend($1, $2); } - | decl_cursor_arglist ',' decl_cursor_arg + | decl_cursor_arg { - int i = $1->nfields++; - - $1->fieldnames[i] = $3->refname; - $1->varnos[i] = $3->dno; - - $$ = $1; + $$ = list_make1($1); } ; @@ -524,10 +496,8 @@ decl_varname : T_WORD char *name; plpgsql_convert_ident(yytext, &name, 1); - /* name should be malloc'd for use as varname */ - $$.name = strdup(name); + $$.name = name; $$.lineno = plpgsql_scanner_lineno(); - pfree(name); } ; @@ -580,11 +550,7 @@ decl_defkey : K_ASSIGN proc_sect : { - PLpgSQL_stmts *new; - - new = malloc(sizeof(PLpgSQL_stmts)); - memset(new, 0, sizeof(PLpgSQL_stmts)); - $$ = new; + $$ = NIL; } | proc_stmts { $$ = $1; } @@ -592,31 +558,17 @@ proc_sect : proc_stmts : proc_stmts proc_stmt { - if ($2 != NULL) - { - if ($1->stmts_used == $1->stmts_alloc) - { - $1->stmts_alloc *= 2; - $1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc); - } - $1->stmts[$1->stmts_used++] = $2; - } - $$ = $1; + if ($2 == NULL) + $$ = $1; + else + $$ = lappend($1, $2); } | proc_stmt { - PLpgSQL_stmts *new; - - new = malloc(sizeof(PLpgSQL_stmts)); - memset(new, 0, sizeof(PLpgSQL_stmts)); - - new->stmts_alloc = 32; - new->stmts = malloc(sizeof(PLpgSQL_stmt *) * new->stmts_alloc); - - if ($1 != NULL) - new->stmts[new->stmts_used++] = $1; - - $$ = new; + if ($1 == NULL) + $$ = NULL; + else + $$ = list_make1($1); } ; @@ -664,9 +616,7 @@ stmt_perform : K_PERFORM lno expr_until_semi { PLpgSQL_stmt_perform *new; - new = malloc(sizeof(PLpgSQL_stmt_perform)); - memset(new, 0, sizeof(PLpgSQL_stmt_perform)); - + new = palloc0(sizeof(PLpgSQL_stmt_perform)); new->cmd_type = PLPGSQL_STMT_PERFORM; new->lineno = $2; new->expr = $3; @@ -679,9 +629,7 @@ stmt_assign : assign_var lno K_ASSIGN expr_until_semi { PLpgSQL_stmt_assign *new; - new = malloc(sizeof(PLpgSQL_stmt_assign)); - memset(new, 0, sizeof(PLpgSQL_stmt_assign)); - + new = palloc0(sizeof(PLpgSQL_stmt_assign)); new->cmd_type = PLPGSQL_STMT_ASSIGN; new->lineno = $2; new->varno = $1; @@ -695,45 +643,38 @@ stmt_getdiag : K_GET K_DIAGNOSTICS lno getdiag_list ';' { PLpgSQL_stmt_getdiag *new; - new = malloc(sizeof(PLpgSQL_stmt_getdiag)); - memset(new, 0, sizeof(PLpgSQL_stmt_getdiag)); - + new = palloc0(sizeof(PLpgSQL_stmt_getdiag)); new->cmd_type = PLPGSQL_STMT_GETDIAG; new->lineno = $3; - new->ndtitems = $4.nused; - new->dtitems = malloc(sizeof(PLpgSQL_diag_item) * $4.nused); - memcpy(new->dtitems, $4.dtitems, sizeof(PLpgSQL_diag_item) * $4.nused); + new->diag_items = $4; $$ = (PLpgSQL_stmt *)new; } ; -getdiag_list : getdiag_list ',' getdiag_target K_ASSIGN getdiag_item +getdiag_list : getdiag_list ',' getdiag_list_item { - if ($1.nused == $1.nalloc) - { - $1.nalloc *= 2; - $1.dtitems = repalloc($1.dtitems, sizeof(PLpgSQL_diag_item) * $1.nalloc); - } - $1.dtitems[$1.nused].target = $3; - $1.dtitems[$1.nused].item = $5; - $1.nused++; - - $$.nalloc = $1.nalloc; - $$.nused = $1.nused; - $$.dtitems = $1.dtitems; + $$ = lappend($1, $3); } - | getdiag_target K_ASSIGN getdiag_item + | getdiag_list_item { - $$.nalloc = 1; - $$.nused = 1; - $$.dtitems = palloc(sizeof(PLpgSQL_diag_item) * $$.nalloc); - $$.dtitems[0].target = $1; - $$.dtitems[0].item = $3; + $$ = list_make1($1); } ; -getdiag_item : K_ROW_COUNT +getdiag_list_item : getdiag_target K_ASSIGN getdiag_kind + { + PLpgSQL_diag_item *new; + + new = palloc(sizeof(PLpgSQL_diag_item)); + new->target = $1; + new->kind = $3; + + $$ = new; + } + ; + +getdiag_kind : K_ROW_COUNT { $$ = PLPGSQL_GETDIAG_ROW_COUNT; } @@ -770,9 +711,7 @@ assign_var : T_SCALAR { PLpgSQL_arrayelem *new; - new = malloc(sizeof(PLpgSQL_arrayelem)); - memset(new, 0, sizeof(PLpgSQL_arrayelem)); - + new = palloc0(sizeof(PLpgSQL_arrayelem)); new->dtype = PLPGSQL_DTYPE_ARRAYELEM; new->subscript = $3; new->arrayparentno = $1; @@ -787,9 +726,7 @@ stmt_if : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';' { PLpgSQL_stmt_if *new; - new = malloc(sizeof(PLpgSQL_stmt_if)); - memset(new, 0, sizeof(PLpgSQL_stmt_if)); - + new = palloc0(sizeof(PLpgSQL_stmt_if)); new->cmd_type = PLPGSQL_STMT_IF; new->lineno = $2; new->cond = $3; @@ -802,53 +739,35 @@ stmt_if : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';' stmt_else : { - PLpgSQL_stmts *new; - - new = malloc(sizeof(PLpgSQL_stmts)); - memset(new, 0, sizeof(PLpgSQL_stmts)); - $$ = new; + $$ = NIL; } | K_ELSIF lno expr_until_then proc_sect stmt_else { - /* - * Translate the structure: into: - * - * IF c1 THEN IF c1 THEN - * ... ... - * ELSIF c2 THEN ELSE - * IF c2 THEN - * ... ... - * ELSE ELSE - * ... ... - * END IF END IF - * END IF - * - */ - - PLpgSQL_stmts *new; + /* + * Translate the structure: into: + * + * IF c1 THEN IF c1 THEN + * ... ... + * ELSIF c2 THEN ELSE + * IF c2 THEN + * ... ... + * ELSE ELSE + * ... ... + * END IF END IF + * END IF + */ PLpgSQL_stmt_if *new_if; /* first create a new if-statement */ - new_if = malloc(sizeof(PLpgSQL_stmt_if)); - memset(new_if, 0, sizeof(PLpgSQL_stmt_if)); - + new_if = palloc0(sizeof(PLpgSQL_stmt_if)); new_if->cmd_type = PLPGSQL_STMT_IF; new_if->lineno = $2; new_if->cond = $3; new_if->true_body = $4; new_if->false_body = $5; - - /* this is a 'container' for the if-statement */ - new = malloc(sizeof(PLpgSQL_stmts)); - memset(new, 0, sizeof(PLpgSQL_stmts)); - - new->stmts_alloc = 64; - new->stmts_used = 1; - new->stmts = malloc(sizeof(PLpgSQL_stmt *) * new->stmts_alloc); - new->stmts[0] = (PLpgSQL_stmt *) new_if; - $$ = new; - + /* wrap the if-statement in a "container" list */ + $$ = list_make1(new_if); } | K_ELSE proc_sect @@ -861,9 +780,7 @@ stmt_loop : opt_label K_LOOP lno loop_body { PLpgSQL_stmt_loop *new; - new = malloc(sizeof(PLpgSQL_stmt_loop)); - memset(new, 0, sizeof(PLpgSQL_stmt_loop)); - + new = palloc0(sizeof(PLpgSQL_stmt_loop)); new->cmd_type = PLPGSQL_STMT_LOOP; new->lineno = $3; new->label = $1; @@ -879,9 +796,7 @@ stmt_while : opt_label K_WHILE lno expr_until_loop loop_body { PLpgSQL_stmt_while *new; - new = malloc(sizeof(PLpgSQL_stmt_while)); - memset(new, 0, sizeof(PLpgSQL_stmt_while)); - + new = palloc0(sizeof(PLpgSQL_stmt_while)); new->cmd_type = PLPGSQL_STMT_WHILE; new->lineno = $3; new->label = $1; @@ -931,86 +846,20 @@ stmt_for : opt_label K_FOR for_control loop_body } ; -for_control : lno for_variable K_IN +for_control : + lno for_variable K_IN { - int tok; - bool reverse = false; - bool execute = false; - PLpgSQL_expr *expr1; - - /* check for REVERSE and EXECUTE */ - tok = yylex(); - if (tok == K_REVERSE) - { - reverse = true; - tok = yylex(); - } + int tok = yylex(); + /* Simple case: EXECUTE is a dynamic FOR loop */ if (tok == K_EXECUTE) - execute = true; - else - plpgsql_push_back_token(tok); - - /* Collect one or two expressions */ - expr1 = read_sql_construct(K_DOTDOT, - K_LOOP, - "LOOP", - true, - "SELECT ", - &tok); - - if (tok == K_DOTDOT) { - /* Found .., so it must be an integer loop */ - PLpgSQL_stmt_fori *new; - PLpgSQL_expr *expr2; - PLpgSQL_var *fvar; - - expr2 = plpgsql_read_expression(K_LOOP, "LOOP"); - - if (execute) - { - plpgsql_error_lineno = $1; - yyerror("cannot specify EXECUTE in integer for-loop"); - } - - /* name should be malloc'd for use as varname */ - fvar = (PLpgSQL_var *) - plpgsql_build_variable(strdup($2.name), - $2.lineno, - plpgsql_build_datatype(INT4OID, - -1), - true); - - /* put the for-variable into the local block */ - plpgsql_add_initdatums(NULL); - - new = malloc(sizeof(PLpgSQL_stmt_fori)); - memset(new, 0, sizeof(PLpgSQL_stmt_fori)); - - new->cmd_type = PLPGSQL_STMT_FORI; - new->lineno = $1; - new->var = fvar; - new->reverse = reverse; - new->lower = expr1; - new->upper = expr2; - - $$ = (PLpgSQL_stmt *) new; - } - else if (execute) - { - /* No .., so it must be a loop over rows */ PLpgSQL_stmt_dynfors *new; + PLpgSQL_expr *expr; - if (reverse) - { - plpgsql_error_lineno = $1; - yyerror("cannot specify REVERSE in loop over rows"); - } - - new = malloc(sizeof(PLpgSQL_stmt_dynfors)); - memset(new, 0, sizeof(PLpgSQL_stmt_dynfors)); + expr = plpgsql_read_expression(K_LOOP, "LOOP"); + new = palloc0(sizeof(PLpgSQL_stmt_dynfors)); new->cmd_type = PLPGSQL_STMT_DYNFORS; new->lineno = $1; if ($2.rec) @@ -1022,47 +871,115 @@ for_control : lno for_variable K_IN plpgsql_error_lineno = $1; yyerror("loop variable of loop over rows must be a record or row variable"); } - new->query = expr1; + new->query = expr; $$ = (PLpgSQL_stmt *) new; } else { - /* No .., so it must be a loop over rows */ - PLpgSQL_stmt_fors *new; - char *newquery; + PLpgSQL_expr *expr1; + bool reverse = false; - if (reverse) + /* + * We have to distinguish between two + * alternatives: FOR var IN a .. b and FOR + * var IN query. Unfortunately this is + * tricky, since the query in the second + * form needn't start with a SELECT + * keyword. We use the ugly hack of + * looking for two periods after the first + * token. We also check for the REVERSE + * keyword, which means it must be an + * integer loop. + */ + if (tok == K_REVERSE) + reverse = true; + else + plpgsql_push_back_token(tok); + + /* + * Read tokens until we see either a ".." + * or a LOOP. The text we read may not + * necessarily be a well-formed SQL + * statement, so we need to invoke + * read_sql_construct directly. + */ + expr1 = read_sql_construct(K_DOTDOT, + K_LOOP, + "LOOP", + "SELECT ", + true, + false, + &tok); + + if (tok == K_DOTDOT) { - plpgsql_error_lineno = $1; - yyerror("cannot specify REVERSE in loop over rows"); + /* Saw "..", so it must be an integer loop */ + PLpgSQL_expr *expr2; + PLpgSQL_var *fvar; + PLpgSQL_stmt_fori *new; + + /* First expression is well-formed */ + check_sql_expr(expr1->query); + + expr2 = plpgsql_read_expression(K_LOOP, "LOOP"); + + fvar = (PLpgSQL_var *) + plpgsql_build_variable($2.name, + $2.lineno, + plpgsql_build_datatype(INT4OID, + -1), + true); + + /* put the for-variable into the local block */ + plpgsql_add_initdatums(NULL); + + new = palloc0(sizeof(PLpgSQL_stmt_fori)); + new->cmd_type = PLPGSQL_STMT_FORI; + new->lineno = $1; + new->var = fvar; + new->reverse = reverse; + new->lower = expr1; + new->upper = expr2; + + $$ = (PLpgSQL_stmt *) new; } - - new = malloc(sizeof(PLpgSQL_stmt_fors)); - memset(new, 0, sizeof(PLpgSQL_stmt_fors)); - - new->cmd_type = PLPGSQL_STMT_FORS; - new->lineno = $1; - if ($2.rec) - new->rec = $2.rec; - else if ($2.row) - new->row = $2.row; else { - plpgsql_error_lineno = $1; - yyerror("loop variable of loop over rows must be a record or row variable"); + /* + * No "..", so it must be a query loop. We've prefixed an + * extra SELECT to the query text, so we need to remove that + * before performing syntax checking. + */ + char *tmp_query; + PLpgSQL_stmt_fors *new; + + if (reverse) + yyerror("cannot specify REVERSE in query FOR loop"); + + Assert(strncmp(expr1->query, "SELECT ", 7) == 0); + tmp_query = pstrdup(expr1->query + 7); + pfree(expr1->query); + expr1->query = tmp_query; + + check_sql_expr(expr1->query); + + new = palloc0(sizeof(PLpgSQL_stmt_fors)); + new->cmd_type = PLPGSQL_STMT_FORS; + new->lineno = $1; + if ($2.rec) + new->rec = $2.rec; + else if ($2.row) + new->row = $2.row; + else + { + plpgsql_error_lineno = $1; + yyerror("loop variable of loop over rows must be record or row variable"); + } + + new->query = expr1; + $$ = (PLpgSQL_stmt *) new; } - /* - * Must get rid of the "SELECT " we prepended - * to expr1's text - */ - newquery = strdup(expr1->query + 7); - free(expr1->query); - expr1->query = newquery; - - new->query = expr1; - - $$ = (PLpgSQL_stmt *) new; } } ; @@ -1120,9 +1037,7 @@ stmt_exit : K_EXIT lno opt_exitlabel opt_exitcond { PLpgSQL_stmt_exit *new; - new = malloc(sizeof(PLpgSQL_stmt_exit)); - memset(new, 0, sizeof(PLpgSQL_stmt_exit)); - + new = palloc0(sizeof(PLpgSQL_stmt_exit)); new->cmd_type = PLPGSQL_STMT_EXIT; new->lineno = $2; new->label = $3; @@ -1136,8 +1051,7 @@ stmt_return : K_RETURN lno { PLpgSQL_stmt_return *new; - new = malloc(sizeof(PLpgSQL_stmt_return)); - memset(new, 0, sizeof(PLpgSQL_stmt_return)); + new = palloc0(sizeof(PLpgSQL_stmt_return)); new->expr = NULL; new->retrecno = -1; new->retrowno = -1; @@ -1169,9 +1083,18 @@ stmt_return : K_RETURN lno if (yylex() != ';') yyerror("RETURN must specify a record or row variable in function returning tuple"); } + else if (plpgsql_curr_compile->fn_rettype == VOIDOID) + { + if (yylex() != ';') + yyerror("function returning void cannot specify RETURN expression"); + } else { - /* ordinary expression case */ + /* + * Note that a well-formed expression is + * _required_ here; anything else is a + * compile-time error. + */ new->expr = plpgsql_read_expression(';', ";"); } @@ -1189,9 +1112,7 @@ stmt_return_next: K_RETURN_NEXT lno if (!plpgsql_curr_compile->fn_retset) yyerror("cannot use RETURN NEXT in a non-SETOF function"); - new = malloc(sizeof(PLpgSQL_stmt_return_next)); - memset(new, 0, sizeof(PLpgSQL_stmt_return_next)); - + new = palloc0(sizeof(PLpgSQL_stmt_return_next)); new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; new->lineno = $2; @@ -1220,15 +1141,13 @@ stmt_raise : K_RAISE lno raise_level raise_msg raise_params ';' { PLpgSQL_stmt_raise *new; - new = malloc(sizeof(PLpgSQL_stmt_raise)); + new = palloc(sizeof(PLpgSQL_stmt_raise)); new->cmd_type = PLPGSQL_STMT_RAISE; new->lineno = $2; new->elog_level = $3; new->message = $4; - new->nparams = $5.nused; - new->params = malloc(sizeof(int) * $5.nused); - memcpy(new->params, $5.nums, sizeof(int) * $5.nused); + new->params = $5; $$ = (PLpgSQL_stmt *)new; } @@ -1236,14 +1155,13 @@ stmt_raise : K_RAISE lno raise_level raise_msg raise_params ';' { PLpgSQL_stmt_raise *new; - new = malloc(sizeof(PLpgSQL_stmt_raise)); + new = palloc(sizeof(PLpgSQL_stmt_raise)); new->cmd_type = PLPGSQL_STMT_RAISE; new->lineno = $2; new->elog_level = $3; new->message = $4; - new->nparams = 0; - new->params = NULL; + new->params = NIL; $$ = (PLpgSQL_stmt *)new; } @@ -1283,23 +1201,11 @@ raise_level : K_EXCEPTION raise_params : raise_params raise_param { - if ($1.nused == $1.nalloc) - { - $1.nalloc *= 2; - $1.nums = repalloc($1.nums, sizeof(int) * $1.nalloc); - } - $1.nums[$1.nused++] = $2; - - $$.nalloc = $1.nalloc; - $$.nused = $1.nused; - $$.nums = $1.nums; + $$ = lappend_int($1, $2); } | raise_param { - $$.nalloc = 1; - $$.nused = 1; - $$.nums = palloc(sizeof(int) * $$.nalloc); - $$.nums[0] = $1; + $$ = list_make1_int($1); } ; @@ -1317,7 +1223,7 @@ stmt_execsql : execsql_start lno { PLpgSQL_stmt_execsql *new; - new = malloc(sizeof(PLpgSQL_stmt_execsql)); + new = palloc(sizeof(PLpgSQL_stmt_execsql)); new->cmd_type = PLPGSQL_STMT_EXECSQL; new->lineno = $2; new->sqlstmt = read_sql_stmt($1); @@ -1327,16 +1233,16 @@ stmt_execsql : execsql_start lno ; stmt_dynexecute : K_EXECUTE lno expr_until_semi - { - PLpgSQL_stmt_dynexecute *new; + { + PLpgSQL_stmt_dynexecute *new; - new = malloc(sizeof(PLpgSQL_stmt_dynexecute)); - new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; - new->lineno = $2; - new->query = $3; + new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); + new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; + new->lineno = $2; + new->query = $3; - $$ = (PLpgSQL_stmt *)new; - } + $$ = (PLpgSQL_stmt *)new; + } ; stmt_open : K_OPEN lno cursor_varptr @@ -1344,9 +1250,7 @@ stmt_open : K_OPEN lno cursor_varptr PLpgSQL_stmt_open *new; int tok; - new = malloc(sizeof(PLpgSQL_stmt_open)); - memset(new, 0, sizeof(PLpgSQL_stmt_open)); - + new = palloc0(sizeof(PLpgSQL_stmt_open)); new->cmd_type = PLPGSQL_STMT_OPEN; new->lineno = $2; new->curvar = $3->varno; @@ -1488,7 +1392,7 @@ stmt_close : K_CLOSE lno cursor_variable ';' { PLpgSQL_stmt_close *new; - new = malloc(sizeof(PLpgSQL_stmt_close)); + new = palloc(sizeof(PLpgSQL_stmt_close)); new->cmd_type = PLPGSQL_STMT_CLOSE; new->lineno = $2; new->curvar = $3; @@ -1539,41 +1443,24 @@ cursor_variable : T_SCALAR ; execsql_start : T_WORD - { $$ = strdup(yytext); } + { $$ = pstrdup(yytext); } | T_ERROR - { $$ = strdup(yytext); } + { $$ = pstrdup(yytext); } ; exception_sect : - { $$ = NULL; } + { $$ = NIL; } | K_EXCEPTION proc_exceptions { $$ = $2; } ; proc_exceptions : proc_exceptions proc_exception { - if ($1->exceptions_used == $1->exceptions_alloc) - { - $1->exceptions_alloc *= 2; - $1->exceptions = realloc($1->exceptions, sizeof(PLpgSQL_exception *) * $1->exceptions_alloc); - } - $1->exceptions[$1->exceptions_used++] = $2; - - $$ = $1; + $$ = lappend($1, $2); } | proc_exception { - PLpgSQL_exceptions *new; - - new = malloc(sizeof(PLpgSQL_exceptions)); - memset(new, 0, sizeof(PLpgSQL_exceptions)); - - new->exceptions_alloc = 16; - new->exceptions_used = 1; - new->exceptions = malloc(sizeof(PLpgSQL_exception *) * new->exceptions_alloc); - new->exceptions[0] = $1; - - $$ = new; + $$ = list_make1($1); } ; @@ -1581,9 +1468,7 @@ proc_exception : K_WHEN lno proc_conditions K_THEN proc_sect { PLpgSQL_exception *new; - new = malloc(sizeof(PLpgSQL_exception)); - memset(new, 0, sizeof(PLpgSQL_exception)); - + new = palloc0(sizeof(PLpgSQL_exception)); new->lineno = $2; new->conditions = $3; new->action = $5; @@ -1643,8 +1528,7 @@ opt_exitlabel : char *name; plpgsql_convert_ident(yytext, &name, 1); - $$ = strdup(name); - pfree(name); + $$ = name; } | T_WORD { @@ -1664,8 +1548,7 @@ opt_lblname : T_WORD char *name; plpgsql_convert_ident(yytext, &name, 1); - $$ = strdup(name); - pfree(name); + $$ = name; } ; @@ -1681,13 +1564,13 @@ lno : PLpgSQL_expr * plpgsql_read_expression(int until, const char *expected) { - return read_sql_construct(until, 0, expected, true, "SELECT ", NULL); + return read_sql_construct(until, 0, expected, "SELECT ", true, true, NULL); } static PLpgSQL_expr * read_sql_stmt(const char *sqlstart) { - return read_sql_construct(';', 0, ";", false, sqlstart, NULL); + return read_sql_construct(';', 0, ";", sqlstart, false, true, NULL); } /* @@ -1696,8 +1579,9 @@ read_sql_stmt(const char *sqlstart) * until: token code for expected terminator * until2: token code for alternate terminator (pass 0 if none) * expected: text to use in complaining that terminator was not found - * isexpression: whether to say we're reading an "expression" or a "statement" * sqlstart: text to prefix to the accumulated SQL text + * isexpression: whether to say we're reading an "expression" or a "statement" + * valid_sql: whether to check the syntax of the expression (plus sqlstart) * endtoken: if not NULL, ending token is stored at *endtoken * (this is only interesting if until2 isn't zero) */ @@ -1705,8 +1589,9 @@ static PLpgSQL_expr * read_sql_construct(int until, int until2, const char *expected, - bool isexpression, const char *sqlstart, + bool isexpression, + bool valid_sql, int *endtoken) { int tok; @@ -1762,8 +1647,19 @@ read_sql_construct(int until, errmsg("missing \"%s\" at end of SQL statement", expected))); } + if (plpgsql_SpaceScanned) plpgsql_dstring_append(&ds, " "); + + /* Check for array overflow */ + if (nparams >= 1024) + { + plpgsql_error_lineno = lno; + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many variables specified in SQL statement"))); + } + switch (tok) { case T_SCALAR: @@ -1793,15 +1689,18 @@ read_sql_construct(int until, if (endtoken) *endtoken = tok; - expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); + expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); expr->dtype = PLPGSQL_DTYPE_EXPR; - expr->query = strdup(plpgsql_dstring_get(&ds)); + expr->query = pstrdup(plpgsql_dstring_get(&ds)); expr->plan = NULL; expr->nparams = nparams; while(nparams-- > 0) expr->params[nparams] = params[nparams]; plpgsql_dstring_free(&ds); + if (valid_sql) + check_sql_expr(expr->query); + return expr; } @@ -1870,7 +1769,6 @@ read_datatype(int tok) return result; } - static PLpgSQL_stmt * make_select_stmt(void) { @@ -1881,18 +1779,16 @@ make_select_stmt(void) PLpgSQL_expr *expr; PLpgSQL_row *row = NULL; PLpgSQL_rec *rec = NULL; - int tok = 0; - int have_nexttok = 0; - int have_into = 0; + int tok; + bool have_into = false; plpgsql_dstring_init(&ds); plpgsql_dstring_append(&ds, "SELECT "); - while(1) + while (1) { - if (!have_nexttok) - tok = yylex(); - have_nexttok = 0; + tok = yylex(); + if (tok == ';') break; if (tok == 0) @@ -1916,69 +1812,23 @@ make_select_stmt(void) { case T_ROW: row = yylval.row; - have_into = 1; + have_into = true; break; case T_RECORD: rec = yylval.rec; - have_into = 1; + have_into = true; break; case T_SCALAR: - { - int nfields = 1; - char *fieldnames[1024]; - int varnos[1024]; - - check_assignable(yylval.scalar); - fieldnames[0] = strdup(yytext); - varnos[0] = yylval.scalar->dno; - - while ((tok = yylex()) == ',') - { - tok = yylex(); - switch(tok) - { - case T_SCALAR: - check_assignable(yylval.scalar); - fieldnames[nfields] = strdup(yytext); - varnos[nfields++] = yylval.scalar->dno; - break; - - default: - plpgsql_error_lineno = plpgsql_scanner_lineno(); - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("\"%s\" is not a variable", - yytext))); - } - } - have_nexttok = 1; - - row = malloc(sizeof(PLpgSQL_row)); - row->dtype = PLPGSQL_DTYPE_ROW; - row->refname = strdup("*internal*"); - row->lineno = plpgsql_scanner_lineno(); - row->rowtupdesc = NULL; - row->nfields = nfields; - row->fieldnames = malloc(sizeof(char *) * nfields); - row->varnos = malloc(sizeof(int) * nfields); - while (--nfields >= 0) - { - row->fieldnames[nfields] = fieldnames[nfields]; - row->varnos[nfields] = varnos[nfields]; - } - - plpgsql_adddatum((PLpgSQL_datum *)row); - - have_into = 1; - } - break; + row = read_into_scalar_list(yytext, yylval.scalar); + have_into = true; + break; default: /* Treat the INTO as non-special */ plpgsql_dstring_append(&ds, " INTO "); - have_nexttok = 1; + plpgsql_push_back_token(tok); break; } continue; @@ -1986,6 +1836,16 @@ make_select_stmt(void) if (plpgsql_SpaceScanned) plpgsql_dstring_append(&ds, " "); + + /* Check for array overflow */ + if (nparams >= 1024) + { + plpgsql_error_lineno = plpgsql_scanner_lineno(); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many parameters specified in SQL statement"))); + } + switch (tok) { case T_SCALAR: @@ -2012,9 +1872,9 @@ make_select_stmt(void) } } - expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); + expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); expr->dtype = PLPGSQL_DTYPE_EXPR; - expr->query = strdup(plpgsql_dstring_get(&ds)); + expr->query = pstrdup(plpgsql_dstring_get(&ds)); expr->plan = NULL; expr->nparams = nparams; while(nparams-- > 0) @@ -2025,8 +1885,7 @@ make_select_stmt(void) { PLpgSQL_stmt_select *select; - select = malloc(sizeof(PLpgSQL_stmt_select)); - memset(select, 0, sizeof(PLpgSQL_stmt_select)); + select = palloc0(sizeof(PLpgSQL_stmt_select)); select->cmd_type = PLPGSQL_STMT_SELECT; select->rec = rec; select->row = row; @@ -2038,7 +1897,7 @@ make_select_stmt(void) { PLpgSQL_stmt_execsql *execsql; - execsql = malloc(sizeof(PLpgSQL_stmt_execsql)); + execsql = palloc(sizeof(PLpgSQL_stmt_execsql)); execsql->cmd_type = PLPGSQL_STMT_EXECSQL; execsql->sqlstmt = expr; @@ -2054,7 +1913,6 @@ make_fetch_stmt(void) PLpgSQL_row *row = NULL; PLpgSQL_rec *rec = NULL; PLpgSQL_stmt_fetch *fetch; - int have_nexttok = 0; /* We have already parsed everything through the INTO keyword */ @@ -2070,66 +1928,18 @@ make_fetch_stmt(void) break; case T_SCALAR: - { - int nfields = 1; - char *fieldnames[1024]; - int varnos[1024]; - - check_assignable(yylval.scalar); - fieldnames[0] = strdup(yytext); - varnos[0] = yylval.scalar->dno; - - while ((tok = yylex()) == ',') - { - tok = yylex(); - switch(tok) - { - case T_SCALAR: - check_assignable(yylval.scalar); - fieldnames[nfields] = strdup(yytext); - varnos[nfields++] = yylval.scalar->dno; - break; - - default: - plpgsql_error_lineno = plpgsql_scanner_lineno(); - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("\"%s\" is not a variable", - yytext))); - } - } - have_nexttok = 1; - - row = malloc(sizeof(PLpgSQL_row)); - row->dtype = PLPGSQL_DTYPE_ROW; - row->refname = strdup("*internal*"); - row->lineno = plpgsql_scanner_lineno(); - row->rowtupdesc = NULL; - row->nfields = nfields; - row->fieldnames = malloc(sizeof(char *) * nfields); - row->varnos = malloc(sizeof(int) * nfields); - while (--nfields >= 0) - { - row->fieldnames[nfields] = fieldnames[nfields]; - row->varnos[nfields] = varnos[nfields]; - } - - plpgsql_adddatum((PLpgSQL_datum *)row); - } + row = read_into_scalar_list(yytext, yylval.scalar); break; default: yyerror("syntax error"); } - if (!have_nexttok) - tok = yylex(); - + tok = yylex(); if (tok != ';') yyerror("syntax error"); - fetch = malloc(sizeof(PLpgSQL_stmt_select)); - memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch)); + fetch = palloc0(sizeof(PLpgSQL_stmt_select)); fetch->cmd_type = PLPGSQL_STMT_FETCH; fetch->rec = rec; fetch->row = row; @@ -2174,4 +1984,143 @@ check_assignable(PLpgSQL_datum *datum) } } +/* + * Given the first datum and name in the INTO list, continue to read + * comma-separated scalar variables until we run out. Then construct + * and return a fake "row" variable that represents the list of + * scalars. + */ +static PLpgSQL_row * +read_into_scalar_list(const char *initial_name, + PLpgSQL_datum *initial_datum) +{ + int nfields; + char *fieldnames[1024]; + int varnos[1024]; + PLpgSQL_row *row; + int tok; + + check_assignable(initial_datum); + fieldnames[0] = pstrdup(initial_name); + varnos[0] = initial_datum->dno; + nfields = 1; + + while ((tok = yylex()) == ',') + { + /* Check for array overflow */ + if (nfields >= 1024) + { + plpgsql_error_lineno = plpgsql_scanner_lineno(); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many INTO variables specified"))); + } + + tok = yylex(); + switch(tok) + { + case T_SCALAR: + check_assignable(yylval.scalar); + fieldnames[nfields] = pstrdup(yytext); + varnos[nfields++] = yylval.scalar->dno; + break; + + default: + plpgsql_error_lineno = plpgsql_scanner_lineno(); + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("\"%s\" is not a variable", + yytext))); + } + } + + /* + * We read an extra, non-comma character from yylex(), so push it + * back onto the input stream + */ + plpgsql_push_back_token(tok); + + row = palloc(sizeof(PLpgSQL_row)); + row->dtype = PLPGSQL_DTYPE_ROW; + row->refname = pstrdup("*internal*"); + row->lineno = plpgsql_scanner_lineno(); + row->rowtupdesc = NULL; + row->nfields = nfields; + row->fieldnames = palloc(sizeof(char *) * nfields); + row->varnos = palloc(sizeof(int) * nfields); + while (--nfields >= 0) + { + row->fieldnames[nfields] = fieldnames[nfields]; + row->varnos[nfields] = varnos[nfields]; + } + + plpgsql_adddatum((PLpgSQL_datum *)row); + + return row; +} + +/* + * When the PL/PgSQL parser expects to see a SQL statement, it is very + * liberal in what it accepts; for example, we often assume an + * unrecognized keyword is the beginning of a SQL statement. This + * avoids the need to duplicate parts of the SQL grammar in the + * PL/PgSQL grammar, but it means we can accept wildly malformed + * input. To try and catch some of the more obviously invalid input, + * we run the strings we expect to be SQL statements through the main + * SQL parser. + * + * We only invoke the raw parser (not the analyzer); this doesn't do + * any database access and does not check any semantic rules, it just + * checks for basic syntactic correctness. We do this here, rather + * than after parsing has finished, because a malformed SQL statement + * may cause the PL/PgSQL parser to become confused about statement + * borders. So it is best to bail out as early as we can. + */ +static void +check_sql_expr(const char *stmt) +{ + ErrorContextCallback syntax_errcontext; + ErrorContextCallback *previous_errcontext; + MemoryContext oldCxt; + + if (!plpgsql_check_syntax) + return; + + /* + * Setup error traceback support for ereport(). The previous + * ereport callback is installed by pl_comp.c, but we don't want + * that to be invoked (since it will try to transpose the syntax + * error to be relative to the CREATE FUNCTION), so temporarily + * remove it from the list of callbacks. + */ + Assert(error_context_stack->callback == plpgsql_compile_error_callback); + + previous_errcontext = error_context_stack; + syntax_errcontext.callback = plpgsql_sql_error_callback; + syntax_errcontext.arg = (char *) stmt; + syntax_errcontext.previous = error_context_stack->previous; + error_context_stack = &syntax_errcontext; + + oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); + (void) raw_parser(stmt); + MemoryContextSwitchTo(oldCxt); + + /* Restore former ereport callback */ + error_context_stack = previous_errcontext; +} + +static void +plpgsql_sql_error_callback(void *arg) +{ + char *sql_stmt = (char *) arg; + + Assert(plpgsql_error_funcname); + + errcontext("SQL statement in PL/PgSQL function \"%s\" near line %d", + plpgsql_error_funcname, plpgsql_error_lineno); + internalerrquery(sql_stmt); + internalerrposition(geterrposition()); + errposition(0); +} + #include "pl_scan.c" diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index ff8ec94c85..6ae93b7f20 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.83 2004/11/30 03:50:29 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.84 2005/02/22 07:18:24 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -76,10 +76,14 @@ static int datums_last = 0; int plpgsql_error_lineno; char *plpgsql_error_funcname; -int plpgsql_DumpExecTree = 0; +bool plpgsql_DumpExecTree = false; +bool plpgsql_check_syntax = false; PLpgSQL_function *plpgsql_curr_compile; +/* A context appropriate for short-term allocs during compilation */ +MemoryContext compile_tmp_cxt; + /* ---------- * Hash table for compiled functions * ---------- @@ -118,7 +122,6 @@ static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo, HeapTuple procTup, PLpgSQL_func_hashkey *hashkey, bool forValidator); -static void plpgsql_compile_error_callback(void *arg); static char **fetchArgNames(HeapTuple procTup, int nargs); static PLpgSQL_row *build_row_var(Oid classOid); static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod); @@ -130,24 +133,7 @@ static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key) static void plpgsql_HashTableInsert(PLpgSQL_function *function, PLpgSQL_func_hashkey *func_key); static void plpgsql_HashTableDelete(PLpgSQL_function *function); - -/* - * This routine is a crock, and so is everyplace that calls it. The problem - * is that the compiled form of a plpgsql function is allocated permanently - * (mostly via malloc()) and never released until backend exit. Subsidiary - * data structures such as fmgr info records therefore must live forever - * as well. A better implementation would store all this stuff in a per- - * function memory context that could be reclaimed at need. In the meantime, - * fmgr_info_cxt must be called specifying TopMemoryContext so that whatever - * it might allocate, and whatever the eventual function might allocate using - * fn_mcxt, will live forever too. - */ -static void -perm_fmgr_info(Oid functionId, FmgrInfo *finfo) -{ - fmgr_info_cxt(functionId, finfo, TopMemoryContext); -} - +static void delete_function(PLpgSQL_function *func); /* ---------- * plpgsql_compile Make an execution tree for a PL/pgSQL function. @@ -187,10 +173,6 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator) if (!function) { - /* First time through in this backend? If so, init hashtable */ - if (!plpgsql_HashTable) - plpgsql_HashTableInit(); - /* Compute hashkey using function signature and actual arg types */ compute_function_hashkey(fcinfo, procStruct, &hashkey, forValidator); hashkey_valid = true; @@ -205,12 +187,8 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator) if (!(function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data))) { - /* - * Nope, drop the hashtable entry. XXX someday, free all the - * subsidiary storage as well. - */ - plpgsql_HashTableDelete(function); - + /* Nope, drop the function and associated storage */ + delete_function(function); function = NULL; } } @@ -250,6 +228,19 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator) /* * This is the slow part of plpgsql_compile(). + * + * While compiling a function, the CurrentMemoryContext is the + * per-function memory context of the function we are compiling. That + * means a palloc() will allocate storage with the same lifetime as + * the function itself. + * + * Because palloc()'d storage will not be immediately freed, temporary + * allocations should either be performed in a short-lived memory + * context or explicitly pfree'd. Since not all backend functions are + * careful about pfree'ing their allocations, it is also wise to + * switch into a short-term context before calling into the + * backend. An appropriate context for performing short-term + * allocations is the compile_tmp_cxt. */ static PLpgSQL_function * do_compile(FunctionCallInfo fcinfo, @@ -273,6 +264,7 @@ do_compile(FunctionCallInfo fcinfo, int parse_rc; Oid rettypeid; char **argnames; + MemoryContext func_cxt; /* * Setup the scanner input and error info. We assume that this @@ -293,7 +285,7 @@ do_compile(FunctionCallInfo fcinfo, * Setup error traceback support for ereport() */ plerrcontext.callback = plpgsql_compile_error_callback; - plerrcontext.arg = forValidator ? proc_source : (char *) NULL; + plerrcontext.arg = forValidator ? proc_source : NULL; plerrcontext.previous = error_context_stack; error_context_stack = &plerrcontext; @@ -302,30 +294,46 @@ do_compile(FunctionCallInfo fcinfo, */ plpgsql_ns_init(); plpgsql_ns_push(NULL); - plpgsql_DumpExecTree = 0; + plpgsql_DumpExecTree = false; datums_alloc = 128; plpgsql_nDatums = 0; + /* This is short-lived, so needn't allocate in function's cxt */ plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc); datums_last = 0; /* - * Create the new function node + * Do extra syntax checks when validating the function + * definition. We skip this when actually compiling functions for + * execution, for performance reasons. */ - function = malloc(sizeof(PLpgSQL_function)); - MemSet(function, 0, sizeof(PLpgSQL_function)); + plpgsql_check_syntax = forValidator; + + /* + * Create the new function node. We allocate the function and all + * of its compile-time storage (e.g. parse tree) in its own memory + * context. This allows us to reclaim the function's storage + * cleanly. + */ + func_cxt = AllocSetContextCreate(TopMemoryContext, + "PL/PgSQL function context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); + function = palloc0(sizeof(*function)); plpgsql_curr_compile = function; - function->fn_name = strdup(NameStr(procStruct->proname)); + function->fn_name = pstrdup(NameStr(procStruct->proname)); function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); function->fn_functype = functype; + function->fn_cxt = func_cxt; switch (functype) { case T_FUNCTION: - /* * Check for a polymorphic returntype. If found, use the * actual returntype type from the caller's FuncExpr node, if @@ -398,7 +406,7 @@ do_compile(FunctionCallInfo fcinfo, function->fn_retbyval = typeStruct->typbyval; function->fn_rettyplen = typeStruct->typlen; function->fn_rettypioparam = getTypeIOParam(typeTup); - perm_fmgr_info(typeStruct->typinput, &(function->fn_retinput)); + fmgr_info(typeStruct->typinput, &(function->fn_retinput)); /* * install $0 reference, but only for polymorphic return @@ -407,7 +415,7 @@ do_compile(FunctionCallInfo fcinfo, if (procStruct->prorettype == ANYARRAYOID || procStruct->prorettype == ANYELEMENTOID) { - (void) plpgsql_build_variable(strdup("$0"), 0, + (void) plpgsql_build_variable("$0", 0, build_datatype(typeTup, -1), true); } @@ -415,9 +423,13 @@ do_compile(FunctionCallInfo fcinfo, ReleaseSysCache(typeTup); /* - * Create the variables for the procedure's parameters + * Create the variables for the procedure's + * parameters. Allocations aren't needed permanently, so + * make them in tmp cxt. */ + MemoryContextSwitchTo(compile_tmp_cxt); argnames = fetchArgNames(procTup, procStruct->pronargs); + MemoryContextSwitchTo(func_cxt); for (i = 0; i < procStruct->pronargs; i++) { @@ -449,7 +461,7 @@ do_compile(FunctionCallInfo fcinfo, format_type_be(argtypeid)))); /* Build variable and add to datum list */ - argvariable = plpgsql_build_variable(strdup(buf), 0, + argvariable = plpgsql_build_variable(buf, 0, argdtype, false); if (argvariable->dtype == PLPGSQL_DTYPE_VAR) @@ -471,29 +483,23 @@ do_compile(FunctionCallInfo fcinfo, plpgsql_ns_additem(argitemtype, argvariable->dno, buf); /* If there's a name for the argument, make an alias */ - if (argnames && argnames[i] && argnames[i][0]) + if (argnames) plpgsql_ns_additem(argitemtype, argvariable->dno, argnames[i]); } break; case T_TRIGGER: - - /* - * Trigger procedures return type is unknown yet - */ + /* Trigger procedure's return type is unknown yet */ function->fn_rettype = InvalidOid; function->fn_retbyval = false; function->fn_retistuple = true; function->fn_retset = false; - /* - * Add the record for referencing NEW - */ - rec = malloc(sizeof(PLpgSQL_rec)); - memset(rec, 0, sizeof(PLpgSQL_rec)); + /* Add the record for referencing NEW */ + rec = palloc0(sizeof(PLpgSQL_rec)); rec->dtype = PLPGSQL_DTYPE_REC; - rec->refname = strdup("new"); + rec->refname = pstrdup("new"); rec->tup = NULL; rec->tupdesc = NULL; rec->freetup = false; @@ -501,13 +507,10 @@ do_compile(FunctionCallInfo fcinfo, plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname); function->new_varno = rec->recno; - /* - * Add the record for referencing OLD - */ - rec = malloc(sizeof(PLpgSQL_rec)); - memset(rec, 0, sizeof(PLpgSQL_rec)); + /* Add the record for referencing OLD */ + rec = palloc0(sizeof(PLpgSQL_rec)); rec->dtype = PLPGSQL_DTYPE_REC; - rec->refname = strdup("old"); + rec->refname = pstrdup("old"); rec->tup = NULL; rec->tupdesc = NULL; rec->freetup = false; @@ -515,58 +518,44 @@ do_compile(FunctionCallInfo fcinfo, plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname); function->old_varno = rec->recno; - /* - * Add the variable tg_name - */ - var = plpgsql_build_variable(strdup("tg_name"), 0, + /* Add the variable tg_name */ + var = plpgsql_build_variable("tg_name", 0, plpgsql_build_datatype(NAMEOID, -1), true); function->tg_name_varno = var->dno; - /* - * Add the variable tg_when - */ - var = plpgsql_build_variable(strdup("tg_when"), 0, + /* Add the variable tg_when */ + var = plpgsql_build_variable("tg_when", 0, plpgsql_build_datatype(TEXTOID, -1), true); function->tg_when_varno = var->dno; - /* - * Add the variable tg_level - */ - var = plpgsql_build_variable(strdup("tg_level"), 0, + /* Add the variable tg_level */ + var = plpgsql_build_variable("tg_level", 0, plpgsql_build_datatype(TEXTOID, -1), true); function->tg_level_varno = var->dno; - /* - * Add the variable tg_op - */ - var = plpgsql_build_variable(strdup("tg_op"), 0, + /* Add the variable tg_op */ + var = plpgsql_build_variable("tg_op", 0, plpgsql_build_datatype(TEXTOID, -1), true); function->tg_op_varno = var->dno; - /* - * Add the variable tg_relid - */ - var = plpgsql_build_variable(strdup("tg_relid"), 0, + /* Add the variable tg_relid */ + var = plpgsql_build_variable("tg_relid", 0, plpgsql_build_datatype(OIDOID, -1), true); function->tg_relid_varno = var->dno; - /* - * Add the variable tg_relname - */ - var = plpgsql_build_variable(strdup("tg_relname"), 0, + /* Add the variable tg_relname */ + var = plpgsql_build_variable("tg_relname", 0, plpgsql_build_datatype(NAMEOID, -1), true); function->tg_relname_varno = var->dno; - /* - * Add the variable tg_nargs - */ - var = plpgsql_build_variable(strdup("tg_nargs"), 0, + /* Add the variable tg_nargs */ + var = plpgsql_build_variable("tg_nargs", 0, plpgsql_build_datatype(INT4OID, -1), true); function->tg_nargs_varno = var->dno; @@ -584,7 +573,7 @@ do_compile(FunctionCallInfo fcinfo, /* * Create the magic FOUND variable. */ - var = plpgsql_build_variable(strdup("found"), 0, + var = plpgsql_build_variable("found", 0, plpgsql_build_datatype(BOOLOID, -1), true); function->found_varno = var->dno; @@ -611,7 +600,7 @@ do_compile(FunctionCallInfo fcinfo, for (i = 0; i < function->fn_nargs; i++) function->fn_argvarnos[i] = arg_varnos[i]; function->ndatums = plpgsql_nDatums; - function->datums = malloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); + function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); for (i = 0; i < plpgsql_nDatums; i++) function->datums[i] = plpgsql_Datums[i]; function->action = plpgsql_yylval.program; @@ -632,16 +621,21 @@ do_compile(FunctionCallInfo fcinfo, plpgsql_error_funcname = NULL; plpgsql_error_lineno = 0; + plpgsql_check_syntax = false; + + MemoryContextSwitchTo(compile_tmp_cxt); + compile_tmp_cxt = NULL; return function; } /* - * error context callback to let us supply a call-stack traceback - * - * If we are validating, the function source is passed as argument. + * error context callback to let us supply a call-stack traceback. If + * we are validating, the function source is passed as an + * argument. This function is public only for the sake of an assertion + * in gram.y */ -static void +void plpgsql_compile_error_callback(void *arg) { if (arg) @@ -725,11 +719,10 @@ plpgsql_parse_word(char *word) { if (strcmp(cp[0], "tg_argv") == 0) { - int save_spacescanned = plpgsql_SpaceScanned; + bool save_spacescanned = plpgsql_SpaceScanned; PLpgSQL_trigarg *trigarg; - trigarg = malloc(sizeof(PLpgSQL_trigarg)); - memset(trigarg, 0, sizeof(PLpgSQL_trigarg)); + trigarg = palloc0(sizeof(PLpgSQL_trigarg)); trigarg->dtype = PLPGSQL_DTYPE_TRIGARG; if (plpgsql_yylex() != '[') @@ -851,9 +844,9 @@ plpgsql_parse_dblword(char *word) */ PLpgSQL_recfield *new; - new = malloc(sizeof(PLpgSQL_recfield)); + new = palloc(sizeof(PLpgSQL_recfield)); new->dtype = PLPGSQL_DTYPE_RECFIELD; - new->fieldname = strdup(cp[1]); + new->fieldname = pstrdup(cp[1]); new->recparentno = ns->itemno; plpgsql_adddatum((PLpgSQL_datum *) new); @@ -957,9 +950,9 @@ plpgsql_parse_tripword(char *word) */ PLpgSQL_recfield *new; - new = malloc(sizeof(PLpgSQL_recfield)); + new = palloc(sizeof(PLpgSQL_recfield)); new->dtype = PLPGSQL_DTYPE_RECFIELD; - new->fieldname = strdup(cp[2]); + new->fieldname = pstrdup(cp[2]); new->recparentno = ns->itemno; plpgsql_adddatum((PLpgSQL_datum *) new); @@ -1038,7 +1031,7 @@ plpgsql_parse_wordtype(char *word) pfree(cp[1]); /* - * Do a lookup on the compilers namestack. But ensure it moves up to + * Do a lookup on the compiler's namestack. But ensure it moves up to * the toplevel. */ old_nsstate = plpgsql_ns_setlocal(false); @@ -1112,13 +1105,18 @@ plpgsql_parse_dblwordtype(char *word) PLpgSQL_nsitem *nse; bool old_nsstate; Oid classOid; - HeapTuple classtup; + HeapTuple classtup = NULL; + HeapTuple attrtup = NULL; + HeapTuple typetup = NULL; Form_pg_class classStruct; - HeapTuple attrtup; Form_pg_attribute attrStruct; - HeapTuple typetup; char *cp[3]; int i; + MemoryContext oldCxt; + int result = T_ERROR; + + /* Avoid memory leaks in the long-term function context */ + oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); /* Do case conversion and word separation */ /* We convert %type to .type momentarily to keep converter happy */ @@ -1127,7 +1125,6 @@ plpgsql_parse_dblwordtype(char *word) word[i] = '.'; plpgsql_convert_ident(word, cp, 3); word[i] = '%'; - pfree(cp[2]); /* * Lookup the first word @@ -1135,8 +1132,8 @@ plpgsql_parse_dblwordtype(char *word) nse = plpgsql_ns_lookup(cp[0], NULL); /* - * If this is a label lookup the second word in that labels namestack - * level + * If this is a label lookup the second word in that label's + * namestack level */ if (nse != NULL) { @@ -1146,26 +1143,15 @@ plpgsql_parse_dblwordtype(char *word) nse = plpgsql_ns_lookup(cp[1], cp[0]); plpgsql_ns_setlocal(old_nsstate); - pfree(cp[0]); - pfree(cp[1]); - - if (nse != NULL) + if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR) { - switch (nse->itemtype) - { - case PLPGSQL_NSTYPE_VAR: - plpgsql_yylval.dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; - return T_DTYPE; - - default: - return T_ERROR; - } + plpgsql_yylval.dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype; + result = T_DTYPE; } - return T_ERROR; } - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; + + /* Return T_ERROR if not found, otherwise T_DTYPE */ + goto done; } /* @@ -1173,20 +1159,13 @@ plpgsql_parse_dblwordtype(char *word) */ classOid = RelnameGetRelid(cp[0]); if (!OidIsValid(classOid)) - { - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } + goto done; + classtup = SearchSysCache(RELOID, ObjectIdGetDatum(classOid), 0, 0, 0); if (!HeapTupleIsValid(classtup)) - { - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } + goto done; /* * It must be a relation, sequence, view, or type @@ -1196,26 +1175,16 @@ plpgsql_parse_dblwordtype(char *word) classStruct->relkind != RELKIND_SEQUENCE && classStruct->relkind != RELKIND_VIEW && classStruct->relkind != RELKIND_COMPOSITE_TYPE) - { - ReleaseSysCache(classtup); - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } + goto done; /* * Fetch the named table field and it's type */ attrtup = SearchSysCacheAttName(classOid, cp[1]); if (!HeapTupleIsValid(attrtup)) - { - ReleaseSysCache(classtup); - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } - attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup); + goto done; + attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup); typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(attrStruct->atttypid), 0, 0, 0); @@ -1223,16 +1192,24 @@ plpgsql_parse_dblwordtype(char *word) elog(ERROR, "cache lookup failed for type %u", attrStruct->atttypid); /* - * Found that - build a compiler type struct and return it + * Found that - build a compiler type struct in the caller's cxt + * and return it */ + MemoryContextSwitchTo(oldCxt); plpgsql_yylval.dtype = build_datatype(typetup, attrStruct->atttypmod); + MemoryContextSwitchTo(compile_tmp_cxt); + result = T_DTYPE; - ReleaseSysCache(classtup); - ReleaseSysCache(attrtup); - ReleaseSysCache(typetup); - pfree(cp[0]); - pfree(cp[1]); - return T_DTYPE; +done: + if (HeapTupleIsValid(classtup)) + ReleaseSysCache(classtup); + if (HeapTupleIsValid(attrtup)) + ReleaseSysCache(attrtup); + if (HeapTupleIsValid(typetup)) + ReleaseSysCache(typetup); + + MemoryContextSwitchTo(oldCxt); + return result; } /* ---------- @@ -1245,17 +1222,22 @@ int plpgsql_parse_tripwordtype(char *word) { Oid classOid; - HeapTuple classtup; + HeapTuple classtup = NULL; Form_pg_class classStruct; - HeapTuple attrtup; + HeapTuple attrtup = NULL; Form_pg_attribute attrStruct; - HeapTuple typetup; + HeapTuple typetup = NULL; char *cp[2]; char *colname[1]; int qualified_att_len; int numdots = 0; int i; RangeVar *relvar; + MemoryContext oldCxt; + int result = T_ERROR; + + /* Avoid memory leaks in the long-term function context */ + oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); /* Do case conversion and word separation */ qualified_att_len = strlen(word) - TYPE_JUNK_LEN; @@ -1284,20 +1266,13 @@ plpgsql_parse_tripwordtype(char *word) relvar = makeRangeVarFromNameList(stringToQualifiedNameList(cp[0], "plpgsql_parse_tripwordtype")); classOid = RangeVarGetRelid(relvar, true); if (!OidIsValid(classOid)) - { - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } + goto done; + classtup = SearchSysCache(RELOID, ObjectIdGetDatum(classOid), 0, 0, 0); if (!HeapTupleIsValid(classtup)) - { - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } + goto done; /* * It must be a relation, sequence, view, or type @@ -1307,29 +1282,17 @@ plpgsql_parse_tripwordtype(char *word) classStruct->relkind != RELKIND_SEQUENCE && classStruct->relkind != RELKIND_VIEW && classStruct->relkind != RELKIND_COMPOSITE_TYPE) - { - ReleaseSysCache(classtup); - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } + goto done; /* * Fetch the named table field and it's type */ plpgsql_convert_ident(cp[1], colname, 1); attrtup = SearchSysCacheAttName(classOid, colname[0]); - pfree(colname[0]); - if (!HeapTupleIsValid(attrtup)) - { - ReleaseSysCache(classtup); - pfree(cp[0]); - pfree(cp[1]); - return T_ERROR; - } - attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup); + goto done; + attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup); typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(attrStruct->atttypid), 0, 0, 0); @@ -1337,17 +1300,24 @@ plpgsql_parse_tripwordtype(char *word) elog(ERROR, "cache lookup failed for type %u", attrStruct->atttypid); /* - * Found that - build a compiler type struct and return it + * Found that - build a compiler type struct in the caller's cxt + * and return it */ + MemoryContextSwitchTo(oldCxt); plpgsql_yylval.dtype = build_datatype(typetup, attrStruct->atttypmod); + MemoryContextSwitchTo(compile_tmp_cxt); + result = T_DTYPE; - ReleaseSysCache(classtup); - ReleaseSysCache(attrtup); - ReleaseSysCache(typetup); - pfree(cp[0]); - pfree(cp[1]); +done: + if (HeapTupleIsValid(classtup)) + ReleaseSysCache(classtup); + if (HeapTupleIsValid(classtup)) + ReleaseSysCache(attrtup); + if (HeapTupleIsValid(typetup)) + ReleaseSysCache(typetup); - return T_DTYPE; + MemoryContextSwitchTo(oldCxt); + return result; } /* ---------- @@ -1403,15 +1373,18 @@ plpgsql_parse_dblwordrowtype(char *word) char *cp; int i; RangeVar *relvar; + MemoryContext oldCxt; + + /* Avoid memory leaks in long-term function context */ + oldCxt = MemoryContextSwitchTo(compile_tmp_cxt); /* Do case conversion and word separation */ /* We convert %rowtype to .rowtype momentarily to keep converter happy */ i = strlen(word) - ROWTYPE_JUNK_LEN; Assert(word[i] == '%'); - - cp = (char *) palloc((i + 1) * sizeof(char)); - memset(cp, 0, (i + 1) * sizeof(char)); - memcpy(cp, word, i * sizeof(char)); + word[i] = '\0'; + cp = pstrdup(word); + word[i] = '%'; /* Lookup the relation */ relvar = makeRangeVarFromNameList(stringToQualifiedNameList(cp, "plpgsql_parse_dblwordrowtype")); @@ -1421,26 +1394,25 @@ plpgsql_parse_dblwordrowtype(char *word) (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("relation \"%s\" does not exist", cp))); - /* - * Build and return the row type struct - */ + /* Build and return the row type struct */ plpgsql_yylval.dtype = plpgsql_build_datatype(get_rel_type_id(classOid), -1); - pfree(cp); - + MemoryContextSwitchTo(oldCxt); return T_DTYPE; } /* - * plpgsql_build_variable - build a datum-array entry of a given datatype + * plpgsql_build_variable - build a datum-array entry of a given + * datatype * - * The returned struct may be a PLpgSQL_var, PLpgSQL_row, or PLpgSQL_rec - * depending on the given datatype. The struct is automatically added - * to the current datum array, and optionally to the current namespace. + * The returned struct may be a PLpgSQL_var, PLpgSQL_row, or + * PLpgSQL_rec depending on the given datatype, and is allocated via + * palloc. The struct is automatically added to the current datum + * array, and optionally to the current namespace. */ PLpgSQL_variable * -plpgsql_build_variable(char *refname, int lineno, PLpgSQL_type *dtype, +plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype, bool add2namespace) { PLpgSQL_variable *result; @@ -1452,11 +1424,9 @@ plpgsql_build_variable(char *refname, int lineno, PLpgSQL_type *dtype, /* Ordinary scalar datatype */ PLpgSQL_var *var; - var = malloc(sizeof(PLpgSQL_var)); - memset(var, 0, sizeof(PLpgSQL_var)); - + var = palloc0(sizeof(PLpgSQL_var)); var->dtype = PLPGSQL_DTYPE_VAR; - var->refname = refname; + var->refname = pstrdup(refname); var->lineno = lineno; var->datatype = dtype; /* other fields might be filled by caller */ @@ -1482,7 +1452,7 @@ plpgsql_build_variable(char *refname, int lineno, PLpgSQL_type *dtype, row = build_row_var(dtype->typrelid); row->dtype = PLPGSQL_DTYPE_ROW; - row->refname = refname; + row->refname = pstrdup(refname); row->lineno = lineno; plpgsql_adddatum((PLpgSQL_datum *) row); @@ -1501,11 +1471,9 @@ plpgsql_build_variable(char *refname, int lineno, PLpgSQL_type *dtype, */ PLpgSQL_rec *rec; - rec = malloc(sizeof(PLpgSQL_rec)); - memset(rec, 0, sizeof(PLpgSQL_rec)); - + rec = palloc0(sizeof(PLpgSQL_rec)); rec->dtype = PLPGSQL_DTYPE_REC; - rec->refname = refname; + rec->refname = pstrdup(refname); rec->lineno = lineno; plpgsql_adddatum((PLpgSQL_datum *) rec); @@ -1517,14 +1485,12 @@ plpgsql_build_variable(char *refname, int lineno, PLpgSQL_type *dtype, break; } case PLPGSQL_TTYPE_PSEUDO: - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("variable \"%s\" has pseudo-type %s", - refname, format_type_be(dtype->typoid)))); - result = NULL; /* keep compiler quiet */ - break; - } + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("variable \"%s\" has pseudo-type %s", + refname, format_type_be(dtype->typoid)))); + result = NULL; /* keep compiler quiet */ + break; default: elog(ERROR, "unrecognized ttype: %d", dtype->ttype); result = NULL; /* keep compiler quiet */ @@ -1545,7 +1511,6 @@ build_row_var(Oid classOid) Form_pg_class classStruct; const char *relname; int i; - MemoryContext oldcxt; /* * Open the relation to get info. @@ -1567,23 +1532,12 @@ build_row_var(Oid classOid) * Create a row datum entry and all the required variables that it * will point to. */ - row = malloc(sizeof(PLpgSQL_row)); - memset(row, 0, sizeof(PLpgSQL_row)); - + row = palloc0(sizeof(PLpgSQL_row)); row->dtype = PLPGSQL_DTYPE_ROW; - - /* - * This is a bit ugly --- need a permanent copy of the rel's tupdesc. - * Someday all these mallocs should go away in favor of a per-function - * memory context ... - */ - oldcxt = MemoryContextSwitchTo(TopMemoryContext); row->rowtupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); - MemoryContextSwitchTo(oldcxt); - row->nfields = classStruct->relnatts; - row->fieldnames = malloc(sizeof(char *) * row->nfields); - row->varnos = malloc(sizeof(int) * row->nfields); + row->fieldnames = palloc(sizeof(char *) * row->nfields); + row->varnos = palloc(sizeof(int) * row->nfields); for (i = 0; i < row->nfields; i++) { @@ -1592,19 +1546,16 @@ build_row_var(Oid classOid) /* * Get the attribute and check for dropped column */ - attrStruct = RelationGetDescr(rel)->attrs[i]; + attrStruct = row->rowtupdesc->attrs[i]; if (!attrStruct->attisdropped) { - const char *attname; - char *refname; + char *attname; + char refname[(NAMEDATALEN * 2) + 100]; PLpgSQL_variable *var; attname = NameStr(attrStruct->attname); - refname = malloc(strlen(relname) + strlen(attname) + 2); - strcpy(refname, relname); - strcat(refname, "."); - strcat(refname, attname); + snprintf(refname, sizeof(refname), "%s.%s", relname, attname); /* * Create the internal variable for the field @@ -1621,10 +1572,8 @@ build_row_var(Oid classOid) attrStruct->atttypmod), false); - /* - * Add the variable to the row. - */ - row->fieldnames[i] = strdup(attname); + /* Add the variable to the row */ + row->fieldnames[i] = attname; row->varnos[i] = var->dno; } else @@ -1697,9 +1646,9 @@ build_datatype(HeapTuple typeTup, int32 typmod) errmsg("type \"%s\" is only a shell", NameStr(typeStruct->typname)))); - typ = (PLpgSQL_type *) malloc(sizeof(PLpgSQL_type)); + typ = (PLpgSQL_type *) palloc(sizeof(PLpgSQL_type)); - typ->typname = strdup(NameStr(typeStruct->typname)); + typ->typname = pstrdup(NameStr(typeStruct->typname)); typ->typoid = HeapTupleGetOid(typeTup); switch (typeStruct->typtype) { @@ -1726,7 +1675,7 @@ build_datatype(HeapTuple typeTup, int32 typmod) typ->typbyval = typeStruct->typbyval; typ->typrelid = typeStruct->typrelid; typ->typioparam = getTypeIOParam(typeTup); - perm_fmgr_info(typeStruct->typinput, &(typ->typinput)); + fmgr_info(typeStruct->typinput, &(typ->typinput)); typ->atttypmod = typmod; return typ; @@ -1757,7 +1706,7 @@ plpgsql_parse_err_condition(char *condname) */ if (strcmp(condname, "others") == 0) { - new = malloc(sizeof(PLpgSQL_condition)); + new = palloc(sizeof(PLpgSQL_condition)); new->sqlerrstate = 0; new->condname = condname; new->next = NULL; @@ -1769,7 +1718,7 @@ plpgsql_parse_err_condition(char *condname) { if (strcmp(condname, exception_label_map[i].label) == 0) { - new = malloc(sizeof(PLpgSQL_condition)); + new = palloc(sizeof(PLpgSQL_condition)); new->sqlerrstate = exception_label_map[i].sqlerrstate; new->condname = condname; new->next = prev; @@ -1836,7 +1785,7 @@ plpgsql_add_initdatums(int **varnos) { if (n > 0) { - *varnos = (int *) malloc(sizeof(int) * n); + *varnos = (int *) palloc(sizeof(int) * n); n = 0; for (i = datums_last; i < plpgsql_nDatums; i++) @@ -1930,12 +1879,30 @@ compute_function_hashkey(FunctionCallInfo fcinfo, } } +static void +delete_function(PLpgSQL_function *func) +{ + /* remove function from hash table */ + plpgsql_HashTableDelete(func); + + /* release the function's storage */ + MemoryContextDelete(func->fn_cxt); + + /* + * Caller should be sure not to use passed-in pointer, as it now + * points to pfree'd storage + */ +} + /* exported so we can call it from plpgsql_init() */ void plpgsql_HashTableInit(void) { HASHCTL ctl; + /* don't allow double-initialization */ + Assert(plpgsql_HashTable == NULL); + memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(PLpgSQL_func_hashkey); ctl.entrysize = sizeof(plpgsql_HashEnt); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 096f22b1d9..b9917db2ca 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.129 2005/02/22 04:43:07 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.130 2005/02/22 07:18:24 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -74,13 +74,14 @@ static PLpgSQL_expr *active_simple_exprs = NULL; * Local function forward declarations ************************************************************/ static void plpgsql_exec_error_callback(void *arg); +static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum); static PLpgSQL_var *copy_var(PLpgSQL_var *var); static PLpgSQL_rec *copy_rec(PLpgSQL_rec *rec); static int exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block); static int exec_stmts(PLpgSQL_execstate *estate, - PLpgSQL_stmts *stmts); + List *stmts); static int exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); static int exec_stmt_assign(PLpgSQL_execstate *estate, @@ -212,29 +213,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo) */ estate.err_text = gettext_noop("during initialization of execution state"); for (i = 0; i < func->ndatums; i++) - { - switch (func->datums[i]->dtype) - { - case PLPGSQL_DTYPE_VAR: - estate.datums[i] = (PLpgSQL_datum *) - copy_var((PLpgSQL_var *) (func->datums[i])); - break; - - case PLPGSQL_DTYPE_REC: - estate.datums[i] = (PLpgSQL_datum *) - copy_rec((PLpgSQL_rec *) (func->datums[i])); - break; - - case PLPGSQL_DTYPE_ROW: - case PLPGSQL_DTYPE_RECFIELD: - case PLPGSQL_DTYPE_ARRAYELEM: - estate.datums[i] = func->datums[i]; - break; - - default: - elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype); - } - } + estate.datums[i] = copy_plpgsql_datum(func->datums[i]); /* * Store the actual call argument values into the variables @@ -467,30 +446,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func, */ estate.err_text = gettext_noop("during initialization of execution state"); for (i = 0; i < func->ndatums; i++) - { - switch (func->datums[i]->dtype) - { - case PLPGSQL_DTYPE_VAR: - estate.datums[i] = (PLpgSQL_datum *) - copy_var((PLpgSQL_var *) (func->datums[i])); - break; - - case PLPGSQL_DTYPE_REC: - estate.datums[i] = (PLpgSQL_datum *) - copy_rec((PLpgSQL_rec *) (func->datums[i])); - break; - - case PLPGSQL_DTYPE_ROW: - case PLPGSQL_DTYPE_RECFIELD: - case PLPGSQL_DTYPE_ARRAYELEM: - case PLPGSQL_DTYPE_TRIGARG: - estate.datums[i] = func->datums[i]; - break; - - default: - elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype); - } - } + estate.datums[i] = copy_plpgsql_datum(func->datums[i]); /* * Put the OLD and NEW tuples into record variables @@ -758,6 +714,35 @@ plpgsql_exec_error_callback(void *arg) * Support functions for copying local execution variables * ---------- */ +static PLpgSQL_datum * +copy_plpgsql_datum(PLpgSQL_datum *datum) +{ + PLpgSQL_datum *result = NULL; + + switch (datum->dtype) + { + case PLPGSQL_DTYPE_VAR: + result = (PLpgSQL_datum *) copy_var((PLpgSQL_var *) datum); + break; + + case PLPGSQL_DTYPE_REC: + result = (PLpgSQL_datum *) copy_rec((PLpgSQL_rec *) datum); + break; + + case PLPGSQL_DTYPE_ROW: + case PLPGSQL_DTYPE_RECFIELD: + case PLPGSQL_DTYPE_ARRAYELEM: + case PLPGSQL_DTYPE_TRIGARG: + result = datum; + break; + + default: + elog(ERROR, "unrecognized dtype: %d", datum->dtype); + } + + return result; +} + static PLpgSQL_var * copy_var(PLpgSQL_var *var) { @@ -920,9 +905,8 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) } PG_CATCH(); { - ErrorData *edata; - PLpgSQL_exceptions *exceptions; - int j; + ErrorData *edata; + ListCell *e; /* Save error info */ MemoryContextSwitchTo(oldcontext); @@ -942,10 +926,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) SPI_restore_connection(); /* Look for a matching exception handler */ - exceptions = block->exceptions; - for (j = 0; j < exceptions->exceptions_used; j++) + foreach (e, block->exceptions) { - PLpgSQL_exception *exception = exceptions->exceptions[j]; + PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e); if (exception_matches_conditions(edata, exception->conditions)) { @@ -955,7 +938,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) } /* If no match found, re-throw the error */ - if (j >= exceptions->exceptions_used) + if (e == NULL) ReThrowError(edata); else FreeErrorData(edata); @@ -1005,14 +988,14 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) * ---------- */ static int -exec_stmts(PLpgSQL_execstate *estate, PLpgSQL_stmts *stmts) +exec_stmts(PLpgSQL_execstate *estate, List *stmts) { - int rc; - int i; + ListCell *s; - for (i = 0; i < stmts->stmts_used; i++) + foreach (s, stmts) { - rc = exec_stmt(estate, stmts->stmts[i]); + PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(s); + int rc = exec_stmt(estate, stmt); if (rc != PLPGSQL_RC_OK) return rc; } @@ -1184,23 +1167,23 @@ exec_stmt_perform(PLpgSQL_execstate *estate, PLpgSQL_stmt_perform *stmt) static int exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt) { - int i; - PLpgSQL_datum *var; - bool isnull = false; + ListCell *lc; - for (i = 0; i < stmt->ndtitems; i++) + foreach (lc, stmt->diag_items) { - PLpgSQL_diag_item *dtitem = &stmt->dtitems[i]; + PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); + PLpgSQL_datum *var; + bool isnull = false; - if (dtitem->target <= 0) + if (diag_item->target <= 0) continue; - var = (estate->datums[dtitem->target]); + var = estate->datums[diag_item->target]; if (var == NULL) continue; - switch (dtitem->item) + switch (diag_item->kind) { case PLPGSQL_GETDIAG_ROW_COUNT: @@ -1218,7 +1201,7 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt) default: elog(ERROR, "unrecognized attribute request: %d", - dtitem->item); + diag_item->kind); } } @@ -1242,12 +1225,12 @@ exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt) if (!isnull && value) { - if (stmt->true_body != NULL) + if (stmt->true_body != NIL) return exec_stmts(estate, stmt->true_body); } else { - if (stmt->false_body != NULL) + if (stmt->false_body != NIL) return exec_stmts(estate, stmt->false_body); } @@ -1749,6 +1732,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) if (estate->fn_rettype == VOIDOID) { /* Special hack for function returning VOID */ + Assert(stmt->expr == NULL); estate->retval = (Datum) 0; estate->retisnull = false; estate->rettype = VOIDOID; @@ -1903,38 +1887,39 @@ exec_init_tuple_store(PLpgSQL_execstate *estate) static int exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) { - Oid paramtypeid; - Datum paramvalue; - bool paramisnull; - char *extval; - int pidx = 0; - char c[2] = {0, 0}; char *cp; PLpgSQL_dstring ds; + ListCell *current_param; plpgsql_dstring_init(&ds); + current_param = list_head(stmt->params); for (cp = stmt->message; *cp; cp++) { /* - * Occurrences of a single % are replaced by the next argument's + * Occurrences of a single % are replaced by the next parameter's * external representation. Double %'s are converted to one %. */ - if ((c[0] = *cp) == '%') + if (cp[0] == '%') { - cp++; - if (*cp == '%') + Oid paramtypeid; + Datum paramvalue; + bool paramisnull; + char *extval; + + if (cp[1] == '%') { - plpgsql_dstring_append(&ds, c); + plpgsql_dstring_append_char(&ds, cp[1]); + cp++; continue; } - cp--; - if (pidx >= stmt->nparams) - { - plpgsql_dstring_append(&ds, c); - continue; - } - exec_eval_datum(estate, estate->datums[stmt->params[pidx]], + + if (current_param == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too few parameters specified for RAISE"))); + + exec_eval_datum(estate, estate->datums[lfirst_int(current_param)], InvalidOid, ¶mtypeid, ¶mvalue, ¶misnull); if (paramisnull) @@ -1942,13 +1927,22 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) else extval = convert_value_to_string(paramvalue, paramtypeid); plpgsql_dstring_append(&ds, extval); - pidx++; + current_param = lnext(current_param); continue; } - plpgsql_dstring_append(&ds, c); + plpgsql_dstring_append_char(&ds, cp[0]); } + /* + * If more parameters were specified than were required to process + * the format string, throw an error + */ + if (current_param != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too many parameters specified for RAISE"))); + /* * Throw the error (may or may not come back) */ diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 555d0d652d..91d7e1558e 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.38 2004/10/10 23:37:45 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.39 2005/02/22 07:18:24 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -59,7 +59,7 @@ void plpgsql_dstring_init(PLpgSQL_dstring *ds) { ds->value = palloc(ds->alloc = 512); - ds->used = 0; + ds->used = 1; ds->value[0] = '\0'; } @@ -74,6 +74,20 @@ plpgsql_dstring_free(PLpgSQL_dstring *ds) pfree(ds->value); } +static void +plpgsql_dstring_expand(PLpgSQL_dstring *ds, int needed) +{ + /* Don't allow truncating the string */ + Assert(needed > ds->alloc); + Assert(ds->used <= ds->alloc); + + /* Might have to double more than once, if needed is large */ + do + { + ds->alloc *= 2; + } while (needed > ds->alloc); + ds->value = repalloc(ds->value, ds->alloc); +} /* ---------- * plpgsql_dstring_append Dynamic string extending @@ -83,20 +97,30 @@ void plpgsql_dstring_append(PLpgSQL_dstring *ds, const char *str) { int len = strlen(str); - int needed = ds->used + len + 1; + int needed = ds->used + len; if (needed > ds->alloc) - { - /* might have to double more than once, if len is large */ - do - { - ds->alloc *= 2; - } while (needed > ds->alloc); - ds->value = repalloc(ds->value, ds->alloc); - } + plpgsql_dstring_expand(ds, needed); - strcpy(&(ds->value[ds->used]), str); + memcpy(&(ds->value[ds->used - 1]), str, len); ds->used += len; + ds->value[ds->used - 1] = '\0'; +} + +/* ---------- + * plpgsql_dstring_append_char Append a single character + * to a dynamic string + * ---------- + */ +void +plpgsql_dstring_append_char(PLpgSQL_dstring *ds, char c) +{ + if (ds->used == ds->alloc) + plpgsql_dstring_expand(ds, ds->used + 1); + + ds->value[ds->used - 1] = c; + ds->value[ds->used] = '\0'; + ds->used++; } @@ -187,7 +211,7 @@ plpgsql_ns_pop(void) * ---------- */ void -plpgsql_ns_additem(int itemtype, int itemno, char *name) +plpgsql_ns_additem(int itemtype, int itemno, const char *name) { PLpgSQL_ns *ns = ns_current; PLpgSQL_nsitem *nse; @@ -286,11 +310,8 @@ plpgsql_ns_rename(char *oldname, char *newname) int i; /* - * Lookup in the current namespace only - */ - - /* - * Lookup name in the namestack + * Lookup name in the namestack; do the lookup in the current + * namespace only. */ for (ns = ns_current; ns != NULL; ns = ns->upper) { @@ -584,20 +605,19 @@ dump_stmt(PLpgSQL_stmt *stmt) } static void -dump_stmts(PLpgSQL_stmts *stmts) +dump_stmts(List *stmts) { - int i; + ListCell *s; dump_indent += 2; - for (i = 0; i < stmts->stmts_used; i++) - dump_stmt(stmts->stmts[i]); + foreach (s, stmts) + dump_stmt((PLpgSQL_stmt *) lfirst(s)); dump_indent -= 2; } static void dump_block(PLpgSQL_stmt_block *block) { - int i; char *name; if (block->label == NULL) @@ -612,9 +632,11 @@ dump_block(PLpgSQL_stmt_block *block) if (block->exceptions) { - for (i = 0; i < block->exceptions->exceptions_used; i++) + ListCell *e; + + foreach (e, block->exceptions) { - PLpgSQL_exception *exc = block->exceptions->exceptions[i]; + PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e); PLpgSQL_condition *cond; dump_ind(); @@ -863,12 +885,12 @@ dump_return_next(PLpgSQL_stmt_return_next *stmt) static void dump_raise(PLpgSQL_stmt_raise *stmt) { - int i; + ListCell *l; dump_ind(); printf("RAISE '%s'", stmt->message); - for (i = 0; i < stmt->nparams; i++) - printf(" %d", stmt->params[i]); + foreach (l, stmt->params) + printf(" %d", lfirst_int(l)); printf("\n"); } @@ -907,20 +929,20 @@ dump_dynfors(PLpgSQL_stmt_dynfors *stmt) static void dump_getdiag(PLpgSQL_stmt_getdiag *stmt) { - int i; + ListCell *lc; dump_ind(); printf("GET DIAGNOSTICS "); - for (i = 0; i < stmt->ndtitems; i++) + foreach (lc, stmt->diag_items) { - PLpgSQL_diag_item *dtitem = &stmt->dtitems[i]; + PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); - if (i != 0) + if (lc != list_head(stmt->diag_items)) printf(", "); - printf("{var %d} = ", dtitem->target); + printf("{var %d} = ", diag_item->target); - switch (dtitem->item) + switch (diag_item->kind) { case PLPGSQL_GETDIAG_ROW_COUNT: printf("ROW_COUNT"); diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index d4e892eb71..06e7342916 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.23 2004/08/01 17:32:22 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.24 2005/02/22 07:18:24 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -47,7 +47,7 @@ extern DLLIMPORT bool check_function_bodies; -static int plpgsql_firstcall = 1; +static bool plpgsql_firstcall = true; static void plpgsql_init_all(void); @@ -65,10 +65,8 @@ plpgsql_init(void) return; plpgsql_HashTableInit(); - RegisterXactCallback(plpgsql_xact_cb, NULL); - - plpgsql_firstcall = 0; + plpgsql_firstcall = false; } /* @@ -78,14 +76,12 @@ static void plpgsql_init_all(void) { /* Execute any postmaster-startup safe initialization */ - if (plpgsql_firstcall) - plpgsql_init(); + plpgsql_init(); /* * Any other initialization that must be done each time a new backend * starts -- currently none */ - } /* ---------- diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index b38b08bce4..df38351a23 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.56 2004/09/16 16:58:44 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.57 2005/02/22 07:18:24 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -50,7 +50,7 @@ **********************************************************************/ /* ---------- - * Compilers namestack item types + * Compiler's namestack item types * ---------- */ enum @@ -147,7 +147,7 @@ enum typedef struct { /* Dynamic string control structure */ int alloc; - int used; + int used; /* Including NUL terminator */ char *value; } PLpgSQL_dstring; @@ -298,6 +298,7 @@ typedef struct } PLpgSQL_nsitem; +/* XXX: consider adapting this to use List */ typedef struct PLpgSQL_ns { /* Compiler namestack level */ int items_alloc; @@ -314,14 +315,6 @@ typedef struct } PLpgSQL_stmt; -typedef struct -{ /* List of execution nodes */ - int stmts_alloc; /* XXX this oughta just be a List ... */ - int stmts_used; - PLpgSQL_stmt **stmts; -} PLpgSQL_stmts; - - typedef struct PLpgSQL_condition { /* One EXCEPTION condition name */ int sqlerrstate; /* SQLSTATE code */ @@ -333,26 +326,17 @@ typedef struct { /* One EXCEPTION ... WHEN clause */ int lineno; PLpgSQL_condition *conditions; - PLpgSQL_stmts *action; + List *action; /* List of statements */ } PLpgSQL_exception; -typedef struct -{ /* List of WHEN clauses */ - int exceptions_alloc; /* XXX this oughta just be a List - * ... */ - int exceptions_used; - PLpgSQL_exception **exceptions; -} PLpgSQL_exceptions; - - typedef struct { /* Block of statements */ int cmd_type; int lineno; char *label; - PLpgSQL_stmts *body; - PLpgSQL_exceptions *exceptions; + List *body; /* List of statements */ + List *exceptions; /* List of WHEN clauses */ int n_initvars; int *initvarnos; } PLpgSQL_stmt_block; @@ -375,7 +359,7 @@ typedef struct typedef struct { /* Get Diagnostics item */ - int item; /* id for diagnostic value desired */ + int kind; /* id for diagnostic value desired */ int target; /* where to assign it */ } PLpgSQL_diag_item; @@ -383,8 +367,7 @@ typedef struct { /* Get Diagnostics statement */ int cmd_type; int lineno; - int ndtitems; - PLpgSQL_diag_item *dtitems; + List *diag_items; /* List of PLpgSQL_diag_item */ } PLpgSQL_stmt_getdiag; @@ -393,8 +376,8 @@ typedef struct int cmd_type; int lineno; PLpgSQL_expr *cond; - PLpgSQL_stmts *true_body; - PLpgSQL_stmts *false_body; + List *true_body; /* List of statements */ + List *false_body; /* List of statements */ } PLpgSQL_stmt_if; @@ -403,7 +386,7 @@ typedef struct int cmd_type; int lineno; char *label; - PLpgSQL_stmts *body; + List *body; /* List of statements */ } PLpgSQL_stmt_loop; @@ -413,7 +396,7 @@ typedef struct int lineno; char *label; PLpgSQL_expr *cond; - PLpgSQL_stmts *body; + List *body; /* List of statements */ } PLpgSQL_stmt_while; @@ -426,7 +409,7 @@ typedef struct PLpgSQL_expr *lower; PLpgSQL_expr *upper; int reverse; - PLpgSQL_stmts *body; + List *body; /* List of statements */ } PLpgSQL_stmt_fori; @@ -438,7 +421,7 @@ typedef struct PLpgSQL_rec *rec; PLpgSQL_row *row; PLpgSQL_expr *query; - PLpgSQL_stmts *body; + List *body; /* List of statements */ } PLpgSQL_stmt_fors; @@ -450,7 +433,7 @@ typedef struct PLpgSQL_rec *rec; PLpgSQL_row *row; PLpgSQL_expr *query; - PLpgSQL_stmts *body; + List *body; /* List of statements */ } PLpgSQL_stmt_dynfors; @@ -527,8 +510,7 @@ typedef struct int lineno; int elog_level; char *message; - int nparams; - int *params; + List *params; } PLpgSQL_stmt_raise; @@ -577,6 +559,7 @@ typedef struct PLpgSQL_function CommandId fn_cmin; int fn_functype; PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */ + MemoryContext fn_cxt; Oid fn_rettype; int fn_rettyplen; @@ -649,8 +632,8 @@ typedef struct * Global variable declarations **********************************************************************/ -extern int plpgsql_DumpExecTree; -extern int plpgsql_SpaceScanned; +extern bool plpgsql_DumpExecTree; +extern bool plpgsql_SpaceScanned; extern int plpgsql_nDatums; extern PLpgSQL_datum **plpgsql_Datums; @@ -663,6 +646,8 @@ extern char *plpgsql_base_yytext; #define plpgsql_yytext plpgsql_base_yytext extern PLpgSQL_function *plpgsql_curr_compile; +extern bool plpgsql_check_syntax; +extern MemoryContext compile_tmp_cxt; /********************************************************************** * Function declarations @@ -684,13 +669,14 @@ extern int plpgsql_parse_wordrowtype(char *word); extern int plpgsql_parse_dblwordrowtype(char *word); extern PLpgSQL_type *plpgsql_parse_datatype(const char *string); extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod); -extern PLpgSQL_variable *plpgsql_build_variable(char *refname, int lineno, +extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype, bool add2namespace); extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname); extern void plpgsql_adddatum(PLpgSQL_datum *new); extern int plpgsql_add_initdatums(int **varnos); extern void plpgsql_HashTableInit(void); +extern void plpgsql_compile_error_callback(void *arg); /* ---------- * Functions in pl_handler.c @@ -717,6 +703,7 @@ extern void plpgsql_xact_cb(XactEvent event, void *arg); extern void plpgsql_dstring_init(PLpgSQL_dstring *ds); extern void plpgsql_dstring_free(PLpgSQL_dstring *ds); extern void plpgsql_dstring_append(PLpgSQL_dstring *ds, const char *str); +extern void plpgsql_dstring_append_char(PLpgSQL_dstring *ds, char c); extern char *plpgsql_dstring_get(PLpgSQL_dstring *ds); /* ---------- @@ -727,7 +714,7 @@ extern void plpgsql_ns_init(void); extern bool plpgsql_ns_setlocal(bool flag); extern void plpgsql_ns_push(char *label); extern void plpgsql_ns_pop(void); -extern void plpgsql_ns_additem(int itemtype, int itemno, char *name); +extern void plpgsql_ns_additem(int itemtype, int itemno, const char *name); extern PLpgSQL_nsitem *plpgsql_ns_lookup(char *name, char *nsname); extern void plpgsql_ns_rename(char *oldname, char *newname); diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index 5e92ba7690..b4643d9e61 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.38 2004/12/17 03:51:36 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.39 2005/02/22 07:18:24 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -54,7 +54,7 @@ static char *scanbuf; static const char *scanstr; /* original input string */ static int scanner_functype; -static int scanner_typereported; +static bool scanner_typereported; static int pushback_token; static bool have_pushback_token; static int lookahead_token; @@ -64,7 +64,7 @@ static int cur_line_num; static char *dolqstart; /* current $foo$ quote start string */ static int dolqlen; /* signal to plpgsql_get_string_value */ -int plpgsql_SpaceScanned = 0; +bool plpgsql_SpaceScanned = false; %} %option 8bit @@ -114,7 +114,7 @@ dolqinside [^$]+ * ---------- */ BEGIN(INITIAL); - plpgsql_SpaceScanned = 0; + plpgsql_SpaceScanned = false; /* ---------- * On the first call to a new source report the @@ -123,7 +123,7 @@ dolqinside [^$]+ */ if (!scanner_typereported) { - scanner_typereported = 1; + scanner_typereported = true; return scanner_functype; } @@ -255,7 +255,7 @@ dump { return O_DUMP; } * Ignore whitespaces but remember this happened * ---------- */ -{space}+ { plpgsql_SpaceScanned = 1; } +{space}+ { plpgsql_SpaceScanned = true; } /* ---------- * Eat up comments @@ -266,7 +266,7 @@ dump { return O_DUMP; } \/\* { start_lineno = plpgsql_scanner_lineno(); BEGIN(IN_COMMENT); } -\*\/ { BEGIN(INITIAL); plpgsql_SpaceScanned = 1; } +\*\/ { BEGIN(INITIAL); plpgsql_SpaceScanned = true; } \n ; . ; <> { @@ -502,7 +502,7 @@ plpgsql_scanner_init(const char *str, int functype) scanstr = str; scanner_functype = functype; - scanner_typereported = 0; + scanner_typereported = false; have_pushback_token = false; have_lookahead_token = false; @@ -538,7 +538,7 @@ plpgsql_scanner_finish(void) /* * Called after a T_STRING token is read to get the string literal's value - * as a malloc'd string. (We make this a separate call because in many + * as a palloc'd string. (We make this a separate call because in many * scenarios there's no need to get the decoded value.) * * Note: we expect the literal to be the most recently lexed token. This @@ -557,14 +557,14 @@ plpgsql_get_string_value(void) /* Token is a $foo$...$foo$ string */ len = yyleng - 2 * dolqlen; Assert(len >= 0); - result = (char *) malloc(len + 1); + result = (char *) palloc(len + 1); memcpy(result, yytext + dolqlen, len); result[len] = '\0'; } else { /* Token is a '...' string */ - result = (char *) malloc(yyleng + 1); /* more than enough room */ + result = (char *) palloc(yyleng + 1); /* more than enough room */ len = 0; for (cp = yytext; *cp; cp++) { diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index a407c670a8..7fec95a279 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -2173,3 +2173,72 @@ select refcursor_test2(20000) as "Should be false", f | t (1 row) +-- +-- tests for "raise" processing +-- +create function raise_test1(int) returns int as $$ +begin + raise notice 'This message has too many parameters!', $1; + return $1; +end; +$$ language plpgsql; +select raise_test1(5); +ERROR: too many parameters specified for RAISE +CONTEXT: PL/pgSQL function "raise_test1" line 2 at raise +create function raise_test2(int) returns int as $$ +begin + raise notice 'This message has too few parameters: %, %, %', $1, $1; + return $1; +end; +$$ language plpgsql; +select raise_test2(10); +ERROR: too few parameters specified for RAISE +CONTEXT: PL/pgSQL function "raise_test2" line 2 at raise +-- +-- reject function definitions that contain malformed SQL queries at +-- compile-time, where possible +-- +create function bad_sql1() returns int as $$ +declare a int; +begin + a := 5; + Johnny Yuma; + a := 10; + return a; +end$$ language plpgsql; +ERROR: syntax error at or near "Johnny" at character 1 +QUERY: Johnny Yuma +CONTEXT: SQL statement in PL/PgSQL function "bad_sql1" near line 4 +LINE 1: Johnny Yuma + ^ +create function bad_sql2() returns int as $$ +declare r record; +begin + for r in select I fought the law, the law won LOOP + raise notice 'in loop'; + end loop; + return 5; +end;$$ language plpgsql; +ERROR: syntax error at or near "fought" at character 11 +QUERY: select I fought the law, the law won +CONTEXT: SQL statement in PL/PgSQL function "bad_sql2" near line 3 +LINE 1: select I fought the law, the law won + ^ +-- a RETURN expression is mandatory, except for void-returning +-- functions, where it is not allowed +create function missing_return_expr() returns int as $$ +begin + return ; +end;$$ language plpgsql; +ERROR: syntax error at end of input at character 8 +QUERY: SELECT +CONTEXT: SQL statement in PL/PgSQL function "missing_return_expr" near line 2 +LINE 1: SELECT + ^ +create function void_return_expr() returns void as $$ +begin + return 5; +end;$$ language plpgsql; +ERROR: function returning void cannot specify RETURN expression at or near "5" at character 72 +LINE 3: return 5; + ^ diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 93827d1432..607b7f2886 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -1863,3 +1863,58 @@ $$ language 'plpgsql'; select refcursor_test2(20000) as "Should be false", refcursor_test2(20) as "Should be true"; + +-- +-- tests for "raise" processing +-- +create function raise_test1(int) returns int as $$ +begin + raise notice 'This message has too many parameters!', $1; + return $1; +end; +$$ language plpgsql; + +select raise_test1(5); + +create function raise_test2(int) returns int as $$ +begin + raise notice 'This message has too few parameters: %, %, %', $1, $1; + return $1; +end; +$$ language plpgsql; + +select raise_test2(10); + +-- +-- reject function definitions that contain malformed SQL queries at +-- compile-time, where possible +-- +create function bad_sql1() returns int as $$ +declare a int; +begin + a := 5; + Johnny Yuma; + a := 10; + return a; +end$$ language plpgsql; + +create function bad_sql2() returns int as $$ +declare r record; +begin + for r in select I fought the law, the law won LOOP + raise notice 'in loop'; + end loop; + return 5; +end;$$ language plpgsql; + +-- a RETURN expression is mandatory, except for void-returning +-- functions, where it is not allowed +create function missing_return_expr() returns int as $$ +begin + return ; +end;$$ language plpgsql; + +create function void_return_expr() returns void as $$ +begin + return 5; +end;$$ language plpgsql; \ No newline at end of file