diff --git a/doc/src/sgml/errcodes.sgml b/doc/src/sgml/errcodes.sgml index a3f56a86b14bd26463f8995c25d8f0f008ae3e42..474c0ca8da711eb96f446898b1f41dcdb11c5f7b 100644 --- a/doc/src/sgml/errcodes.sgml +++ b/doc/src/sgml/errcodes.sgml @@ -1,4 +1,4 @@ - + <productname>PostgreSQL</productname> Error Codes @@ -62,14 +62,14 @@ - - + + Error Code Meaning - Constant + Condition Name @@ -292,6 +292,17 @@ + +Class 20 — Case Not Found + + + +20000 +CASE NOT FOUND +case_not_found + + + Class 21 — Cardinality Violation diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 09ad6944dba5a065745f46346149e3084a4e6121..d2853d2d901b2644b40963c22eb34dd6d2ce7d12 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1,4 +1,4 @@ - + <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language @@ -1581,9 +1581,9 @@ SELECT * FROM getallfoo(); Conditionals - IF statements let you execute commands based on - certain conditions. PL/pgSQL has five forms of - IF: + IF and CASE statements let you execute + alternative commands based on certain conditions. + PL/pgSQL has five forms of IF: IF ... THEN @@ -1601,6 +1601,22 @@ SELECT * FROM getallfoo(); IF ... THEN ... ELSEIF ... THEN ... ELSE + + and four forms of CASE: + + + CASE ... WHEN ... THEN ... END CASE + + + CASE ... WHEN ... THEN ... ELSE ... END CASE + + + CASE WHEN ... THEN ... END CASE + + + CASE WHEN ... THEN ... ELSE ... END CASE + + @@ -1751,6 +1767,93 @@ END IF; ELSEIF is an alias for ELSIF. + + + Simple <literal>CASE</> + + +CASE search-expression + WHEN expression , expression ... THEN + statements + WHEN expression , expression ... THEN + statements + ... + ELSE + statements +END CASE; + + + + The simple form of CASE provides conditional execution + based on equality of operands. The search-expression + is evaluated (once) and successively compared to each + expression in the WHEN clauses. + If a match is found, then the corresponding + statements are executed, and then control + passes to the next statement after END CASE. (Subsequent + WHEN expressions are not evaluated.) If no match is + found, the ELSE statements are + executed; but if ELSE is not present, then a + CASE_NOT_FOUND exception is raised. + + + + Here is a simple example: + + +CASE x + WHEN 1, 2 THEN + msg := 'one or two'; + ELSE + msg := 'other value than one or two'; +END CASE; + + + + + + Searched <literal>CASE</> + + +CASE + WHEN boolean-expression THEN + statements + WHEN boolean-expression THEN + statements + ... + ELSE + statements +END CASE; + + + + The searched form of CASE provides conditional execution + based on truth of boolean expressions. Each WHEN clause's + boolean-expression is evaluated in turn, + until one is found that yields true. Then the + corresponding statements are executed, and + then control passes to the next statement after END CASE. + (Subsequent WHEN expressions are not evaluated.) + If no true result is found, the ELSE + statements are executed; + but if ELSE is not present, then a + CASE_NOT_FOUND exception is raised. + + + + Here is an example: + + +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; + + + + diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h index 1caaf69f2d76c30e7b89d090c4127dcdbb1bb93c..9cfdd16bde2454747eafb828e9a4fa071b154446 100644 --- a/src/include/utils/errcodes.h +++ b/src/include/utils/errcodes.h @@ -11,7 +11,7 @@ * * 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 $ * *------------------------------------------------------------------------- */ @@ -101,6 +101,9 @@ /* 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') diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index b67a8bba776a28a493401ce233ee038e70678a4c..dfd37f67717a426d8374a32ce25a246e18b6e003 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -9,7 +9,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -37,6 +37,8 @@ static PLpgSQL_stmt_fetch *read_fetch_direction(void); 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); @@ -102,6 +104,7 @@ static List *read_raise_options(void); PLpgSQL_nsitem *nsitem; PLpgSQL_diag_item *diagitem; PLpgSQL_stmt_fetch *fetch; + PLpgSQL_case_when *casewhen; } %type decl_sect @@ -116,7 +119,7 @@ static List *read_raise_options(void); %type decl_stmts decl_stmt %type expr_until_semi expr_until_rightbracket -%type expr_until_then expr_until_loop +%type expr_until_then expr_until_loop opt_expr_until_when %type opt_exitcond %type assign_var @@ -135,12 +138,16 @@ static List *read_raise_options(void); %type stmt_return stmt_raise stmt_execsql stmt_execsql_insert %type stmt_dynexecute stmt_for stmt_perform stmt_getdiag %type stmt_open stmt_fetch stmt_move stmt_close stmt_null +%type stmt_case %type proc_exceptions %type exception_sect %type proc_exception %type proc_conditions proc_condition +%type case_when +%type case_when_list opt_case_else + %type getdiag_list %type getdiag_list_item %type getdiag_kind getdiag_target @@ -157,6 +164,7 @@ static List *read_raise_options(void); %token K_ASSIGN %token K_BEGIN %token K_BY +%token K_CASE %token K_CLOSE %token K_CONSTANT %token K_CONTINUE @@ -581,9 +589,7 @@ decl_defkey : K_ASSIGN ; proc_sect : - { - $$ = NIL; - } + { $$ = NIL; } | proc_stmts { $$ = $1; } ; @@ -598,7 +604,7 @@ proc_stmts : proc_stmts proc_stmt | proc_stmt { if ($1 == NULL) - $$ = NULL; + $$ = NIL; else $$ = list_make1($1); } @@ -610,6 +616,8 @@ proc_stmt : pl_block ';' { $$ = $1; } | stmt_if { $$ = $1; } + | stmt_case + { $$ = $1; } | stmt_loop { $$ = $1; } | stmt_while @@ -808,6 +816,67 @@ stmt_else : } ; +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; @@ -2804,6 +2873,103 @@ read_raise_options(void) 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 diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 2ba45befb71e899473ec68a39918e8a119fcec90..aeb5d365b495a3eb976bae9439ccd078c203d078 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -94,6 +94,8 @@ static int exec_stmt_getdiag(PLpgSQL_execstate *estate, 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, @@ -1229,7 +1231,7 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) 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); @@ -1251,6 +1253,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *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; @@ -1442,6 +1448,91 @@ exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt) } +/*----------- + * 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. diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index cb9e9c99ec3e33e1aea7f048997557fc80c7126c..aab349bdb2c0359b06cf9f4dd96c020045616e4f 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -466,7 +466,7 @@ plpgsql_convert_ident(const char *s, char **output, int numidents) 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"); @@ -474,6 +474,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt) return _("assignment"); case PLPGSQL_STMT_IF: return "IF"; + case PLPGSQL_STMT_CASE: + return "CASE"; case PLPGSQL_STMT_LOOP: return "LOOP"; case PLPGSQL_STMT_WHILE: @@ -526,6 +528,7 @@ static void dump_stmt(PLpgSQL_stmt *stmt); 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); @@ -561,7 +564,7 @@ static void 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); @@ -572,6 +575,9 @@ dump_stmt(PLpgSQL_stmt *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; @@ -714,6 +720,44 @@ dump_if(PLpgSQL_stmt_if *stmt) 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) { @@ -1025,7 +1069,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt) foreach(lc, stmt->options) { PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc); - + dump_ind(); switch (opt->opt_type) { @@ -1034,7 +1078,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt) break; case PLPGSQL_RAISEOPTION_MESSAGE: printf(" MESSAGE = "); - break; + break; case PLPGSQL_RAISEOPTION_DETAIL: printf(" DETAIL = "); break; @@ -1044,7 +1088,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt) } dump_expr(opt->expr); printf("\n"); - } + } dump_indent -= 2; } dump_indent -= 2; diff --git a/src/pl/plpgsql/src/plerrcodes.h b/src/pl/plpgsql/src/plerrcodes.h index 5fbdf255d820756c8d664cb15026c48639bdba0f..e6ab24dfd0e9990898377084983e98cce0c04264 100644 --- a/src/pl/plpgsql/src/plerrcodes.h +++ b/src/pl/plpgsql/src/plerrcodes.h @@ -9,7 +9,7 @@ * * 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 $ * *------------------------------------------------------------------------- */ @@ -79,6 +79,10 @@ "invalid_role_specification", ERRCODE_INVALID_ROLE_SPECIFICATION }, +{ + "case_not_found", ERRCODE_CASE_NOT_FOUND +}, + { "cardinality_violation", ERRCODE_CARDINALITY_VIOLATION }, diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 53d691a596ab091226e9261f916bb721929f2582..735a6810053bdf3249a97442fb4ef90638712088 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -71,11 +71,12 @@ enum * 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, @@ -390,6 +391,25 @@ typedef struct } 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; diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index 73258ec364802ac675f8fde39b708a17665d0bb3..77e9335a0529f6dc8095a45bb330a02302dcbc23 100644 --- a/src/pl/plpgsql/src/scan.l +++ b/src/pl/plpgsql/src/scan.l @@ -9,7 +9,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -116,6 +116,7 @@ dolqinside [^$]+ 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; } diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 9f18f45126acca0b96b8fec3a536873836969cba..582c06785a8aee50c04bcbd95756676da4ff9335 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -2,24 +2,24 @@ -- 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 @@ -84,10 +84,10 @@ create table PHone ( ); 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 @@ -597,11 +597,11 @@ begin 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 @@ -745,19 +745,19 @@ begin 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 @@ -2936,7 +2936,7 @@ CONTEXT: PL/pgSQL function "footest" line 4 at EXECUTE statement 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 @@ -2960,7 +2960,7 @@ select * from sc_test(); (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 @@ -2978,7 +2978,7 @@ ERROR: cursor can only scan forward 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 @@ -3002,7 +3002,7 @@ select * from sc_test(); (5 rows) create or replace function sc_test() returns setof integer as $$ -declare +declare c refcursor; x integer; begin @@ -3288,9 +3288,9 @@ drop function return_dquery(); -- 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; @@ -3414,3 +3414,133 @@ select raise_test(); 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); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index f64bfabc1e5385d79f1d8c4af55e793151e06c59..0267dda30ca21662ee7dbe0b6cf659aab93c01d0 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -2,24 +2,24 @@ -- 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 ( @@ -116,10 +116,10 @@ create unique index PHone_name on PHone using btree (slotname bpchar_ops); -- ************************************************************ --- * +-- * -- * Trigger procedures and functions for the patchfield -- * test of PL/pgSQL --- * +-- * -- ************************************************************ @@ -708,11 +708,11 @@ begin 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 @@ -868,19 +868,19 @@ begin 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 @@ -2444,7 +2444,7 @@ 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 @@ -2461,7 +2461,7 @@ $$ language plpgsql; 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 @@ -2478,7 +2478,7 @@ $$ language plpgsql; 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 @@ -2495,7 +2495,7 @@ $$ language plpgsql; select * from sc_test(); create or replace function sc_test() returns setof integer as $$ -declare +declare c refcursor; x integer; begin @@ -2688,9 +2688,9 @@ drop function return_dquery(); 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; @@ -2812,3 +2812,69 @@ $$ 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);