Decouple psqlscan.l from surrounding program.
authorTom Lane
Fri, 18 Mar 2016 19:05:49 +0000 (15:05 -0400)
committerTom Lane
Fri, 18 Mar 2016 19:05:59 +0000 (15:05 -0400)
Remove assorted external references from psqlscan.l in preparation for
making it usable by other frontend programs.  This mostly involves
getting rid of direct calls to psql_error() and GetVariable() in favor
of introducing a callback-functions struct to encapsulate variable
fetching and error printing.  In addition, pass the current encoding
and standard-strings status as additional parameters to psql_scan_setup
instead of looking directly at "pset" or calling additional functions.

I did not bother to change some references to psql_error that are in
functions that will soon migrate to a psql-specific backslash-command
lexer.  Other than that, this version of psqlscan.l is capable of
compiling standalone.  It still depends on assorted src/common functions
as well as some encoding-related libpq functions, but we expect that
all programs using it will be happy with those dependencies.

Kyotaro Horiguchi, somewhat editorialized on by me

src/bin/psql/common.c
src/bin/psql/common.h
src/bin/psql/mainloop.c
src/bin/psql/mainloop.h
src/bin/psql/psqlscan.h
src/bin/psql/psqlscan.l
src/bin/psql/startup.c

index 2cb2e9bb3b6da24626b8ce42de2fd94b541d2654..2b67a439da76481baade0273cf9ecb749854f62d 100644 (file)
@@ -107,6 +107,65 @@ setQFout(const char *fname)
 }
 
 
+/*
+ * Variable-fetching callback for flex lexer
+ *
+ * If the specified variable exists, return its value as a string (malloc'd
+ * and expected to be freed by the caller); else return NULL.
+ *
+ * If "escape" is true, return the value suitably quoted and escaped,
+ * as an identifier or string literal depending on "as_ident".
+ * (Failure in escaping should lead to returning NULL.)
+ */
+char *
+psql_get_variable(const char *varname, bool escape, bool as_ident)
+{
+   char       *result;
+   const char *value;
+
+   value = GetVariable(pset.vars, varname);
+   if (!value)
+       return NULL;
+
+   if (escape)
+   {
+       char       *escaped_value;
+
+       if (!pset.db)
+       {
+           psql_error("can't escape without active connection\n");
+           return NULL;
+       }
+
+       if (as_ident)
+           escaped_value =
+               PQescapeIdentifier(pset.db, value, strlen(value));
+       else
+           escaped_value =
+               PQescapeLiteral(pset.db, value, strlen(value));
+
+       if (escaped_value == NULL)
+       {
+           const char *error = PQerrorMessage(pset.db);
+
+           psql_error("%s", error);
+           return NULL;
+       }
+
+       /*
+        * Rather than complicate the lexer's API with a notion of which
+        * free() routine to use, just pay the price of an extra strdup().
+        */
+       result = pg_strdup(escaped_value);
+       PQfreemem(escaped_value);
+   }
+   else
+       result = pg_strdup(value);
+
+   return result;
+}
+
+
 /*
  * Error reporting for scripts. Errors should look like
  *  psql:filename:lineno: message
index ce7b93f9e5e1c80716b0e8c35980c50caa027d34..ba4c5699b3d65ef1f149e3e49354d3ac91b7ab73 100644 (file)
@@ -18,6 +18,8 @@
 extern bool openQueryOutputFile(const char *fname, FILE **fout, bool *is_pipe);
 extern bool setQFout(const char *fname);
 
+extern char *psql_get_variable(const char *varname, bool escape, bool as_ident);
+
 extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2);
 
 extern void NoticeProcessor(void *arg, const char *message);
index dadbd293971e3d323855e389817989d98c3b91c3..bade35139b3e3569fa85245347134ebf823f7ce5 100644 (file)
@@ -8,7 +8,6 @@
 #include "postgres_fe.h"
 #include "mainloop.h"
 
-
 #include "command.h"
 #include "common.h"
 #include "input.h"
 #include "mb/pg_wchar.h"
 
 
+/* callback functions for our flex lexer */
+const PsqlScanCallbacks psqlscan_callbacks = {
+   psql_get_variable,
+   psql_error
+};
+
+
 /*
  * Main processing loop for reading lines of input
  * and sending them to the backend.
@@ -61,7 +67,7 @@ MainLoop(FILE *source)
    pset.stmt_lineno = 1;
 
    /* Create working state */
