Hello http://archives.postgresql.org/pgsql-patches/2007-10/msg00161.php
I actualized this patch for current CVS Regards Pavel Stehule
*** ./doc/src/sgml/plpgsql.sgml.orig 2008-03-23 01:24:19.000000000 +0100 --- ./doc/src/sgml/plpgsql.sgml 2008-03-24 20:41:27.000000000 +0100 *************** *** 1005,1011 **** <command>EXECUTE</command> statement is provided: <synopsis> ! EXECUTE <replaceable class="command">command-string</replaceable> <optional> INTO <optional>STRICT</optional> <replaceable>target</replaceable> </optional>; </synopsis> where <replaceable>command-string</replaceable> is an expression --- 1005,1011 ---- <command>EXECUTE</command> statement is provided: <synopsis> ! EXECUTE <replaceable class="command">command-string</replaceable> <optional> INTO <optional>STRICT</optional> <replaceable>target</replaceable> </optional> <optional> USING <replaceable class="parameter">expression</replaceable> <optional>, ...</optional> </optional>; </synopsis> where <replaceable>command-string</replaceable> is an expression *************** *** 1046,1051 **** --- 1046,1066 ---- If the <literal>STRICT</> option is given, an error is reported unless the query produces exactly one row. </para> + + <para> + The <command>EXECUTE</command> statement can take parameters. To refer + to the parameters use $1, $2, $3, etc. Any parameter have to be bind to + any variable or any expression with USING clause. You cannot use bind + arguments to pass the names of schema objects to a dynamic SQL statement. + The use of arguments is perfect protection from SQL injection. + <programlisting> + EXECUTE 'SELECT count(*) FROM ' + || tabname::regclass + || ' WHERE inserted_by = $1 AND inserted <= $2' + INTO c + USING checked_user, checked_date; + </programlisting> + </para> <para> <command>SELECT INTO</command> is not currently supported within *************** *** 1997,2003 **** rows: <synopsis> <optional> <<<replaceable>label</replaceable>>> </optional> ! FOR <replaceable>target</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP <replaceable>statements</replaceable> END LOOP <optional> <replaceable>label</replaceable> </optional>; </synopsis> --- 2012,2018 ---- rows: <synopsis> <optional> <<<replaceable>label</replaceable>>> </optional> ! FOR <replaceable>target</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> <optional> USING <replaceable class="parameter">expression</replaceable> <optional>, ...</optional> </optional> LOOP <replaceable>statements</replaceable> END LOOP <optional> <replaceable>label</replaceable> </optional>; </synopsis> *** ./src/pl/plpgsql/src/gram.y.orig 2008-01-01 20:46:00.000000000 +0100 --- ./src/pl/plpgsql/src/gram.y 2008-03-24 20:41:27.000000000 +0100 *************** *** 21,26 **** --- 21,27 ---- static PLpgSQL_expr *read_sql_construct(int until, int until2, + int until3, const char *expected, const char *sqlstart, bool isexpression, *************** *** 200,205 **** --- 201,207 ---- %token K_THEN %token K_TO %token K_TYPE + %token K_USING %token K_WARNING %token K_WHEN %token K_WHILE *************** *** 892,899 **** { PLpgSQL_stmt_dynfors *new; PLpgSQL_expr *expr; ! expr = plpgsql_read_expression(K_LOOP, "LOOP"); new = palloc0(sizeof(PLpgSQL_stmt_dynfors)); new->cmd_type = PLPGSQL_STMT_DYNFORS; --- 894,907 ---- { PLpgSQL_stmt_dynfors *new; PLpgSQL_expr *expr; + int term; ! expr = read_sql_construct(K_LOOP, ! K_USING, ! 0, ! "LOOP|USING", ! "SELECT ", ! true, true, &term); new = palloc0(sizeof(PLpgSQL_stmt_dynfors)); new->cmd_type = PLPGSQL_STMT_DYNFORS; *************** *** 920,925 **** --- 928,948 ---- yyerror("loop variable of loop over rows must be a record or row variable or list of scalar variables"); } new->query = expr; + + if (term == K_USING) + { + for(;;) + { + int term; + + expr = read_sql_construct(',', K_LOOP, 0, ", or LOOP", + "SELECT ", + true, true, &term); + new->params = lappend(new->params, expr); + if (term == K_LOOP) + break; + } + } $$ = (PLpgSQL_stmt *) new; } *************** *** 954,959 **** --- 977,983 ---- */ expr1 = read_sql_construct(K_DOTDOT, K_LOOP, + 0, "LOOP", "SELECT ", true, *************** *** 975,980 **** --- 999,1005 ---- /* Read and check the second one */ expr2 = read_sql_construct(K_LOOP, K_BY, + 0, "LOOP", "SELECT ", true, *************** *** 1222,1228 **** for (;;) { ! expr = read_sql_construct(',', ';', ", or ;", "SELECT ", true, true, &term); new->params = lappend(new->params, expr); --- 1247,1253 ---- for (;;) { ! expr = read_sql_construct(',', ';', 0, ", or ;", "SELECT ", true, true, &term); new->params = lappend(new->params, expr); *************** *** 1307,1316 **** PLpgSQL_expr *expr; int endtoken; ! expr = read_sql_construct(K_INTO, ';', "INTO|;", "SELECT ", true, true, &endtoken); ! new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; new->lineno = $2; --- 1332,1341 ---- PLpgSQL_expr *expr; int endtoken; ! expr = read_sql_construct(K_INTO, K_USING, ';', "INTO or USING or ;", "SELECT ", true, true, &endtoken); ! new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; new->lineno = $2; *************** *** 1319,1334 **** new->strict = false; new->rec = NULL; new->row = NULL; /* If we found "INTO", collect the argument */ if (endtoken == K_INTO) { new->into = true; read_into_target(&new->rec, &new->row, &new->strict); ! if (yylex() != ';') yyerror("syntax error"); } ! $$ = (PLpgSQL_stmt *)new; } ; --- 1344,1375 ---- new->strict = false; new->rec = NULL; new->row = NULL; + new->params = NULL; /* If we found "INTO", collect the argument */ if (endtoken == K_INTO) { new->into = true; read_into_target(&new->rec, &new->row, &new->strict); ! ! endtoken = yylex(); ! if (endtoken != ';' && endtoken != K_USING) yyerror("syntax error"); } ! ! if (endtoken == K_USING) ! { ! for(;;) ! { ! expr = read_sql_construct(',', ';', 0, ", or ;", ! "SELECT ", ! true, true, &endtoken); ! new->params = lappend(new->params, expr); ! if (endtoken == ';') ! break; ! } ! } ! $$ = (PLpgSQL_stmt *)new; } ; *************** *** 1348,1354 **** if ($3->cursor_explicit_expr == NULL) { /* be nice if we could use opt_scrollable here */ ! tok = yylex(); if (tok == K_NOSCROLL) { new->cursor_options |= CURSOR_OPT_NO_SCROLL; --- 1389,1395 ---- if ($3->cursor_explicit_expr == NULL) { /* be nice if we could use opt_scrollable here */ ! tok = yylex(); if (tok == K_NOSCROLL) { new->cursor_options |= CURSOR_OPT_NO_SCROLL; *************** *** 1733,1745 **** PLpgSQL_expr * plpgsql_read_expression(int until, const char *expected) { ! 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, ";", sqlstart, false, true, NULL); } /* --- 1774,1786 ---- PLpgSQL_expr * plpgsql_read_expression(int until, const char *expected) { ! return read_sql_construct(until, 0, 0, expected, "SELECT ", true, true, NULL); } static PLpgSQL_expr * read_sql_stmt(const char *sqlstart) { ! return read_sql_construct(';', 0, 0, ";", sqlstart, false, true, NULL); } /* *************** *** 1747,1752 **** --- 1788,1794 ---- * * until: token code for expected terminator * until2: token code for alternate terminator (pass 0 if none) + * until3: token code for alternate terminator (pass 0 if none) * expected: text to use in complaining that terminator was not found * sqlstart: text to prefix to the accumulated SQL text * isexpression: whether to say we're reading an "expression" or a "statement" *************** *** 1757,1762 **** --- 1799,1805 ---- static PLpgSQL_expr * read_sql_construct(int until, int until2, + int until3, const char *expected, const char *sqlstart, bool isexpression, *************** *** 1783,1788 **** --- 1826,1833 ---- break; if (tok == until2 && parenlevel == 0) break; + if (tok == until3 && parenlevel == 0) + break; if (tok == '(' || tok == '[') parenlevel++; else if (tok == ')' || tok == ']') *************** *** 2066,2079 **** else if (pg_strcasecmp(yytext, "absolute") == 0) { fetch->direction = FETCH_ABSOLUTE; ! fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } else if (pg_strcasecmp(yytext, "relative") == 0) { fetch->direction = FETCH_RELATIVE; ! fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } --- 2111,2124 ---- else if (pg_strcasecmp(yytext, "absolute") == 0) { fetch->direction = FETCH_ABSOLUTE; ! fetch->expr = read_sql_construct(K_FROM, K_IN, 0, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } else if (pg_strcasecmp(yytext, "relative") == 0) { fetch->direction = FETCH_RELATIVE; ! fetch->expr = read_sql_construct(K_FROM, K_IN, 0, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } *************** *** 2088,2094 **** else if (tok != T_SCALAR) { plpgsql_push_back_token(tok); ! fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } --- 2133,2139 ---- else if (tok != T_SCALAR) { plpgsql_push_back_token(tok); ! fetch->expr = read_sql_construct(K_FROM, K_IN, 0, "FROM or IN", "SELECT ", true, true, NULL); check_FROM = false; } *************** *** 2233,2239 **** new = palloc0(sizeof(PLpgSQL_stmt_return_query)); new->cmd_type = PLPGSQL_STMT_RETURN_QUERY; new->lineno = lineno; ! new->query = read_sql_construct(';', 0, ")", "", false, true, NULL); return (PLpgSQL_stmt *) new; } --- 2278,2284 ---- new = palloc0(sizeof(PLpgSQL_stmt_return_query)); new->cmd_type = PLPGSQL_STMT_RETURN_QUERY; new->lineno = lineno; ! new->query = read_sql_construct(';',0, 0, ")", "", false, true, NULL); return (PLpgSQL_stmt *) new; } *** ./src/pl/plpgsql/src/pl_exec.c.orig 2008-01-01 20:46:00.000000000 +0100 --- ./src/pl/plpgsql/src/pl_exec.c 2008-03-24 20:41:27.000000000 +0100 *************** *** 33,38 **** --- 33,39 ---- #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/typcache.h" + #include "nodes/pg_list.h" static const char *const raise_skip_msg = "RAISE"; *************** *** 65,70 **** --- 66,81 ---- static SimpleEstateStackEntry *simple_estate_stack = NULL; static long int simple_estate_id_counter = 0; + typedef struct PreparedParamsData + { + Datum *values; /* evaluated arguments */ + bool *freevals; /* pfree-able arguments */ + char *nulls; /* char array of null info */ + Oid *types; /* types of arguments */ + int nargs; /* number of arguments */ + } PreparedParamsData; + + /************************************************************ * Local function forward declarations ************************************************************/ *************** *** 177,182 **** --- 188,196 ---- static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void plpgsql_create_econtext(PLpgSQL_execstate *estate); static void free_var(PLpgSQL_var *var); + static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate, + List *params); + static void free_params_data(PreparedParamsData *evp); /* ---------- *************** *** 2676,2686 **** exec_eval_cleanup(estate); ! /* ! * Call SPI_execute() without preparing a saved plan. ! */ ! exec_res = SPI_execute(querystr, estate->readonly_func, 0); switch (exec_res) { case SPI_OK_SELECT: --- 2690,2715 ---- exec_eval_cleanup(estate); ! /* Evaluate and use using clause params */ ! if (stmt->params) ! { ! void *plan; ! ! PreparedParamsData *up = exec_eval_using_params(estate, stmt->params); ! ! plan = SPI_prepare(querystr, up->nargs, up->types); ! if (plan == NULL) ! elog(ERROR, "SPI_prepare failed for \"%s\": %s", ! querystr, SPI_result_code_string(SPI_result)); + exec_res = SPI_execute_plan(plan, up->values, up->nulls, estate->readonly_func, 0); + + SPI_freeplan(plan); + free_params_data(up); + } + else + exec_res = SPI_execute(querystr, estate->readonly_func, 0); + switch (exec_res) { case SPI_OK_SELECT: *************** *** 2856,2870 **** exec_eval_cleanup(estate); ! /* ! * Prepare a plan and open an implicit cursor for the query ! */ ! plan = SPI_prepare(querystr, 0, NULL); ! if (plan == NULL) ! elog(ERROR, "SPI_prepare failed for \"%s\": %s", ! querystr, SPI_result_code_string(SPI_result)); ! portal = SPI_cursor_open(NULL, plan, NULL, NULL, estate->readonly_func); if (portal == NULL) elog(ERROR, "could not open implicit cursor for query \"%s\": %s", querystr, SPI_result_code_string(SPI_result)); --- 2885,2915 ---- exec_eval_cleanup(estate); ! /* Evaluate and use using clause params */ ! if (stmt->params) ! { ! PreparedParamsData *up = exec_eval_using_params(estate, stmt->params); ! ! plan = SPI_prepare(querystr, up->nargs, up->types); ! if (plan == NULL) ! elog(ERROR, "SPI_prepare failed for \"%s\": %s", ! querystr, SPI_result_code_string(SPI_result)); ! ! portal = SPI_cursor_open(NULL, plan, up->values, up->nulls, ! estate->readonly_func); ! free_params_data(up); ! } ! else ! { ! plan = SPI_prepare(querystr, 0, NULL); ! if (plan == NULL) ! elog(ERROR, "SPI_prepare failed for \"%s\": %s", ! querystr, SPI_result_code_string(SPI_result)); ! ! portal = SPI_cursor_open(NULL, plan, NULL, NULL, estate->readonly_func); + } + if (portal == NULL) elog(ERROR, "could not open implicit cursor for query \"%s\": %s", querystr, SPI_result_code_string(SPI_result)); *************** *** 5070,5072 **** --- 5115,5194 ---- var->freeval = false; } } + + /* + * exec_eval_using_params --- evaluate params of using clause + * EXECUTE INTO USING + */ + static PreparedParamsData * + exec_eval_using_params(PLpgSQL_execstate *estate, List *params) + { + PreparedParamsData *up; + ListCell *current_param; + int i; + int nargs; + + + up = (PreparedParamsData *) palloc(sizeof(PreparedParamsData)); + nargs = list_length(params); + + up->types = (Oid *) palloc(nargs * sizeof(Oid)); + up->values = (Datum *) palloc(nargs * sizeof(Datum)); + up->nulls = (char *) palloc(nargs * sizeof(char)); + up->freevals = (bool *) palloc(nargs * sizeof(bool)); + up->nargs = nargs; + + current_param = list_head(params); + for (i = 0; i < nargs; i++) + { + bool isnull; + + up->freevals[i] = false; + up->values[i] = exec_eval_expr(estate, + (PLpgSQL_expr *) lfirst(current_param), + &isnull, + &up->types[i]); + up->nulls[i] = isnull ? 'n' : ' '; + + /* pass-by-ref non null values must be copied into plpgsql context */ + if (!isnull && OidIsValid(up->types[i])) + { + int16 typLen; + bool typByVal; + + get_typlenbyval(up->types[i], &typLen, &typByVal); + + if (!typByVal) + { + up->values[i] = datumCopy(up->values[i], typByVal, typLen); + up->freevals[i] = true; + } + } + + current_param = lnext(current_param); + + exec_eval_cleanup(estate); + } + + return up; + } + + /* + * free_params_data --- pfree all pass-by-reference values used in using clause + */ + static void + free_params_data(PreparedParamsData *up) + { + int i; + + for (i = 0; i < up->nargs; i++) + if (up->freevals[i]) + pfree(DatumGetPointer(up->values[i])); + + pfree(up->types); + pfree(up->values); + pfree(up->nulls); + pfree(up->freevals); + + pfree(up); + } *** ./src/pl/plpgsql/src/pl_funcs.c.orig 2008-01-01 20:46:00.000000000 +0100 --- ./src/pl/plpgsql/src/pl_funcs.c 2008-03-24 20:52:44.000000000 +0100 *************** *** 1007,1025 **** stmt->strict ? " STRICT" : "", stmt->row->rowno, stmt->row->refname); } dump_indent -= 2; } static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt) { dump_ind(); printf("FORS %s EXECUTE ", (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname); dump_expr(stmt->query); printf("\n"); ! dump_stmts(stmt->body); - dump_ind(); printf(" ENDFORS\n"); } --- 1007,1058 ---- stmt->strict ? " STRICT" : "", stmt->row->rowno, stmt->row->refname); } + if (stmt->params != NULL) + { + ListCell *lc; + int i = 1; + + dump_ind(); + printf(" USING\n"); + dump_indent += 2; + foreach(lc, stmt->params) + { + dump_ind(); + printf(" parameter %d: ", i++); + dump_expr((PLpgSQL_expr *) lfirst(lc)); + printf("\n"); + } + dump_indent -= 2; + } dump_indent -= 2; } static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt) { + ListCell *lc; + int i = 1; + dump_ind(); printf("FORS %s EXECUTE ", (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname); dump_expr(stmt->query); printf("\n"); ! if (stmt->params != NULL) ! { ! dump_indent += 2; ! dump_ind(); ! printf(" USING\n"); ! dump_indent += 2; ! foreach(lc, stmt->params) ! { ! dump_ind(); ! printf(" parameter $%d: ", i++); ! dump_expr((PLpgSQL_expr *) lfirst(lc)); ! printf("\n"); ! } ! dump_indent -= 4; ! } dump_stmts(stmt->body); dump_ind(); printf(" ENDFORS\n"); } *** ./src/pl/plpgsql/src/plpgsql.h.orig 2008-03-24 20:40:16.000000000 +0100 --- ./src/pl/plpgsql/src/plpgsql.h 2008-03-24 20:41:27.000000000 +0100 *************** *** 430,435 **** --- 430,436 ---- PLpgSQL_row *row; PLpgSQL_expr *query; List *body; /* List of statements */ + List *params; /* USING params */ } PLpgSQL_stmt_dynfors; *************** *** 534,539 **** --- 535,541 ---- bool strict; /* INTO STRICT flag */ PLpgSQL_rec *rec; /* INTO target, if record */ PLpgSQL_row *row; /* INTO target, if row */ + List *params; /* USING params */ } PLpgSQL_stmt_dynexecute; *** ./src/pl/plpgsql/src/scan.l.orig 2008-01-01 20:46:00.000000000 +0100 --- ./src/pl/plpgsql/src/scan.l 2008-03-24 20:41:27.000000000 +0100 *************** *** 161,166 **** --- 161,167 ---- then { return K_THEN; } to { return K_TO; } type { return K_TYPE; } + using { return K_USING; } warning { return K_WARNING; } when { return K_WHEN; } while { return K_WHILE; } *** ./src/test/regress/expected/plpgsql.out.orig 2008-03-24 20:40:40.000000000 +0100 --- ./src/test/regress/expected/plpgsql.out 2008-03-24 20:41:27.000000000 +0100 *************** *** 3128,3130 **** --- 3128,3177 ---- c9f0f895fb98ab9159f51fd0297e236d | 8 | t (9 rows) + -- test EXECUTE USING + create function exc_using(int) + returns int + as $$ + declare i int; + begin + for i in execute 'select * from ' || 'generate_series' || '(1,$1)' using $1 loop + raise notice '%', i; + end loop; + execute 'select $1+$1*3' into i using $1; + return i; + end + $$ language plpgsql; + select exc_using(5); + NOTICE: 1 + NOTICE: 2 + NOTICE: 3 + NOTICE: 4 + NOTICE: 5 + exc_using + ----------- + 20 + (1 row) + + drop function exc_using(int); + -- same for varlena type + create function exc_using(varchar) + returns setof varchar + as $$ + declare v varchar; + begin + for v in execute 'select roomno from '|| 'WSlot'::regclass || ' where slotname = $1' using $1 loop + return next v; + end loop; + execute 'select upper($1)' into v using $1; + return next v; + return; + end + $$ language plpgsql; + select * from exc_using('WS.001.1b'); + exc_using + ----------- + 001 + WS.001.1B + (2 rows) + + drop function exc_using(varchar); *** ./src/test/regress/sql/plpgsql.sql.orig 2007-07-25 06:19:09.000000000 +0200 --- ./src/test/regress/sql/plpgsql.sql 2008-03-24 20:46:46.000000000 +0100 *************** *** 2580,2583 **** end; $$ language plpgsql; ! select * from ret_query2(8); \ Chybà znak konce Åádku na konci souboru --- 2580,2620 ---- end; $$ language plpgsql; ! select * from ret_query2(8); ! ! -- test EXECUTE USING ! create function exc_using(int) ! returns int ! as $$ ! declare i int; ! begin ! for i in execute 'select * from ' || 'generate_series' || '(1,$1)' using $1 loop ! raise notice '%', i; ! end loop; ! execute 'select $1+$1*3' into i using $1; ! return i; ! end ! $$ language plpgsql; ! ! select exc_using(5); ! ! drop function exc_using(int); ! ! -- same for varlena type ! create function exc_using(varchar) ! returns setof varchar ! as $$ ! declare v varchar; ! begin ! for v in execute 'select roomno from '|| 'WSlot'::regclass || ' where slotname = $1' using $1 loop ! return next v; ! end loop; ! execute 'select upper($1)' into v using $1; ! return next v; ! return; ! end ! $$ language plpgsql; ! ! select * from exc_using('WS.001.1b'); ! ! drop function exc_using(varchar);
- Sent via pgsql-patches mailing list (pgsql-patches@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-patches