postgresql/src/interfaces/ecpg/preproc/ecpg.c

494 lines
13 KiB
C
Raw Normal View History

2010-09-20 22:08:53 +02:00
/* src/interfaces/ecpg/preproc/ecpg.c */
2009-08-05 13:42:20 +02:00
/* Main for ecpg, the PostgreSQL embedded SQL precompiler. */
/* Copyright (c) 1996-2020, PostgreSQL Global Development Group */
#include "postgres_fe.h"
1999-07-19 03:18:05 +02:00
#include <unistd.h>
#include "getopt_long.h"
#include "preproc_extern.h"
int ret_value = 0;
2011-04-10 17:42:00 +02:00
bool autocommit = false,
auto_create_c = false,
system_includes = false,
2004-01-28 21:43:03 +01:00
force_indicator = true,
questionmarks = false,
regression_mode = false,
auto_prepare = false;
2007-11-15 22:14:46 +01:00
char *output_filename;
2003-08-04 02:43:34 +02:00
enum COMPAT_MODE compat = ECPG_COMPAT_PGSQL;
struct _include_path *include_paths = NULL;
struct cursor *cur = NULL;
struct typedefs *types = NULL;
struct _defines *defines = NULL;
static void
help(const char *progname)
{
printf(_("%s is the PostgreSQL embedded SQL preprocessor for C programs.\n\n"),
progname);
printf(_("Usage:\n"
" %s [OPTION]... FILE...\n\n"),
progname);
printf(_("Options:\n"));
printf(_(" -c automatically generate C code from embedded SQL code;\n"
2009-01-23 13:43:32 +01:00
" this affects EXEC SQL TYPE\n"));
printf(_(" -C MODE set compatibility mode; MODE can be one of\n"
" \"INFORMIX\", \"INFORMIX_SE\", \"ORACLE\"\n"));
#ifdef YYDEBUG
printf(_(" -d generate parser debug output\n"));
#endif
printf(_(" -D SYMBOL define SYMBOL\n"));
printf(_(" -h parse a header file, this option includes option \"-c\"\n"));
printf(_(" -i parse system include files as well\n"));
printf(_(" -I DIRECTORY search DIRECTORY for include files\n"));
printf(_(" -o OUTFILE write result to OUTFILE\n"));
2009-01-23 13:43:32 +01:00
printf(_(" -r OPTION specify run-time behavior; OPTION can be:\n"
" \"no_indicator\", \"prepare\", \"questionmarks\"\n"));
printf(_(" --regression run in regression testing mode\n"));
printf(_(" -t turn on autocommit of transactions\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_("\nIf no output file is specified, the name is formed by adding .c to the\n"
"input file name, after stripping off .pgc if present.\n"));
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}
static void
add_include_path(char *path)
{
2003-08-04 02:43:34 +02:00
struct _include_path *ip = include_paths,
*new;
new = mm_alloc(sizeof(struct _include_path));
new->path = path;
new->next = NULL;
2002-09-04 22:31:48 +02:00
if (ip == NULL)
include_paths = new;
else
{
2003-08-04 02:43:34 +02:00
for (; ip->next != NULL; ip = ip->next);
ip->next = new;
}
}
2000-01-18 14:03:49 +01:00
static void
add_preprocessor_define(char *define)
{
struct _defines *pd = defines;
2003-08-04 02:43:34 +02:00
char *ptr,
*define_copy = mm_strdup(define);
2000-01-18 14:03:49 +01:00
defines = mm_alloc(sizeof(struct _defines));
2003-08-04 02:43:34 +02:00
2003-03-18 11:46:39 +01:00
/* look for = sign */
ptr = strchr(define_copy, '=');
if (ptr != NULL)
{
2003-08-04 02:43:34 +02:00
char *tmp;
/* symbol has a value */
2003-08-04 02:43:34 +02:00
for (tmp = ptr - 1; *tmp == ' '; tmp--);
2003-03-18 11:46:39 +01:00
tmp[1] = '\0';
defines->olddef = define_copy;
defines->newdef = ptr + 1;
2003-03-18 11:46:39 +01:00
}
else
{
defines->olddef = define_copy;
defines->newdef = mm_strdup("1");
2003-03-18 11:46:39 +01:00
}
2000-01-18 14:03:49 +01:00
defines->pertinent = true;
defines->used = NULL;
2000-01-18 14:03:49 +01:00
defines->next = pd;
}
#define ECPG_GETOPT_LONG_REGRESSION 1
int
main(int argc, char *const argv[])
{
static struct option ecpg_options[] = {
{"regression", no_argument, NULL, ECPG_GETOPT_LONG_REGRESSION},
2007-11-15 22:14:46 +01:00
{NULL, 0, NULL, 0}
};
int fnr,
c,
out_option = 0;
2011-04-10 17:42:00 +02:00
bool verbose = false,
header_mode = false;
struct _include_path *ip;
const char *progname;
2004-08-29 07:07:03 +02:00
char my_exec_path[MAXPGPATH];
char include_path[MAXPGPATH];
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("ecpg"));
progname = get_progname(argv[0]);
if (find_my_exec(argv[0], my_exec_path) < 0)
{
fprintf(stderr, _("%s: could not locate my own executable path\n"), argv[0]);
return ILLEGAL_OPTION;
}
if (argc > 1)
{
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
help(progname);
exit(0);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
printf("ecpg (PostgreSQL) %s\n", PG_VERSION);
exit(0);
}
}
output_filename = NULL;
while ((c = getopt_long(argc, argv, "vcio:I:tD:dC:r:h", ecpg_options, NULL)) != -1)
{
switch (c)
{
case ECPG_GETOPT_LONG_REGRESSION:
regression_mode = true;
break;
case 'o':
output_filename = mm_strdup(optarg);
if (strcmp(output_filename, "-") == 0)
base_yyout = stdout;
else
base_yyout = fopen(output_filename, PG_BINARY_W);
2003-08-04 02:43:34 +02:00
if (base_yyout == NULL)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
progname, output_filename, strerror(errno));
output_filename = NULL;
}
else
out_option = 1;
break;
case 'I':
add_include_path(optarg);
break;
case 't':
autocommit = true;
break;
case 'v':
1999-12-23 13:33:19 +01:00
verbose = true;
The first fix is to allow an input file with a relative path and without a ".pgc " extension. The second patch fixes a coredump when there is more than one input file (in that case, cur and types were not set to NULL before processing the second f ile) The patch below modifies the accepted grammar of ecpg to accept FETCH [direction] [amount] cursor name i.e. the IN|FROM clause becomes optional (as in Oracle and Informix). This removes the incompatibility mentioned in section "Porting From Other RDBMS Packages" p169, PostgreSQL Programmer's Guide. The grammar is modified in such a way as to avoid shift/reduce conflicts. It does not accept the statement "EXEC SQL FETCH;" anymore, as the old grammar did (this seems to be a bug of the old grammar anyway). This patch cleans up the handling of space characters in the scanner; some patte rns require \n to be in {space}, some do not. A second fix is the handling of cpp continuati on lines; the old pattern did not match these. The parser is patched to fix an off-by-one error in the #line directives. The pa rser is also enhanced to report the correct location of errors in declarations in the "E XEC SQL DECLARE SECTION". Finally, some right recursions in the parser were replaced by left-recursions. This patch adds preprocessor directives to ecpg; in particular EXEC SQL IFDEF, EXEC SQL IFNDEF, EXEC SQL ELSE, EXEC SQL ELIF and EXEC SQL ENDIF "EXEC SQL IFDEF" is used with defines made with "EXEC SQL DEFINE" and defines, specified on the command line with -D. Defines, specified on the command line are persistent across multiple input files. Defines can be nested up to a maximum level of 128 (see patch). There is a fair amount of error checking to make sure directives are matched properly. I need preprocessor directives for porting code, that is written for an Informix database, to a PostgreSQL database, while maintaining compatibility with the original code. I decided not to extend the already large ecpg grammar. Everything is done in the scanner by adding some states, e.g. to skip all input except newlines and directives. The preprocessor commands are compatible with Informix. Oracle uses a cpp replacement. Rene Hogendoorn
1999-12-21 18:42:16 +01:00
break;
case 'h':
header_mode = true;
/* this must include "-c" to make sense, so fall through */
/* FALLTHROUGH */
case 'c':
auto_create_c = true;
break;
case 'i':
system_includes = true;
break;
case 'C':
if (pg_strcasecmp(optarg, "INFORMIX") == 0 || pg_strcasecmp(optarg, "INFORMIX_SE") == 0)
2003-03-18 11:46:39 +01:00
{
2004-08-29 07:07:03 +02:00
char pkginclude_path[MAXPGPATH];
char informix_path[MAXPGPATH];
compat = (pg_strcasecmp(optarg, "INFORMIX") == 0) ? ECPG_COMPAT_INFORMIX : ECPG_COMPAT_INFORMIX_SE;
get_pkginclude_path(my_exec_path, pkginclude_path);
snprintf(informix_path, MAXPGPATH, "%s/informix/esql", pkginclude_path);
add_include_path(informix_path);
2003-03-18 11:46:39 +01:00
}
else if (pg_strcasecmp(optarg, "ORACLE") == 0)
{
compat = ECPG_COMPAT_ORACLE;
}
else
{
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
return ILLEGAL_OPTION;
2003-08-04 02:43:34 +02:00
}
break;
case 'r':
if (pg_strcasecmp(optarg, "no_indicator") == 0)
force_indicator = false;
else if (pg_strcasecmp(optarg, "prepare") == 0)
auto_prepare = true;
else if (pg_strcasecmp(optarg, "questionmarks") == 0)
questionmarks = true;
else
{
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
return ILLEGAL_OPTION;
}
break;
2000-01-18 14:03:49 +01:00
case 'D':
add_preprocessor_define(optarg);
break;
case 'd':
#ifdef YYDEBUG
base_yydebug = 1;
#else
fprintf(stderr, _("%s: parser debug support (-d) not available\n"),
progname);
#endif
break;
default:
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
1998-09-01 05:29:17 +02:00
return ILLEGAL_OPTION;
}
}
add_include_path(".");
add_include_path("/usr/local/include");
get_include_path(my_exec_path, include_path);
add_include_path(include_path);
add_include_path("/usr/include");
1999-12-23 13:33:19 +01:00
if (verbose)
{
fprintf(stderr,
_("%s, the PostgreSQL embedded C preprocessor, version %s\n"),
progname, PG_VERSION);
2009-01-23 13:43:32 +01:00
fprintf(stderr, _("EXEC SQL INCLUDE ... search starts here:\n"));
1999-12-23 13:33:19 +01:00
for (ip = include_paths; ip != NULL; ip = ip->next)
fprintf(stderr, " %s\n", ip->path);
fprintf(stderr, _("end of search list\n"));
return 0;
1999-12-23 13:33:19 +01:00
}
if (optind >= argc) /* no files specified */
{
fprintf(stderr, _("%s: no input files specified\n"), progname);
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), argv[0]);
return ILLEGAL_OPTION;
}
else
{
/* after the options there must not be anything but filenames */
for (fnr = optind; fnr < argc; fnr++)
{
2007-11-15 22:14:46 +01:00
char *ptr2ext;
/* If argv[fnr] is "-" we have to read from stdin */
if (strcmp(argv[fnr], "-") == 0)
{
2003-08-04 02:43:34 +02:00
input_filename = mm_alloc(strlen("stdin") + 1);
strcpy(input_filename, "stdin");
base_yyin = stdin;
}
else
{
input_filename = mm_alloc(strlen(argv[fnr]) + 5);
strcpy(input_filename, argv[fnr]);
/* take care of relative paths */
ptr2ext = last_dir_separator(input_filename);
ptr2ext = (ptr2ext ? strrchr(ptr2ext, '.') : strrchr(input_filename, '.'));
/* no extension? */
if (ptr2ext == NULL)
{
ptr2ext = input_filename + strlen(input_filename);
/* no extension => add .pgc or .pgh */
ptr2ext[0] = '.';
ptr2ext[1] = 'p';
ptr2ext[2] = 'g';
2004-08-29 07:07:03 +02:00
ptr2ext[3] = (header_mode == true) ? 'h' : 'c';
ptr2ext[4] = '\0';
}
2003-08-04 02:43:34 +02:00
base_yyin = fopen(input_filename, PG_BINARY_R);
}
if (out_option == 0) /* calculate the output name */
{
if (strcmp(input_filename, "stdin") == 0)
base_yyout = stdout;
else
{
output_filename = mm_alloc(strlen(input_filename) + 3);
strcpy(output_filename, input_filename);
ptr2ext = strrchr(output_filename, '.');
/* make extension = .c resp. .h */
2004-08-29 07:07:03 +02:00
ptr2ext[1] = (header_mode == true) ? 'h' : 'c';
ptr2ext[2] = '\0';
base_yyout = fopen(output_filename, PG_BINARY_W);
if (base_yyout == NULL)
{
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
progname, output_filename, strerror(errno));
free(output_filename);
output_filename = NULL;
free(input_filename);
continue;
}
}
}
if (base_yyin == NULL)
fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
progname, argv[fnr], strerror(errno));
else
{
struct cursor *ptr;
struct _defines *defptr;
struct typedefs *typeptr;
1999-05-25 18:15:34 +02:00
/* remove old cursor definitions if any are still there */
for (ptr = cur; ptr != NULL;)
{
struct cursor *this = ptr;
struct arguments *l1,
*l2;
free(ptr->command);
free(ptr->connection);
free(ptr->name);
for (l1 = ptr->argsinsert; l1; l1 = l2)
{
l2 = l1->next;
free(l1);
}
for (l1 = ptr->argsresult; l1; l1 = l2)
{
l2 = l1->next;
free(l1);
}
ptr = ptr->next;
free(this);
}
cur = NULL;
2000-01-18 14:03:49 +01:00
/* remove non-pertinent old defines as well */
while (defines && !defines->pertinent)
{
defptr = defines;
defines = defines->next;
2000-01-18 14:03:49 +01:00
free(defptr->newdef);
free(defptr->olddef);
free(defptr);
2000-01-18 14:03:49 +01:00
}
for (defptr = defines; defptr != NULL; defptr = defptr->next)
{
struct _defines *this = defptr->next;
if (this && !this->pertinent)
{
defptr->next = this->next;
free(this->newdef);
free(this->olddef);
free(this);
}
}
1999-05-25 18:15:34 +02:00
/* and old typedefs */
for (typeptr = types; typeptr != NULL;)
{
struct typedefs *this = typeptr;
free(typeptr->name);
ECPGfree_struct_member(typeptr->struct_member_list);
2000-01-18 14:03:49 +01:00
free(typeptr->type);
typeptr = typeptr->next;
free(this);
}
2000-01-18 14:03:49 +01:00
types = NULL;
2000-01-18 14:03:49 +01:00
/* initialize whenever structures */
memset(&when_error, 0, sizeof(struct when));
memset(&when_nf, 0, sizeof(struct when));
memset(&when_warn, 0, sizeof(struct when));
2000-01-18 14:03:49 +01:00
/* and structure member lists */
memset(struct_member_list, 0, sizeof(struct_member_list));
2010-02-26 03:01:40 +01:00
/*
* and our variable counter for out of scope cursors'
* variables
*/
ecpg_internal_var = 0;
2000-02-17 20:48:58 +01:00
/* finally the actual connection */
connection = NULL;
2003-08-04 02:43:34 +02:00
/* initialize lex */
lex_init();
2002-01-10 11:42:54 +01:00
/* we need several includes */
/* but not if we are in header mode */
2007-11-15 22:14:46 +01:00
if (regression_mode)
fprintf(base_yyout, "/* Processed by ecpg (regression mode) */\n");
else
fprintf(base_yyout, "/* Processed by ecpg (%s) */\n", PG_VERSION);
2004-08-29 07:07:03 +02:00
if (header_mode == false)
{
fprintf(base_yyout, "/* These include files are added by the preprocessor */\n#include <ecpglib.h>\n#include <ecpgerrno.h>\n#include <sqlca.h>\n");
/* add some compatibility headers */
if (INFORMIX_MODE)
fprintf(base_yyout, "/* Needed for informix compatibility */\n#include <ecpg_informix.h>\n");
2003-08-04 02:43:34 +02:00
fprintf(base_yyout, "/* End of automatic include section */\n");
}
2007-11-15 22:14:46 +01:00
if (regression_mode)
fprintf(base_yyout, "#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))\n");
output_line_number();
2004-08-29 07:07:03 +02:00
/* and parse the source */
base_yyparse();
/*
* Check whether all cursors were indeed opened. It does not
* really make sense to declare a cursor but not open it.
2009-01-23 13:43:32 +01:00
*/
for (ptr = cur; ptr != NULL; ptr = ptr->next)
if (!(ptr->opened))
2009-01-23 13:43:32 +01:00
mmerror(PARSE_ERROR, ET_WARNING, "cursor \"%s\" has been declared but not opened", ptr->name);
2004-08-29 07:07:03 +02:00
if (base_yyin != NULL && base_yyin != stdin)
fclose(base_yyin);
if (out_option == 0 && base_yyout != stdout)
fclose(base_yyout);
2010-07-06 21:19:02 +02:00
/*
* If there was an error, delete the output file.
*/
if (ret_value != 0)
{
if (strcmp(output_filename, "-") != 0 && unlink(output_filename) != 0)
fprintf(stderr, _("could not remove output file \"%s\"\n"), output_filename);
}
}
if (output_filename && out_option == 0)
{
free(output_filename);
output_filename = NULL;
}
free(input_filename);
}
}
1999-12-08 10:52:29 +01:00
return ret_value;
}