Hi, all.
> > I don't think we actually want backslash-continuations. The feature we
> > want is "allow SQL statements span multiple lines", and using the psql
> > lexer solves that. We don't need the backslash-continuations when we
> > have that.
>
> Sure. The feature *I* initially wanted was to have multi-line
> meta-commands. For this feature ISTM that continuations are, alas, the
> solution.
>
> >> Indeed there are plenty of links already which are generated by
> >> makefiles
> >> (see src/bin/pg_xlogdump/*), and probably a copy is made on
> >> windows. There
> >> should no file duplication within the source tree.
> >
> > Yeah, following the example of pg_xlogdump and others is the way to
> > go.
> >
> > Docs need updating, and there's probably some cleanup to do before
> > this is ready for committing, but overall I think this is definitely
> > the right direction.
>
> I've created an entry for the next commitfest, and put the status to
> "waiting on author".
>
> > I complained upthread that this makes it impossible to use
> > "multi-statements" in pgbench, as they would be split into separate
> > statements, but looking at psqlscan.l there is actually a syntax for
> > that in psql already. You escape the semicolon as \;, e.g. "SELECT 1
> > \; SELECT 2;", and then both queries will be sent to the server as
> > one. So even that's OK.
>
> Good!
Hmm. psqlscan.l handles multistatement naturally.
I worked on that and the attached patche set does,
- backslash continuation for pgbench metacommands.
set variable \
<some value>
- SQL statement natural continuation lines.
SELECT :foo
FROM :bar;
- SQL multi-statement.
SELECT 1; SELECT 2;
The work to be left is eliminating double-format of Command
struct.
regards,
--
Kyotaro Horiguchi
NTT Open Source Software Center
>From 274bc1cd6de4fb5806e308b002b086b1dfdf7479 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <[email protected]>
Date: Thu, 23 Jul 2015 20:44:37 +0900
Subject: [PATCH 1/3] Prepare for share psqlscan with pgbench.
Eliminate direct usage of pset variables and enable parts unnecessary
for other than psql to be disabled by defining OUTSIDE_PSQL.
---
src/bin/psql/mainloop.c | 6 ++--
src/bin/psql/psqlscan.h | 14 +++++----
src/bin/psql/psqlscan.l | 79 ++++++++++++++++++++++++++++++++-----------------
src/bin/psql/startup.c | 4 +--
4 files changed, 67 insertions(+), 36 deletions(-)
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index b6cef94..e98cb94 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -233,7 +233,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.db, pset.vars, pset.encoding);
success = true;
line_saved_in_history = false;
@@ -373,7 +374,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.db, pset.vars, pset.encoding);
line_saved_in_history = false;
prompt_status = PROMPT_READY;
}
diff --git a/src/bin/psql/psqlscan.h b/src/bin/psql/psqlscan.h
index 55070ca..4bf8dcb 100644
--- a/src/bin/psql/psqlscan.h
+++ b/src/bin/psql/psqlscan.h
@@ -11,7 +11,11 @@
#include "pqexpbuffer.h"
#include "prompt.h"
-
+#if !defined OUTSIDE_PSQL
+#include "variables.h"
+#else
+typedef int * VariableSpace;
+#endif
/* Abstract type for lexer's internal state */
typedef struct PsqlScanStateData *PsqlScanState;
@@ -36,12 +40,11 @@ enum slash_option_type
OT_NO_EVAL /* no expansion of backticks or variables */
};
-
extern PsqlScanState psql_scan_create(void);
extern void psql_scan_destroy(PsqlScanState state);
-extern void psql_scan_setup(PsqlScanState state,
- const char *line, int line_len);
+extern void psql_scan_setup(PsqlScanState state, const char *line, int line_len,
+ PGconn *db, VariableSpace vars, int encoding);
extern void psql_scan_finish(PsqlScanState state);
extern PsqlScanResult psql_scan(PsqlScanState state,
@@ -52,6 +55,7 @@ extern void psql_scan_reset(PsqlScanState state);
extern bool psql_scan_in_quote(PsqlScanState state);
+#if !defined OUTSIDE_PSQL
extern char *psql_scan_slash_command(PsqlScanState state);
extern char *psql_scan_slash_option(PsqlScanState state,
@@ -60,5 +64,5 @@ extern char *psql_scan_slash_option(PsqlScanState state,
bool semicolon);
extern void psql_scan_slash_command_end(PsqlScanState state);
-
+#endif /* if !defined OUTSIDE_PSQL */
#endif /* PSQLSCAN_H */
diff --git a/src/bin/psql/psqlscan.l b/src/bin/psql/psqlscan.l
index be059ab..f9a19cd 100644
--- a/src/bin/psql/psqlscan.l
+++ b/src/bin/psql/psqlscan.l
@@ -43,11 +43,6 @@
#include <ctype.h>
-#include "common.h"
-#include "settings.h"
-#include "variables.h"
-
-
/*
* We use a stack of flex buffers to handle substitution of psql variables.
* Each stacked buffer contains the as-yet-unread text from one psql variable.
@@ -81,10 +76,12 @@ typedef struct PsqlScanStateData
const char *scanline; /* current input line at outer level */
/* safe_encoding, curline, refline are used by emit() to replace FFs */
+ PGconn *db; /* active connection */
int encoding; /* encoding being used now */
bool safe_encoding; /* is current encoding "safe"? */
const char *curline; /* actual flex input string for cur buf */
const char *refline; /* original data for cur buffer */
+ VariableSpace vars; /* "shell variable" repository */
/*
* All this state lives across successive input lines, until explicitly
@@ -126,6 +123,15 @@ static void escape_variable(bool as_ident);
#define ECHO emit(yytext, yyleng)
+/* Provide dummy macros when no use of psql variables */
+#if defined OUTSIDE_PSQL
+#define GetVariable(space,name) NULL
+#define standard_strings() true
+#define psql_error(fmt,...) do { \
+ fprintf(stderr, "psql_error is called. abort.\n");\
+ exit(1);\
+} while(0)
+#endif
%}
%option 8bit
@@ -736,11 +742,14 @@ other .
:{variable_char}+ {
/* Possible psql variable substitution */
- char *varname;
- const char *value;
+ char *varname = NULL;
+ const char *value = NULL;
- varname = extract_substring(yytext + 1, yyleng - 1);
- value = GetVariable(pset.vars, varname);
+ if (cur_state->vars)
+ {
+ varname = extract_substring(yytext + 1, yyleng - 1);
+ value = GetVariable(cur_state->vars, varname);
+ }
if (value)
{
@@ -769,7 +778,8 @@ other .
ECHO;
}
- free(varname);
+ if (varname)
+ free(varname);
}
:'{variable_char}+' {
@@ -1033,9 +1043,12 @@ other .
char *varname;
const char *value;
- varname = extract_substring(yytext + 1, yyleng - 1);
- value = GetVariable(pset.vars, varname);
- free(varname);
+ if (cur_state->vars)
+ {
+ varname = extract_substring(yytext + 1, yyleng - 1);
+ value = GetVariable(cur_state->vars, varname);
+ free(varname);
+ }
/*
* The variable value is just emitted without any
@@ -1227,17 +1240,19 @@ psql_scan_destroy(PsqlScanState state)
* or freed until after psql_scan_finish is called.
*/
void
-psql_scan_setup(PsqlScanState state,
- const char *line, int line_len)
+psql_scan_setup(PsqlScanState state, const char *line, int line_len,
+ PGconn *db, VariableSpace vars, int encoding)
{
/* 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->encoding = encoding;
state->safe_encoding = pg_valid_server_encoding_id(state->encoding);
+ state->vars = vars;
+
/* needed for prepare_buffer */
cur_state = state;
@@ -1459,6 +1474,7 @@ psql_scan_in_quote(PsqlScanState state)
return state->start_state != INITIAL;
}
+#if !defined OUTSIDE_PSQL
/*
* Scan the command name of a psql backslash command. This should be called
* after psql_scan() returns PSCAN_BACKSLASH. It is assumed that the input
@@ -1615,7 +1631,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, cur_state->encoding);
}
}
}
@@ -1744,6 +1760,14 @@ evaluate_backtick(void)
termPQExpBuffer(&cmd_output);
}
+#else
+static void
+evaluate_backtick(void)
+{
+ fprintf(stderr, "Unexpected call of evaluate_backtick.\n");
+ exit(1);
+}
+#endif /* if !defined OUTSIDE_PSQL*/
/*
* Push the given string onto the stack of stuff to scan.
@@ -1944,15 +1968,18 @@ escape_variable(bool as_ident)
char *varname;
const char *value;
- /* Variable lookup. */
- varname = extract_substring(yytext + 2, yyleng - 3);
- value = GetVariable(pset.vars, varname);
- free(varname);
+ /* Variable lookup if possible. */
+ if (cur_state->vars && cur_state->db)
+ {
+ varname = extract_substring(yytext + 2, yyleng - 3);
+ value = GetVariable(cur_state->vars, varname);
+ free(varname);
+ }
/* Escaping. */
if (value)
{
- if (!pset.db)
+ if (!cur_state->db)
psql_error("can't escape without active connection\n");
else
{
@@ -1960,16 +1987,14 @@ escape_variable(bool as_ident)
if (as_ident)
escaped_value =
- PQescapeIdentifier(pset.db, value, strlen(value));
+ PQescapeIdentifier(cur_state->db, value, strlen(value));
else
escaped_value =
- PQescapeLiteral(pset.db, value, strlen(value));
+ PQescapeLiteral(cur_state->db, value, strlen(value));
if (escaped_value == NULL)
{
- const char *error = PQerrorMessage(pset.db);
-
- psql_error("%s", error);
+ psql_error("%s", PQerrorMessage(cur_state->db));
}
else
{
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 28ba75a..c143dfe 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -305,8 +305,8 @@ main(int argc, char *argv[])
scan_state = psql_scan_create();
psql_scan_setup(scan_state,
- options.action_string,
- strlen(options.action_string));
+ options.action_string, strlen(options.action_string),
+ pset.db, pset.vars, pset.encoding);
successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR
? EXIT_SUCCESS : EXIT_FAILURE;
--
1.8.3.1
>From 2fd47e1a7de8ad49999241cc83271de78e4b2b6e Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <[email protected]>
Date: Fri, 24 Jul 2015 10:58:23 +0900
Subject: [PATCH 2/3] Make use of psqlscan for parsing of custom script.
Get rid of home-made parser and allow backslash continuation for
backslash commands, multiline SQL statements and SQL multi statement
in custom scripts.
---
src/bin/pgbench/Makefile | 16 +-
src/bin/pgbench/pgbench.c | 479 +++++++++++++++++++++++++++++++---------------
2 files changed, 341 insertions(+), 154 deletions(-)
diff --git a/src/bin/pgbench/Makefile b/src/bin/pgbench/Makefile
index 18fdf58..a0a736b 100644
--- a/src/bin/pgbench/Makefile
+++ b/src/bin/pgbench/Makefile
@@ -5,11 +5,13 @@ PGAPPICON = win32
subdir = src/bin/pgbench
top_builddir = ../../..
+psqlincdir = ../psql
include $(top_builddir)/src/Makefile.global
OBJS = pgbench.o exprparse.o $(WIN32RES)
-override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
+
+override CPPFLAGS := -DOUTSIDE_PSQL -I. -I$(srcdir) -I$(libpq_srcdir) -I$(psqlincdir) $(CPPFLAGS)
ifneq ($(PORTNAME), win32)
override CFLAGS += $(PTHREAD_CFLAGS)
@@ -18,6 +20,16 @@ endif
all: pgbench
+psqlscan.c: FLEXFLAGS = -Cfe -p -p
+psqlscan.c: FLEX_NO_BACKUP=yes
+
+psqlscan.l: % : $(top_srcdir)/src/bin/psql/%
+ rm -f $@ && $(LN_S) $< .
+
+psqlscan.c: psqlscan.l
+
+pgbench.o: psqlscan.c
+
pgbench: $(OBJS) | submake-libpq submake-libpgport
$(CC) $(CFLAGS) $^ $(libpq_pgport) $(PTHREAD_LIBS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
@@ -39,4 +51,4 @@ clean distclean:
rm -f pgbench$(X) $(OBJS)
maintainer-clean: distclean
- rm -f exprparse.c exprscan.c
+ rm -f exprparse.c exprscan.c psqlscan.l psqlscan.c
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 6f5bd99..3b1ab7a 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -54,7 +54,7 @@
#endif
#include "pgbench.h"
-
+#include "psqlscan.h"
/*
* Multi-platform pthread implementations
*/
@@ -262,7 +262,7 @@ typedef enum QueryMode
static QueryMode querymode = QUERY_SIMPLE;
static const char *QUERYMODE[] = {"simple", "extended", "prepared"};
-typedef struct
+typedef struct Command_t
{
char *line; /* full text of command line */
int command_num; /* unique index of this Command struct */
@@ -271,6 +271,7 @@ typedef struct
char *argv[MAX_ARGS]; /* command word list */
int cols[MAX_ARGS]; /* corresponding column starting from 1 */
PgBenchExpr *expr; /* parsed expression */
+ struct Command_t *next; /* more command if any, for multistatements */
} Command;
typedef struct
@@ -293,6 +294,21 @@ typedef struct
double sum2_lag; /* sum(lag*lag) */
} AggVals;
+typedef enum
+{
+ PS_IDLE,
+ PS_IN_STATEMENT,
+ PS_IN_BACKSLASH_CMD
+} ParseState;
+
+typedef struct ParseInfo
+{
+ PsqlScanState scan_state;
+ PQExpBuffer outbuf;
+ ParseState mode;
+} ParseInfoData;
+typedef ParseInfoData *ParseInfo;
+
static Command **sql_files[MAX_FILES]; /* SQL script files */
static int num_files; /* number of script files */
static int num_commands = 0; /* total number of Command structs */
@@ -2222,217 +2238,348 @@ syntax_error(const char *source, const int lineno,
exit(1);
}
-/* Parse a command; return a Command struct, or NULL if it's a comment */
+static ParseInfo
+createParseInfo(void)
+{
+ ParseInfo ret = (ParseInfo) pg_malloc(sizeof(ParseInfoData));
+
+ ret->scan_state = psql_scan_create();
+ ret->outbuf = createPQExpBuffer();
+ ret->mode = PS_IDLE;
+
+ return ret;
+}
+
+#define parse_reset_outbuf(pcs) resetPQExpBuffer((pcs)->outbuf)
+#define parse_finish_scan(pcs) psql_scan_finish((pcs)->scan_state)
+
+/* copy a string after removing newlines and collapsing whitespaces */
+static char *
+strdup_nonl(const char *in)
+{
+ char *ret, *p, *q;
+
+ ret = pg_strdup(in);
+
+ /* Replace newlines into spaces */
+ for (p = ret ; *p ; p++)
+ if (*p == '\n') *p = ' ';
+
+ /* collapse successive spaces */
+ for (p = q = ret ; *p ; p++, q++)
+ {
+ while (isspace(*p) && isspace(*(p + 1))) p++;
+ if (p > q) *q = *p;
+ }
+ *q = '\0';
+
+ return ret;
+}
+
+/* Parse a backslash command; return a Command struct */
static Command *
-process_commands(char *buf, const char *source, const int lineno)
+process_backslash_commands(ParseInfo proc_state, char *buf,
+ const char *source, const int lineno)
{
const char delim[] = " \f\n\r\t\v";
Command *my_commands;
int j;
char *p,
+ *start,
*tok;
-
- /* Make the string buf end at the next newline */
- if ((p = strchr(buf, '\n')) != NULL)
- *p = '\0';
+ int max_args = -1;
/* Skip leading whitespace */
p = buf;
while (isspace((unsigned char) *p))
p++;
+ start = p;
+
+ if (proc_state->mode != PS_IN_BACKSLASH_CMD)
+ {
+ if (*p != '\\')
+ return NULL;
+
+ /* This is the first line of a backslash command */
+ proc_state->mode = PS_IN_BACKSLASH_CMD;
+ }
+
+ /*
+ * Make the string buf end at the next newline, or move to just after the
+ * end of line
+ */
+ if ((p = strchr(start, '\n')) != NULL)
+ *p = '\0';
+ else
+ p = start + strlen(start);
+
+ /* continued line ends with a backslash */
+ if (*(--p) == '\\')
+ {
+ *p-- = '\0';
+ appendPQExpBufferStr(proc_state->outbuf, start);
+
+ /* Add a delimiter at the end of the line if necessary */
+ if (!isspace(*p))
+ appendPQExpBufferChar(proc_state->outbuf, ' ');
- /* If the line is empty or actually a comment, we're done */
- if (*p == '\0' || strncmp(p, "--", 2) == 0)
return NULL;
+ }
+
+ appendPQExpBufferStr(proc_state->outbuf, start);
+ proc_state->mode = PS_IDLE;
+
+ /* Start parsing the backslash command */
+
+ p = proc_state->outbuf->data;
/* Allocate and initialize Command structure */
my_commands = (Command *) pg_malloc(sizeof(Command));
- my_commands->line = pg_strdup(buf);
+ my_commands->line = pg_strdup(p);
my_commands->command_num = num_commands++;
- my_commands->type = 0; /* until set */
+ my_commands->type = META_COMMAND;
my_commands->argc = 0;
+ my_commands->next = NULL;
- if (*p == '\\')
- {
- int max_args = -1;
+ j = 0;
+ tok = strtok(++p, delim);
- my_commands->type = META_COMMAND;
+ if (tok != NULL && pg_strcasecmp(tok, "set") == 0)
+ max_args = 2;
- j = 0;
- tok = strtok(++p, delim);
+ while (tok != NULL)
+ {
+ my_commands->cols[j] = tok - buf + 1;
+ my_commands->argv[j++] = pg_strdup(tok);
+ my_commands->argc++;
+ if (max_args >= 0 && my_commands->argc >= max_args)
+ tok = strtok(NULL, "");
+ else
+ tok = strtok(NULL, delim);
+ }
+ parse_reset_outbuf(proc_state);
- if (tok != NULL && pg_strcasecmp(tok, "set") == 0)
- max_args = 2;
+ if (pg_strcasecmp(my_commands->argv[0], "setrandom") == 0)
+ {
+ /*
+ * parsing: \setrandom variable min max [uniform] \setrandom
+ * variable min max (gaussian|exponential) threshold
+ */
- while (tok != NULL)
+ if (my_commands->argc < 4)
{
- my_commands->cols[j] = tok - buf + 1;
- my_commands->argv[j++] = pg_strdup(tok);
- my_commands->argc++;
- if (max_args >= 0 && my_commands->argc >= max_args)
- tok = strtok(NULL, "");
- else
- tok = strtok(NULL, delim);
+ syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
+ "missing arguments", NULL, -1);
}
- if (pg_strcasecmp(my_commands->argv[0], "setrandom") == 0)
- {
- /*
- * parsing: \setrandom variable min max [uniform] \setrandom
- * variable min max (gaussian|exponential) threshold
- */
+ /* argc >= 4 */
- if (my_commands->argc < 4)
+ if (my_commands->argc == 4 || /* uniform without/with
+ * "uniform" keyword */
+ (my_commands->argc == 5 &&
+ pg_strcasecmp(my_commands->argv[4], "uniform") == 0))
+ {
+ /* nothing to do */
+ }
+ else if ( /* argc >= 5 */
+ (pg_strcasecmp(my_commands->argv[4], "gaussian") == 0) ||
+ (pg_strcasecmp(my_commands->argv[4], "exponential") == 0))
+ {
+ if (my_commands->argc < 6)
{
syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "missing arguments", NULL, -1);
- }
-
- /* argc >= 4 */
-
- if (my_commands->argc == 4 || /* uniform without/with
- * "uniform" keyword */
- (my_commands->argc == 5 &&
- pg_strcasecmp(my_commands->argv[4], "uniform") == 0))
- {
- /* nothing to do */
+ "missing threshold argument", my_commands->argv[4], -1);
}
- else if ( /* argc >= 5 */
- (pg_strcasecmp(my_commands->argv[4], "gaussian") == 0) ||
- (pg_strcasecmp(my_commands->argv[4], "exponential") == 0))
- {
- if (my_commands->argc < 6)
- {
- syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "missing threshold argument", my_commands->argv[4], -1);
- }
- else if (my_commands->argc > 6)
- {
- syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "too many arguments", my_commands->argv[4],
- my_commands->cols[6]);
- }
- }
- else /* cannot parse, unexpected arguments */
+ else if (my_commands->argc > 6)
{
syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "unexpected argument", my_commands->argv[4],
- my_commands->cols[4]);
+ "too many arguments", my_commands->argv[4],
+ my_commands->cols[6]);
}
}
- else if (pg_strcasecmp(my_commands->argv[0], "set") == 0)
+ else /* cannot parse, unexpected arguments */
{
- if (my_commands->argc < 3)
- {
- syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "missing argument", NULL, -1);
- }
+ syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
+ "unexpected argument", my_commands->argv[4],
+ my_commands->cols[4]);
+ }
+ }
+ else if (pg_strcasecmp(my_commands->argv[0], "set") == 0)
+ {
+ if (my_commands->argc < 3)
+ {
+ syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
+ "missing argument", NULL, -1);
+ }
- expr_scanner_init(my_commands->argv[2], source, lineno,
- my_commands->line, my_commands->argv[0],
- my_commands->cols[2] - 1);
+ expr_scanner_init(my_commands->argv[2], source, lineno,
+ my_commands->line, my_commands->argv[0],
+ my_commands->cols[2] - 1);
- if (expr_yyparse() != 0)
- {
- /* dead code: exit done from syntax_error called by yyerror */
- exit(1);
- }
+ if (expr_yyparse() != 0)
+ {
+ /* dead code: exit done from syntax_error called by yyerror */
+ exit(1);
+ }
- my_commands->expr = expr_parse_result;
+ my_commands->expr = expr_parse_result;
- expr_scanner_finish();
- }
- else if (pg_strcasecmp(my_commands->argv[0], "sleep") == 0)
+ expr_scanner_finish();
+ }
+ else if (pg_strcasecmp(my_commands->argv[0], "sleep") == 0)
+ {
+ if (my_commands->argc < 2)
{
- if (my_commands->argc < 2)
- {
- syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "missing argument", NULL, -1);
- }
-
- /*
- * Split argument into number and unit to allow "sleep 1ms" etc.
- * We don't have to terminate the number argument with null
- * because it will be parsed with atoi, which ignores trailing
- * non-digit characters.
- */
- if (my_commands->argv[1][0] != ':')
- {
- char *c = my_commands->argv[1];
+ syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
+ "missing argument", NULL, -1);
+ }
- while (isdigit((unsigned char) *c))
- c++;
- if (*c)
- {
- my_commands->argv[2] = c;
- if (my_commands->argc < 3)
- my_commands->argc = 3;
- }
- }
+ /*
+ * Split argument into number and unit to allow "sleep 1ms" etc. We
+ * don't have to terminate the number argument with null because it
+ * will be parsed with atoi, which ignores trailing non-digit
+ * characters.
+ */
+ if (my_commands->argv[1][0] != ':')
+ {
+ char *c = my_commands->argv[1];
- if (my_commands->argc >= 3)
+ while (isdigit((unsigned char) *c))
+ c++;
+ if (*c)
{
- if (pg_strcasecmp(my_commands->argv[2], "us") != 0 &&
- pg_strcasecmp(my_commands->argv[2], "ms") != 0 &&
- pg_strcasecmp(my_commands->argv[2], "s") != 0)
- {
- syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "unknown time unit, must be us, ms or s",
- my_commands->argv[2], my_commands->cols[2]);
- }
+ my_commands->argv[2] = c;
+ if (my_commands->argc < 3)
+ my_commands->argc = 3;
}
-
- /* this should be an error?! */
- for (j = 3; j < my_commands->argc; j++)
- fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
- my_commands->argv[0], my_commands->argv[j]);
}
- else if (pg_strcasecmp(my_commands->argv[0], "setshell") == 0)
+
+ if (my_commands->argc >= 3)
{
- if (my_commands->argc < 3)
+ if (pg_strcasecmp(my_commands->argv[2], "us") != 0 &&
+ pg_strcasecmp(my_commands->argv[2], "ms") != 0 &&
+ pg_strcasecmp(my_commands->argv[2], "s") != 0)
{
syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "missing argument", NULL, -1);
+ "unknown time unit, must be us, ms or s",
+ my_commands->argv[2], my_commands->cols[2]);
}
}
- else if (pg_strcasecmp(my_commands->argv[0], "shell") == 0)
+
+ /* this should be an error?! */
+ for (j = 3; j < my_commands->argc; j++)
+ fprintf(stderr, "%s: extra argument \"%s\" ignored\n",
+ my_commands->argv[0], my_commands->argv[j]);
+ }
+ else if (pg_strcasecmp(my_commands->argv[0], "setshell") == 0)
+ {
+ if (my_commands->argc < 3)
{
- if (my_commands->argc < 1)
- {
- syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "missing command", NULL, -1);
- }
+ syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
+ "missing argument", NULL, -1);
}
- else
+ }
+ else if (pg_strcasecmp(my_commands->argv[0], "shell") == 0)
+ {
+ if (my_commands->argc < 1)
{
syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
- "invalid command", NULL, -1);
+ "missing command", NULL, -1);
}
}
else
{
- my_commands->type = SQL_COMMAND;
+ syntax_error(source, lineno, my_commands->line, my_commands->argv[0],
+ "invalid command", NULL, -1);
+ }
+
+ return my_commands;
+}
+
+/* Parse a input line, return non-null if any command terminates. */
+static Command *
+process_commands(ParseInfo proc_state, char *buf,
+ const char *source, const int lineno)
+{
+ Command *command = NULL;
+ Command *retcomd = NULL;
+ PsqlScanState scan_state = proc_state->scan_state;
+ promptStatus_t prompt_status = PROMPT_READY; /* dummy */
+ PQExpBuffer qbuf = proc_state->outbuf;
+ PsqlScanResult scan_result;
+
+ if (proc_state->mode != PS_IN_STATEMENT)
+ {
+ command = process_backslash_commands(proc_state, buf, source, lineno);
+
+ /* go to next line for continuation line of backslash command. */
+ if (command != NULL || proc_state->mode == PS_IN_BACKSLASH_CMD)
+ return command;
+ }
+
+ /* Parse statements */
+ psql_scan_setup(scan_state, buf, strlen(buf), NULL, NULL, 0);
+
+next_command:
+ scan_result = psql_scan(scan_state, qbuf, &prompt_status);
+
+ if (scan_result == PSCAN_SEMICOLON)
+ {
+ proc_state->mode = PS_IDLE;
+ /*
+ * Command is terminated. Fill the struct.
+ */
+ command = (Command*) pg_malloc(sizeof(Command));
+ command->line = strdup_nonl(qbuf->data);
+ command->command_num = num_commands++;
+ command->type = SQL_COMMAND;
+ command->argc = 0;
+ command->next = NULL;
+
+ /* Put this command at the end of returning command chain */
+ if (!retcomd)
+ retcomd = command;
+ else
+ {
+ Command *pcomm = retcomd;
+ while (pcomm->next) pcomm = pcomm->next;
+ pcomm->next = command;
+ }
switch (querymode)
{
- case QUERY_SIMPLE:
- my_commands->argv[0] = pg_strdup(p);
- my_commands->argc++;
- break;
- case QUERY_EXTENDED:
- case QUERY_PREPARED:
- if (!parseQuery(my_commands, p))
- exit(1);
- break;
- default:
+ case QUERY_SIMPLE:
+ command->argv[0] = pg_strdup(qbuf->data);
+ command->argc++;
+ break;
+ case QUERY_EXTENDED:
+ case QUERY_PREPARED:
+ if (!parseQuery(command, qbuf->data))
exit(1);
+ break;
+ default:
+ exit(1);
}
+
+ parse_reset_outbuf(proc_state);
+
+ /* Ask for the next statement in this line */
+ goto next_command;
+ }
+ else if (scan_result == PSCAN_BACKSLASH)
+ {
+ fprintf(stderr, "Unexpected backslash in SQL statement: %s:%d\n", source, lineno);
+ exit(1);
}
- return my_commands;
+ proc_state->mode = PS_IN_STATEMENT;
+ psql_scan_finish(scan_state);
+
+ return retcomd;
}
+
/*
* Read a line from fd, and return it in a malloc'd buffer.
* Return NULL at EOF.
@@ -2487,6 +2634,7 @@ process_file(char *filename)
index;
char *buf;
int alloc_num;
+ ParseInfo proc_state = createParseInfo();
if (num_files >= MAX_FILES)
{
@@ -2507,33 +2655,47 @@ process_file(char *filename)
return false;
}
+ proc_state->mode = PS_IDLE;
+
lineno = 0;
index = 0;
while ((buf = read_line_from_file(fd)) != NULL)
{
- Command *command;
+ Command *command = NULL;
lineno += 1;
- command = process_commands(buf, filename, lineno);
-
+ command = process_commands(proc_state, buf, filename, lineno);
free(buf);
if (command == NULL)
+ {
+ /*
+ * command is NULL when psql_scan returns PSCAN_EOL or
+ * PSCAN_INCOMPLETE. Immediately ask for the next line for the
+ * cases.
+ */
continue;
+ }
- my_commands[index] = command;
- index++;
+ while (command)
+ {
+ my_commands[index++] = command;
+ command = command->next;
+ }
- if (index >= alloc_num)
+ if (index > alloc_num)
{
alloc_num += COMMANDS_ALLOC_NUM;
- my_commands = pg_realloc(my_commands, sizeof(Command *) * alloc_num);
+ my_commands = pg_realloc(my_commands,
+ sizeof(Command *) * alloc_num);
}
}
fclose(fd);
+ parse_finish_scan(proc_state);
+
my_commands[index] = NULL;
sql_files[num_files++] = my_commands;
@@ -2551,6 +2713,7 @@ process_builtin(char *tb, const char *source)
index;
char buf[BUFSIZ];
int alloc_num;
+ ParseInfo proc_state = createParseInfo();
alloc_num = COMMANDS_ALLOC_NUM;
my_commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num);
@@ -2577,10 +2740,12 @@ process_builtin(char *tb, const char *source)
lineno += 1;
- command = process_commands(buf, source, lineno);
+ command = process_commands(proc_state, buf, source, lineno);
if (command == NULL)
continue;
+ /* builtin doesn't need multistatements */
+ Assert(command->next == NULL);
my_commands[index] = command;
index++;
@@ -2592,6 +2757,7 @@ process_builtin(char *tb, const char *source)
}
my_commands[index] = NULL;
+ parse_finish_scan(proc_state);
return my_commands;
}
@@ -3922,3 +4088,12 @@ pthread_join(pthread_t th, void **thread_return)
}
#endif /* WIN32 */
+
+/*
+ * psqlscan.c is #include'd here instead of being compiled on its own.
+ * This is because we need postgres_fe.h to be read before any system
+ * include files, else things tend to break on platforms that have
+ * multiple infrastructures for stdio.h and so on. flex is absolutely
+ * uncooperative about that, so we can't compile psqlscan.c on its own.
+ */
+#include "psqlscan.c"
--
1.8.3.1
>From 884d7fadc48f59709a466383d0891d714662ab77 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <[email protected]>
Date: Tue, 4 Aug 2015 20:54:28 +0900
Subject: [PATCH 3/3] Change MSVC Build script
---
src/tools/msvc/Mkvcbuild.pm | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 3abbb4c..f018a29 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -68,7 +68,7 @@ my $frontend_extrasource = {
[ 'src/bin/pgbench/exprscan.l', 'src/bin/pgbench/exprparse.y' ], };
my @frontend_excludes = (
'pgevent', 'pg_basebackup', 'pg_rewind', 'pg_dump',
- 'pg_xlogdump', 'scripts');
+ 'pg_xlogdump', 'pgbench', 'scripts');
sub mkvcbuild
{
@@ -671,6 +671,14 @@ sub mkvcbuild
}
$pg_xlogdump->AddFile('src/backend/access/transam/xlogreader.c');
+ # fix up pg_xlogdump once it's been set up
+ # files symlinked on Unix are copied on windows
+ my $pgbench = AddSimpleFrontend('pgbench');
+ $pgbench->AddDefine('FRONTEND');
+ $pgbench->AddDefine('OUTSIDE_PSQL');
+ $pgbench->AddFile('src/bin/psql/psqlscan.l');
+ $pgbench->AddIncludeDir('src/bin/psql');
+
$solution->Save();
return $solution->{vcver};
}
--
1.8.3.1
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers