correct queue

Hello

 I finished this patch.

 Proposal: http://archives.postgresql.org/pgsql-hackers/2008-01/msg00696.php

 It's compatible with PL/SQL (Oracle) and SQL/PSM (ANSI).

 CASE statements is parsed and transformed to CASE expression and
 statements paths. Result of CASE expression is used as index to array
 of statements paths. It's fast but I have to once time reparse SQL
 queries - it generate about 150 lines code, because I need to get all
 parameter's positions. It's one disadvantage. On second hand, this
 statement needs only one expression evaluation.

 Sample:

 CREATE OR REPLACE FUNCTION foo(int)
 RETURNS void AS $$
 BEGIN
  CASE $1
      WHEN 1,2,3 THEN
         RAISE NOTICE '1,2';
         RAISE NOTICE '3';
      WHEN 4 THEN
         RAISE NOTICE '4';
      ELSE
         RAISE NOTICE 'other than 1,2,3,4';
  END CASE;
  RETURN;
 END;
 $$ LANGUAGE plpgsql;

 This statement is transformated to:
 three statement paths:
 [0]
         RAISE NOTICE 'other than 1,2,3,4';
 [1]
         RAISE NOTICE '1,2';
         RAISE NOTICE '3';
 [2]
         RAISE NOTICE '4';

 and case expression
 CASE $1
  WHEN 1 THEN 1
  WHEN 2 THEN 1
  WHEN 3 THEN 1
  WHEN 4 THEN 2
 END;

 When result is NULL then it uses 0 path.

 Questions:
 a) is possible to use SQL scanner? Now, scanner isn't directly used everywhere.

 any notes and comments are welcome

 Regards

Pavel Stehule
*** ./gram.y.orig	2008-03-28 17:33:45.000000000 +0100
--- ./gram.y	2008-03-31 13:46:08.000000000 +0200
***************
*** 15,23 ****
   */
  
  #include "plpgsql.h"
- 
  #include "parser/parser.h"
! 
  
  static PLpgSQL_expr		*read_sql_construct(int until,
  											int until2,
--- 15,24 ----
   */
  
  #include "plpgsql.h"
  #include "parser/parser.h"
! #include <errno.h>
! #include <ctype.h>
! #include <string.h>
  
  static PLpgSQL_expr		*read_sql_construct(int until,
  											int until2,
***************
*** 46,52 ****
  static	char			*check_label(const char *yytxt);
  static	void			 check_labels(const char *start_label,
  									  const char *end_label);
! 
  %}
  
  %name-prefix="plpgsql_yy"
--- 47,54 ----
  static	char			*check_label(const char *yytxt);
  static	void			 check_labels(const char *start_label,
  									  const char *end_label);
! static PLpgSQL_stmt	*make_case(int lineno, PLpgSQL_expr *case_expr, 
! 								    List *when_clause_list, List *else_stmts);
  %}
  
  %name-prefix="plpgsql_yy"
***************
*** 79,84 ****
--- 81,91 ----
  			char *end_label;
  			List *stmts;
  		}						loop_body;
+ 		struct
+ 		{	
+ 			List *expr_list;
+ 			PLpgSQL_expr	*expr;
+ 		}						when_expr;
  		List					*list;
  		PLpgSQL_type			*dtype;
  		PLpgSQL_datum			*scalar;	/* a VAR, RECFIELD, or TRIGARG */
***************
*** 95,100 ****
--- 102,108 ----
  		PLpgSQL_nsitem			*nsitem;
  		PLpgSQL_diag_item		*diagitem;
  		PLpgSQL_stmt_fetch		*fetch;
+ 		PLpgSQL_when_clause		*whenclause;
  }
  
  %type <declhdr> decl_sect
***************
*** 109,115 ****
  %type <str>		decl_stmts decl_stmt
  
  %type <expr>	expr_until_semi expr_until_rightbracket
! %type <expr>	expr_until_then expr_until_loop
  %type <expr>	opt_exitcond
  
  %type <ival>	assign_var
--- 117,123 ----
  %type <str>		decl_stmts decl_stmt
  
  %type <expr>	expr_until_semi expr_until_rightbracket
! %type <expr>	expr_until_then expr_until_loop opt_expr_until_when
  %type <expr>	opt_exitcond
  
  %type <ival>	assign_var
***************
*** 128,133 ****
--- 136,145 ----
  %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 <when_expr> case_when_expr
+ %type <whenclause> when_clause
+ %type <list>	when_clause_list opt_case_default
  
  %type <list>	proc_exceptions
  %type <exception_block> exception_sect
***************
*** 154,159 ****
--- 166,172 ----
  %token	K_ASSIGN
  %token	K_BEGIN
  %token	K_BY
+ %token	K_CASE
  %token	K_CLOSE
  %token	K_CONSTANT
  %token	K_CONTINUE
***************
*** 611,616 ****
--- 624,631 ----
  						{ $$ = $1; }
  				| stmt_if
  						{ $$ = $1; }
+ 				| stmt_case
+ 						{ $$ = $1; }
  				| stmt_loop
  						{ $$ = $1; }
  				| stmt_while
***************
*** 809,814 ****
--- 824,869 ----
  					}
  				;
  
+ stmt_case		: K_CASE lno opt_expr_until_when when_clause_list opt_case_default K_END K_CASE ';'
+ 					{
+ 						$$ = make_case($2, $3, $4, $5);
+ 					}
+ 				;
+ 
+ opt_case_default	:
+ 					{
+ 						$$ = NIL;
+ 					}
+ 				| K_ELSE proc_stmts
+ 					{
+ 						$$ = $2;
+ 					}
+ 				;
+ 
+ when_clause_list	: when_clause_list when_clause
+ 					{
+ 						$$ = lappend($1, $2);
+ 					}
+ 				| when_clause
+ 					{
+ 						$$ = list_make1($1);
+ 					}
+ 				;
+ 
+ when_clause		: K_WHEN lno case_when_expr proc_stmts
+ 					{
+ 						PLpgSQL_when_clause *new = palloc0(sizeof(PLpgSQL_when_clause));
+ 
+ 						new->cmd_type		= PLPGSQL_STMT_WHEN_CLAUSE;
+ 						new->lineno	= $2;
+ 						new->when_expr	= $3.expr;
+ 						new->when_expr_list	= $3.expr_list;
+ 						new->then_stmts	= $4;
+ 
+ 						$$ = new;
+ 					}
+ 				;
+ 
  stmt_loop		: opt_block_label K_LOOP lno loop_body
  					{
  						PLpgSQL_stmt_loop *new;
***************
*** 1631,1636 ****
--- 1686,1738 ----
  					{ $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
  				;
  
+ case_when_expr			:
+ 					{
+ 						int	tok;
+ 						PLpgSQL_expr	*expr;
+ 
+ 						$$.expr_list = NIL;
+ 						$$.expr = NULL;
+ 
+ 						expr = read_sql_construct(',', K_THEN, "THEN", 
+ 											    "SELECT ", true, true, &tok);
+ 
+ 						if (tok == K_THEN)
+ 						{
+ 							$$.expr = expr;
+ 						}
+ 						else
+ 						{
+ 							$$.expr_list = list_make1(expr);
+ 							for(;;)
+ 							{
+ 								expr = read_sql_construct(',',K_THEN, "THEN",
+ 													    "SELECT ", true, true, &tok);
+ 								$$.expr_list = lappend($$.expr_list, expr);
+ 								if (tok == K_THEN)
+ 								{
+ 									break;
+ 								}	
+ 							}
+ 						}
+ 					}
+ 				    ;
+ 
+ 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;
+ 					}
+ 				    ;
+ 
  opt_block_label	:
  					{
  						plpgsql_ns_push(NULL);
***************
*** 2529,2534 ****
--- 2631,2892 ----
  	}
  }
  
+ /*
+  * This function joins an PLpgSQL_expr to expression stack. It's used
+  * for CASE statement where from some expr is created one expression.
+  * Using main scanner can simplify this code. Reparsing is necessary
+  * for detecting parameters in SQL query.
+  */
+ static void
+ add_expr(PLpgSQL_expr *expr, PLpgSQL_dstring *ds, int *nparams, int *params)
+ {
+ 	char	*query;
+ 	char	buff[32];
+ 
+ 	if (strncmp(expr->query, "SELECT", 6) != 0)
+ 	{
+ 		plpgsql_error_lineno = plpgsql_scanner_lineno();
+ 		/* internal error */
+ 		elog(ERROR, "expected \"SELECT (\", got \"%s\"",
+ 							    expr->query);
+ 	}
+ 
+ 	/* Have to parse SQL string, because we need to find parameters */
+ 	for (query = expr->query + 7; *query;)
+ 	{
+ 		char c = *query++;
+ 
+ 		if (c == '\'')
+ 		{
+ 			plpgsql_dstring_append_char(ds, c);
+ 			/* skip string */
+ 			while ((c = *query++))
+ 			{
+ 				plpgsql_dstring_append_char(ds, c);
+ 				if (c == '\\')
+ 				{
+ 					if (*query == '\\' || *query == '\'')
+ 					{
+ 						plpgsql_dstring_append_char(ds, *query++);
+ 						continue;
+ 					}
+ 				}
+ 				else if (c == '\'')
+ 				{
+ 					if (*query == '\'')
+ 					{
+ 						plpgsql_dstring_append_char(ds, *query++);
+ 						continue;
+ 					}
+ 					else
+ 						break;
+ 				}
+ 			}
+ 			continue;
+ 		}
+ 		if (c == '"')
+ 		{
+ 			plpgsql_dstring_append_char(ds, c);
+ 			/* skip column */
+ 			while ((c = *query++))
+ 			{
+ 				plpgsql_dstring_append_char(ds, c);
+ 				if (c =='\\')
+ 				{
+ 					if (*query == '\\' || *query == '"')
+ 					{
+ 						plpgsql_dstring_append_char(ds, *query++);
+ 						continue;
+ 					}
+ 				}
+ 				else if (c == '"')
+ 					break;
+ 			}
+ 			continue;
+ 		}
+ 		else if (c == '$')
+ 		{
+ 			/* there is possible $n */
+ 			if (*query && isdigit(*query))
+ 			{
+ 				char	*endptr;
+ 				long int	varpos;
+ 				int dno;
+ 				int	i;
+ 
+ 				errno = 0;
+ 				varpos = strtol(query, &endptr, 10);
+ 				if (errno != 0)
+ 					elog(ERROR, "strtol failure");
+ 
+ 				if (varpos < 1 || varpos >= MAX_EXPR_PARAMS)
+ 					elog(ERROR, "parsing query failure, wrong param $%ld", varpos);
+ 
+ 				dno = expr->params[varpos-1];
+ 				for (i = 0; i < *nparams; i++)
+ 					if (params[i] == dno)
+ 						break;
+ 
+ 				snprintf(buff, sizeof(buff), "$%d", i+1);
+ 				/* when not found variable */
+ 				if (i >= *nparams)
+ 				{
+ 					if (*nparams >= MAX_EXPR_PARAMS)
+ 					{
+ 						plpgsql_error_lineno = plpgsql_scanner_lineno();
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 								 errmsg("too many variables specified in SQL statement")));
+ 					}
+ 					params[*nparams] = dno;
+ 					(*nparams)++;
+ 				}
+ 
+ 				plpgsql_dstring_append(ds, buff);
+ 
+ 				query = endptr;
+ 				continue;
+ 			}
+ 			/* $x$ separated string */
+ 			else
+ 			{
+ 				char *sep = query - 1;
+ 				int 	sep_len;
+ 
+ 				/* read to another $ symbol */
+ 				while (*query)
+ 					if (*query++ == '$')
+ 					{
+ 						sep_len = query - sep;
+ 						break;
+ 					}
+ 
+ 				/* separator is complete */ 
+ 				if (*query)
+ 				{
+ 					char	*sepstr = palloc(sep_len + 1);
+ 					char	*nextsep;
+ 
+ 					strncpy(sepstr, sep, sep_len);
+ 					sepstr[sep_len] = '\0';
+ 					nextsep = strstr(query, sepstr);
+ 					pfree(sepstr);
+ 
+ 					if (nextsep != NULL)
+ 					{
+ 						char 	aux = nextsep[sep_len];
+ 						nextsep[sep_len] = '\0';
+ 						plpgsql_dstring_append(ds, sep);
+ 						nextsep[sep_len] = aux;
+ 
+ 						query = nextsep + sep_len;
+ 						continue;	
+ 					}
+ 					elog(ERROR, "syntax error, unterminated literal");
+ 				}
+ 				elog(ERROR, "syntax error, unterminated $ separator");
+ 			}
+ 		}
+ 		plpgsql_dstring_append_char(ds, c);
+ 	}
+ }
+ 
+ /*
+  * CASE statement is transformated to case expression for getting an offset
+  * CASE expr0 
+  *      WHEN expr1, expr2 THEN statements
+  * END CASE;
+  * ----------
+  * CASE expr
+  * 	WHEN expr1 THEN 0
+  *      WHEN expr2 THEN 0
+  * END;
+  */
+ static PLpgSQL_stmt *
+ make_case(int lineno, PLpgSQL_expr *case_expr, 
+ 					    List *when_clause_list, List *else_stmts)
+ {
+ 	ListCell	*l;
+ 	int	offset 		= 1;
+ 	PLpgSQL_stmt_case 	*new;
+ 	PLpgSQL_expr	*expr;
+ 	PLpgSQL_dstring		ds;
+ 	int					nparams = 0;
+ 	int					params[MAX_EXPR_PARAMS];
+ 	char	buff[32];				/* snprintf buffer */
+ 
+ 	new = palloc(sizeof(PLpgSQL_stmt_case) 
+ 				+ list_length(when_clause_list) * sizeof(List *));
+ 	new->cmd_type = PLPGSQL_STMT_CASE;
+ 	new->lineno = lineno;
+ 	new->npaths = list_length(when_clause_list) + 1;
+ 
+ 	/* when ELSE is missing, then stmts_array[0] is NULL */
+ 	new->stmts_array[0] = else_stmts;
+ 
+ 	plpgsql_dstring_init(&ds);
+ 	plpgsql_dstring_append(&ds, "SELECT CASE ");
+ 
+ 	if (case_expr)
+ 		add_expr(case_expr, &ds, &nparams, params);
+ 
+ 	foreach(l, when_clause_list)
+ 	{
+ 		ListCell 	*cse;
+ 
+ 		PLpgSQL_when_clause *wc = (PLpgSQL_when_clause *) lfirst(l);
+ 
+ 		if (wc->when_expr_list)
+ 		{
+ 			/* check case_expr when comma separated predicates are used */
+ 			if (case_expr == NULL)
+ 				ereport(ERROR,
+ 					(errcode(ERRCODE_SYNTAX_ERROR),
+ 					 errmsg("syntax error at WHEN"),
+ 					 errdetail("Comma separated predicates are supported only in simple CASE statement.")));
+ 
+ 
+ 			foreach(cse, wc->when_expr_list)
+ 			{
+ 				PLpgSQL_expr *expr = (PLpgSQL_expr *) lfirst(cse);
+ 
+ 				plpgsql_dstring_append(&ds, " WHEN ");
+ 				add_expr(expr, &ds, &nparams, params);
+ 				plpgsql_dstring_append(&ds, " THEN ");
+ 				snprintf(buff,sizeof(buff), " %d ", offset);
+ 				plpgsql_dstring_append(&ds, buff);
+ 			}
+ 		}
+ 		else
+ 		{
+ 			plpgsql_dstring_append(&ds, " WHEN ");
+ 			add_expr(wc->when_expr, &ds, &nparams, params);
+ 			plpgsql_dstring_append(&ds, " THEN ");
+ 			snprintf(buff,sizeof(buff), " %d ", offset);
+ 			plpgsql_dstring_append(&ds, buff);
+ 		}
+ 		new->stmts_array[offset++] = wc->then_stmts;
+ 	}
+ 
+ 	plpgsql_dstring_append(&ds, " END ");
+ 
+ 	expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+ 	expr->dtype			= PLPGSQL_DTYPE_EXPR;
+ 	expr->query			= pstrdup(plpgsql_dstring_get(&ds));
+ 	expr->plan			= NULL;
+ 	expr->nparams		= nparams;
+ 	while(nparams-- > 0)
+ 		expr->params[nparams] = params[nparams];
+ 
+ 	plpgsql_dstring_free(&ds);
+ 
+ 	check_sql_expr(expr->query);
+ 	    
+ 	new->case_expr = expr;
+ 
+ 	return (PLpgSQL_stmt *) new;
+ }
+ 
  /* Needed to avoid conflict between different prefix settings: */
  #undef yylex
  
*** ./pl_exec.c.orig	2008-03-28 22:55:46.000000000 +0100
--- ./pl_exec.c	2008-03-29 22:51:11.000000000 +0100
***************
*** 116,121 ****
--- 116,123 ----
  					 PLpgSQL_stmt_dynexecute *stmt);
  static int exec_stmt_dynfors(PLpgSQL_execstate *estate,
  				  PLpgSQL_stmt_dynfors *stmt);
+ static int exec_stmt_case(PLpgSQL_execstate *estate,
+ 				    PLpgSQL_stmt_case *stmt);
  
  static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
  					 PLpgSQL_function *func,
***************
*** 1276,1281 ****
--- 1278,1287 ----
  			rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
  			break;
  
+ 		case PLPGSQL_STMT_CASE:
+ 			rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+ 			break;
+ 
  		default:
  			estate->err_stmt = save_estmt;
  			elog(ERROR, "unrecognized cmdtype: %d", stmt->cmd_type);
***************
*** 1402,1407 ****
--- 1408,1444 ----
  	return PLPGSQL_RC_OK;
  }
  
+ /*-----------
+  * case_stmt				
+  *
+  *
+  *-----------
+  */
+ static int
+ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
+ {
+ 	bool	isnull;
+ 	int	offset;
+ 	
+ 	offset = exec_eval_integer(estate, stmt->case_expr, &isnull);
+ 
+ 	if (isnull)
+ 	{
+ 		/* else path ToDo exception CASE_NOT_FOUND */
+ 		if (stmt->stmts_array[0] != NULL)
+ 			return exec_stmts(estate, stmt->stmts_array[0]);
+ 		else
+ 			ereport(ERROR, 
+ 				    (errcode(MAKE_SQLSTATE('2','0','0','0','0')),
+ 		    		     errmsg("case not found")));	
+ 	}
+ 
+ 	if (offset < 1 || offset >= stmt->npaths)
+ 		elog(ERROR, "unexpected value of controll CASE expression %d", offset);
+ 
+ 	return exec_stmts(estate, stmt->stmts_array[offset]);
+ }
+ 
  
  /* ----------
   * exec_stmt_loop			Loop over statements until
***************
*** 5067,5069 ****
--- 5104,5107 ----
  		var->freeval = false;
  	}
  }
+ 
*** ./pl_funcs.c.orig	2008-03-29 22:57:58.000000000 +0100
--- ./pl_funcs.c	2008-03-29 23:31:16.000000000 +0100
***************
*** 508,513 ****
--- 508,515 ----
  			return "CLOSE";
  		case PLPGSQL_STMT_PERFORM:
  			return "PERFORM";
+ 		case PLPGSQL_STMT_CASE:
+ 			return "CASE";
  	}
  
  	return "unknown";
***************
*** 543,548 ****
--- 545,551 ----
  static void dump_close(PLpgSQL_stmt_close *stmt);
  static void dump_perform(PLpgSQL_stmt_perform *stmt);
  static void dump_expr(PLpgSQL_expr *expr);
+ static void dump_case(PLpgSQL_stmt_case *stmt);
  
  static void
  dump_ind(void)
***************
*** 619,624 ****
--- 622,630 ----
  		case PLPGSQL_STMT_PERFORM:
  			dump_perform((PLpgSQL_stmt_perform *) stmt);
  			break;
+ 		case PLPGSQL_STMT_CASE:
+ 			dump_case((PLpgSQL_stmt_case *) stmt);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
  			break;
***************
*** 707,712 ****
--- 713,752 ----
  	printf("    ENDIF\n");
  }
  
+ static void 
+ dump_case(PLpgSQL_stmt_case *stmt)
+ {
+ 	int 		i = 0;
+ 
+ 	dump_ind();
+ 	printf("CASE statement \n");
+ 	dump_indent += 6;
+ 	dump_ind();
+ 	dump_expr(stmt->case_expr);
+ 	printf("\n");
+ 	dump_ind();
+ 	printf("PATHS:\n");
+ 	for (i = 0; i < stmt->npaths; i++)
+ 	{
+ 		dump_ind();
+ 		if (i == 0 && stmt->stmts_array[0])
+ 		        printf("  {ELSE path}\n");
+ 		else if (i == 0 && !stmt->stmts_array[0])
+ 		{
+ 		        printf("  {ELSE path undefined}\n");
+ 			continue;
+ 		}	    
+ 		else
+ 		        printf("  {%d path}\n", i);
+ 		dump_indent += 2;
+ 		dump_stmts(stmt->stmts_array[i]);
+ 		dump_indent -= 2;
+ 	}
+ 	dump_indent -= 6;
+ 	dump_ind();
+ 	printf("    ENDCASE\n");
+ }
+ 
  static void
  dump_loop(PLpgSQL_stmt_loop *stmt)
  {
*** ./plpgsql.h.orig	2008-03-28 21:46:14.000000000 +0100
--- ./plpgsql.h	2008-03-29 19:03:11.000000000 +0100
***************
*** 92,98 ****
  	PLPGSQL_STMT_OPEN,
  	PLPGSQL_STMT_FETCH,
  	PLPGSQL_STMT_CLOSE,
! 	PLPGSQL_STMT_PERFORM
  };
  
  
--- 92,100 ----
  	PLPGSQL_STMT_OPEN,
  	PLPGSQL_STMT_FETCH,
  	PLPGSQL_STMT_CLOSE,
! 	PLPGSQL_STMT_PERFORM,
! 	PLPGSQL_STMT_WHEN_CLAUSE,
! 	PLPGSQL_STMT_CASE
  };
  
  
***************
*** 376,381 ****
--- 378,403 ----
  } PLpgSQL_stmt_if;
  
  
+ typedef struct					/* part of CASE statement */
+ {
+ 	int		cmd_type;
+ 	int		lineno;
+ 	PLpgSQL_expr *when_expr;
+ 	List	*when_expr_list;
+ 	List	*then_stmts;
+ } PLpgSQL_when_clause;
+ 
+ 
+ typedef struct					/* CASE statement */
+ {
+ 	int		cmd_type;
+ 	int		lineno;
+ 	int		npaths;
+ 	PLpgSQL_expr *case_expr;
+ 	List	*stmts_array[1];
+ } PLpgSQL_stmt_case;
+ 
+ 
  typedef struct
  {								/* Unconditional LOOP statement		*/
  	int			cmd_type;
*** ./scan.l.orig	2008-03-28 17:33:48.000000000 +0100
--- ./scan.l	2008-03-28 17:34:13.000000000 +0100
***************
*** 115,120 ****
--- 115,121 ----
  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;		}
-- 
Sent via pgsql-patches mailing list (pgsql-patches@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-patches

Reply via email to