diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index b332aa435df..e7a53f3c9d0 100644
--- a/doc/src/sgml/ecpg.sgml
+++ b/doc/src/sgml/ecpg.sgml
@@ -5793,6 +5793,14 @@ EXEC SQL UPDATE Tbl SET col = MYNUMBER;
embedded SQL query because in this case the embedded SQL precompiler is not
able to see this declaration.
+
+
+ If multiple input files are named on the ecpg
+ preprocessor's command line, the effects of EXEC SQL
+ DEFINE and EXEC SQL UNDEF do not carry
+ across files: each file starts with only the symbols defined
+ by switches on the command line.
+
diff --git a/doc/src/sgml/ref/ecpg-ref.sgml b/doc/src/sgml/ref/ecpg-ref.sgml
index f3b6034f42a..43f2d8bdaa7 100644
--- a/doc/src/sgml/ref/ecpg-ref.sgml
+++ b/doc/src/sgml/ref/ecpg-ref.sgml
@@ -93,10 +93,12 @@ PostgreSQL documentation
-
+
- Define a C preprocessor symbol.
+ Define a preprocessor symbol, equivalently to the EXEC SQL
+ DEFINE directive. If no value is
+ specified, the symbol is defined with the value 1.
diff --git a/src/interfaces/ecpg/preproc/ecpg.c b/src/interfaces/ecpg/preproc/ecpg.c
index 93e66fc60f0..73c37631acc 100644
--- a/src/interfaces/ecpg/preproc/ecpg.c
+++ b/src/interfaces/ecpg/preproc/ecpg.c
@@ -82,35 +82,46 @@ add_include_path(char *path)
}
}
+/*
+ * Process a command line -D switch
+ */
static void
add_preprocessor_define(char *define)
{
- struct _defines *pd = defines;
- char *ptr,
- *define_copy = mm_strdup(define);
+ /* copy the argument to avoid relying on argv storage */
+ char *define_copy = mm_strdup(define);
+ char *ptr;
+ struct _defines *newdef;
- defines = mm_alloc(sizeof(struct _defines));
+ newdef = mm_alloc(sizeof(struct _defines));
/* look for = sign */
ptr = strchr(define_copy, '=');
if (ptr != NULL)
{
+ /* symbol has a value */
char *tmp;
- /* symbol has a value */
- for (tmp = ptr - 1; *tmp == ' '; tmp--);
+ /* strip any spaces between name and '=' */
+ for (tmp = ptr - 1; tmp >= define_copy && *tmp == ' '; tmp--);
tmp[1] = '\0';
- defines->olddef = define_copy;
- defines->newdef = ptr + 1;
+
+ /*
+ * Note we don't bother to separately malloc cmdvalue; it will never
+ * be freed so that's not necessary.
+ */
+ newdef->cmdvalue = ptr + 1;
}
else
{
- defines->olddef = define_copy;
- defines->newdef = mm_strdup("1");
+ /* define it as "1"; again no need to malloc it */
+ newdef->cmdvalue = "1";
}
- defines->pertinent = true;
- defines->used = NULL;
- defines->next = pd;
+ newdef->name = define_copy;
+ newdef->value = mm_strdup(newdef->cmdvalue);
+ newdef->used = NULL;
+ newdef->next = defines;
+ defines = newdef;
}
#define ECPG_GETOPT_LONG_REGRESSION 1
@@ -348,6 +359,8 @@ main(int argc, char *const argv[])
{
struct cursor *ptr;
struct _defines *defptr;
+ struct _defines *prevdefptr;
+ struct _defines *nextdefptr;
struct typedefs *typeptr;
struct declared_list *list;
@@ -385,28 +398,28 @@ main(int argc, char *const argv[])
free(this);
}
- /* remove non-pertinent old defines as well */
- while (defines && !defines->pertinent)
+ /* restore defines to their command-line state */
+ prevdefptr = NULL;
+ for (defptr = defines; defptr != NULL; defptr = nextdefptr)
{
- defptr = defines;
- defines = defines->next;
-
- free(defptr->newdef);
- free(defptr->olddef);
- free(defptr);
- }
-
- for (defptr = defines; defptr != NULL; defptr = defptr->next)
- {
- struct _defines *this = defptr->next;
-
- if (this && !this->pertinent)
+ nextdefptr = defptr->next;
+ if (defptr->cmdvalue != NULL)
{
- defptr->next = this->next;
-
- free(this->newdef);
- free(this->olddef);
- free(this);
+ /* keep it, resetting the value */
+ free(defptr->value);
+ defptr->value = mm_strdup(defptr->cmdvalue);
+ prevdefptr = defptr;
+ }
+ else
+ {
+ /* remove it */
+ if (prevdefptr != NULL)
+ prevdefptr->next = nextdefptr;
+ else
+ defines = nextdefptr;
+ free(defptr->name);
+ free(defptr->value);
+ free(defptr);
}
}
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index e56cd31d274..bcfbd0978bb 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -69,7 +69,14 @@ char *token_start;
static int state_before_str_start;
static int state_before_str_stop;
-struct _yy_buffer
+/*
+ * State for handling include files and macro expansion. We use a new
+ * flex input buffer for each level of include or macro, and create a
+ * struct _yy_buffer to remember the previous level. There is not a struct
+ * for the currently active input source; that state is kept in the global
+ * variables YY_CURRENT_BUFFER, yylineno, and input_filename.
+ */
+static struct _yy_buffer
{
YY_BUFFER_STATE buffer;
long lineno;
@@ -77,8 +84,6 @@ struct _yy_buffer
struct _yy_buffer *next;
} *yy_buffer = NULL;
-static char *old;
-
/*
* Vars for handling ifdef/elif/endif constructs. preproc_tos is the current
* nesting depth of such constructs, and stacked_if_value[preproc_tos] is the
@@ -444,6 +449,8 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
%{
/* code to execute during start of each call of yylex() */
+ char *newdefsymbol = NULL;
+
token_start = NULL;
%}
@@ -1010,6 +1017,7 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
}
{identifier} {
+ /* First check to see if it's a define symbol to expand */
if (!isdefine())
{
int kwvalue;
@@ -1198,17 +1206,23 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
yytext[i+1] = '\0';
- for (ptr = defines; ptr != NULL; ptr2 = ptr, ptr = ptr->next)
+ /* Find and unset any matching define; should be only 1 */
+ for (ptr = defines; ptr; ptr2 = ptr, ptr = ptr->next)
{
- if (strcmp(yytext, ptr->olddef) == 0)
+ if (strcmp(yytext, ptr->name) == 0)
{
- if (ptr2 == NULL)
- defines = ptr->next;
- else
- ptr2->next = ptr->next;
- free(ptr->newdef);
- free(ptr->olddef);
- free(ptr);
+ free(ptr->value);
+ ptr->value = NULL;
+ /* We cannot forget it if there's a cmdvalue */
+ if (ptr->cmdvalue == NULL)
+ {
+ if (ptr2 == NULL)
+ defines = ptr->next;
+ else
+ ptr2->next = ptr->next;
+ free(ptr->name);
+ free(ptr);
+ }
break;
}
}
@@ -1413,11 +1427,17 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
;
yytext[i+1] = '\0';
- for (defptr = defines;
- defptr != NULL &&
- strcmp(yytext, defptr->olddef) != 0;
- defptr = defptr->next)
- /* skip */ ;
+ /* Does a definition exist? */
+ for (defptr = defines; defptr; defptr = defptr->next)
+ {
+ if (strcmp(yytext, defptr->name) == 0)
+ {
+ /* Found it, but is it currently undefined? */
+ if (defptr->value == NULL)
+ defptr = NULL; /* pretend it's not found */
+ break;
+ }
+ }
this_active = (defptr ? ifcond : !ifcond);
stacked_if_value[preproc_tos].active =
@@ -1438,7 +1458,7 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
yyterminate();
}
{identifier} {
- old = mm_strdup(yytext);
+ newdefsymbol = mm_strdup(yytext);
BEGIN(def);
startlit();
}
@@ -1447,26 +1467,31 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
yyterminate();
}
{space}*";" {
- struct _defines *ptr, *this;
+ struct _defines *ptr;
+ /* Does it already exist? */
for (ptr = defines; ptr != NULL; ptr = ptr->next)
{
- if (strcmp(old, ptr->olddef) == 0)
- {
- free(ptr->newdef);
- ptr->newdef = mm_strdup(literalbuf);
- }
+ if (strcmp(newdefsymbol, ptr->name) == 0)
+ {
+ free(ptr->value);
+ ptr->value = mm_strdup(literalbuf);
+ /* Don't leak newdefsymbol */
+ free(newdefsymbol);
+ break;
+ }
}
if (ptr == NULL)
{
- this = (struct _defines *) mm_alloc(sizeof(struct _defines));
+ /* Not present, make a new entry */
+ ptr = (struct _defines *) mm_alloc(sizeof(struct _defines));
- /* initial definition */
- this->olddef = old;
- this->newdef = mm_strdup(literalbuf);
- this->next = defines;
- this->used = NULL;
- defines = this;
+ ptr->name = newdefsymbol;
+ ptr->value = mm_strdup(literalbuf);
+ ptr->cmdvalue = NULL;
+ ptr->used = NULL;
+ ptr->next = defines;
+ defines = ptr;
}
BEGIN(C);
@@ -1483,6 +1508,7 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
<> {
if (yy_buffer == NULL)
{
+ /* No more input */
if (preproc_tos > 0)
{
preproc_tos = 0;
@@ -1492,16 +1518,20 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
}
else
{
+ /* Revert to previous input source */
struct _yy_buffer *yb = yy_buffer;
int i;
struct _defines *ptr;
+ /* Check to see if we are exiting a macro value */
for (ptr = defines; ptr; ptr = ptr->next)
+ {
if (ptr->used == yy_buffer)
{
ptr->used = NULL;
- break;
+ break; /* there can't be multiple matches */
}
+ }
if (yyin != NULL)
fclose(yyin);
@@ -1727,15 +1757,24 @@ ecpg_isspace(char ch)
return false;
}
-static bool isdefine(void)
+/*
+ * If yytext matches a define symbol, begin scanning the symbol's value
+ * and return true
+ */
+static bool
+isdefine(void)
{
struct _defines *ptr;
/* is it a define? */
for (ptr = defines; ptr; ptr = ptr->next)
{
- if (strcmp(yytext, ptr->olddef) == 0 && ptr->used == NULL)
+ /* notice we do not match anything being actively expanded */
+ if (strcmp(yytext, ptr->name) == 0 &&
+ ptr->value != NULL &&
+ ptr->used == NULL)
{
+ /* Save state associated with the current buffer */
struct _yy_buffer *yb;
yb = mm_alloc(sizeof(struct _yy_buffer));
@@ -1744,10 +1783,17 @@ static bool isdefine(void)
yb->lineno = yylineno;
yb->filename = mm_strdup(input_filename);
yb->next = yy_buffer;
+ yy_buffer = yb;
- ptr->used = yy_buffer = yb;
+ /* Mark symbol as being actively expanded */
+ ptr->used = yb;
- yy_scan_string(ptr->newdef);
+ /*
+ * We use yy_scan_string which will copy the value, so there's
+ * no need to worry about a possible undef happening while we
+ * are still scanning it.
+ */
+ yy_scan_string(ptr->value);
return true;
}
}
@@ -1755,7 +1801,12 @@ static bool isdefine(void)
return false;
}
-static bool isinformixdefine(void)
+/*
+ * Handle replacement of INFORMIX built-in defines. This works just
+ * like isdefine() except for the source of the string to scan.
+ */
+static bool
+isinformixdefine(void)
{
const char *new = NULL;
diff --git a/src/interfaces/ecpg/preproc/type.h b/src/interfaces/ecpg/preproc/type.h
index 5935cd74730..ce2124361fd 100644
--- a/src/interfaces/ecpg/preproc/type.h
+++ b/src/interfaces/ecpg/preproc/type.h
@@ -163,13 +163,25 @@ struct typedefs
struct typedefs *next;
};
+/*
+ * Info about a defined symbol (macro), coming from a -D command line switch
+ * or a define command in the program. These are stored in a simple list.
+ * Because ecpg supports compiling multiple files per run, we have to remember
+ * the command-line definitions and be able to revert to those; this motivates
+ * storing cmdvalue separately from value.
+ * name and value are separately-malloc'd strings; cmdvalue typically isn't.
+ * used is NULL unless we are currently expanding the macro, in which case
+ * it points to the buffer before the one scanning the macro; we reset it
+ * to NULL upon returning to that buffer. This is used to prevent recursive
+ * expansion of the macro.
+ */
struct _defines
{
- char *olddef;
- char *newdef;
- int pertinent;
- void *used;
- struct _defines *next;
+ char *name; /* symbol's name */
+ char *value; /* current value, or NULL if undefined */
+ const char *cmdvalue; /* value set on command line, or NULL */
+ void *used; /* buffer pointer, or NULL */
+ struct _defines *next; /* list link */
};
/* This is a linked list of the variable names and types. */
diff --git a/src/interfaces/ecpg/test/expected/sql-define.c b/src/interfaces/ecpg/test/expected/sql-define.c
index 29583ecd741..e97caec5b05 100644
--- a/src/interfaces/ecpg/test/expected/sql-define.c
+++ b/src/interfaces/ecpg/test/expected/sql-define.c
@@ -6,6 +6,21 @@
/* End of automatic include section */
#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
+#line 1 "define_prelim.pgc"
+/*
+ * Test that the effects of these commands don't carry over to the next
+ * file named on the ecpg command line.
+ */
+
+
+/* Processed by ecpg (regression mode) */
+/* These include files are added by the preprocessor */
+#include
+#include
+#include
+/* End of automatic include section */
+#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
+
#line 1 "define.pgc"
#line 1 "sqlca.h"
@@ -195,11 +210,57 @@ if (sqlca.sqlcode < 0) sqlprint ( );}
- { ECPGdisconnect(__LINE__, "CURRENT");
-#line 56 "define.pgc"
+ /* test handling of a macro defined on the command line */
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select 123", ECPGt_EOIT,
+ ECPGt_int,&(i),(long)1,(long)1,sizeof(int),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 57 "define.pgc"
if (sqlca.sqlcode < 0) sqlprint ( );}
-#line 56 "define.pgc"
+#line 57 "define.pgc"
+
+ printf("original CMDLINESYM: %d\n", i);
+
+
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select 42", ECPGt_EOIT,
+ ECPGt_int,&(i),(long)1,(long)1,sizeof(int),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 62 "define.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint ( );}
+#line 62 "define.pgc"
+
+ printf("redefined CMDLINESYM: %d\n", i);
+
+
+
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select 43", ECPGt_EOIT,
+ ECPGt_int,&(i),(long)1,(long)1,sizeof(int),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 67 "define.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint ( );}
+#line 67 "define.pgc"
+
+ printf("redefined CMDLINESYM: %d\n", i);
+
+
+
+
+
+
+
+ /* this macro should not have carried over from define_prelim.pgc */
+
+
+
+
+ { ECPGdisconnect(__LINE__, "CURRENT");
+#line 81 "define.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint ( );}
+#line 81 "define.pgc"
return 0;
}
diff --git a/src/interfaces/ecpg/test/expected/sql-define.stderr b/src/interfaces/ecpg/test/expected/sql-define.stderr
index 20601b63cf9..c4da9927e8c 100644
--- a/src/interfaces/ecpg/test/expected/sql-define.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-define.stderr
@@ -48,5 +48,29 @@
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_process_output on line 53: OK: SET
[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 57: query: select 123; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 57: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 57: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 57: RESULT: 123 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 62: query: select 42; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 62: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 62: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 62: RESULT: 42 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 67: query: select 43; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 67: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 67: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 67: RESULT: 43 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ecpg_finish: connection ecpg1_regression closed
[NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-define.stdout b/src/interfaces/ecpg/test/expected/sql-define.stdout
index 864cd850bf3..eaddc7f8c32 100644
--- a/src/interfaces/ecpg/test/expected/sql-define.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-define.stdout
@@ -1 +1,4 @@
i: 1, s: 29-abcdef
+original CMDLINESYM: 123
+redefined CMDLINESYM: 42
+redefined CMDLINESYM: 43
diff --git a/src/interfaces/ecpg/test/sql/Makefile b/src/interfaces/ecpg/test/sql/Makefile
index 7f032659b9b..3ff190a5233 100644
--- a/src/interfaces/ecpg/test/sql/Makefile
+++ b/src/interfaces/ecpg/test/sql/Makefile
@@ -34,5 +34,8 @@ TESTS = array array.c \
all: $(TESTS)
+define.c: define.pgc define_prelim.pgc $(ECPG_TEST_DEPENDENCIES)
+ $(ECPG) -DCMDLINESYM=123 -o $@ $(srcdir)/define_prelim.pgc $<
+
oldexec.c: oldexec.pgc $(ECPG_TEST_DEPENDENCIES)
$(ECPG) -r questionmarks -o $@ $<
diff --git a/src/interfaces/ecpg/test/sql/define.pgc b/src/interfaces/ecpg/test/sql/define.pgc
index ed58a4bde0c..83f328df468 100644
--- a/src/interfaces/ecpg/test/sql/define.pgc
+++ b/src/interfaces/ecpg/test/sql/define.pgc
@@ -53,6 +53,31 @@ int main(void)
exec sql SET TIMEZONE TO TZVAR;
exec sql endif;
+ /* test handling of a macro defined on the command line */
+ exec sql select CMDLINESYM INTO :i;
+ printf("original CMDLINESYM: %d\n", i);
+
+ exec sql define CMDLINESYM 42;
+
+ exec sql select CMDLINESYM INTO :i;
+ printf("redefined CMDLINESYM: %d\n", i);
+
+ exec sql define CMDLINESYM 43;
+
+ exec sql select CMDLINESYM INTO :i;
+ printf("redefined CMDLINESYM: %d\n", i);
+
+ exec sql undef CMDLINESYM;
+
+ exec sql ifdef CMDLINESYM;
+ exec sql insert into test values (NUMBER, 'no string');
+ exec sql endif;
+
+ /* this macro should not have carried over from define_prelim.pgc */
+ exec sql ifdef NONCMDLINESYM;
+ exec sql insert into test values (NUMBER, 'no string');
+ exec sql endif;
+
exec sql disconnect;
return 0;
}
diff --git a/src/interfaces/ecpg/test/sql/define_prelim.pgc b/src/interfaces/ecpg/test/sql/define_prelim.pgc
new file mode 100644
index 00000000000..7a984f74c84
--- /dev/null
+++ b/src/interfaces/ecpg/test/sql/define_prelim.pgc
@@ -0,0 +1,6 @@
+/*
+ * Test that the effects of these commands don't carry over to the next
+ * file named on the ecpg command line.
+ */
+exec sql define CMDLINESYM 999;
+exec sql define NONCMDLINESYM 1234;
diff --git a/src/interfaces/ecpg/test/sql/meson.build b/src/interfaces/ecpg/test/sql/meson.build
index 88a3acb9afe..4da6e19f5f1 100644
--- a/src/interfaces/ecpg/test/sql/meson.build
+++ b/src/interfaces/ecpg/test/sql/meson.build
@@ -31,6 +31,7 @@ pgc_files = [
]
pgc_extra_flags = {
+ 'define': ['-DCMDLINESYM=123', files('define_prelim.pgc')],
'oldexec': ['-r', 'questionmarks'],
}