diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index 1c8fa43b219d74816fa7ee2a9fdd2f9dcd443bb8..210d14427740fad735a7a3f110ece2aee08b884c 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 <declhdr> decl_sect @@ -108,7 +102,8 @@ static void check_assignable(PLpgSQL_datum *datum); %type <ival> decl_const decl_notnull %type <expr> decl_defval decl_cursor_query %type <dtype> decl_datatype -%type <row> decl_cursor_args decl_cursor_arglist +%type <row> decl_cursor_args +%type <list> decl_cursor_arglist %type <nsitem> decl_aliasitem %type <str> decl_stmts decl_stmt @@ -126,7 +121,7 @@ static void check_assignable(PLpgSQL_datum *datum); %type <str> opt_exitlabel %type <str> execsql_start -%type <stmts> proc_sect proc_stmts stmt_else loop_body +%type <list> proc_sect proc_stmts stmt_else loop_body %type <stmt> proc_stmt pl_block %type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit %type <stmt> stmt_return stmt_return_next stmt_raise stmt_execsql @@ -134,16 +129,17 @@ static void check_assignable(PLpgSQL_datum *datum); %type <stmt> stmt_dynexecute stmt_getdiag %type <stmt> stmt_open stmt_fetch stmt_close stmt_null -%type <exceptions> exception_sect proc_exceptions +%type <list> exception_sect proc_exceptions %type <exception> proc_exception %type <condition> proc_conditions -%type <intlist> raise_params +%type <list> raise_params %type <ival> raise_level raise_param %type <str> raise_msg -%type <dtlist> getdiag_list -%type <ival> getdiag_item getdiag_target +%type <list> getdiag_list +%type <diagitem> getdiag_list_item +%type <ival> getdiag_kind getdiag_target %type <ival> 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; @@ -421,63 +415,41 @@ decl_cursor_args : $$ = NULL; } | '(' decl_cursor_arglist ')' - { - /* Copy the temp arrays to malloc'd storage */ - int nfields = $2->nfields; - char **ftmp; - int *vtmp; - - ftmp = malloc(nfields * sizeof(char *)); - vtmp = malloc(nfields * sizeof(int)); - memcpy(ftmp, $2->fieldnames, nfields * sizeof(char *)); - memcpy(vtmp, $2->varnos, nfields * sizeof(int)); - - pfree($2->fieldnames); - pfree($2->varnos); - - $2->fieldnames = ftmp; - $2->varnos = vtmp; - - plpgsql_adddatum((PLpgSQL_datum *)$2); - - $$ = $2; - } - ; - -decl_cursor_arglist : decl_cursor_arg { PLpgSQL_row *new; + int i; + ListCell *l; - new = malloc(sizeof(PLpgSQL_row)); - memset(new, 0, sizeof(PLpgSQL_row)); - + new = palloc0(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->nfields = list_length($2); + new->fieldnames = palloc(new->nfields * sizeof(char *)); + new->varnos = palloc(new->nfields * sizeof(int)); - new->fieldnames[0] = $1->refname; - new->varnos[0] = $1->dno; + 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); + plpgsql_adddatum((PLpgSQL_datum *) new); $$ = new; } - | decl_cursor_arglist ',' decl_cursor_arg - { - int i = $1->nfields++; - - $1->fieldnames[i] = $3->refname; - $1->varnos[i] = $3->dno; + ; - $$ = $1; +decl_cursor_arglist : decl_cursor_arglist decl_cursor_arg + { + $$ = lappend($1, $2); + } + | decl_cursor_arg + { + $$ = 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; + + /* + * 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); - if (reverse) + /* + * 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"); - } - /* - * 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; + /* + * 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"); + } - $$ = (PLpgSQL_stmt *) new; + 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 ff8ec94c8515cd6faba6a7bb588e34bcee1274d9..6ae93b7f204920b90a1170e8fa15350a9acd121e 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 096f22b1d944684c30976872a18e53ee28fc2f8e..b9917db2ca22c9b8745e0562dcc3461816ac4c3e 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 == '%') - { - plpgsql_dstring_append(&ds, c); - continue; - } - cp--; - if (pidx >= stmt->nparams) + 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; } - 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 555d0d652d5f797cf942473d95555ae5394f86ae..91d7e1558e3735d53e8c799c14bbffbbd11323e0 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 d4e892eb719e2465ea9957b5e41948d0f48e62ed..06e73429169144028ec607d4126d76c42ff86a0a 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 b38b08bce4b7d7664b082556d735d5524cbf80af..df38351a23eb01c7ef55a305b70141439463eb32 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 5e92ba76905c5ab49d2df9d19f255053f56dceb7..b4643d9e618ca4468f4b1eee3b0f737f6663b165 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); } -<IN_COMMENT>\*\/ { BEGIN(INITIAL); plpgsql_SpaceScanned = 1; } +<IN_COMMENT>\*\/ { BEGIN(INITIAL); plpgsql_SpaceScanned = true; } <IN_COMMENT>\n ; <IN_COMMENT>. ; <IN_COMMENT><<EOF>> { @@ -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 a407c670a8764e012d7b0213ceffee5eb717b327..7fec95a2794660c362e7ee0cf8ca0391f2f80e3f 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 93827d143253e48279b6cb9185c832b1bb0f5bf7..607b7f288601015ec6607ab6b718ec7ce61c3868 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