-   scan_state = psql_scan_create();
+   scan_state = psql_scan_create(&psqlscan_callbacks);
 
    query_buf = createPQExpBuffer();
    previous_buf = createPQExpBuffer();
@@ -233,7 +239,8 @@ MainLoop(FILE *source)
        /*
         * Parse line, looking for command separators.
         */
-       psql_scan_setup(scan_state, line, strlen(line));
+       psql_scan_setup(scan_state, line, strlen(line),
+                       pset.encoding, standard_strings());
        success = true;
        line_saved_in_history = false;
 
@@ -373,7 +380,8 @@ MainLoop(FILE *source)
                    resetPQExpBuffer(query_buf);
                    /* reset parsing state since we are rescanning whole line */
                    psql_scan_reset(scan_state);
-                   psql_scan_setup(scan_state, line, strlen(line));
+                   psql_scan_setup(scan_state, line, strlen(line),
+                                   pset.encoding, standard_strings());
                    line_saved_in_history = false;
                    prompt_status = PROMPT_READY;
                }
index e6476ca7c6c4dd61f7a5265d90f110f51c2cef23..5ee8dc7f63fa756f756b9042ae7a7a3aefcc412a 100644 (file)
@@ -8,6 +8,10 @@
 #ifndef MAINLOOP_H
 #define MAINLOOP_H
 
+#include "psqlscan.h"
+
+extern const PsqlScanCallbacks psqlscan_callbacks;
+
 extern int MainLoop(FILE *source);
 
 #endif   /* MAINLOOP_H */
index 674ba69eda91ad1296cd53f827d1ce231be4f114..82c66dcdf9c1b15b4c9b554809a2834436dbaf46 100644 (file)
@@ -36,12 +36,23 @@ enum slash_option_type
    OT_NO_EVAL                  /* no expansion of backticks or variables */
 };
 
+/* Callback functions to be used by the lexer */
+typedef struct PsqlScanCallbacks
+{
+   /* Fetch value of a variable, as a pfree'able string; NULL if unknown */
+   /* This pointer can be NULL if no variable substitution is wanted */
+   char       *(*get_variable) (const char *varname, bool escape, bool as_ident);
+   /* Print an error message someplace appropriate */
+   void        (*write_error) (const char *fmt,...) pg_attribute_printf(1, 2);
+} PsqlScanCallbacks;
+
 
-extern PsqlScanState psql_scan_create(void);
+extern PsqlScanState psql_scan_create(const PsqlScanCallbacks *callbacks);
 extern void psql_scan_destroy(PsqlScanState state);
 
 extern void psql_scan_setup(PsqlScanState state,
-               const char *line, int line_len);
+               const char *line, int line_len,
+               int encoding, bool std_strings);
 extern void psql_scan_finish(PsqlScanState state);
 
 extern PsqlScanResult psql_scan(PsqlScanState state,
index bbe0172737c7fd585a31996a4663ab0f27493e18..b741ab8fc5d15b7605b72fab49ca072112420f05 100644 (file)
@@ -2,7 +2,7 @@
 /*-------------------------------------------------------------------------
  *
  * psqlscan.l
- *   lexical scanner for psql
+ *   lexical scanner for psql (and other frontend programs)
  *
  * This code is mainly needed to determine where the end of a SQL statement
  * is: we are looking for semicolons that are not within quotes, comments,
 
 #include "psqlscan.h"
 
-#include 
-
-#include "common.h"
-#include "settings.h"
-#include "variables.h"
+#include "libpq-fe.h"
 
 
 /*
@@ -83,6 +79,7 @@ typedef struct PsqlScanStateData
    /* safe_encoding, curline, refline are used by emit() to replace FFs */
    int         encoding;       /* encoding being used now */
    bool        safe_encoding;  /* is current encoding "safe"? */
+   bool        std_strings;    /* are string literals standard? */
    const char *curline;        /* actual flex input string for cur buf */
    const char *refline;        /* original data for cur buffer */
 
@@ -94,6 +91,11 @@ typedef struct PsqlScanStateData
    int         paren_depth;    /* depth of nesting in parentheses */
    int         xcdepth;        /* depth of nesting in slash-star comments */
    char       *dolqstart;      /* current $foo$ quote start string */
+
+   /*
+    * Callback functions provided by the program making use of the lexer.
+    */
+   const PsqlScanCallbacks *callbacks;
 } PsqlScanStateData;
 
 static PsqlScanState cur_state;    /* current state while active */
@@ -135,6 +137,7 @@ static void escape_variable(bool as_ident);
 %option nounput
 %option noyywrap
 %option warn
+%option prefix="psql_yy"
 
 /*
  * All of the following definitions and rules should exactly match
@@ -508,7 +511,7 @@ other           .
                }
 
 {xqstart}      {
-                   if (standard_strings())
+                   if (cur_state->std_strings)
                        BEGIN(xq);
                    else
                        BEGIN(xe);
@@ -737,10 +740,15 @@ other         .
 :{variable_char}+  {
                    /* Possible psql variable substitution */
                    char   *varname;
-                   const char *value;
+                   char   *value;
 
                    varname = extract_substring(yytext + 1, yyleng - 1);
-                   value = GetVariable(pset.vars, varname);
+                   if (cur_state->callbacks->get_variable)
+                       value = cur_state->callbacks->get_variable(varname,
+                                                                  false,
+                                                                  false);
+                   else
+                       value = NULL;
 
                    if (value)
                    {
@@ -748,8 +756,8 @@ other           .
                        if (var_is_current_source(cur_state, varname))
                        {
                            /* Recursive expansion --- don't go there */
-                           psql_error("skipping recursive expansion of variable \"%s\"\n",
-                                      varname);
+                           cur_state->callbacks->write_error("skipping recursive expansion of variable \"%s\"\n",
+                                                             varname);
                            /* Instead copy the string as is */
                            ECHO;
                        }
@@ -759,6 +767,7 @@ other           .
                            push_new_buffer(value, varname);
                            /* yy_scan_string already made buffer active */
                        }
