>
>
> Fabien is pressed for time, so I've been speaking with him out-of-thread
> about how I should go about implementing it.
>
> The v1 patch will be \if <expr>, \elseif <expr>, \else, \endif, where
> <expr> will be naively evaluated via ParseVariableBool().
>
> \ifs and \endifs must be in the same "file" (each MainLoop will start a
> new if-stack). This is partly for sanity (you can see the pairings unless
> the programmer is off in \gset meta-land), partly for ease  of design (data
> structures live in MainLoop), but mostly because it would an absolute
> requirement if we ever got around to doing \while.
>
> I hope to have something ready for the next commitfest.
>
> As for the fate of \quit_if, I can see it both ways. On the one hand,
> it's super-simple, already written, and handy.
>
> On the other hand, it's easily replaced by
>
> \if <expr>
>     \q
> \endif
>
>
> So I'll leave that as a separate reviewable patch.
>
> As for loops, I don't think anyone was pushing for implementing \while
> now, only to have a decision about what it would look like and how it would
> work. There's a whole lot of recording infrastructure (the input could be a
> stream) needed to make it happen. Moreover, I think \gexec scratched a
> lot of the itches that would have been solved via a psql looping structure.
>


And here's the patch. I've changed the subject line and will be submitting
a new entry to the commitfest.
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 9915731..2c3fccd 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2007,6 +2007,83 @@ hello 10
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><literal>\if</literal> <replaceable 
class="parameter">expr</replaceable></term>
+        <term><literal>\elseif</literal> <replaceable 
class="parameter">expr</replaceable></term>
+        <term><literal>\else</literal></term>
+        <term><literal>\endif</literal></term>
+        <listitem>
+        <para>
+        This group of commands implements nestable conditional blocks, like
+        this:
+        </para>
+<programlisting>
+\if true
+    \if 1
+        \if yes
+            \if on
+                \echo 'all true'
+all true
+            \endif
+        \endif
+    \endif
+\endif
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+    \echo 'all false'
+all false
+\endif
+\if true
+\else
+    \echo 'should not print #1'
+\endif
+\if false
+\elseif true
+\else
+    \echo 'should not print #2'
+\endif
+</programlisting>
+        <para>
+        Conditional blocks must begin with a <command>\if</command> and end
+        with an <command>\endif</command>, and the pairs must be found in
+        the same source file. If an EOF is reached on the main file or an
+        <command>\include</command>-ed file before all 
+        <command>\if</command>-<command>\endif</command> are matched, then
+        psql will raise an error.
+        </para>
+        <para>
+        The <command>\if</command> and <command>\elseif</command> commands
+        read the rest of the line and evaluate it as a boolean expression.
+        Currently, expressions are limited to the same values allowed for
+        other option booleans (true, false, 1, 0, on, off, yes, no, etc).
+        </para>
+        <para>
+        Queries within a false branch of a conditional block will not be
+        sent to the server.
+        </para>
+        <para>
+        Non-conditional <command>\</command>-commands within a false branch
+        of a conditional block will not be evaluated for correctness. The
+        command will be ignored along with all remaining input to the end
+        of the line.
+        </para>
+        <para>
+        Expressions on <command>\if</command> and <command>\elseif</command>
+        commands within a false branch of a conditional block will not be
+        evaluated.
+        </para>
+        <para>
+        A conditional block can at most one <command>\else</command> command.
+        </para>
+        <para>
+        The <command>\elseif</command> command cannot follow the
+        <command>\else</command> command.
+        </para>
+        </listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><literal>\ir</literal> or <literal>\include_relative</literal> 
<replaceable class="parameter">filename</replaceable></term>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4139b77..d4e0bb8 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -49,6 +49,7 @@
 #include "psqlscanslash.h"
 #include "settings.h"
 #include "variables.h"
+#include "fe_utils/psqlscan_int.h"
 
 /*
  * Editable database object types.
@@ -132,7 +133,7 @@ HandleSlashCmds(PsqlScanState scan_state,
                status = PSQL_CMD_ERROR;
        }
 
-       if (status != PSQL_CMD_ERROR)
+       if ((status != PSQL_CMD_ERROR) && psqlscan_branch_active(scan_state))
        {
                /* eat any remaining arguments after a valid command */
                /* note we suppress evaluation of backticks here */
@@ -194,6 +195,32 @@ read_connect_arg(PsqlScanState scan_state)
        return result;
 }
 
+/*
+ * Read and interpret argument as a boolean expression.
+ */
+static bool
+read_boolean_expression(PsqlScanState scan_state, const char *action)
+{
+       bool    result = false;
+       char    *expr = psql_scan_slash_option(scan_state,
+                                                                               
        OT_NORMAL, NULL, false);
+       if (expr)
+       {
+               if (ParseVariableBool(expr, action))
+                       result = true;
+               free(expr);
+       }
+       return result;
+}
+
+static bool
+is_branching_command(const char *cmd)
+{
+       return ((strcmp(cmd, "if") == 0 || \
+                       strcmp(cmd, "elseif") == 0 || \
+                       strcmp(cmd, "else") == 0 || \
+                       strcmp(cmd, "endif") == 0));
+}
 
 /*
  * Subroutine to actually try to execute a backslash command.
@@ -207,6 +234,14 @@ exec_command(const char *cmd,
                                                                 * failed */
        backslashResult status = PSQL_CMD_SKIP_LINE;
 
+       if (!psqlscan_branch_active(scan_state) && !is_branching_command(cmd) )
+       {
+               /* Continue with an empty buffer as if the command were never 
read */
+               resetPQExpBuffer(query_buf); /* TODO: is this needed? */
+               psql_scan_reset(scan_state); /* TODO: is this needed or just 
for interactive? */
+               return status;
+       }
+
        /*
         * \a -- toggle field alignment This makes little sense but we keep it
         * around.
@@ -984,6 +1019,84 @@ exec_command(const char *cmd,
                }
        }
 
+       else if (strcmp(cmd, "if") == 0)
+       {
+               ifState new_if_state = IFSTATE_IGNORED;
+               if (psqlscan_branch_active(scan_state))
+               {
+                       if (read_boolean_expression(scan_state, "\\if"))
+                               new_if_state = IFSTATE_TRUE;
+                       else
+                               new_if_state = IFSTATE_FALSE;
+               }
+               psqlscan_branch_push(scan_state,new_if_state);
+               psql_scan_reset(scan_state);
+       }
+
+       else if (strcmp(cmd, "elseif") == 0)
+       {
+               if (psqlscan_branch_empty(scan_state))
+                       psql_error("encountered un-matched \\elseif\n");
+
+               switch (psqlscan_branch_get_state(scan_state))
+               {
+                       case IFSTATE_IGNORED:
+                               /* inactive branch, do nothing */
+                               break;
+                       case IFSTATE_TRUE:
+                               /* just finished true section of active branch 
*/
+                               psqlscan_branch_set_state(scan_state, 
IFSTATE_IGNORED);
+                               break;
+                       case IFSTATE_FALSE:
+                               /* determine if this section is true or not */
+                               if (read_boolean_expression(scan_state, 
"\\elseif"))
+                                       psqlscan_branch_set_state(scan_state, 
IFSTATE_TRUE);
+                               break;
+                       case IFSTATE_ELSE_TRUE:
+                       case IFSTATE_ELSE_FALSE:
+                               psql_error("encountered \\elseif after 
\\else\n");
+                               break;
+                       default:
+                               break;
+               }
+               psql_scan_reset(scan_state);
+       }
+
+       else if (strcmp(cmd, "else") == 0)
+       {
+               if (psqlscan_branch_empty(scan_state))
+                       psql_error("encountered un-matched \\else\n");
+
+               switch (psqlscan_branch_get_state(scan_state))
+               {
+                       case IFSTATE_TRUE:
+                               /* just finished true section of active branch 
*/
+                       case IFSTATE_IGNORED:
+                               /* whole branch was inactive */
+                               psqlscan_branch_set_state(scan_state, 
IFSTATE_ELSE_FALSE);
+                               break;
+                       case IFSTATE_FALSE:
+                               /* just finished true section of active branch 
*/
+                               psqlscan_branch_set_state(scan_state, 
IFSTATE_ELSE_TRUE);
+                               break;
+                       case IFSTATE_ELSE_TRUE:
+                       case IFSTATE_ELSE_FALSE:
+                               psql_error("encountered \\else after \\else\n");
+                               break;
+                       default:
+                               break;
+               }
+               psql_scan_reset(scan_state);
+       }
+
+       else if (strcmp(cmd, "endif") == 0)
+       {
+               if (psqlscan_branch_empty(scan_state))
+                       psql_error("encountered un-matched \\endif\n");
+               psqlscan_branch_end_state(scan_state);
+               psql_scan_reset(scan_state);
+       }
+
        /* \l is list databases */
        else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
                         strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index bb306a4..8f12e55 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -15,7 +15,7 @@
 #include "settings.h"
 
 #include "mb/pg_wchar.h"
-
+#include "fe_utils/psqlscan_int.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -23,7 +23,6 @@ const PsqlScanCallbacks psqlscan_callbacks = {
        psql_error
 };
 
-
 /*
  * Main processing loop for reading lines of input
  *     and sending them to the backend.
@@ -51,6 +50,9 @@ MainLoop(FILE *source)
        volatile int count_eof = 0;
        volatile bool die_on_error = false;
 
+       /* only needed at the end to detect unbalanced ifs in scan_state */
+       bool if_endifs_balanced = true;
+
        /* Save the prior command source */
        FILE       *prev_cmd_source;
        bool            prev_cmd_interactive;
@@ -285,21 +287,28 @@ MainLoop(FILE *source)
                        if (scan_result == PSCAN_SEMICOLON ||
                                (scan_result == PSCAN_EOL && pset.singleline))
                        {
-                               /*
-                                * Save query in history.  We use history_buf 
to accumulate
-                                * multi-line queries into a single history 
entry.
-                                */
-                               if (pset.cur_cmd_interactive && 
!line_saved_in_history)
+                               if (psqlscan_branch_active(scan_state))
                                {
-                                       pg_append_history(line, history_buf);
-                                       pg_send_history(history_buf);
-                                       line_saved_in_history = true;
+                                       /*
+                                        * Save query in history.  We use 
history_buf to accumulate
+                                        * multi-line queries into a single 
history entry.
+                                        */
+                                       if (pset.cur_cmd_interactive && 
!line_saved_in_history)
+                                       {
+                                               pg_append_history(line, 
history_buf);
+                                               pg_send_history(history_buf);
+                                               line_saved_in_history = true;
+                                       }
+
+                                       /* execute query */
+                                       success = SendQuery(query_buf->data);
                                }
+                               else
+                                       success = true;
 
-                               /* execute query */
-                               success = SendQuery(query_buf->data);
                                slashCmdStatus = success ? PSQL_CMD_SEND : 
PSQL_CMD_ERROR;
                                pset.stmt_lineno = 1;
+                               slashCmdStatus = success ? PSQL_CMD_SEND : 
PSQL_CMD_ERROR;
 
                                /* transfer query to previous_buf by 
pointer-swapping */
                                {
@@ -352,21 +361,28 @@ MainLoop(FILE *source)
                                if ((slashCmdStatus == PSQL_CMD_SEND || 
slashCmdStatus == PSQL_CMD_NEWEDIT) &&
                                        query_buf->len == 0)
                                {
+                                       /* TODO check if-then-skip-state */
                                        /* copy previous buffer to current for 
handling */
                                        appendPQExpBufferStr(query_buf, 
previous_buf->data);
                                }
 
                                if (slashCmdStatus == PSQL_CMD_SEND)
                                {
-                                       success = SendQuery(query_buf->data);
-
-                                       /* transfer query to previous_buf by 
pointer-swapping */
+                                       if (psqlscan_branch_active(scan_state))
                                        {
-                                               PQExpBuffer swap_buf = 
previous_buf;
+                                               success = 
SendQuery(query_buf->data);
 
-                                               previous_buf = query_buf;
-                                               query_buf = swap_buf;
+                                               /* transfer query to 
previous_buf by pointer-swapping */
+                                               {
+                                                       PQExpBuffer swap_buf = 
previous_buf;
+
+                                                       previous_buf = 
query_buf;
+                                                       query_buf = swap_buf;
+                                               }
                                        }
+                                       else
+                                               success = true;
+
                                        resetPQExpBuffer(query_buf);
 
                                        /* flush any paren nesting info after 
forced send */
@@ -425,12 +441,17 @@ MainLoop(FILE *source)
        if (query_buf->len > 0 && !pset.cur_cmd_interactive &&
                successResult == EXIT_SUCCESS)
        {
-               /* save query in history */
-               if (pset.cur_cmd_interactive)
-                       pg_send_history(history_buf);
+               if (psqlscan_branch_active(scan_state))
+               {
+                       /* save query in history */
+                       if (pset.cur_cmd_interactive)
+                               pg_send_history(history_buf);
 
-               /* execute query */
-               success = SendQuery(query_buf->data);
+                       /* execute query */
+                       success = SendQuery(query_buf->data);
+               }
+               else
+                       success = true;
 
                if (!success && die_on_error)
                        successResult = EXIT_USER;
@@ -451,11 +472,17 @@ MainLoop(FILE *source)
        destroyPQExpBuffer(previous_buf);
        destroyPQExpBuffer(history_buf);
 
+       if (slashCmdStatus != PSQL_CMD_TERMINATE)
+               if_endifs_balanced = psqlscan_branch_empty(scan_state);
+
        psql_scan_destroy(scan_state);
 
        pset.cur_cmd_source = prev_cmd_source;
        pset.cur_cmd_interactive = prev_cmd_interactive;
        pset.lineno = prev_lineno;
 
+       if (! if_endifs_balanced )
+               psql_error("found EOF before closing \\endif(s)\n");
+
        return successResult;
 }      /* MainLoop() */
diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h
index 228a5e0..47f4c32 100644
--- a/src/bin/psql/mainloop.h
+++ b/src/bin/psql/mainloop.h
@@ -14,4 +14,5 @@ extern const PsqlScanCallbacks psqlscan_callbacks;
 
 extern int     MainLoop(FILE *source);
 
+
 #endif   /* MAINLOOP_H */
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 1b29341..881c4f8 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -904,6 +904,9 @@ psql_scan_create(const PsqlScanCallbacks *callbacks)
 
        psql_scan_reset(state);
 
+       state->branch_stack = NULL;
+       state->branch_block_active = true;
+
        return state;
 }
 
@@ -919,6 +922,13 @@ psql_scan_destroy(PsqlScanState state)
 
        yylex_destroy(state->scanner);
 
+       while (state->branch_stack != NULL)
+       {
+               IfStackElem *p = state->branch_stack;
+               state->branch_stack = state->branch_stack->next;
+               free(p);
+       }
+
        free(state);
 }
 
@@ -1426,3 +1436,103 @@ psqlscan_escape_variable(PsqlScanState state, const 
char *txt, int len,
                psqlscan_emit(state, txt, len);
        }
 }
+
+/*
+ * psqlscan_branch_empty
+ *
+ * True if there are no active \if-structures
+ */
+bool
+psqlscan_branch_empty(PsqlScanState state)
+{
+       return (state->branch_stack == NULL);
+}
+
+/*
+ * psqlscan_branch_active
+ *
+ * True if the current \if-block (if any) is true and queries/commands
+ * should be executed.
+ */
+bool
+psqlscan_branch_active(PsqlScanState state)
+{
+       return state->branch_block_active;
+}
+
+/*
+ * Fetch the current state of the top of the stack
+ */
+ifState
+psqlscan_branch_get_state(PsqlScanState state)
+{
+       if (psqlscan_branch_empty(state))
+               return IFSTATE_NONE;
+       return state->branch_stack->if_state;
+}
+
+/*
+ * psqlscan_branch_update_active
+ *
+ * Scan the branch_stack to determine whether the next statements
+ * can execute or should be skipped. Cache this result in
+ * branch_block_active.
+ */
+static void
+psqlscan_branch_update_active(PsqlScanState state)
+{
+       ifState s = psqlscan_branch_get_state(state);
+       state->branch_block_active  = ( (s == IFSTATE_NONE) ||
+                                                                       (s == 
IFSTATE_TRUE) ||
+                                                                       (s == 
IFSTATE_ELSE_TRUE));
+}
+
+/*
+ * psqlscan_branch_push
+ *
+ * Create a new \if branch.
+ */
+bool
+psqlscan_branch_push(PsqlScanState state, ifState new_state)
+{
+       IfStackElem *p = pg_malloc0(sizeof(IfStackElem));
+       p->if_state = new_state;
+       p->next = state->branch_stack;
+       state->branch_stack = p;
+       psqlscan_branch_update_active(state);
+       return true;
+}
+
+/*
+ * psqlscan_branch_set_state
+ *
+ * Change the state of the topmost branch.
+ * Returns false if there was branch state to set.
+ */
+bool
+psqlscan_branch_set_state(PsqlScanState state, ifState new_state)
+{
+       if (psqlscan_branch_empty(state))
+               return false;
+       state->branch_stack->if_state = new_state;
+       psqlscan_branch_update_active(state);
+       return true;
+}
+
+/*
+ * psqlscan_branch_end_state
+ * 
+ * Destroy the topmost branch because and \endif was encountered.
+ * Returns false if there was no branch to end.
+ */
+bool
+psqlscan_branch_end_state(PsqlScanState state)
+{
+       IfStackElem *p = state->branch_stack;
+       if (!p)
+               return false;
+       state->branch_stack = state->branch_stack->next;
+       free(p);
+       psqlscan_branch_update_active(state);
+       return true;
+}
diff --git a/src/include/fe_utils/psqlscan_int.h 
b/src/include/fe_utils/psqlscan_int.h
index 0fddc7a..e9773ca 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -75,6 +75,30 @@ typedef struct StackElem
        struct StackElem *next;
 } StackElem;
 
+typedef enum _ifState
+{
+       IFSTATE_NONE = 0,       /* Not currently in an \if block */
+       IFSTATE_TRUE,           /* currently in an \if or \elseif which is true
+                                                * and all parent branches (if 
any) are true */
+       IFSTATE_FALSE,          /* currently in an \if or \elseif which is false
+                                                * but no true branch has yet 
been seen,
+                                                * and all parent branches (if 
any) are true */
+       IFSTATE_IGNORED,        /* currently in an \elseif which follows a true 
\if
+                                                * or the whole \if is a child 
of a false parent */
+       IFSTATE_ELSE_TRUE,      /* currently in an \else which is true
+                                                * and all parent branches (if 
any) are true */
+       IFSTATE_ELSE_FALSE      /* currently in an \else which is false or 
ignored */
+} ifState;
+
+/*
+ * The state of nested ifs is stored in a stack.
+ */
+typedef struct IfStackElem
+{
+       ifState         if_state;
+       struct IfStackElem *next;
+} IfStackElem;
+
 /*
  * All working state of the lexer must be stored in PsqlScanStateData
  * between calls.  This allows us to have multiple open lexer operations,
@@ -118,6 +142,12 @@ typedef struct PsqlScanStateData
         * Callback functions provided by the program making use of the lexer.
         */
        const PsqlScanCallbacks *callbacks;
+
+       /*
+        * \if branch state variables
+        */
+       IfStackElem *branch_stack;
+       bool branch_block_active;
 } PsqlScanStateData;
 
 
@@ -141,4 +171,21 @@ extern void psqlscan_escape_variable(PsqlScanState state,
                                                 const char *txt, int len,
                                                 bool as_ident);
 
+/*
+ * branching commands
+ */
+extern bool psqlscan_branch_empty(PsqlScanState state);
+
+extern bool psqlscan_branch_active(PsqlScanState state);
+
+extern ifState psqlscan_branch_get_state(PsqlScanState state);
+
+extern bool psqlscan_branch_push(PsqlScanState state,
+                                                                       ifState 
new_state);
+
+extern bool psqlscan_branch_set_state(PsqlScanState state,
+                                                                       ifState 
new_state);
+
+extern bool psqlscan_branch_end_state(PsqlScanState state);
+
 #endif   /* PSQLSCAN_INT_H */
diff --git a/src/test/regress/expected/psql.out 
b/src/test/regress/expected/psql.out
index 464436a..7492a92 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2686,6 +2686,33 @@ deallocate q;
 \pset format aligned
 \pset expanded off
 \pset border 1
+\if true
+       \if 1
+               \if yes
+                       \if on
+                               \echo 'all true'
+all true
+                       \endif
+               \endif
+       \endif
+\endif
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+       \echo 'all false'
+all false
+\endif
+\if true
+\else
+       \echo 'should not print #1'
+\endif
+\if false
+\elseif true
+\else
+       \echo 'should not print #2'
+\endif
 -- SHOW_CONTEXT
 \set SHOW_CONTEXT never
 do $$
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 900aa7e..ef1be30 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -357,6 +357,36 @@ deallocate q;
 \pset expanded off
 \pset border 1
 
+\if true
+       \if 1
+               \if yes
+                       \if on
+                               \echo 'all true'
+                       \endif
+               \endif
+       \endif
+\endif
+
+\if false
+\elseif 0
+\elseif no
+\elseif off
+\else
+       \echo 'all false'
+\endif
+
+\if true
+\else
+       \echo 'should not print #1'
+\endif
+
+\if false
+\elseif true
+\else
+       \echo 'should not print #2'
+\endif
+
+
 -- SHOW_CONTEXT
 
 \set SHOW_CONTEXT never
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to