Add STRICT to PL/pgSQL SELECT INTO, so exceptions are thrown if more or

less than one row is returned by the SELECT, for Oracle PL/SQL
compatibility.

Improve SELECT INTO documentation.

Matt Miller
This commit is contained in:
Bruce Momjian 2006-06-15 18:02:22 +00:00
parent eb5558bce8
commit a584c12426
6 changed files with 78 additions and 44 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.95 2006/06/12 16:45:30 momjian Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.96 2006/06/15 18:02:22 momjian Exp $ -->
<chapter id="plpgsql"> <chapter id="plpgsql">
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title> <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@ -1081,7 +1081,7 @@ tax := subtotal * 0.06;
variable, or list of scalar variables. This is done by: variable, or list of scalar variables. This is done by:
<synopsis> <synopsis>
SELECT INTO <replaceable>target</replaceable> <replaceable>select_expressions</replaceable> FROM ...; SELECT INTO <optional>STRICT</optional> <replaceable>target</replaceable> <replaceable>select_expressions</replaceable> FROM ...;
</synopsis> </synopsis>
where <replaceable>target</replaceable> can be a record variable, a row where <replaceable>target</replaceable> can be a record variable, a row
@ -1122,47 +1122,43 @@ SELECT INTO <replaceable>target</replaceable> <replaceable>select_expressions</r
</para> </para>
<para> <para>
If the query returns zero rows, null values are assigned to the If <literal>STRICT</literal> is not specified then
target(s). If the query returns multiple rows, the first <replaceable>target</replaceable> will be set to the first row
row is assigned to the target(s) and the rest are discarded. returned by the query, or if the query returned no rows,
(Note that <quote>the first row</> is not well-defined unless you've null values are assigned. (Note that <quote>the first row</> is not
used <literal>ORDER BY</>.) well-defined unless you've used <literal>ORDER BY</>.)
</para> You can check the special <literal>FOUND</literal> variable to
determine if any rows were found:
<para>
You can check the special <literal>FOUND</literal> variable (see
<xref linkend="plpgsql-statements-diagnostics">) after a
<command>SELECT INTO</command> statement to determine whether the
assignment was successful, that is, at least one row was was returned by
the query. For example:
<programlisting> <programlisting>
SELECT INTO myrec * FROM emp WHERE empname = myname; SELECT INTO STRICT myrec * FROM emp WHERE empname = myname;
IF NOT FOUND THEN IF NOT FOUND THEN
RAISE EXCEPTION 'employee % not found', myname; RAISE EXCEPTION 'employee % not found', myname;
END IF; END IF;
</programlisting> </programlisting>
</para>
<para> <para>
To test for whether a record/row result is null, you can use the If the <literal>STRICT</literal> option is specified, a query must
<literal>IS NULL</literal> conditional. There is, however, no return exactly one row or a run-time error will be thrown, either
way to tell whether any additional rows might have been <literal>NO_DATA_FOUND</> (no rows) or <literal>TOO_MANY_ROWS</>
discarded. Here is an example that handles the case where no (more than one row). You can must use exception blocks to determine
rows have been returned: the number of rows generated by the query:
<programlisting>
DECLARE
users_rec RECORD;
BEGIN
SELECT INTO users_rec * FROM users WHERE user_id=3;
IF users_rec.homepage IS NULL THEN <programlisting>
-- user entered no homepage, return "http://" BEGIN;
RETURN 'http://'; SELECT INTO STRICT myrec * FROM emp WHERE empname = myname;
END IF; EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'employee % not found', myname;
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'employee % not unique', myname;
END; END;
</programlisting> </programlisting>
Only <command>SELECT INTO STRICT</command> allows you to check if more
than one row was retrieved. <command>SELECT INTO STRICT</command>
matches Oracle's PL/SQL <command>SELECT INTO</command> behavior.
</para> </para>
</sect2> </sect2>
<sect2 id="plpgsql-statements-perform"> <sect2 id="plpgsql-statements-perform">
@ -1425,7 +1421,7 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
<listitem> <listitem>
<para> <para>
A <command>SELECT INTO</command> statement sets A <command>SELECT INTO</command> statement sets
<literal>FOUND</literal> true if it returns a row, false if no <literal>FOUND</literal> true if a row is assigned, false if no
row is returned. row is returned.
</para> </para>
</listitem> </listitem>

View File

