-<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.23 2008/01/15 01:36:53 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.24 2008/05/15 22:39:48 tgl Exp $ -->
<appendix id="errcodes-appendix">
<title><productname>PostgreSQL</productname> Error Codes</title>
<tgroup cols="3">
<colspec colnum="1" colname="errorcode">
- <colspec colnum="3" colname="constant">
- <spanspec namest="errorcode" nameend="constant" spanname="span13">
+ <colspec colnum="3" colname="condname">
+ <spanspec namest="errorcode" nameend="condname" spanname="span13">
<thead>
<row>
<entry>Error Code</entry>
<entry>Meaning</entry>
- <entry>Constant</entry>
+ <entry>Condition Name</entry>
</row>
</thead>
</row>
+<row>
+<entry spanname="span13"><emphasis role="bold">Class 20 — Case Not Found</></entry>
+</row>
+
+<row>
+<entry><literal>20000</literal></entry>
+<entry>CASE NOT FOUND</entry>
+<entry>case_not_found</entry>
+</row>
+
+
<row>
<entry spanname="span13"><emphasis role="bold">Class 21 — Cardinality Violation</></entry>
</row>
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.129 2008/05/13 22:10:29 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.130 2008/05/15 22:39:49 tgl Exp $ -->
<chapter id="plpgsql">
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
<title>Conditionals</title>
<para>
- <literal>IF</> statements let you execute commands based on
- certain conditions. <application>PL/pgSQL</> has five forms of
- <literal>IF</>:
+ <command>IF</> and <command>CASE</> statements let you execute
+ alternative commands based on certain conditions.
+ <application>PL/pgSQL</> has five forms of <command>IF</>:
<itemizedlist>
<listitem>
<para><literal>IF ... THEN</></>
<para><literal>IF ... THEN ... ELSEIF ... THEN ... ELSE</></>
</listitem>
</itemizedlist>
+
+ and four forms of <command>CASE</>:
+ <itemizedlist>
+ <listitem>
+ <para><literal>CASE ... WHEN ... THEN ... END CASE</></>
+ </listitem>
+ <listitem>
+ <para><literal>CASE ... WHEN ... THEN ... ELSE ... END CASE</></>
+ </listitem>
+ <listitem>
+ <para><literal>CASE WHEN ... THEN ... END CASE</></>
+ </listitem>
+ <listitem>
+ <para><literal>CASE WHEN ... THEN ... ELSE ... END CASE</></>
+ </listitem>
+ </itemizedlist>
</para>
<sect3>
<literal>ELSEIF</> is an alias for <literal>ELSIF</>.
</para>
</sect3>
+
+ <sect3>
+ <title>Simple <literal>CASE</></title>
+
+<synopsis>
+CASE <replaceable>search-expression</replaceable>
+ WHEN <replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> <optional> ... </optional></optional> THEN
+ <replaceable>statements</replaceable>
+ <optional> WHEN <replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> <optional> ... </optional></optional> THEN
+ <replaceable>statements</replaceable>
+ ... </optional>
+ <optional> ELSE
+ <replaceable>statements</replaceable> </optional>
+END CASE;
+</synopsis>
+
+ <para>
+ The simple form of <command>CASE</> provides conditional execution
+ based on equality of operands. The <replaceable>search-expression</>
+ is evaluated (once) and successively compared to each
+ <replaceable>expression</> in the <literal>WHEN</> clauses.
+ If a match is found, then the corresponding
+ <replaceable>statements</replaceable> are executed, and then control
+ passes to the next statement after <literal>END CASE</>. (Subsequent
+ <literal>WHEN</> expressions are not evaluated.) If no match is
+ found, the <literal>ELSE</> <replaceable>statements</replaceable> are
+ executed; but if <literal>ELSE</> is not present, then a
+ <literal>CASE_NOT_FOUND</literal> exception is raised.
+ </para>
+
+ <para>
+ Here is a simple example:
+
+<programlisting>
+CASE x
+ WHEN 1, 2 THEN
+ msg := 'one or two';
+ ELSE
+ msg := 'other value than one or two';
+END CASE;
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Searched <literal>CASE</></title>
+
+<synopsis>
+CASE
+ WHEN <replaceable>boolean-expression</replaceable> THEN
+ <replaceable>statements</replaceable>
+ <optional> WHEN <replaceable>boolean-expression</replaceable> THEN
+ <replaceable>statements</replaceable>
+ ... </optional>
+ <optional> ELSE
+ <replaceable>statements</replaceable> </optional>
+END CASE;
+</synopsis>
+
+ <para>
+ The searched form of <command>CASE</> provides conditional execution
+ based on truth of boolean expressions. Each <literal>WHEN</> clause's
+ <replaceable>boolean-expression</replaceable> is evaluated in turn,
+ until one is found that yields <literal>true</>. Then the
+ corresponding <replaceable>statements</replaceable> are executed, and
+ then control passes to the next statement after <literal>END CASE</>.
+ (Subsequent <literal>WHEN</> expressions are not evaluated.)
+ If no true result is found, the <literal>ELSE</>
+ <replaceable>statements</replaceable> are executed;
+ but if <literal>ELSE</> is not present, then a
+ <literal>CASE_NOT_FOUND</literal> exception is raised.
+ </para>
+
+ <para>
+ Here is an example:
+
+<programlisting>
+CASE
+ WHEN x BETWEEN 0 AND 10 THEN
+ msg := 'value is between zero and ten';
+ WHEN x BETWEEN 11 AND 20 THEN
+ msg := 'value is between eleven and twenty';
+END CASE;
+</programlisting>
+ </para>
+
+ </sect3>
</sect2>
<sect2 id="plpgsql-control-structures-loops">
*
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.24 2008/01/01 19:45:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.25 2008/05/15 22:39:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* Class 0P - Invalid Role Specification */
#define ERRCODE_INVALID_ROLE_SPECIFICATION MAKE_SQLSTATE('0','P', '0','0','0')
+/* Class 20 - Case Not Found */
+#define ERRCODE_CASE_NOT_FOUND MAKE_SQLSTATE('2','0', '0','0','0')
+
/* Class 21 - Cardinality Violation */
/* (this means something returned the wrong number of rows) */
#define ERRCODE_CARDINALITY_VIOLATION MAKE_SQLSTATE('2','1', '0','0','0')
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.112 2008/05/13 22:10:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.113 2008/05/15 22:39:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static PLpgSQL_stmt *make_return_stmt(int lineno);
static PLpgSQL_stmt *make_return_next_stmt(int lineno);
static PLpgSQL_stmt *make_return_query_stmt(int lineno);
+static PLpgSQL_stmt *make_case(int lineno, PLpgSQL_expr *t_expr,
+ List *case_when_list, List *else_stmts);
static void check_assignable(PLpgSQL_datum *datum);
static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
bool *strict);
PLpgSQL_nsitem *nsitem;
PLpgSQL_diag_item *diagitem;
PLpgSQL_stmt_fetch *fetch;
+ PLpgSQL_case_when *casewhen;
}
%type <declhdr> decl_sect
%type <str> decl_stmts decl_stmt
%type <expr> expr_until_semi expr_until_rightbracket
-%type <expr> expr_until_then expr_until_loop
+%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
%type <ival> assign_var
%type <stmt> stmt_return stmt_raise stmt_execsql stmt_execsql_insert
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
+%type <stmt> stmt_case
%type <list> proc_exceptions
%type <exception_block> exception_sect
%type <exception> proc_exception
%type <condition> proc_conditions proc_condition
+%type <casewhen> case_when
+%type <list> case_when_list opt_case_else
+
%type <list> getdiag_list
%type <diagitem> getdiag_list_item
%type <ival> getdiag_kind getdiag_target
%token K_ASSIGN
%token K_BEGIN
%token K_BY
+%token K_CASE
%token K_CLOSE
%token K_CONSTANT
%token K_CONTINUE
;
proc_sect :
- {
- $$ = NIL;
- }
+ { $$ = NIL; }
| proc_stmts
{ $$ = $1; }
;
| proc_stmt
{
if ($1 == NULL)
- $$ = NULL;
+ $$ = NIL;
else
$$ = list_make1($1);
}
{ $$ = $1; }
| stmt_if
{ $$ = $1; }
+ | stmt_case
+ { $$ = $1; }
| stmt_loop
{ $$ = $1; }
| stmt_while
}
;
+stmt_case : K_CASE lno opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
+ {
+ $$ = make_case($2, $3, $4, $5);
+ }
+ ;
+
+opt_expr_until_when :
+ {
+ PLpgSQL_expr *expr = NULL;
+ int tok = yylex();
+
+ if (tok != K_WHEN)
+ {
+ plpgsql_push_back_token(tok);
+ expr = plpgsql_read_expression(K_WHEN, "WHEN");
+ }
+ plpgsql_push_back_token(K_WHEN);
+ $$ = expr;
+ }
+ ;
+
+case_when_list : case_when_list case_when
+ {
+ $$ = lappend($1, $2);
+ }
+ | case_when
+ {
+ $$ = list_make1($1);
+ }
+ ;
+
+case_when : K_WHEN lno expr_until_then proc_sect
+ {
+ PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
+
+ new->lineno = $2;
+ new->expr = $3;
+ new->stmts = $4;
+ $$ = new;
+ }
+ ;
+
+opt_case_else :
+ {
+ $$ = NIL;
+ }
+ | K_ELSE proc_sect
+ {
+ /*
+ * proc_sect could return an empty list, but we
+ * must distinguish that from not having ELSE at all.
+ * Simplest fix is to return a list with one NULL
+ * pointer, which make_case() must take care of.
+ */
+ if ($2 != NIL)
+ $$ = $2;
+ else
+ $$ = list_make1(NULL);
+ }
+ ;
+
stmt_loop : opt_block_label K_LOOP lno loop_body
{
PLpgSQL_stmt_loop *new;
return result;
}
+/*
+ * Fix up CASE statement
+ */
+static PLpgSQL_stmt *
+make_case(int lineno, PLpgSQL_expr *t_expr,
+ List *case_when_list, List *else_stmts)
+{
+ PLpgSQL_stmt_case *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_case));
+ new->cmd_type = PLPGSQL_STMT_CASE;
+ new->lineno = lineno;
+ new->t_expr = t_expr;
+ new->t_varno = 0;
+ new->case_when_list = case_when_list;
+ new->have_else = (else_stmts != NIL);
+ /* Get rid of list-with-NULL hack */
+ if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL)
+ new->else_stmts = NIL;
+ else
+ new->else_stmts = else_stmts;
+
+ /*
+ * When test expression is present, we create a var for it and then
+ * convert all the WHEN expressions to "VAR IN (original_expression)".
+ * This is a bit klugy, but okay since we haven't yet done more than
+ * read the expressions as text. (Note that previous parsing won't
+ * have complained if the WHEN ... THEN expression contained multiple
+ * comma-separated values.)
+ */
+ if (t_expr)
+ {
+ ListCell *l;
+ PLpgSQL_var *t_var;
+ int t_varno;
+
+ /*
+ * We don't yet know the result datatype of t_expr. Build the
+ * variable as if it were INT4; we'll fix this at runtime if needed.
+ */
+ t_var = (PLpgSQL_var *)
+ plpgsql_build_variable("*case*", lineno,
+ plpgsql_build_datatype(INT4OID, -1),
+ false);
+ t_varno = t_var->varno;
+ new->t_varno = t_varno;
+
+ foreach(l, case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+ PLpgSQL_expr *expr = cwt->expr;
+ int nparams = expr->nparams;
+ PLpgSQL_expr *new_expr;
+ PLpgSQL_dstring ds;
+ char buff[32];
+
+ /* Must add the CASE variable as an extra param to expression */
+ if (nparams >= MAX_EXPR_PARAMS)
+ {
+ plpgsql_error_lineno = cwt->lineno;
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too many variables specified in SQL statement")));
+ }
+
+ new_expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams + 1) - sizeof(int));
+ memcpy(new_expr, expr,
+ sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+ new_expr->nparams = nparams + 1;
+ new_expr->params[nparams] = t_varno;
+
+ /* And do the string hacking */
+ plpgsql_dstring_init(&ds);
+
+ plpgsql_dstring_append(&ds, "SELECT $");
+ snprintf(buff, sizeof(buff), "%d", nparams + 1);
+ plpgsql_dstring_append(&ds, buff);
+ plpgsql_dstring_append(&ds, " IN (");
+
+ /* copy expression query without SELECT keyword */
+ Assert(strncmp(expr->query, "SELECT ", 7) == 0);
+ plpgsql_dstring_append(&ds, expr->query + 7);
+ plpgsql_dstring_append_char(&ds, ')');
+
+ new_expr->query = pstrdup(plpgsql_dstring_get(&ds));
+
+ plpgsql_dstring_free(&ds);
+ pfree(expr->query);
+ pfree(expr);
+
+ cwt->expr = new_expr;
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
+
/* Needed to avoid conflict between different prefix settings: */
#undef yylex
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.214 2008/05/13 22:10:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.215 2008/05/15 22:39:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
PLpgSQL_stmt_getdiag *stmt);
static int exec_stmt_if(PLpgSQL_execstate *estate,
PLpgSQL_stmt_if *stmt);
+static int exec_stmt_case(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_case *stmt);
static int exec_stmt_loop(PLpgSQL_execstate *estate,
PLpgSQL_stmt_loop *stmt);
static int exec_stmt_while(PLpgSQL_execstate *estate,
CHECK_FOR_INTERRUPTS();
- switch (stmt->cmd_type)
+ switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
{
case PLPGSQL_STMT_BLOCK:
rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
break;
+ case PLPGSQL_STMT_CASE:
+ rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+ break;
+
case PLPGSQL_STMT_LOOP:
rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
break;
}
+/*-----------
+ * exec_stmt_case
+ *-----------
+ */
+static int
+exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
+{
+ PLpgSQL_var *t_var = NULL;
+ bool isnull;
+ ListCell *l;
+
+ if (stmt->t_expr != NULL)
+ {
+ /* simple case */
+ Datum t_val;
+ Oid t_oid;
+
+ t_val = exec_eval_expr(estate, stmt->t_expr, &isnull, &t_oid);
+
+ t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno];
+
+ /*
+ * When expected datatype is different from real, change it.
+ * Note that what we're modifying here is an execution copy
+ * of the datum, so this doesn't affect the originally stored
+ * function parse tree.
+ */
+ if (t_var->datatype->typoid != t_oid)
+ t_var->datatype = plpgsql_build_datatype(t_oid, -1);
+
+ /* now we can assign to the variable */
+ exec_assign_value(estate,
+ (PLpgSQL_datum *) t_var,
+ t_val,
+ t_oid,
+ &isnull);
+
+ exec_eval_cleanup(estate);
+ }
+
+ /* Now search for a successful WHEN clause */
+ foreach(l, stmt->case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+ bool value;
+
+ value = exec_eval_boolean(estate, cwt->expr, &isnull);
+ exec_eval_cleanup(estate);
+ if (!isnull && value)
+ {
+ /* Found it */
+
+ /* We can now discard any value we had for the temp variable */
+ if (t_var != NULL)
+ {
+ free_var(t_var);
+ t_var->value = (Datum) 0;
+ t_var->isnull = true;
+ }
+
+ /* Evaluate the statement(s), and we're done */
+ return exec_stmts(estate, cwt->stmts);
+ }
+ }
+
+ /* We can now discard any value we had for the temp variable */
+ if (t_var != NULL)
+ {
+ free_var(t_var);
+ t_var->value = (Datum) 0;
+ t_var->isnull = true;
+ }
+
+ /* SQL2003 mandates this error if there was no ELSE clause */
+ if (!stmt->have_else)
+ ereport(ERROR,
+ (errcode(ERRCODE_CASE_NOT_FOUND),
+ errmsg("case not found"),
+ errhint("CASE statement is missing ELSE part.")));
+
+ /* Evaluate the ELSE statements, and we're done */
+ return exec_stmts(estate, stmt->else_stmts);
+}
+
+
/* ----------
* exec_stmt_loop Loop over statements until
* an exit occurs.
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.71 2008/05/13 22:10:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.72 2008/05/15 22:39:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
const char *
plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
{
- switch (stmt->cmd_type)
+ switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
{
case PLPGSQL_STMT_BLOCK:
return _("statement block");
return _("assignment");
case PLPGSQL_STMT_IF:
return "IF";
+ case PLPGSQL_STMT_CASE:
+ return "CASE";
case PLPGSQL_STMT_LOOP:
return "LOOP";
case PLPGSQL_STMT_WHILE:
static void dump_block(PLpgSQL_stmt_block *block);
static void dump_assign(PLpgSQL_stmt_assign *stmt);
static void dump_if(PLpgSQL_stmt_if *stmt);
+static void dump_case(PLpgSQL_stmt_case *stmt);
static void dump_loop(PLpgSQL_stmt_loop *stmt);
static void dump_while(PLpgSQL_stmt_while *stmt);
static void dump_fori(PLpgSQL_stmt_fori *stmt);
dump_stmt(PLpgSQL_stmt *stmt)
{
printf("%3d:", stmt->lineno);
- switch (stmt->cmd_type)
+ switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
{
case PLPGSQL_STMT_BLOCK:
dump_block((PLpgSQL_stmt_block *) stmt);
case PLPGSQL_STMT_IF:
dump_if((PLpgSQL_stmt_if *) stmt);
break;
+ case PLPGSQL_STMT_CASE:
+ dump_case((PLpgSQL_stmt_case *) stmt);
+ break;
case PLPGSQL_STMT_LOOP:
dump_loop((PLpgSQL_stmt_loop *) stmt);
break;
printf(" ENDIF\n");
}
+static void
+dump_case(PLpgSQL_stmt_case *stmt)
+{
+ ListCell *l;
+
+ dump_ind();
+ printf("CASE %d ", stmt->t_varno);
+ if (stmt->t_expr)
+ dump_expr(stmt->t_expr);
+ printf("\n");
+ dump_indent += 6;
+ foreach(l, stmt->case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+
+ dump_ind();
+ printf("WHEN ");
+ dump_expr(cwt->expr);
+ printf("\n");
+ dump_ind();
+ printf("THEN\n");
+ dump_indent += 2;
+ dump_stmts(cwt->stmts);
+ dump_indent -= 2;
+ }
+ if (stmt->have_else)
+ {
+ dump_ind();
+ printf("ELSE\n");
+ dump_indent += 2;
+ dump_stmts(stmt->else_stmts);
+ dump_indent -= 2;
+ }
+ dump_indent -= 6;
+ dump_ind();
+ printf(" ENDCASE\n");
+}
+
static void
dump_loop(PLpgSQL_stmt_loop *stmt)
{
foreach(lc, stmt->options)
{
PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
-
+
dump_ind();
switch (opt->opt_type)
{
break;
case PLPGSQL_RAISEOPTION_MESSAGE:
printf(" MESSAGE = ");
- break;
+ break;
case PLPGSQL_RAISEOPTION_DETAIL:
printf(" DETAIL = ");
break;
}
dump_expr(opt->expr);
printf("\n");
- }
+ }
dump_indent -= 2;
}
dump_indent -= 2;
*
* Copyright (c) 2003-2008, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.13 2008/01/15 01:36:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.14 2008/05/15 22:39:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
"invalid_role_specification", ERRCODE_INVALID_ROLE_SPECIFICATION
},
+{
+ "case_not_found", ERRCODE_CASE_NOT_FOUND
+},
+
{
"cardinality_violation", ERRCODE_CARDINALITY_VIOLATION
},
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.99 2008/05/13 22:10:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.100 2008/05/15 22:39:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Execution tree node types
* ----------
*/
-enum
+enum PLpgSQL_stmt_types
{
PLPGSQL_STMT_BLOCK,
PLPGSQL_STMT_ASSIGN,
PLPGSQL_STMT_IF,
+ PLPGSQL_STMT_CASE,
PLPGSQL_STMT_LOOP,
PLPGSQL_STMT_WHILE,
PLPGSQL_STMT_FORI,
} PLpgSQL_stmt_if;
+typedef struct /* CASE statement */
+{
+ int cmd_type;
+ int lineno;
+ PLpgSQL_expr *t_expr; /* test expression, or NULL if none */
+ int t_varno; /* var to store test expression value into */
+ List *case_when_list; /* List of PLpgSQL_case_when structs */
+ bool have_else; /* flag needed because list could be empty */
+ List *else_stmts; /* List of statements */
+} PLpgSQL_stmt_case;
+
+typedef struct /* one arm of CASE statement */
+{
+ int lineno;
+ PLpgSQL_expr *expr; /* boolean expression for this case */
+ List *stmts; /* List of statements */
+} PLpgSQL_case_when;
+
+
typedef struct
{ /* Unconditional LOOP statement */
int cmd_type;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.63 2008/05/13 22:10:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.64 2008/05/15 22:39:49 tgl Exp $
*
*-------------------------------------------------------------------------
*/
alias { return K_ALIAS; }
begin { return K_BEGIN; }
by { return K_BY; }
+case { return K_CASE; }
close { return K_CLOSE; }
constant { return K_CONSTANT; }
continue { return K_CONTINUE; }
-- PLPGSQL
--
-- Scenario:
---
+--
-- A building with a modern TP cable installation where any
-- of the wall connectors can be used to plug in phones,
-- ethernet interfaces or local office hubs. The backside
-- of the wall connectors is wired to one of several patch-
-- fields in the building.
---
+--
-- In the patchfields, there are hubs and all the slots
-- representing the wall connectors. In addition there are
-- slots that can represent a phone line from the central
-- phone system.
---
+--
-- Triggers ensure consistency of the patching information.
---
+--
-- Functions are used to build up powerful views that let
-- you look behind the wall when looking at a patchfield
-- or into a room.
---
+--
create table Room (
roomno char(8),
comment text
);
create unique index PHone_name on PHone using btree (slotname bpchar_ops);
-- ************************************************************
--- *
+-- *
-- * Trigger procedures and functions for the patchfield
-- * test of PL/pgSQL
--- *
+-- *
-- ************************************************************
-- ************************************************************
-- * AFTER UPDATE on Room
mytype := substr(myname, 1, 2);
link := mytype || substr(blname, 1, 2);
if link = ''PLPL'' then
- raise exception
+ raise exception
''backlink between two phone lines does not make sense'';
end if;
if link in (''PLWS'', ''WSPL'') then
- raise exception
+ raise exception
''direct link of phone line to wall slot not permitted'';
end if;
if mytype = ''PS'' then
mytype := substr(myname, 1, 2);
link := mytype || substr(blname, 1, 2);
if link = ''PHPH'' then
- raise exception
+ raise exception
''slotlink between two phones does not make sense'';
end if;
if link in (''PHHS'', ''HSPH'') then
- raise exception
+ raise exception
''link of phone to hub does not make sense'';
end if;
if link in (''PHIF'', ''IFPH'') then
- raise exception
+ raise exception
''link of phone to hub does not make sense'';
end if;
if link in (''PSWS'', ''WSPS'') then
- raise exception
+ raise exception
''slotlink from patchslot to wallslot not permitted'';
end if;
if mytype = ''PS'' then
drop function footest();
-- test scrollable cursor support
create function sc_test() returns setof integer as $$
-declare
+declare
c scroll cursor for select f1 from int4_tbl;
x integer;
begin
(5 rows)
create or replace function sc_test() returns setof integer as $$
-declare
+declare
c no scroll cursor for select f1 from int4_tbl;
x integer;
begin
HINT: Declare it with SCROLL option to enable backward scan.
CONTEXT: PL/pgSQL function "sc_test" line 6 at FETCH
create or replace function sc_test() returns setof integer as $$
-declare
+declare
c refcursor;
x integer;
begin
(5 rows)
create or replace function sc_test() returns setof integer as $$
-declare
+declare
c refcursor;
x integer;
begin
-- Tests for 8.4's new RAISE features
create or replace function raise_test() returns void as $$
begin
- raise notice '% % %', 1, 2, 3
+ raise notice '% % %', 1, 2, 3
using errcode = '55001', detail = 'some detail info', hint = 'some hint';
- raise '% % %', 1, 2, 3
+ raise '% % %', 1, 2, 3
using errcode = 'division_by_zero', detail = 'some detail info';
end;
$$ language plpgsql;
ERROR: RAISE without parameters cannot be used outside an exception handler
CONTEXT: PL/pgSQL function "raise_test"
drop function raise_test();
+-- test CASE statement
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+ b int = 1;
+begin
+ case $1
+ when 1 then
+ return 'one';
+ when 2 then
+ return 'two';
+ when 3,4,3+5 then
+ return 'three, four or eight';
+ when a then
+ return 'ten';
+ when a+b, a+b+1 then
+ return 'eleven, twelve';
+ end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test
+-----------
+ two
+(1 row)
+
+select case_test(3);
+ case_test
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(4);
+ case_test
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(5); -- fails
+ERROR: case not found
+HINT: CASE statement is missing ELSE part.
+CONTEXT: PL/pgSQL function "case_test" line 4 at CASE
+select case_test(8);
+ case_test
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(10);
+ case_test
+-----------
+ ten
+(1 row)
+
+select case_test(11);
+ case_test
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(12);
+ case_test
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(13); -- fails
+ERROR: case not found
+HINT: CASE statement is missing ELSE part.
+CONTEXT: PL/pgSQL function "case_test" line 4 at CASE
+create or replace function catch() returns void as $$
+begin
+ raise notice '%', case_test(6);
+exception
+ when case_not_found then
+ raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+select catch();
+NOTICE: caught case_not_found 20000 case not found
+ catch
+-------
+
+(1 row)
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+ case
+ when $1 = 1 then
+ return 'one';
+ when $1 = a + 2 then
+ return 'twelve';
+ else
+ return 'other';
+ end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test
+-----------
+ other
+(1 row)
+
+select case_test(12);
+ case_test
+-----------
+ twelve
+(1 row)
+
+select case_test(13);
+ case_test
+-----------
+ other
+(1 row)
+
+drop function catch();
+drop function case_test(bigint);
-- PLPGSQL
--
-- Scenario:
---
+--
-- A building with a modern TP cable installation where any
-- of the wall connectors can be used to plug in phones,
-- ethernet interfaces or local office hubs. The backside
-- of the wall connectors is wired to one of several patch-
-- fields in the building.
---
+--
-- In the patchfields, there are hubs and all the slots
-- representing the wall connectors. In addition there are
-- slots that can represent a phone line from the central
-- phone system.
---
+--
-- Triggers ensure consistency of the patching information.
---
+--
-- Functions are used to build up powerful views that let
-- you look behind the wall when looking at a patchfield
-- or into a room.
---
+--
create table Room (
-- ************************************************************
--- *
+-- *
-- * Trigger procedures and functions for the patchfield
-- * test of PL/pgSQL
--- *
+-- *
-- ************************************************************
mytype := substr(myname, 1, 2);
link := mytype || substr(blname, 1, 2);
if link = ''PLPL'' then
- raise exception
+ raise exception
''backlink between two phone lines does not make sense'';
end if;
if link in (''PLWS'', ''WSPL'') then
- raise exception
+ raise exception
''direct link of phone line to wall slot not permitted'';
end if;
if mytype = ''PS'' then
mytype := substr(myname, 1, 2);
link := mytype || substr(blname, 1, 2);
if link = ''PHPH'' then
- raise exception
+ raise exception
''slotlink between two phones does not make sense'';
end if;
if link in (''PHHS'', ''HSPH'') then
- raise exception
+ raise exception
''link of phone to hub does not make sense'';
end if;
if link in (''PHIF'', ''IFPH'') then
- raise exception
+ raise exception
''link of phone to hub does not make sense'';
end if;
if link in (''PSWS'', ''WSPS'') then
- raise exception
+ raise exception
''slotlink from patchslot to wallslot not permitted'';
end if;
if mytype = ''PS'' then
-- test scrollable cursor support
create function sc_test() returns setof integer as $$
-declare
+declare
c scroll cursor for select f1 from int4_tbl;
x integer;
begin
select * from sc_test();
create or replace function sc_test() returns setof integer as $$
-declare
+declare
c no scroll cursor for select f1 from int4_tbl;
x integer;
begin
select * from sc_test(); -- fails because of NO SCROLL specification
create or replace function sc_test() returns setof integer as $$
-declare
+declare
c refcursor;
x integer;
begin
select * from sc_test();
create or replace function sc_test() returns setof integer as $$
-declare
+declare
c refcursor;
x integer;
begin
create or replace function raise_test() returns void as $$
begin
- raise notice '% % %', 1, 2, 3
+ raise notice '% % %', 1, 2, 3
using errcode = '55001', detail = 'some detail info', hint = 'some hint';
- raise '% % %', 1, 2, 3
+ raise '% % %', 1, 2, 3
using errcode = 'division_by_zero', detail = 'some detail info';
end;
$$ language plpgsql;
select raise_test();
drop function raise_test();
+
+-- test CASE statement
+
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+ b int = 1;
+begin
+ case $1
+ when 1 then
+ return 'one';
+ when 2 then
+ return 'two';
+ when 3,4,3+5 then
+ return 'three, four or eight';
+ when a then
+ return 'ten';
+ when a+b, a+b+1 then
+ return 'eleven, twelve';
+ end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(3);
+select case_test(4);
+select case_test(5); -- fails
+select case_test(8);
+select case_test(10);
+select case_test(11);
+select case_test(12);
+select case_test(13); -- fails
+
+create or replace function catch() returns void as $$
+begin
+ raise notice '%', case_test(6);
+exception
+ when case_not_found then
+ raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+
+select catch();
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+ case
+ when $1 = 1 then
+ return 'one';
+ when $1 = a + 2 then
+ return 'twelve';
+ else
+ return 'other';
+ end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(12);
+select case_test(13);
+
+drop function catch();
+drop function case_test(bigint);