Thread: Grammar-problems with pl/pgsql in gram.y
Hi, I want to learn, how the pl/plsql-parser/compiler works. Therefore I planned to implement a simple ELSIF, like oracle does. I added the following K_ELSIF branch to gram.y, in the hope that, when ELSIF is parsed, simply another if-structure in inserted. --------------------------------------------------------- stmt_else :{ PLpgSQL_stmts *new; new = malloc(sizeof(PLpgSQL_stmts)); memset(new, 0, sizeof(PLpgSQL_stmts)); $$ = new; elog(NOTICE, "empty ELSE detected");}| K_ELSIF expr_until_then proc_sect stmt_else{ PLpgSQL_stmt_if *new; elog(NOTICE, "ELSIF detected"); new = malloc(sizeof(PLpgSQL_stmt_if)); memset(new,0, sizeof(PLpgSQL_stmt_if)); new->cmd_type = PLPGSQL_STMT_IF; // new->lineno = $2; new->cond = $2; new->true_body = $3; new->false_body = $4; $$ = (PLpgSQL_stmts *)new; }| K_ELSE proc_sect{ $$ = $2; elog(NOTICE, "ELSE detected (%s)", strdup(yytext));}; -------------------------------------------------------------- A testprocedure, which looks like that: -------------------------------------------------------------- DECLARE iPar1PI ALIAS FOR $1; iTmp integer; iResult varchar; BEGIN iTmp = iPar1PI; raise notice '1.0'; if iTmp IS NULL then raise notice '2.0'; iResult = 'Echt NULL'; else if iTmp = 0 then raise notice '2.1.0'; iResult = 'Null (0)'; elsif (iTmp < 0) THEN raise notice '2.1.1'; iResult = 'Negativ'; elsif (iTmp > 0) THEN raise notice '2.1.2'; iResult = 'Positiv'; else raise notice '2.1.3'; iResult = 'Gibts nicht!'; end if; end if; raise notice'3.0'; return iResult; END; -------------------------------------------------------------- is dumped in this way ... -------------------------------------------------------------- Execution tree of successfully compiled PL/pgSQL function kr_test: Functions data area: entry 0: VAR $1 type int4 (typoid 23) atttypmod -1 entry 1: VAR found typebool (typoid 16) atttypmod -1 entry 2: VAR itmp type int4 (typoid 23) atttypmod -1 entry 3: VAR iresult type varchar (typoid 1043) atttypmod -1 Functions statements: 8:BLOCK <<*unnamed*>>10: ASSIGN var 2 := 'SELECT $1 {$1=0}'12: RAISE ''1.0''14: IF 'SELECT $1 IS NULL {$1=2}' THEN15: RAISE ''2.0''16: ASSIGN var 3 := 'SELECT 'Echt NULL'' ELSE19: IF 'SELECT $1 = 0 {$1=2}' THEN20: RAISE ''2.1.0''21: ASSIGN var 3 := 'SELECT 'Null (0)'' ELSE ENDIF ENDIF37: RAISE ''3.0''39: RETURN 'SELECT $1 {$1=3}' END -- *unnamed* End of execution tree of function kr_test -------------------------------------------------------------- So my question is: Why does my inserted PLpgSQL_stmt_if *new; is not executed, because I do it in the same way like stmt_if does? Who can halp me (Maybe Jan??) Regards, Klaus
Klaus Reger wrote: > Hi, > > I want to learn, how the pl/plsql-parser/compiler works. Therefore I planned > to implement a simple ELSIF, like oracle does. > > I added the following K_ELSIF branch to gram.y, in the hope that, when ELSIF > is parsed, simply another if-structure in inserted. > > --------------------------------------------------------- > stmt_else : > { > PLpgSQL_stmts *new; > > new = malloc(sizeof(PLpgSQL_stmts)); > memset(new, 0, sizeof(PLpgSQL_stmts)); > $$ = new; > elog(NOTICE, "empty ELSE detected"); > } > | K_ELSIF expr_until_then proc_sect stmt_else > { > PLpgSQL_stmt_if *new; > elog(NOTICE, "ELSIF detected"); > new = malloc(sizeof(PLpgSQL_stmt_if)); > memset(new, 0, sizeof(PLpgSQL_stmt_if)); > new->cmd_type = PLPGSQL_STMT_IF; > // new->lineno = $2; > new->cond = $2; > new->true_body = $3; > new->false_body = $4; > > $$ = (PLpgSQL_stmts *)new; Here it is. stmt_else is defined as type <stmts>, not <stmt>. The PLpgSQL_stmt_if struct has a condition query and two statement lists (type <stmts>). You're trying to put a single statement into the else part instead of a listof statements. Maybe it'll work if you surround it with another PLpgSQL_stmts struct where your new PLpgSQL_stmt_if is the only statement in it's list. Since I have some bigger work outstanding for PL/pgSQL, send the resulting patch (if you get it to work) directly to me. Jan -- #======================================================================# # It's easier to get forgiveness for being wrong than for being right. # # Let's break this rule - forgive me. # #================================================== JanWieck@Yahoo.com # _________________________________________________________ Do You Yahoo!? Get your free @yahoo.com address at http://mail.yahoo.com
Am Mittwoch, 16. Mai 2001 16:10 schrieb Jan Wieck: > Here it is. stmt_else is defined as type <stmts>, not <stmt>. > The PLpgSQL_stmt_if struct has a condition query and two > statement lists (type <stmts>). You're trying to put a single > statement into the else part instead of a list of statements. Thank you for the hint! That was it. > Maybe it'll work if you surround it with another > PLpgSQL_stmts struct where your new PLpgSQL_stmt_if is the > only statement in it's list. Since I have some bigger work > outstanding for PL/pgSQL, send the resulting patch (if you > get it to work) directly to me. The patch follows this message. May you tell me what kind of work it is, cause I'm so curous :-). By the way, the next thing I try is a EXCEPTION WHEN OTHER-clause, like in Oracle. Let's look if I'm successful. Ciao, Klaus ---------------------------------------------------------------------------- diff -Naurb src/gram.y src.elsif/gram.y --- src/gram.y Wed May 16 18:00:53 2001 +++ src.elsif/gram.y Wed May 16 17:39:19 2001 @@ -147,6 +147,7 @@%token K_DIAGNOSTICS%token K_DOTDOT%token K_ELSE +%token K_ELSIF%token K_END%token K_EXCEPTION%token K_EXECUTE @@ -544,6 +545,7 @@ new->stmts[0] = (struct PLpgSQL_stmt *)$1; $$ = new; + } ; @@ -721,8 +723,53 @@ memset(new, 0, sizeof(PLpgSQL_stmts)); $$ = new; } + | K_ELSIF lno expr_until_then proc_sect stmt_else + { + /* + * Translate the structure: into: + * + * IF c1 THEN IF c1 THEN + * ... ... + * ELSIF c2 THEN ELSE + * IF c2 THEN + * ... ... + * ELSE ELSE + * ... ... + * END IF END IF + * END IF + * + */ + + PLpgSQL_stmts *new; + PLpgSQL_stmt_if *new_if; + + /* first create a new if-statement */ + new_if = malloc(sizeof(PLpgSQL_stmt_if)); + memset(new_if, 0, sizeof(PLpgSQL_stmt_if)); + + new_if->cmd_type = PLPGSQL_STMT_IF; + new_if->lineno = $2; + new_if->cond = $3; + new_if->true_body = $4; + new_if->false_body = $5; + + /* this is a 'container' for the if-statement */ + new = malloc(sizeof(PLpgSQL_stmts)); + memset(new, 0, sizeof(PLpgSQL_stmts)); + + new->stmts_alloc = 64; + new->stmts_used = 1; + new->stmts = malloc(sizeof(PLpgSQL_stmt *) * new->stmts_alloc); + new->stmts[0] = (struct PLpgSQL_stmt *)new_if; + + $$ = new; + + } + | K_ELSE proc_sect - { $$ = $2; } + { + $$ = $2; + } ;stmt_loop : opt_label K_LOOP lno loop_body @@ -1271,7 +1318,6 @@ break; } } - expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int)); expr->dtype = PLPGSQL_DTYPE_EXPR; expr->query = strdup(plpgsql_dstring_get(&ds)); diff -Naurb src/scan.l src.elsif/scan.l --- src/scan.l Wed May 16 18:01:36 2001 +++ src.elsif/scan.l Tue May 15 12:49:43 2001 @@ -99,6 +99,7 @@default { return K_DEFAULT; }diagnostics { return K_DIAGNOSTICS; }else { return K_ELSE; } +elsif { return K_ELSIF; }end { return K_END; }exception { returnK_EXCEPTION; }execute { return K_EXECUTE; }
Klaus Reger wrote: > Am Mittwoch, 16. Mai 2001 16:10 schrieb Jan Wieck: > > Here it is. stmt_else is defined as type <stmts>, not <stmt>. > > The PLpgSQL_stmt_if struct has a condition query and two > > statement lists (type <stmts>). You're trying to put a single > > statement into the else part instead of a list of statements. > Thank you for the hint! That was it. > > > Maybe it'll work if you surround it with another > > PLpgSQL_stmts struct where your new PLpgSQL_stmt_if is the > > only statement in it's list. Since I have some bigger work > > outstanding for PL/pgSQL, send the resulting patch (if you > > get it to work) directly to me. > The patch follows this message. May you tell me what kind of work it is, > cause I'm so curous :-). By the way, the next thing I try is a > EXCEPTION WHEN OTHER-clause, like in Oracle. Let's look if I'm successful. complete CURSOR support. With some enhancements in SPI plus a little fix in ProcessPortalFetch() I have up to now Explicit CURSOR: DECLARE <cursor_name> CURSOR [(<arg> <type> [, ...])] IS <select_statement>; The select statement can use any so far declared variable or functions arguments in additionto the optional cursor arguments. These will be evaluated at OPEN time. There is a new datatype 'refcursor'. The above declaration will create a local variable of thattype with a default "value" of the variables name. This "value" will be used for the global cursors name during OPEN. BEGIN OPEN <cursor_name> [(<expression> [, ...])]; FETCH <cursor_name> INTO {<record> | <row> | <var> [, ...]}; CLOSE <cursor_name>; The FETCH command sets the global FOUND variable, so a typical loop over a resultset looks like LOOP FETCH mycur INTO myrec; EXIT WHEN NOT FOUND; --Process the row END LOOP; Reference CURSOR: DECLARE <cursor_name> REFCURSOR [ := <string> ]; BEGIN OPEN <cursor_name> FOR <select_statement>; OPEN <cursor_name> FOR EXECUTE <string>; The new datatype 'refcursor' can be used to pass any cursor around between functions and the application. Cursorsused inside of functions only don't need transaction blocks. Of course, they'll not survive the current transactionsend, but if funcA() creates a cursor and then calls funcB(refcursor) with it, there does not need to bea transaction block around it. What I need to do now is fixing some memory allocation issues in PL/pgSQL and move FOR loops to use implicitcursors internally. Your patch looks like it doesn't conflict with any of my work. I'll commit it soon. For the EXCEPTIONS thing, well that's another issue. We could of course simulate/generate some of the exceptions like "no data found" and the other one I forgot (telling that a SELECT INTO returned multiple results). But we cannot catch a duplicate key error, a division by zero or a referential integrity violation, because when it happensa statement is half way done and the only way to cleanup is rolling back the entire transaction (for now, Vadimis working on savepoints). So I suggest you don't spend much of your time before we have them. Jan -- #======================================================================# # It's easier to get forgiveness for being wrong than for being right. # # Let's break this rule - forgive me. # #================================================== JanWieck@Yahoo.com # _________________________________________________________ Do You Yahoo!? Get your free @yahoo.com address at http://mail.yahoo.com
Am Mittwoch, 16. Mai 2001 21:29 schrieb Jan Wieck: > For the EXCEPTIONS thing, well that's another issue. We could > of course simulate/generate some of the exceptions like "no > data found" and the other one I forgot (telling that a SELECT > INTO returned multiple results). But we cannot catch a > duplicate key error, a division by zero or a referential > integrity violation, because when it happens a statement is > half way done and the only way to cleanup is rolling back the > entire transaction (for now, Vadim is working on savepoints). > So I suggest you don't spend much of your time before we have > them. OK, I understand. For the beginning I only would like to have a possibility, to catch any exception and create my own error handling, ignoring any transaction-stuff. Because I have to port Procedures from Oracle to PostgreSQL, I am looking, to imitate the way Oracle takes. As I understand with my actual knowledge, this would mean, that every(!) call of elog, which terminates the process, has to be caught. But this seems to great for a new Postgres-hacker, like I am. Or do you see any other possibility (maybe extending PLpgSQL_execstate)? CU, Klaus
Klaus Reger wrote: > Am Mittwoch, 16. Mai 2001 21:29 schrieb Jan Wieck: > > For the EXCEPTIONS thing, well that's another issue. We could > > of course simulate/generate some of the exceptions like "no > > data found" and the other one I forgot (telling that a SELECT > > INTO returned multiple results). But we cannot catch a > > duplicate key error, a division by zero or a referential > > integrity violation, because when it happens a statement is > > half way done and the only way to cleanup is rolling back the > > entire transaction (for now, Vadim is working on savepoints). > > So I suggest you don't spend much of your time before we have > > them. > OK, I understand. For the beginning I only would like to have a possibility, > to catch any exception and create my own error handling, ignoring any > transaction-stuff. Because I have to port Procedures from Oracle to > PostgreSQL, I am looking, to imitate the way Oracle takes. > > As I understand with my actual knowledge, this would mean, that every(!) call > of elog, which terminates the process, has to be caught. But this seems to > great for a new Postgres-hacker, like I am. Or do you see any other > possibility (maybe extending PLpgSQL_execstate)? Every(!) call to elog with ERROR (or more severe) level causes finally a longjump() back into the tcop mainloop. PL/pgSQL and PL/Tcl do catch it - PL/pgSQL to tell something on DEBUG level and PL/Tcl mainly to unwind the Tcl interpreters call stack. But the backend is in an inconsistent state at that time, andany subsequent call to access methods could cause unpredictable results up to complete database corruption.There is no other way right now than to go ahead and continue with transaction abort. Imitation of the Oracle way is something many ppl around here would appreciate, but not at the risk of corrupting the entire database - that's too high a price. That said, you'll have little to no chance of getting this feature applied to the CVS. Doing EXCEPTIONS requires savepoints and a real "back to statements start state" functionality. The recent approach of "simulating CURSOR" just on the PL grammar level without real cursor support in the SPI layer failed for exactly thesame reason. Before we know "how" to do it, we cannot decide on the exact appearence, because implementationdetails might "require" at least some difference to other databases. If we accept such a "fake" only approach, we introduce backward compatibility problems, since somebody will tell us for sure "I used the syntaxof 7.2 already and porting my +20K line application now ...". In PostgreSQL it's easier to get new *features* added than existing features ripped out - and people rely on that. Jan PS: Aber laß mal den Kopf nicht hängen, wir finden bestimmt was wo Du Dich austoben kannst :-) -- #======================================================================# # It's easier to get forgiveness for being wrong than for being right. # # Let's break this rule - forgive me. # #================================================== JanWieck@Yahoo.com # _________________________________________________________ Do You Yahoo!? Get your free @yahoo.com address at http://mail.yahoo.com
Klaus Reger wrote: > Am Mittwoch, 16. Mai 2001 16:10 schrieb Jan Wieck: > > Here it is. stmt_else is defined as type <stmts>, not <stmt>. > > The PLpgSQL_stmt_if struct has a condition query and two > > statement lists (type <stmts>). You're trying to put a single > > statement into the else part instead of a list of statements. > Thank you for the hint! That was it. > > > Maybe it'll work if you surround it with another > > PLpgSQL_stmts struct where your new PLpgSQL_stmt_if is the > > only statement in it's list. Since I have some bigger work > > outstanding for PL/pgSQL, send the resulting patch (if you > > get it to work) directly to me. > The patch follows this message. May you tell me what kind of work it is, > cause I'm so curous :-). By the way, the next thing I try is a > EXCEPTION WHEN OTHER-clause, like in Oracle. Let's look if I'm successful. Patch applied. Really a smart solution, just touching the gram.y and scan.l files and using the existing instruction code. Thanks for the contribution. What about a CASE ... WHEN ...? I think that could be implemented in a similar way. Don't do it right now, I might be ready to commit the cursor stuff early next week and it'll touch alot more than just the grammar and scanner. Jan -- #======================================================================# # It's easier to get forgiveness for being wrong than for being right. # # Let's break this rule - forgive me. # #================================================== JanWieck@Yahoo.com # _________________________________________________________ Do You Yahoo!? Get your free @yahoo.com address at http://mail.yahoo.com
Jan Wieck <JanWieck@Yahoo.com> writes: > Patch applied. How about some documentation? regards, tom lane
Am Samstag, 19. Mai 2001 00:07 schrieb Tom Lane: > Jan Wieck <JanWieck@Yahoo.com> writes: > > Patch applied. > > How about some documentation? > > regards, tom lane Good idea! It will follow. Regards, Klaus
Am Samstag, 19. Mai 2001 00:07 schrieben Sie: > How about some documentation? Her is some documentation. Because I don't have the tools and scripts installed, to format sgml, I never have written SGML-docs and my english is bad, please revisit and correct the additions. Thank you Klaus -- TWC GmbH Schlossbergring 9 79098 Freiburg i. Br. http://www.twc.de
Here is the patch I applied. I cleaned it up a bit. Thanks a lot. [ Charset ISO-8859-1 unsupported, converting... ] > Am Samstag, 19. Mai 2001 00:07 schrieben Sie: > > > How about some documentation? > > Her is some documentation. Because I don't have the tools and scripts > installed, to format sgml, I never have written SGML-docs and my english is > bad, please revisit and correct the additions. > > Thank you > > Klaus > > -- > TWC GmbH > Schlossbergring 9 > 79098 Freiburg i. Br. > http://www.twc.de [ Attachment, skipping... ] > > ---------------------------(end of broadcast)--------------------------- > TIP 2: you can get off all lists at once with the unregister command > (send "unregister YourEmailAddressHere" to majordomo@postgresql.org) -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 853-3000 + If your life is a hard drive, | 830 Blythe Avenue + Christ can be your backup. | Drexel Hill, Pennsylvania 19026 Index: plsql.sgml =================================================================== RCS file: /home/projects/pgsql/cvsroot/pgsql/doc/src/sgml/plsql.sgml,v retrieving revision 2.32 diff -w -b -i -B -c -r2.32 plsql.sgml *** plsql.sgml 2001/05/17 21:50:16 2.32 --- plsql.sgml 2001/05/22 12:38:49 *************** *** 880,891 **** <title>Conditional Control: IF statements</title> <para> ! <function>IF</function> statements let you take action ! according to certain conditions. PL/pgSQL has three forms of ! IF: IF-THEN, IF-THEN-ELSE, IF-THEN-ELSE IF. NOTE: All ! PL/pgSQL IF statements need a corresponding <function>END ! IF</function> statement. In ELSE-IF statements you need two: ! one for the first IF and one for the second (ELSE IF). </para> <variablelist> --- 880,890 ---- <title>Conditional Control: IF statements</title> <para> ! <function>IF</function> statements let you execute commands based on ! certain conditions. PL/PgSQL has four forms of IF: IF-THEN, IF-THEN-ELSE, ! IF-THEN-ELSE IF, IF-THEN-ELSIF-THEN-ELSE. NOTE: All PL/PgSQL IF statements need ! a corresponding <function>END IF</function> clause. With ELSE-IF statements, ! you need two: one for the first IF and one for the second (ELSE IF). </para> <variablelist> *************** *** 979,984 **** --- 978,1018 ---- </para> </listitem> </varlistentry> + + <varlistentry> + <term> + IF-THEN-ELSIF-ELSE + </term> + + <listitem> + <para> + IF-THEN-ELSIF-ELSE allows you test multiple conditions + in one statement. Internally it is handled as nested + IF-THEN-ELSE-IF-THEN commands. The optional ELSE + branch is executed when none of the conditions are met. + </para> + + <para> + Here is an example: + </para> + + <programlisting> + IF number = 0 THEN + result := ''zero''; + ELSIF number < 0 THEN + result := ''negative''; + ELSIF number > 0 THEN + result := ''negative''; + ELSE + -- now it seems to be NULL + result := ''NULL''; + END IF; + </programlisting> + </para> + </listitem> + </varlistentry> + + </variablelist> </sect3>