+                       free(value);
                    }
                    else
                    {
@@ -1026,15 +1035,18 @@ other           .
 
 :{variable_char}+  {
                    /* Possible psql variable substitution */
-                   if (option_type == OT_NO_EVAL)
+                   if (option_type == OT_NO_EVAL ||
+                       cur_state->callbacks->get_variable == NULL)
                        ECHO;
                    else
                    {
                        char   *varname;
-                       const char *value;
+                       char   *value;
 
                        varname = extract_substring(yytext + 1, yyleng - 1);
-                       value = GetVariable(pset.vars, varname);
+                       value = cur_state->callbacks->get_variable(varname,
+                                                                  false,
+                                                                  false);
                        free(varname);
 
                        /*
@@ -1045,7 +1057,10 @@ other            .
                         * Note that we needn't guard against recursion here.
                         */
                        if (value)
+                       {
                            appendPQExpBufferStr(output_buf, value);
+                           free(value);
+                       }
                        else
                            ECHO;
 
@@ -1191,14 +1206,20 @@ other           .
 
 /*
  * Create a lexer working state struct.
+ *
+ * callbacks is a struct of function pointers that encapsulate some
+ * behavior we need from the surrounding program.  This struct must
+ * remain valid for the lifespan of the PsqlScanState.
  */
 PsqlScanState
-psql_scan_create(void)
+psql_scan_create(const PsqlScanCallbacks *callbacks)
 {
    PsqlScanState state;
 
    state = (PsqlScanStateData *) pg_malloc0(sizeof(PsqlScanStateData));
 
+   state->callbacks = callbacks;
+
    psql_scan_reset(state);
 
    return state;
@@ -1225,18 +1246,25 @@ psql_scan_destroy(PsqlScanState state)
  * be called when scanning is complete.  Note that the lexer retains
  * a pointer to the storage at *line --- this string must not be altered
  * or freed until after psql_scan_finish is called.
+ *
+ * encoding is the libpq identifier for the character encoding in use,
+ * and std_strings says whether standard_conforming_strings is on.
  */
 void
 psql_scan_setup(PsqlScanState state,
-               const char *line, int line_len)
+               const char *line, int line_len,
+               int encoding, bool std_strings)
 {
    /* Mustn't be scanning already */
    Assert(state->scanbufhandle == NULL);
    Assert(state->buffer_stack == NULL);
 
    /* Do we need to hack the character set encoding? */
-   state->encoding = pset.encoding;
-   state->safe_encoding = pg_valid_server_encoding_id(state->encoding);
+   state->encoding = encoding;
+   state->safe_encoding = pg_valid_server_encoding_id(encoding);
+
+   /* Save standard-strings flag as well */
+   state->std_strings = std_strings;
 
    /* needed for prepare_buffer */
    cur_state = state;
@@ -1615,7 +1643,7 @@ psql_scan_slash_option(PsqlScanState state,
                    {
                        if (!inquotes && type == OT_SQLID)
                            *cp = pg_tolower((unsigned char) *cp);
-                       cp += PQmblen(cp, pset.encoding);
+                       cp += PQmblen(cp, state->encoding);
                    }
                }
            }
@@ -1936,53 +1964,31 @@ extract_substring(const char *txt, int len)
  * If the variable name is found, escape its value using the appropriate
  * quoting method and emit the value to output_buf.  (Since the result is
  * surely quoted, there is never any reason to rescan it.)  If we don't
- * find the variable or the escaping function fails, emit the token as-is.
+ * find the variable or escaping fails, emit the token as-is.
  */
 static void
 escape_variable(bool as_ident)
 {
    char       *varname;
-   const char *value;
+   char       *value;
 
    /* Variable lookup. */
    varname = extract_substring(yytext + 2, yyleng - 3);
-   value = GetVariable(pset.vars, varname);
+   if (cur_state->callbacks->get_variable)
+       value = cur_state->callbacks->get_variable(varname, true, as_ident);
+   else
+       value = NULL;
    free(varname);
 
-   /* Escaping. */
    if (value)
    {
-       if (!pset.db)
-           psql_error("can't escape without active connection\n");
-       else
-       {
-           char   *escaped_value;
-
-           if (as_ident)
-               escaped_value =
-                   PQescapeIdentifier(pset.db, value, strlen(value));
-           else
-               escaped_value =
-                   PQescapeLiteral(pset.db, value, strlen(value));
-
-           if (escaped_value == NULL)
-           {
-               const char *error = PQerrorMessage(pset.db);
-
-               psql_error("%s", error);
-           }
-           else
-           {
-               appendPQExpBufferStr(output_buf, escaped_value);
-               PQfreemem(escaped_value);
-               return;
-           }
-       }
+       /* Emit the suitably-escaped value */
+       appendPQExpBufferStr(output_buf, value);
+       free(value);
+   }
+   else
+   {
+       /* Emit original token as-is */
+       emit(yytext, yyleng);
    }
-
-   /*
-    * If we reach this point, some kind of error has occurred.  Emit the
-    * original text into the output buffer.
-    */
-   emit(yytext, yyleng);
 }
index 6916f6f461212b478767f6d08e75999361f5bd47..4bb3fdc595af2f5cfd41ff8d762caccd897fa6a0 100644 (file)
@@ -336,10 +336,10 @@ main(int argc, char *argv[])
                if (pset.echo == PSQL_ECHO_ALL)
                    puts(cell->val);
 
-               scan_state = psql_scan_create();
+               scan_state = psql_scan_create(&psqlscan_callbacks);
                psql_scan_setup(scan_state,
-                               cell->val,
-                               strlen(cell->val));
+                               cell->val, strlen(cell->val),
+                               pset.encoding, standard_strings());
 
                successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR
                    ? EXIT_SUCCESS : EXIT_FAILURE;