@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.91 2006/06/12 16:45:30 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.92 2006/06/15 18:02:22 momjian Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -157,6 +157,7 @@ static void check_labels(const char *start_label,
%token K_ELSE %token K_ELSE
%token K_ELSIF %token K_ELSIF
%token K_END %token K_END
%token K_STRICT
%token K_EXCEPTION %token K_EXCEPTION
%token K_EXECUTE %token K_EXECUTE
%token K_EXIT %token K_EXIT
@ -2001,6 +2002,7 @@ make_select_stmt(void)
PLpgSQL_rec *rec = NULL; PLpgSQL_rec *rec = NULL;
int tok; int tok;
bool have_into = false; bool have_into = false;
bool have_strict = false;
plpgsql_dstring_init(&ds); plpgsql_dstring_init(&ds);
plpgsql_dstring_append(&ds, "SELECT "); plpgsql_dstring_append(&ds, "SELECT ");
@ -2028,6 +2030,11 @@ make_select_stmt(void)
errmsg("INTO specified more than once"))); errmsg("INTO specified more than once")));
} }
tok = yylex(); tok = yylex();
if (tok == K_STRICT)
{
have_strict = true;
tok = yylex();
}
switch (tok) switch (tok)
{ {
case T_ROW: case T_ROW:
@ -2108,6 +2115,7 @@ make_select_stmt(void)
select->rec = rec; select->rec = rec;
select->row = row; select->row = row;
select->query = expr; select->query = expr;
select->strict = have_strict;
return (PLpgSQL_stmt *)select; return (PLpgSQL_stmt *)select;
} }

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.170 2006/06/12 16:45:30 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.171 2006/06/15 18:02:22 momjian Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -1701,23 +1701,41 @@ exec_stmt_select(PLpgSQL_execstate *estate, PLpgSQL_stmt_select *stmt)
/* /*
* Run the query * Run the query
* Retrieving two rows can be slower than a single row, e.g.
* a sequential scan where the scan has to be completed to
* check for a second row. For this reason, we only do the
* second-line check for STRICT.
*/ */
exec_run_select(estate, stmt->query, 1, NULL); exec_run_select(estate, stmt->query, stmt->strict ? 2 : 1, NULL);
tuptab = estate->eval_tuptable; tuptab = estate->eval_tuptable;
n = estate->eval_processed; n = estate->eval_processed;
/* /*
* If the query didn't return any rows, set the target to NULL and return. * If SELECT ... INTO specified STRICT, and the query didn't
* find exactly one row, throw an error. If STRICT was not specified,
* then allow the query to find any number of rows.
*/ */
if (n == 0) if (n == 0)
{ {
if (!stmt->strict)
{
/* null the target */
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc); exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
exec_eval_cleanup(estate); exec_eval_cleanup(estate);
return PLPGSQL_RC_OK; return PLPGSQL_RC_OK;
} }
else
ereport(ERROR,
(errcode(ERRCODE_NO_DATA),
errmsg("query returned no rows")));
}
else if (n > 1 && stmt->strict)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("query more than one row")));
/* /*
* Put the result into the target and set found to true * Put the first result into the target and set found to true
*/ */
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
exec_set_found(estate, true); exec_set_found(estate, true);

View File

@ -9,7 +9,7 @@
* *
* Copyright (c) 2003-2006, PostgreSQL Global Development Group * Copyright (c) 2003-2006, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.7 2006/03/05 15:59:10 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.8 2006/06/15 18:02:22 momjian Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -722,3 +722,13 @@
{ {
"index_corrupted", ERRCODE_INDEX_CORRUPTED "index_corrupted", ERRCODE_INDEX_CORRUPTED
}, },
{
"no_data_found", ERRCODE_NO_DATA
},
{
"too_many_rows", ERRCODE_CARDINALITY_VIOLATION
},

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.75 2006/06/12 16:45:30 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.76 2006/06/15 18:02:22 momjian Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -432,6 +432,7 @@ typedef struct
{ /* SELECT ... INTO statement */ { /* SELECT ... INTO statement */
int cmd_type; int cmd_type;
int lineno; int lineno;
bool strict;
PLpgSQL_rec *rec; PLpgSQL_rec *rec;
PLpgSQL_row *row; PLpgSQL_row *row;
PLpgSQL_expr *query; PLpgSQL_expr *query;

View File

@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.50 2006/06/12 16:45:30 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.51 2006/06/15 18:02:22 momjian Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -129,6 +129,7 @@ else { return K_ELSE; }
elseif { return K_ELSIF; } elseif { return K_ELSIF; }
elsif { return K_ELSIF; } elsif { return K_ELSIF; }
end { return K_END; } end { return K_END; }
strict { return K_STRICT; }
exception { return K_EXCEPTION; } exception { return K_EXCEPTION; }
execute { return K_EXECUTE; } execute { return K_EXECUTE; }
exit { return K_EXIT; } exit { return K_EXIT; }