Alvaro Herrera <alvhe...@commandprompt.com> 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 <pgad...@pse-consulting.de> + * Dimitri Fontaine <dimi...@2ndquadrant.fr> * * 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 (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers