Alvaro Herrera <[email protected]> writes:
> Maybe it would be worthwhile to split the parts that parse a file and
> execute from a file, and submit separately. It is obviously
> self-contained and serves a useful purpose on its own. It also forces
> you to think harder about renaming the parse function :-)
So, you will find two new branches for those purposes at the repository,
named cfparser and pg_execute_from_file:
http://git.postgresql.org/gitweb?p=postgresql-extension.git;a=summary
Please find attached the patches extracted from those branches. Note
that currently, the main "extension" branch still contains the
modifications, I intend to merge/rebase that against the master after
the commits.
I've also merged the master repository into my feature branch, and git
just did it all by itself. I like it when the tools are helping! :)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
***************
*** 55,60 ****
--- 55,61 ----
#include "utils/guc.h"
#include "utils/ps_status.h"
#include "utils/relmapper.h"
+ #include "utils/cfparser.h"
#include "pg_trace.h"
***************
*** 5018,5117 **** str_time(pg_time_t tnow)
}
/*
- * Parse one line from recovery.conf. 'cmdline' is the raw line from the
- * file. If the line is parsed successfully, returns true, false indicates
- * syntax error. On success, *key_p and *value_p are set to the parameter
- * name and value on the line, respectively. If the line is an empty line,
- * consisting entirely of whitespace and comments, function returns true
- * and *keyp_p and *value_p are set to NULL.
- *
- * The pointers returned in *key_p and *value_p point to an internal buffer
- * that is valid only until the next call of parseRecoveryCommandFile().
- */
- static bool
- parseRecoveryCommandFileLine(char *cmdline, char **key_p, char **value_p)
- {
- char *ptr;
- char *bufp;
- char *key;
- char *value;
- static char *buf = NULL;
-
- *key_p = *value_p = NULL;
-
- /*
- * Allocate the buffer on first use. It's used to hold both the parameter
- * name and value.
- */
- if (buf == NULL)
- buf = malloc(MAXPGPATH + 1);
- bufp = buf;
-
- /* Skip any whitespace at the beginning of line */
- for (ptr = cmdline; *ptr; ptr++)
- {
- if (!isspace((unsigned char) *ptr))
- break;
- }
- /* Ignore empty lines */
- if (*ptr == '\0' || *ptr == '#')
- return true;
-
- /* Read the parameter name */
- key = bufp;
- while (*ptr && !isspace((unsigned char) *ptr) &&
- *ptr != '=' && *ptr != '\'')
- *(bufp++) = *(ptr++);
- *(bufp++) = '\0';
-
- /* Skip to the beginning quote of the parameter value */
- ptr = strchr(ptr, '\'');
- if (!ptr)
- return false;
- ptr++;
-
- /* Read the parameter value to *bufp. Collapse any '' escapes as we go. */
- value = bufp;
- for (;;)
- {
- if (*ptr == '\'')
- {
- ptr++;
- if (*ptr == '\'')
- *(bufp++) = '\'';
- else
- {
- /* end of parameter */
- *bufp = '\0';
- break;
- }
- }
- else if (*ptr == '\0')
- return false; /* unterminated quoted string */
- else
- *(bufp++) = *ptr;
-
- ptr++;
- }
- *(bufp++) = '\0';
-
- /* Check that there's no garbage after the value */
- while (*ptr)
- {
- if (*ptr == '#')
- break;
- if (!isspace((unsigned char) *ptr))
- return false;
- ptr++;
- }
-
- /* Success! */
- *key_p = key;
- *value_p = value;
- return true;
- }
-
- /*
* See if there is a recovery command file (recovery.conf), and if so
* read in parameters for archive recovery and XLOG streaming.
*
--- 5019,5024 ----
***************
*** 5147,5153 **** readRecoveryCommandFile(void)
char *tok1;
char *tok2;
! if (!parseRecoveryCommandFileLine(cmdline, &tok1, &tok2))
{
syntaxError = true;
break;
--- 5054,5060 ----
char *tok1;
char *tok2;
! if (!cfParseOneLine(cmdline, &tok1, &tok2))
{
syntaxError = true;
break;
*** a/src/backend/utils/misc/Makefile
--- b/src/backend/utils/misc/Makefile
***************
*** 15,21 **** include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS = guc.o help_config.o pg_rusage.o ps_status.o superuser.o tzparser.o \
! rbtree.o
# This location might depend on the installation directories. Therefore
# we can't subsitute it into pg_config.h.
--- 15,21 ----
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS = guc.o help_config.o pg_rusage.o ps_status.o superuser.o tzparser.o \
! rbtree.o cfparser.o
# This location might depend on the installation directories. Therefore
# we can't subsitute it into pg_config.h.
*** /dev/null
--- b/src/backend/utils/misc/cfparser.c
***************
*** 0 ****
--- 1,112 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cfparser.c
+ * Function for parsing RecoveryCommandFile lines
+ *
+ * This very simple file format (varible = value) is now also used in the
+ * extension control file format
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/cfparser.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #include "postgres.h"
+
+ /*
+ * Parse one line from recovery.conf. 'cmdline' is the raw line from the
+ * file. If the line is parsed successfully, returns true, false indicates
+ * syntax error. On success, *key_p and *value_p are set to the parameter
+ * name and value on the line, respectively. If the line is an empty line,
+ * consisting entirely of whitespace and comments, function returns true
+ * and *keyp_p and *value_p are set to NULL.
+ *
+ * The pointers returned in *key_p and *value_p point to an internal buffer
+ * that is valid only until the next call of parseRecoveryCommandFile().
+ */
+ bool
+ cfParseOneLine(char *cmdline, char **key_p, char **value_p)
+ {
+ char *ptr;
+ char *bufp;
+ char *key;
+ char *value;
+ static char *buf = NULL;
+
+ *key_p = *value_p = NULL;
+
+ /*
+ * Allocate the buffer on first use. It's used to hold both the parameter
+ * name and value.
+ */
+ if (buf == NULL)
+ buf = malloc(MAXPGPATH + 1);
+ bufp = buf;
+
+ /* Skip any whitespace at the beginning of line */
+ for (ptr = cmdline; *ptr; ptr++)
+ {
+ if (!isspace((unsigned char) *ptr))
+ break;
+ }
+ /* Ignore empty lines */
+ if (*ptr == '\0' || *ptr == '#')
+ return true;
+
+ /* Read the parameter name */
+ key = bufp;
+ while (*ptr && !isspace((unsigned char) *ptr) &&
+ *ptr != '=' && *ptr != '\'')
+ *(bufp++) = *(ptr++);
+ *(bufp++) = '\0';
+
+ /* Skip to the beginning quote of the parameter value */
+ ptr = strchr(ptr, '\'');
+ if (!ptr)
+ return false;
+ ptr++;
+
+ /* Read the parameter value to *bufp. Collapse any '' escapes as we go. */
+ value = bufp;
+ for (;;)
+ {
+ if (*ptr == '\'')
+ {
+ ptr++;
+ if (*ptr == '\'')
+ *(bufp++) = '\'';
+ else
+ {
+ /* end of parameter */
+ *bufp = '\0';
+ break;
+ }
+ }
+ else if (*ptr == '\0')
+ return false; /* unterminated quoted string */
+ else
+ *(bufp++) = *ptr;
+
+ ptr++;
+ }
+ *(bufp++) = '\0';
+
+ /* Check that there's no garbage after the value */
+ while (*ptr)
+ {
+ if (*ptr == '#')
+ break;
+ if (!isspace((unsigned char) *ptr))
+ return false;
+ ptr++;
+ }
+
+ /* Success! */
+ *key_p = key;
+ *value_p = value;
+ return true;
+ }
*** /dev/null
--- b/src/include/utils/cfparser.h
***************
*** 0 ****
--- 1,18 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cfparser.h
+ * Function for parsing RecoveryCommandFile lines
+ *
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/cfparser.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef CFPARSER_H
+ #define CFPARSER_H
+
+ bool cfParseOneLine(char *cmdline, char **key_p, char **value_p);
+
+ #endif /* CFPARSER_H */
*** a/src/backend/utils/adt/genfile.c
--- b/src/backend/utils/adt/genfile.c
***************
*** 7,12 ****
--- 7,13 ----
* Copyright (c) 2004-2010, PostgreSQL Global Development Group
*
* Author: Andreas Pflug <[email protected]>
+ * Dimitri Fontaine <[email protected]>
*
* IDENTIFICATION
* src/backend/utils/adt/genfile.c
***************
*** 30,35 ****
--- 31,46 ----
#include "utils/memutils.h"
#include "utils/timestamp.h"
+ #include "tcop/pquery.h"
+ #include "tcop/tcopprot.h"
+ #include "tcop/utility.h"
+ #include "access/transam.h"
+ #include "access/xact.h"
+ #include "utils/resowner.h"
+ #include "utils/snapmgr.h"
+ #include "parser/analyze.h"
+ #include "access/printtup.h"
+
typedef struct
{
char *location;
***************
*** 264,266 **** pg_ls_dir(PG_FUNCTION_ARGS)
--- 275,459 ----
SRF_RETURN_DONE(funcctx);
}
+
+ /*
+ * Read a file then execute the SQL commands it contains.
+ */
+ Datum
+ pg_execute_from_file(PG_FUNCTION_ARGS)
+ {
+ text *filename_t = PG_GETARG_TEXT_P(0);
+ char *filename;
+ FILE *file;
+ int64 fsize = -1, nbytes;
+ struct stat fst;
+ char *query_string = NULL;
+
+ CommandDest dest = DestNone;
+ MemoryContext oldcontext;
+ List *parsetree_list;
+ ListCell *parsetree_item;
+ bool save_log_statement_stats = log_statement_stats;
+ bool was_logged = false;
+ bool isTopLevel;
+ char msec_str[32];
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to get file information"))));
+
+ /*
+ * Only superuser can call pg_execute_from_file, and CREATE EXTENSION
+ * uses that too. Don't double check the PATH. Also note that
+ * extension's install files are not in $PGDATA but `pg_config
+ * --sharedir`.
+ */
+ filename = text_to_cstring(filename_t);
+
+ if (stat(filename, &fst) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", filename)));
+
+ fsize = Int64GetDatum((int64) fst.st_size);
+
+ if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" for reading: %m",
+ filename)));
+
+ if (ferror(file))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m", filename)));
+
+ query_string = (char *)palloc((fsize+1)*sizeof(char));
+ memset(query_string, 0, fsize+1);
+ nbytes = fread(query_string, 1, (size_t) fsize, file);
+ pg_verifymbstr(query_string, nbytes, false);
+ FreeFile(file);
+
+ /*
+ elog(NOTICE, "pg_execute_from_file('%s') read %d/%d bytes:", filename, nbytes, fsize);
+ elog(NOTICE, "%s", query_string);
+ */
+
+ /*
+ * Code pasted from postgres.c:exec_simple_query, main differences are:
+ * - don't override unnamed portal, name it after filename instead
+ * - don't start nor finish a transaction
+ * - don't set stats or tracing markers
+ */
+ oldcontext = MemoryContextSwitchTo(MessageContext);
+ parsetree_list = pg_parse_query(query_string);
+ MemoryContextSwitchTo(oldcontext);
+
+ isTopLevel = false;
+
+ foreach(parsetree_item, parsetree_list)
+ {
+ Node *parsetree = (Node *) lfirst(parsetree_item);
+ bool snapshot_set = false;
+ const char *commandTag;
+ char completionTag[COMPLETION_TAG_BUFSIZE];
+ List *querytree_list,
+ *plantree_list;
+ Portal portal;
+ DestReceiver *receiver;
+ int16 format = 0; /* TEXT */
+
+ commandTag = CreateCommandTag(parsetree);
+
+ /* If we got a cancel signal in parsing or prior command, quit */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Set up a snapshot if parse analysis/planning will need one.
+ */
+ if (analyze_requires_snapshot(parsetree))
+ {
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
+ }
+
+ /*
+ * OK to analyze, rewrite, and plan this query.
+ *
+ * Switch to appropriate context for constructing querytrees (again,
+ * these must outlive the execution context).
+ */
+ oldcontext = MemoryContextSwitchTo(MessageContext);
+
+ querytree_list = pg_analyze_and_rewrite(parsetree, query_string,
+ NULL, 0);
+
+ plantree_list = pg_plan_queries(querytree_list, 0, NULL);
+
+ /* Done with the snapshot used for parsing/planning */
+ if (snapshot_set)
+ PopActiveSnapshot();
+
+ /* If we got a cancel signal in analysis or planning, quit */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Create a portal to run the query or queries in. Name if after the
+ * given filename. If there already is one, silently drop it.
+ */
+ portal = CreatePortal(filename, true, true);
+ /* Don't display the portal in pg_cursors */
+ portal->visible = false;
+
+ /*
+ * We don't have to copy anything into the portal, because everything
+ * we are passing here is in MessageContext, which will outlive the
+ * portal anyway.
+ */
+ PortalDefineQuery(portal,
+ NULL,
+ query_string,
+ commandTag,
+ plantree_list,
+ NULL);
+
+ /*
+ * Start the portal. No parameters here.
+ */
+ PortalStart(portal, NULL, InvalidSnapshot);
+ PortalSetResultFormat(portal, 1, &format);
+
+ /*
+ * Now we can create the destination receiver object.
+ */
+ receiver = CreateDestReceiver(dest);
+ if (dest == DestRemote)
+ SetRemoteDestReceiverParams(receiver, portal);
+
+ /*
+ * Switch back to transaction context for execution.
+ */
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Run the portal to completion, and then drop it (and the receiver).
+ */
+ (void) PortalRun(portal,
+ FETCH_ALL,
+ isTopLevel,
+ receiver,
+ receiver,
+ completionTag);
+
+ (*receiver->rDestroy) (receiver);
+
+ PortalDrop(portal, false);
+
+ if (!IsA(parsetree, TransactionStmt) && lnext(parsetree_item) != NULL)
+ {
+ CommandCounterIncrement();
+ }
+ }
+ PG_RETURN_VOID();
+ }
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 3386,3399 **** DESCR("reload configuration files");
DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
DESCR("rotate log file");
! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ ));
DESCR("return file information");
! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ ));
DESCR("read text from a file");
! DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ ));
DESCR("list all files in a directory");
! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ ));
DESCR("sleep for the specified time in seconds");
DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ ));
DESCR("convert boolean to text");
--- 3386,3401 ----
DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
DESCR("rotate log file");
! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ ));
DESCR("return file information");
! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ ));
DESCR("read text from a file");
! DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ ));
DESCR("list all files in a directory");
! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ ));
DESCR("sleep for the specified time in seconds");
+ DATA(insert OID = 3627 ( pg_execute_from_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "25" _null_ _null_ _null_ _null_ pg_execute_from_file _null_ _null_ _null_ ));
+ DESCR("execute queries read from a file");
DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ ));
DESCR("convert boolean to text");
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 442,447 **** extern Datum pg_relation_filepath(PG_FUNCTION_ARGS);
--- 442,448 ----
extern Datum pg_stat_file(PG_FUNCTION_ARGS);
extern Datum pg_read_file(PG_FUNCTION_ARGS);
extern Datum pg_ls_dir(PG_FUNCTION_ARGS);
+ extern Datum pg_execute_from_file(PG_FUNCTION_ARGS);
/* misc.c */
extern Datum current_database(PG_FUNCTION_ARGS);
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers