Commit 3d1e01ca authored by Tom Lane's avatar Tom Lane

Support INSERT/UPDATE/DELETE RETURNING in plpgsql, with rowcount checking

as per yesterday's proposal.  Also make things a tad more orthogonal by
adding the recent STRICT addition to EXECUTE INTO.
Jonah Harris and Tom Lane
parent 29fa0513
This diff is collapsed.
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.94 2006/08/14 00:46:53 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.95 2006/08/14 21:14:41 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -28,11 +28,13 @@ static PLpgSQL_expr *read_sql_construct(int until, ...@@ -28,11 +28,13 @@ static PLpgSQL_expr *read_sql_construct(int until,
int *endtoken); int *endtoken);
static PLpgSQL_expr *read_sql_stmt(const char *sqlstart); static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_select_stmt(int lineno); static PLpgSQL_stmt *make_execsql_stmt(const char *sqlstart, int lineno);
static PLpgSQL_stmt *make_fetch_stmt(int lineno, int curvar); static PLpgSQL_stmt *make_fetch_stmt(int lineno, int curvar);
static PLpgSQL_stmt *make_return_stmt(int lineno); static PLpgSQL_stmt *make_return_stmt(int lineno);
static PLpgSQL_stmt *make_return_next_stmt(int lineno); static PLpgSQL_stmt *make_return_next_stmt(int lineno);
static void check_assignable(PLpgSQL_datum *datum); static void check_assignable(PLpgSQL_datum *datum);
static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
bool *strict);
static PLpgSQL_row *read_into_scalar_list(const char *initial_name, static PLpgSQL_row *read_into_scalar_list(const char *initial_name,
PLpgSQL_datum *initial_datum); PLpgSQL_datum *initial_datum);
static PLpgSQL_row *make_scalar_list1(const char *initial_name, static PLpgSQL_row *make_scalar_list1(const char *initial_name,
...@@ -120,9 +122,8 @@ static void check_labels(const char *start_label, ...@@ -120,9 +122,8 @@ static void check_labels(const char *start_label,
%type <loop_body> loop_body %type <loop_body> loop_body
%type <stmt> proc_stmt pl_block %type <stmt> proc_stmt pl_block
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit %type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
%type <stmt> stmt_return stmt_raise stmt_execsql %type <stmt> stmt_return stmt_raise stmt_execsql stmt_execsql_insert
%type <stmt> stmt_for stmt_select stmt_perform %type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
%type <stmt> stmt_dynexecute stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_close stmt_null %type <stmt> stmt_open stmt_fetch stmt_close stmt_null
%type <list> proc_exceptions %type <list> proc_exceptions
...@@ -169,6 +170,7 @@ static void check_labels(const char *start_label, ...@@ -169,6 +170,7 @@ static void check_labels(const char *start_label,
%token K_IF %token K_IF
%token K_IN %token K_IN
%token K_INFO %token K_INFO
%token K_INSERT
%token K_INTO %token K_INTO
%token K_IS %token K_IS
%token K_LOG %token K_LOG
...@@ -186,7 +188,6 @@ static void check_labels(const char *start_label, ...@@ -186,7 +188,6 @@ static void check_labels(const char *start_label,
%token K_RESULT_OID %token K_RESULT_OID
%token K_RETURN %token K_RETURN
%token K_REVERSE %token K_REVERSE
%token K_SELECT
%token K_STRICT %token K_STRICT
%token K_THEN %token K_THEN
%token K_TO %token K_TO
...@@ -591,8 +592,6 @@ proc_stmt : pl_block ';' ...@@ -591,8 +592,6 @@ proc_stmt : pl_block ';'
{ $$ = $1; } { $$ = $1; }
| stmt_for | stmt_for
{ $$ = $1; } { $$ = $1; }
| stmt_select
{ $$ = $1; }
| stmt_exit | stmt_exit
{ $$ = $1; } { $$ = $1; }
| stmt_return | stmt_return
...@@ -601,6 +600,8 @@ proc_stmt : pl_block ';' ...@@ -601,6 +600,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; } { $$ = $1; }
| stmt_execsql | stmt_execsql
{ $$ = $1; } { $$ = $1; }
| stmt_execsql_insert
{ $$ = $1; }
| stmt_dynexecute | stmt_dynexecute
{ $$ = $1; } { $$ = $1; }
| stmt_perform | stmt_perform
...@@ -1127,12 +1128,6 @@ for_variable : T_SCALAR ...@@ -1127,12 +1128,6 @@ for_variable : T_SCALAR
} }
; ;
stmt_select : K_SELECT lno
{
$$ = make_select_stmt($2);
}
;
stmt_exit : exit_type lno opt_label opt_exitcond stmt_exit : exit_type lno opt_label opt_exitcond
{ {
PLpgSQL_stmt_exit *new; PLpgSQL_stmt_exit *new;
...@@ -1259,14 +1254,28 @@ loop_body : proc_sect K_END K_LOOP opt_label ';' ...@@ -1259,14 +1254,28 @@ loop_body : proc_sect K_END K_LOOP opt_label ';'
stmt_execsql : execsql_start lno stmt_execsql : execsql_start lno
{ {
PLpgSQL_stmt_execsql *new; $$ = make_execsql_stmt($1, $2);
}
;
new = palloc(sizeof(PLpgSQL_stmt_execsql)); /* this matches any otherwise-unrecognized starting keyword */
new->cmd_type = PLPGSQL_STMT_EXECSQL; execsql_start : T_WORD
new->lineno = $2; { $$ = pstrdup(yytext); }
new->sqlstmt = read_sql_stmt($1); | T_ERROR
{ $$ = pstrdup(yytext); }
;
$$ = (PLpgSQL_stmt *)new; stmt_execsql_insert : K_INSERT lno K_INTO
{
/*
* We have to special-case INSERT so that its INTO
* won't be treated as an INTO-variables clause.
*
* Fortunately, this is the only valid use of INTO
* in a pl/pgsql SQL command, and INTO is already
* a fully reserved word in the main grammar.
*/
$$ = make_execsql_stmt("INSERT INTO", $2);
} }
; ;
...@@ -1276,46 +1285,24 @@ stmt_dynexecute : K_EXECUTE lno ...@@ -1276,46 +1285,24 @@ stmt_dynexecute : K_EXECUTE lno
PLpgSQL_expr *expr; PLpgSQL_expr *expr;
int endtoken; int endtoken;
expr = read_sql_construct(K_INTO, ';', "INTO|;", "SELECT ", expr = read_sql_construct(K_INTO, ';', "INTO|;",
"SELECT ",
true, true, &endtoken); true, true, &endtoken);
new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
new->lineno = $2; new->lineno = $2;
new->query = expr; new->query = expr;
new->into = false;
new->strict = false;
new->rec = NULL; new->rec = NULL;
new->row = NULL; new->row = NULL;
/* /* If we found "INTO", collect the argument */
* If we saw "INTO", look for a following row
* var, record var, or list of scalars.
*/
if (endtoken == K_INTO) if (endtoken == K_INTO)
{ {
switch (yylex()) new->into = true;
{ read_into_target(&new->rec, &new->row, &new->strict);
case T_ROW:
new->row = yylval.row;
check_assignable((PLpgSQL_datum *) new->row);
break;
case T_RECORD:
new->rec = yylval.rec;
check_assignable((PLpgSQL_datum *) new->rec);
break;
case T_SCALAR:
new->row = read_into_scalar_list(yytext, yylval.scalar);
break;
default:
plpgsql_error_lineno = $2;
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error at \"%s\"", yytext),
errdetail("Expected record variable, row variable, "
"or list of scalar variables.")));
}
if (yylex() != ';') if (yylex() != ';')
yyerror("syntax error"); yyerror("syntax error");
} }
...@@ -1502,12 +1489,6 @@ cursor_variable : T_SCALAR ...@@ -1502,12 +1489,6 @@ cursor_variable : T_SCALAR
} }
; ;
execsql_start : T_WORD
{ $$ = pstrdup(yytext); }
| T_ERROR
{ $$ = pstrdup(yytext); }
;
exception_sect : exception_sect :
{ $$ = NULL; } { $$ = NULL; }
| K_EXCEPTION lno | K_EXCEPTION lno
...@@ -1892,12 +1873,13 @@ read_datatype(int tok) ...@@ -1892,12 +1873,13 @@ read_datatype(int tok)
} }
static PLpgSQL_stmt * static PLpgSQL_stmt *
make_select_stmt(int lineno) make_execsql_stmt(const char *sqlstart, int lineno)
{ {
PLpgSQL_dstring ds; PLpgSQL_dstring ds;
int nparams = 0; int nparams = 0;
int params[MAX_EXPR_PARAMS]; int params[MAX_EXPR_PARAMS];
char buf[32]; char buf[32];
PLpgSQL_stmt_execsql *execsql;
PLpgSQL_expr *expr; PLpgSQL_expr *expr;
PLpgSQL_row *row = NULL; PLpgSQL_row *row = NULL;
PLpgSQL_rec *rec = NULL; PLpgSQL_rec *rec = NULL;
...@@ -1906,12 +1888,11 @@ make_select_stmt(int lineno) ...@@ -1906,12 +1888,11 @@ make_select_stmt(int lineno)
bool have_strict = false; bool have_strict = false;
plpgsql_dstring_init(&ds); plpgsql_dstring_init(&ds);
plpgsql_dstring_append(&ds, "SELECT "); plpgsql_dstring_append(&ds, sqlstart);
while (1) for (;;)
{ {
tok = yylex(); tok = yylex();
if (tok == ';') if (tok == ';')
break; break;
if (tok == 0) if (tok == 0)
...@@ -1930,37 +1911,8 @@ make_select_stmt(int lineno) ...@@ -1930,37 +1911,8 @@ make_select_stmt(int lineno)
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INTO specified more than once"))); errmsg("INTO specified more than once")));
} }
tok = yylex();
if (tok == K_STRICT)
{
have_strict = true;
tok = yylex();
}
switch (tok)
{
case T_ROW:
row = yylval.row;
check_assignable((PLpgSQL_datum *) row);
have_into = true; have_into = true;
break; read_into_target(&rec, &row, &have_strict);
case T_RECORD:
rec = yylval.rec;
check_assignable((PLpgSQL_datum *) rec);
have_into = true;
break;
case T_SCALAR:
row = read_into_scalar_list(yytext, yylval.scalar);
have_into = true;
break;
default:
/* Treat the INTO as non-special */
plpgsql_dstring_append(&ds, " INTO ");
plpgsql_push_back_token(tok);
break;
}
continue; continue;
} }
...@@ -2007,31 +1959,16 @@ make_select_stmt(int lineno) ...@@ -2007,31 +1959,16 @@ make_select_stmt(int lineno)
check_sql_expr(expr->query); check_sql_expr(expr->query);
if (have_into)
{
PLpgSQL_stmt_select *select;
select = palloc0(sizeof(PLpgSQL_stmt_select));
select->cmd_type = PLPGSQL_STMT_SELECT;
select->lineno = lineno;
select->rec = rec;
select->row = row;
select->query = expr;
select->strict = have_strict;
return (PLpgSQL_stmt *)select;
}
else
{
PLpgSQL_stmt_execsql *execsql;
execsql = palloc(sizeof(PLpgSQL_stmt_execsql)); execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL; execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
execsql->lineno = lineno; execsql->lineno = lineno;
execsql->sqlstmt = expr; execsql->sqlstmt = expr;
execsql->into = have_into;
execsql->strict = have_strict;
execsql->rec = rec;
execsql->row = row;
return (PLpgSQL_stmt *)execsql; return (PLpgSQL_stmt *) execsql;
}
} }
...@@ -2039,38 +1976,12 @@ static PLpgSQL_stmt * ...@@ -2039,38 +1976,12 @@ static PLpgSQL_stmt *
make_fetch_stmt(int lineno, int curvar) make_fetch_stmt(int lineno, int curvar)
{ {
int tok; int tok;
PLpgSQL_row *row = NULL; PLpgSQL_rec *rec;
PLpgSQL_rec *rec = NULL; PLpgSQL_row *row;
PLpgSQL_stmt_fetch *fetch; PLpgSQL_stmt_fetch *fetch;
/* We have already parsed everything through the INTO keyword */ /* We have already parsed everything through the INTO keyword */
read_into_target(&rec, &row, NULL);
tok = yylex();
switch (tok)
{
case T_ROW:
row = yylval.row;
check_assignable((PLpgSQL_datum *) row);
break;
case T_RECORD:
rec = yylval.rec;
check_assignable((PLpgSQL_datum *) rec);
break;
case T_SCALAR:
row = read_into_scalar_list(yytext, yylval.scalar);
break;
default:
plpgsql_error_lineno = plpgsql_scanner_lineno();
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error at \"%s\"", yytext),
errdetail("Expected record variable, row variable, "
"or list of scalar variables.")));
}
tok = yylex(); tok = yylex();
if (tok != ';') if (tok != ';')
yyerror("syntax error"); yyerror("syntax error");
...@@ -2232,6 +2143,54 @@ check_assignable(PLpgSQL_datum *datum) ...@@ -2232,6 +2143,54 @@ check_assignable(PLpgSQL_datum *datum)
} }
} }
/*
* Read the argument of an INTO clause. On entry, we have just read the
* INTO keyword.
*/
static void
read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict)
{
int tok;
/* Set default results */
*rec = NULL;
*row = NULL;
if (strict)
*strict = false;
tok = yylex();
if (strict && tok == K_STRICT)
{
*strict = true;
tok = yylex();
}
switch (tok)
{
case T_ROW:
*row = yylval.row;
check_assignable((PLpgSQL_datum *) *row);
break;
case T_RECORD:
*rec = yylval.rec;
check_assignable((PLpgSQL_datum *) *rec);
break;
case T_SCALAR:
*row = read_into_scalar_list(yytext, yylval.scalar);
break;
default:
plpgsql_error_lineno = plpgsql_scanner_lineno();
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("syntax error at \"%s\"", yytext),
errdetail("Expected record variable, row variable, "
"or list of scalar variables following INTO.")));
}
}
/* /*
* Given the first datum and name in the INTO list, continue to read * Given the first datum and name in the INTO list, continue to read
* comma-separated scalar variables until we run out. Then construct * comma-separated scalar variables until we run out. Then construct
......
This diff is collapsed.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.53 2006/06/12 16:45:30 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.54 2006/08/14 21:14:41 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -439,8 +439,6 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt) ...@@ -439,8 +439,6 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
return "for with integer loopvar"; return "for with integer loopvar";
case PLPGSQL_STMT_FORS: case PLPGSQL_STMT_FORS:
return "for over select rows"; return "for over select rows";
case PLPGSQL_STMT_SELECT:
return "select into variables";
case PLPGSQL_STMT_EXIT: case PLPGSQL_STMT_EXIT:
return "exit"; return "exit";
case PLPGSQL_STMT_RETURN: case PLPGSQL_STMT_RETURN:
...@@ -485,7 +483,6 @@ static void dump_loop(PLpgSQL_stmt_loop *stmt); ...@@ -485,7 +483,6 @@ static void dump_loop(PLpgSQL_stmt_loop *stmt);
static void dump_while(PLpgSQL_stmt_while *stmt); static void dump_while(PLpgSQL_stmt_while *stmt);
static void dump_fori(PLpgSQL_stmt_fori *stmt); static void dump_fori(PLpgSQL_stmt_fori *stmt);
static void dump_fors(PLpgSQL_stmt_fors *stmt); static void dump_fors(PLpgSQL_stmt_fors *stmt);
static void dump_select(PLpgSQL_stmt_select *stmt);
static void dump_exit(PLpgSQL_stmt_exit *stmt); static void dump_exit(PLpgSQL_stmt_exit *stmt);
static void dump_return(PLpgSQL_stmt_return *stmt); static void dump_return(PLpgSQL_stmt_return *stmt);
static void dump_return_next(PLpgSQL_stmt_return_next *stmt); static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
...@@ -537,9 +534,6 @@ dump_stmt(PLpgSQL_stmt *stmt) ...@@ -537,9 +534,6 @@ dump_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_FORS: case PLPGSQL_STMT_FORS:
dump_fors((PLpgSQL_stmt_fors *) stmt); dump_fors((PLpgSQL_stmt_fors *) stmt);
break; break;
case PLPGSQL_STMT_SELECT:
dump_select((PLpgSQL_stmt_select *) stmt);
break;
case PLPGSQL_STMT_EXIT: case PLPGSQL_STMT_EXIT:
dump_exit((PLpgSQL_stmt_exit *) stmt); dump_exit((PLpgSQL_stmt_exit *) stmt);
break; break;
...@@ -731,29 +725,6 @@ dump_fors(PLpgSQL_stmt_fors *stmt) ...@@ -731,29 +725,6 @@ dump_fors(PLpgSQL_stmt_fors *stmt)
printf(" ENDFORS\n"); printf(" ENDFORS\n");
} }
static void
dump_select(PLpgSQL_stmt_select *stmt)
{
dump_ind();
printf("SELECT ");
dump_expr(stmt->query);
printf("\n");
dump_indent += 2;
if (stmt->rec != NULL)
{
dump_ind();
printf(" target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
}
if (stmt->row != NULL)
{
dump_ind();
printf(" target = %d %s\n", stmt->row->rowno, stmt->row->refname);
}
dump_indent -= 2;
}
static void static void
dump_open(PLpgSQL_stmt_open *stmt) dump_open(PLpgSQL_stmt_open *stmt)
{ {
...@@ -891,6 +862,23 @@ dump_execsql(PLpgSQL_stmt_execsql *stmt) ...@@ -891,6 +862,23 @@ dump_execsql(PLpgSQL_stmt_execsql *stmt)
printf("EXECSQL "); printf("EXECSQL ");
dump_expr(stmt->sqlstmt); dump_expr(stmt->sqlstmt);
printf("\n"); printf("\n");
dump_indent += 2;
if (stmt->rec != NULL)
{
dump_ind();
printf(" INTO%s target = %d %s\n",
stmt->strict ? " STRICT" : "",
stmt->rec->recno, stmt->rec->refname);
}
if (stmt->row != NULL)
{
dump_ind();
printf(" INTO%s target = %d %s\n",
stmt->strict ? " STRICT" : "",
stmt->row->rowno, stmt->row->refname);
}
dump_indent -= 2;
} }
static void static void
...@@ -905,12 +893,16 @@ dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt) ...@@ -905,12 +893,16 @@ dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt)
if (stmt->rec != NULL) if (stmt->rec != NULL)
{ {
dump_ind(); dump_ind();
printf(" target = %d %s\n", stmt->rec->recno, stmt->rec->refname); printf(" INTO%s target = %d %s\n",
stmt->strict ? " STRICT" : "",
stmt->rec->recno, stmt->rec->refname);
} }
else if (stmt->row != NULL) if (stmt->row != NULL)
{ {
dump_ind(); dump_ind();
printf(" target = %d %s\n", stmt->row->rowno, stmt->row->refname); printf(" INTO%s target = %d %s\n",
stmt->strict ? " STRICT" : "",
stmt->row->rowno, stmt->row->refname);
} }
dump_indent -= 2; dump_indent -= 2;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.78 2006/08/08 19:15:09 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.79 2006/08/14 21:14:41 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -80,7 +80,6 @@ enum ...@@ -80,7 +80,6 @@ enum
PLPGSQL_STMT_WHILE, PLPGSQL_STMT_WHILE,
PLPGSQL_STMT_FORI, PLPGSQL_STMT_FORI,
PLPGSQL_STMT_FORS, PLPGSQL_STMT_FORS,
PLPGSQL_STMT_SELECT,
PLPGSQL_STMT_EXIT, PLPGSQL_STMT_EXIT,
PLPGSQL_STMT_RETURN, PLPGSQL_STMT_RETURN,
PLPGSQL_STMT_RETURN_NEXT, PLPGSQL_STMT_RETURN_NEXT,
...@@ -428,17 +427,6 @@ typedef struct ...@@ -428,17 +427,6 @@ typedef struct
} PLpgSQL_stmt_dynfors; } PLpgSQL_stmt_dynfors;
typedef struct
{ /* SELECT ... INTO statement */
int cmd_type;
int lineno;
bool strict;
PLpgSQL_rec *rec;
PLpgSQL_row *row;
PLpgSQL_expr *query;
} PLpgSQL_stmt_select;
typedef struct typedef struct
{ /* OPEN a curvar */ { /* OPEN a curvar */
int cmd_type; int cmd_type;
...@@ -510,6 +498,12 @@ typedef struct ...@@ -510,6 +498,12 @@ typedef struct
int cmd_type; int cmd_type;
int lineno; int lineno;
PLpgSQL_expr *sqlstmt; PLpgSQL_expr *sqlstmt;
bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? */
/* note: mod_stmt is set when we plan the query */
bool into; /* INTO supplied? */
bool strict; /* INTO STRICT flag */
PLpgSQL_rec *rec; /* INTO target, if record */
PLpgSQL_row *row; /* INTO target, if row */
} PLpgSQL_stmt_execsql; } PLpgSQL_stmt_execsql;
...@@ -517,9 +511,11 @@ typedef struct ...@@ -517,9 +511,11 @@ typedef struct
{ /* Dynamic SQL string to execute */ { /* Dynamic SQL string to execute */
int cmd_type; int cmd_type;
int lineno; int lineno;
PLpgSQL_rec *rec; /* INTO record or row variable */ PLpgSQL_expr *query; /* string expression */
PLpgSQL_row *row; bool into; /* INTO supplied? */
PLpgSQL_expr *query; bool strict; /* INTO STRICT flag */
PLpgSQL_rec *rec; /* INTO target, if record */
PLpgSQL_row *row; /* INTO target, if row */
} PLpgSQL_stmt_dynexecute; } PLpgSQL_stmt_dynexecute;
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.53 2006/08/14 00:46:53 tgl Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.54 2006/08/14 21:14:42 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -137,6 +137,7 @@ get { return K_GET; } ...@@ -137,6 +137,7 @@ get { return K_GET; }
if { return K_IF; } if { return K_IF; }
in { return K_IN; } in { return K_IN; }
info { return K_INFO; } info { return K_INFO; }
insert { return K_INSERT; }
into { return K_INTO; } into { return K_INTO; }
is { return K_IS; } is { return K_IS; }
log { return K_LOG; } log { return K_LOG; }
...@@ -154,7 +155,6 @@ result_oid { return K_RESULT_OID; } ...@@ -154,7 +155,6 @@ result_oid { return K_RESULT_OID; }
return { return K_RETURN; } return { return K_RETURN; }
reverse { return K_REVERSE; } reverse { return K_REVERSE; }
row_count { return K_ROW_COUNT; } row_count { return K_ROW_COUNT; }
select { return K_SELECT; }
strict { return K_STRICT; } strict { return K_STRICT; }
then { return K_THEN; } then { return K_THEN; }
to { return K_TO; } to { return K_TO; }
......
...@@ -2048,6 +2048,7 @@ select * from foo; ...@@ -2048,6 +2048,7 @@ select * from foo;
20 20
(2 rows) (2 rows)
drop table foo;
-- Test for pass-by-ref values being stored in proper context -- Test for pass-by-ref values being stored in proper context
create function test_variable_storage() returns text as $$ create function test_variable_storage() returns text as $$
declare x text; declare x text;
...@@ -2794,3 +2795,142 @@ select multi_datum_use(42); ...@@ -2794,3 +2795,142 @@ select multi_datum_use(42);
t t
(1 row) (1 row)
--
-- Test STRICT limiter in both planned and EXECUTE invocations.
-- Note that a data-modifying query is quasi strict (disallow multi rows)
-- by default in the planned case, but not in EXECUTE.
--
create temp table foo (f1 int, f2 int);
insert into foo values (1,2), (3,4);
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
insert into foo values(5,6) returning * into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
NOTICE: x.f1 = 5, x.f2 = 6
footest
---------
(1 row)
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail due to implicit strict
insert into foo values(7,8),(9,10) returning * into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
ERROR: query returned more than one row
CONTEXT: PL/pgSQL function "footest" line 4 at SQL statement
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
execute 'insert into foo values(5,6) returning *' into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
NOTICE: x.f1 = 5, x.f2 = 6
footest
---------
(1 row)
create or replace function footest() returns void as $$
declare x record;
begin
-- this should work since EXECUTE isn't as picky
execute 'insert into foo values(7,8),(9,10) returning *' into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
NOTICE: x.f1 = 7, x.f2 = 8
footest
---------
(1 row)
select * from foo;
f1 | f2
----+----
1 | 2
3 | 4
5 | 6
5 | 6
7 | 8
9 | 10
(6 rows)
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
select * from foo where f1 = 3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
NOTICE: x.f1 = 3, x.f2 = 4
footest
---------
(1 row)
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, no rows
select * from foo where f1 = 0 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
ERROR: query returned no rows
CONTEXT: PL/pgSQL function "footest" line 4 at SQL statement
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, too many rows
select * from foo where f1 > 3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
ERROR: query returned more than one row
CONTEXT: PL/pgSQL function "footest" line 4 at SQL statement
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
execute 'select * from foo where f1 = 3' into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
NOTICE: x.f1 = 3, x.f2 = 4
footest
---------
(1 row)
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, no rows
execute 'select * from foo where f1 = 0' into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
ERROR: query returned no rows
CONTEXT: PL/pgSQL function "footest" line 4 at execute statement
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, too many rows
execute 'select * from foo where f1 > 3' into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
ERROR: query returned more than one row
CONTEXT: PL/pgSQL function "footest" line 4 at execute statement
drop function footest();
...@@ -1777,6 +1777,8 @@ reset statement_timeout; ...@@ -1777,6 +1777,8 @@ reset statement_timeout;
select * from foo; select * from foo;
drop table foo;
-- Test for pass-by-ref values being stored in proper context -- Test for pass-by-ref values being stored in proper context
create function test_variable_storage() returns text as $$ create function test_variable_storage() returns text as $$
declare x text; declare x text;
...@@ -2324,3 +2326,117 @@ begin ...@@ -2324,3 +2326,117 @@ begin
end$$ language plpgsql; end$$ language plpgsql;
select multi_datum_use(42); select multi_datum_use(42);
--
-- Test STRICT limiter in both planned and EXECUTE invocations.
-- Note that a data-modifying query is quasi strict (disallow multi rows)
-- by default in the planned case, but not in EXECUTE.
--
create temp table foo (f1 int, f2 int);
insert into foo values (1,2), (3,4);
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
insert into foo values(5,6) returning * into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail due to implicit strict
insert into foo values(7,8),(9,10) returning * into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
execute 'insert into foo values(5,6) returning *' into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- this should work since EXECUTE isn't as picky
execute 'insert into foo values(7,8),(9,10) returning *' into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
select * from foo;
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
select * from foo where f1 = 3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, no rows
select * from foo where f1 = 0 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, too many rows
select * from foo where f1 > 3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- should work
execute 'select * from foo where f1 = 3' into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, no rows
execute 'select * from foo where f1 = 0' into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, too many rows
execute 'select * from foo where f1 > 3' into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select footest();
drop function footest();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment