On 10/24/14, 6:17 PM, Jim Nasby wrote:
- Does anyone have a tangible suggestion for how to reduce the code
duplication in patch #6?

Between execute_sql_string() and tcop/exec_simple_query()? Is there stuff in
exec_simple that's not safe for bgwriter? I'm not seeing why we can't use
exec_simple. :/

There are some differences if you compare them closely.

Without doing a deep dive, I'm guessing that most of the extra stuff would be 
safe to re-use; it just wouldn't affect execute_sql_string. Obviously we could 
add a boolean to exec_simple_query for the case when it's being used by a 
bgwriter. Though, it seems like the biggest differences have to do with logging

Here's the differences I see:

- Disallowing transaction commands
- Logging
- What memory context is used (could we just use that differently in a 
pg_backend backend?)
- Portal output format
- What to do with the output of intermediate commands (surely there's other 
places we need to handle that? plpgsql maybe?)

I think all of those except logging could be handled by a function serving both 
exec_simple_query and execute_sql_command that accepts a few booleans (or maybe 
just one to indicate the caller) and some if's. At least I don't think it'd be 
too bad, without actually writing it.

I'm not sure what to do about logging. If the original backend has logging 
turned on, is it that horrible to do the same logging as exec_simple_query 
would do?

Another option would be factoring out parts of exec_simple_query; the for loop 
over the parse tree might be a good candidate. But I suspect that would be 
uglier than a separate support function.

I do feel uncomfortable with the amount of duplication there is right now 
though...

I took a stab at this by refactoring the guts of exec_simple_query (patch 
attached) into a new function called exec_query_string (also attached in raw 
form). As indicated it needs a bit more work. In particular, how it's dealing 
with excluding transactional commands is rather ugly. Why do we need to do that 
in pg_background?

Andres was concerned about the performance impact of doing this. I tested this 
by doing

for i in {1..999999}; do echo 'SELECT 1;' >> test.sql; done

and then

time psql -f test.sql > /dev/nul

It appears there may be a ~1% performance hit, but my laptop isn't repeatable 
enough to be sure. I did try manually in-lining exec_query_string to see if it 
was the function call causing the issue; it didn't seem to make a difference.
--
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com
Author: Jim Nasby <jim.na...@bluetreble.com>
Date: Thu, 30 Oct 2014 20:25:34 -0500

Move the bulk of exec_simple_query into exec_query_string() so that
pg_backend can also make use of it.

I’m not thrilled about the name, and I’m not sure tcopprot.h is the
right place to expose this. Also note the XXX comments.
---
 contrib/pg_background/pg_background.c | 141 ++--------------------------------
 src/backend/tcop/postgres.c           |  83 ++++++++++++++++----
 src/include/tcop/tcopprot.h           |   7 ++
 3 files changed, 80 insertions(+), 151 deletions(-)

diff --git a/contrib/pg_background/pg_background.c 
b/contrib/pg_background/pg_background.c
index a566ffb..075ecd8 100644
--- a/contrib/pg_background/pg_background.c
+++ b/contrib/pg_background/pg_background.c
@@ -817,10 +817,6 @@ pg_background_worker_main(Datum main_arg)
 static void
 execute_sql_string(const char *sql)
 {
-       List       *raw_parsetree_list;
-       ListCell   *lc1;
-       bool            isTopLevel;
-       int                     commands_remaining;
        MemoryContext   parsecontext;
        MemoryContext   oldcontext;
 
@@ -839,139 +835,16 @@ execute_sql_string(const char *sql)
                                                                                
 ALLOCSET_DEFAULT_MINSIZE,
                                                                                
 ALLOCSET_DEFAULT_INITSIZE,
                                                                                
 ALLOCSET_DEFAULT_MAXSIZE);
-       oldcontext = MemoryContextSwitchTo(parsecontext);
-       raw_parsetree_list = pg_parse_query(sql);
-       commands_remaining = list_length(raw_parsetree_list);
-       isTopLevel = commands_remaining == 1;
-       MemoryContextSwitchTo(oldcontext);
 
        /*
-        * Do parse analysis, rule rewrite, planning, and execution for each raw
-        * parsetree.  We must fully execute each query before beginning parse
-        * analysis on the next one, since there may be interdependencies.
+        * Do the real work
         */
-       foreach(lc1, raw_parsetree_list)
-       {
-               Node       *parsetree = (Node *) lfirst(lc1);
-               const char *commandTag;
-               char        completionTag[COMPLETION_TAG_BUFSIZE];
-               List       *querytree_list,
-                                  *plantree_list;
-               bool            snapshot_set = false;
-               Portal          portal;
-               DestReceiver *receiver;
-               int16           format = 1;
-
-               /*
-                * We don't allow transaction-control commands like COMMIT and 
ABORT
-                * here.  The entire SQL statement is executed as a single 
transaction
-                * which commits if no errors are encountered.
-                */
-               if (IsA(parsetree, TransactionStmt))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("transaction control statements 
are not allowed in pg_background")));
-
-               /*
-                * Get the command name for use in status display (it also 
becomes the
-                * default completion tag, down inside PortalRun).  Set 
ps_status and
-                * do any special start-of-SQL-command processing needed by the
-                * destination.
-                */
-               commandTag = CreateCommandTag(parsetree);
-               set_ps_display(commandTag, false);
-               BeginCommand(commandTag, DestNone);
-
-               /* 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.
-                *
-                * As with parsing, we need to make sure this data outlives the
-                * transaction, because of the possibility that the statement 
might
-                * perform internal transaction control.
-                */
-               oldcontext = MemoryContextSwitchTo(parsecontext);
-               querytree_list = pg_analyze_and_rewrite(parsetree, sql, 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();
-
-               /*
-                * Execute the query using the unnamed portal.
-                */
-               portal = CreatePortal("", true, true);
-               /* Don't display the portal in pg_cursors */
-               portal->visible = false;
-               PortalDefineQuery(portal, NULL, sql, commandTag, plantree_list, 
NULL);
-               PortalStart(portal, NULL, 0, InvalidSnapshot);
-
-               /* We always use binary format, for efficiency. */
-               PortalSetResultFormat(portal, 1, &format);
-
-               /*
-                * Tuples returned by any command other than the last are simply
-                * discarded; but those returned by the last (or only) command 
are
-                * redirected to the shared memory queue we're using for 
communication
-                * with the launching backend. If the launching backend is gone 
or has
-                * detached us, these messages will just get dropped on the 
floor.
-                */
-               --commands_remaining;
-               if (commands_remaining > 0)
-                       receiver = CreateDestReceiver(DestNone);
-               else
-               {
-                       receiver = CreateDestReceiver(DestRemote);
-                       SetRemoteDestReceiverParams(receiver, portal);
-               }
-
-               /*
-                * Only once the portal and destreceiver have been established 
can
-                * we return to the transaction context.  All that stuff needs 
to
-                * survive an internal commit inside PortalRun!
-                */
-               MemoryContextSwitchTo(oldcontext);
-
-               /* Here's where we actually execute the command. */
-               (void) PortalRun(portal, FETCH_ALL, isTopLevel, receiver, 
receiver,
-                                                completionTag);
-
-               /* Clean up the receiver. */
-               (*receiver->rDestroy) (receiver);
-
-               /* Clean up the portal. */
-               PortalDrop(portal, false);
-
-               /*
-                * If this is the last parsetree, close down transaction 
statement
-                * before reporting CommandComplete.  Otherwise, we need a
-                * CommandCounterIncrement.
-                */
-               if (lnext(lc1) == NULL)
-                       finish_xact_command();
-               else
-                       CommandCounterIncrement();
-
-               /*
-                * Send a CommandComplete message even if we suppressed the 
query
-                * results.  The user backend will report the command tags in 
the
-                * absence of any true query results.
-                */
-               EndCommand(completionTag, DestRemote);
-       }
-
-       /* Make sure there's not still a transaction open. */
-       finish_xact_command();
+       exec_query_string(query_string,
+                                         DestRemote,
+                                         &parsecontext,
+                                         true, /* allow_transactions */
+                                         true /* last_result_only */
+                                         );
 }
 
 /*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e44f5fa..3b998fc 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -823,13 +823,7 @@ static void
 exec_simple_query(const char *query_string)
 {
        CommandDest dest = whereToSendOutput;
-       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];
 
 
        /*
@@ -849,6 +843,46 @@ exec_simple_query(const char *query_string)
                ResetUsage();
 
        /*
+        * Do the real work
+        */
+       exec_query_string(query_string,
+                                         dest,
+                                         MessageContext,
+                                         true, /* allow_transactions */
+                                         false /* last_result_only */
+                                         );
+
+       if (save_log_statement_stats)
+               ShowUsage("QUERY STATISTICS");
+
+       TRACE_POSTGRESQL_QUERY_DONE(query_string);
+
+       debug_query_string = NULL;
+}
+
+/*
+ * exec_query_string
+ *
+ * This is the guts of exec_simply_query, but is also used by
+ * execute_sql_string in contrib/pg_background
+ */
+void
+exec_query_string(const char *query_string,
+                                 CommandDest dest, /* Where to send output */
+                                 MemoryContext parsecontext, /* Context we 
should use to parse query_string */
+                                 bool allow_transactions, /* Should we allow 
transaction statements? */
+                                 bool last_result_only /* If true, drop all 
command output except for the last command */
+                                 )
+{
+       MemoryContext oldcontext;
+       List       *parsetree_list;
+       ListCell   *parsetree_item;
+       bool            was_logged = false;
+       bool            isTopLevel;
+       char            msec_str[32];
+       int                     commands_remaining;
+
+       /*
         * Start up a transaction command.  All queries generated by the
         * query_string will be in this same command block, *unless* we find a
         * BEGIN/COMMIT/ABORT statement; we have to force a new xact command 
after
@@ -875,6 +909,7 @@ exec_simple_query(const char *query_string)
         * we are in aborted transaction state!)
         */
        parsetree_list = pg_parse_query(query_string);
+       commands_remaining = list_length(parsetree_list);
 
        /* Log immediately if dictated by log_statement */
        if (check_log_statement(parsetree_list))
@@ -915,6 +950,13 @@ exec_simple_query(const char *query_string)
                DestReceiver *receiver;
                int16           format;
 
+               if (!allow_transactions && IsA(parsetree, TransactionStmt))
+                       /* XXX this needs to be more flexible */
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("transaction control statements 
are not allowed in pg_background")));
+
+
                /*
                 * Get the command name for use in status display (it also 
becomes the
                 * default completion tag, down inside PortalRun).  Set 
ps_status and
@@ -925,6 +967,7 @@ exec_simple_query(const char *query_string)
 
                set_ps_display(commandTag, false);
 
+               /* XXX what should we do here in the case of last_result_only = 
true? */
                BeginCommand(commandTag, dest);
 
                /*
@@ -944,7 +987,8 @@ exec_simple_query(const char *query_string)
                                         errdetail_abort()));
 
                /* Make sure we are in a transaction command */
-               start_xact_command();
+               if (allow_transactions)
+                       start_xact_command();
 
                /* If we got a cancel signal in parsing or prior command, quit 
*/
                CHECK_FOR_INTERRUPTS();
@@ -1027,10 +1071,19 @@ exec_simple_query(const char *query_string)
 
                /*
                 * Now we can create the destination receiver object.
+                *
+                * If we sending the last result only, throw output away unless 
it's
+                * the last command.
                 */
-               receiver = CreateDestReceiver(dest);
-               if (dest == DestRemote)
-                       SetRemoteDestReceiverParams(receiver, portal);
+               --commands_remaining;
+               if (last_result_only && commands_remaining > 0)
+                       receiver = CreateDestReceiver(DestNone);
+               else
+               {
+                       receiver = CreateDestReceiver(dest);
+                       if (dest == DestRemote)
+                               SetRemoteDestReceiverParams(receiver, portal);
+               }
 
                /*
                 * Switch back to transaction context for execution.
@@ -1055,7 +1108,9 @@ exec_simple_query(const char *query_string)
                {
                        /*
                         * If this was a transaction control statement, commit 
it. We will
-                        * start a new xact command for the next command (if 
any).
+                        * start a new xact command for the next command (if 
any). Note
+                        * that if allow_transactions is false then we should 
never get to
+                        * this point.
                         */
                        finish_xact_command();
                }
@@ -1121,12 +1176,6 @@ exec_simple_query(const char *query_string)
                        break;
        }
 
-       if (save_log_statement_stats)
-               ShowUsage("QUERY STATISTICS");
-
-       TRACE_POSTGRESQL_QUERY_DONE(query_string);
-
-       debug_query_string = NULL;
 }
 
 /*
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index fd3df58..39c6a39 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -86,4 +86,11 @@ extern bool set_plan_disabling_options(const char *arg,
                                                   GucContext context, 
GucSource source);
 extern const char *get_stats_option_name(const char *arg);
 
+extern void exec_query_string(const char *query_string,
+                                 CommandDest dest, /* Where to send output */
+                                 MemoryContext parsecontext, /* Context we 
should use to parse query_string */
+                                 bool allow_transactions, /* Should we allow 
transaction statements? */
+                                 bool last_result_only /* If true, drop all 
command output except for the last command */
+                                 );
+
 #endif   /* TCOPPROT_H */
-- 
2.1.2

/*
 * exec_query_string
 *
 * This is the guts of exec_simply_query, but is also used by
 * execute_sql_string in contrib/pg_background
 */
void
exec_query_string(const char *query_string,
                                  CommandDest dest, /* Where to send output */
                                  MemoryContext parsecontext, /* Context we 
should use to parse query_string */
                                  bool allow_transactions, /* Should we allow 
transaction statements? */
                                  bool last_result_only /* If true, drop all 
command output except for the last command */
                                  )
{
        MemoryContext oldcontext;
        List       *parsetree_list;
        ListCell   *parsetree_item;
        bool            was_logged = false;
        bool            isTopLevel;
        char            msec_str[32];
        int                     commands_remaining;

        /*
         * Start up a transaction command.  All queries generated by the
         * query_string will be in this same command block, *unless* we find a
         * BEGIN/COMMIT/ABORT statement; we have to force a new xact command 
after
         * one of those, else bad things will happen in xact.c. (Note that this
         * will normally change current memory context.)
         */
        start_xact_command();

        /*
         * Zap any pre-existing unnamed statement.  (While not strictly 
necessary,
         * it seems best to define simple-Query mode as if it used the unnamed
         * statement and portal; this ensures we recover any storage used by 
prior
         * unnamed operations.)
         */
        drop_unnamed_stmt();

        /*
         * Switch to appropriate context for constructing parsetrees.
         */
        oldcontext = MemoryContextSwitchTo(MessageContext);

        /*
         * Do basic parsing of the query or queries (this should be safe even if
         * we are in aborted transaction state!)
         */
        parsetree_list = pg_parse_query(query_string);
        commands_remaining = list_length(parsetree_list);

        /* Log immediately if dictated by log_statement */
        if (check_log_statement(parsetree_list))
        {
                ereport(LOG,
                                (errmsg("statement: %s", query_string),
                                 errhidestmt(true),
                                 errdetail_execute(parsetree_list)));
                was_logged = true;
        }

        /*
         * Switch back to transaction context to enter the loop.
         */
        MemoryContextSwitchTo(oldcontext);

        /*
         * We'll tell PortalRun it's a top-level command iff there's exactly one
         * raw parsetree.  If more than one, it's effectively a transaction 
block
         * and we want PreventTransactionChain to reject unsafe commands. (Note:
         * we're assuming that query rewrite cannot add commands that are
         * significant to PreventTransactionChain.)
         */
        isTopLevel = (list_length(parsetree_list) == 1);

        /*
         * Run through the raw parsetree(s) and process each one.
         */
        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;

                if (!allow_transactions && IsA(parsetree, TransactionStmt))
                        /* XXX this needs to be more flexible */
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                         errmsg("transaction control statements 
are not allowed in pg_background")));


                /*
                 * Get the command name for use in status display (it also 
becomes the
                 * default completion tag, down inside PortalRun).  Set 
ps_status and
                 * do any special start-of-SQL-command processing needed by the
                 * destination.
                 */
                commandTag = CreateCommandTag(parsetree);

                set_ps_display(commandTag, false);

                /* XXX what should we do here in the case of last_result_only = 
true? */
                BeginCommand(commandTag, dest);

                /*
                 * If we are in an aborted transaction, reject all commands 
except
                 * COMMIT/ABORT.  It is important that this test occur before 
we try
                 * to do parse analysis, rewrite, or planning, since all those 
phases
                 * try to do database accesses, which may fail in abort state. 
(It
                 * might be safe to allow some additional utility commands in 
this
                 * state, but not many...)
                 */
                if (IsAbortedTransactionBlockState() &&
                        !IsTransactionExitStmt(parsetree))
                        ereport(ERROR,
                                        
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
                                         errmsg("current transaction is 
aborted, "
                                                  "commands ignored until end 
of transaction block"),
                                         errdetail_abort()));

                /* Make sure we are in a transaction command */
                if (allow_transactions)
                        start_xact_command();

                /* 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 unnamed portal to run the query or queries in. If 
there
                 * already is one, silently drop it.
                 */
                portal = CreatePortal("", 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, 0, InvalidSnapshot);

                /*
                 * Select the appropriate output format: text unless we are 
doing a
                 * FETCH from a binary cursor.  (Pretty grotty to have to do 
this here
                 * --- but it avoids grottiness in other places.  Ah, the joys 
of
                 * backward compatibility...)
                 */
                format = 0;                             /* TEXT is default */
                if (IsA(parsetree, FetchStmt))
                {
                        FetchStmt  *stmt = (FetchStmt *) parsetree;

                        if (!stmt->ismove)
                        {
                                Portal          fportal = 
GetPortalByName(stmt->portalname);

                                if (PortalIsValid(fportal) &&
                                        (fportal->cursorOptions & 
CURSOR_OPT_BINARY))
                                        format = 1; /* BINARY */
                        }
                }
                PortalSetResultFormat(portal, 1, &format);

                /*
                 * Now we can create the destination receiver object.
                 *
                 * If we sending the last result only, throw output away unless 
it's
                 * the last command.
                 */
                --commands_remaining;
                if (last_result_only && commands_remaining > 0)
                        receiver = CreateDestReceiver(DestNone);
                else
                {
                        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))
                {
                        /*
                         * If this was a transaction control statement, commit 
it. We will
                         * start a new xact command for the next command (if 
any). Note
                         * that if allow_transactions is false then we should 
never get to
                         * this point.
                         */
                        finish_xact_command();
                }
                else if (lnext(parsetree_item) == NULL)
                {
                        /*
                         * If this is the last parsetree of the query string, 
close down
                         * transaction statement before reporting 
command-complete.  This
                         * is so that any end-of-transaction errors are 
reported before
                         * the command-complete message is issued, to avoid 
confusing
                         * clients who will expect either a command-complete 
message or an
                         * error, not one and then the other.  But for 
compatibility with
                         * historical Postgres behavior, we do not force a 
transaction
                         * boundary between queries appearing in a single query 
string.
                         */
                        finish_xact_command();
                }
                else
                {
                        /*
                         * We need a CommandCounterIncrement after every query, 
except
                         * those that start or end a transaction block.
                         */
                        CommandCounterIncrement();
                }

                /*
                 * Tell client that we're done with this query.  Note we emit 
exactly
                 * one EndCommand report for each raw parsetree, thus one for 
each SQL
                 * command the client sent, regardless of rewriting. (But a 
command
                 * aborted by error will not send an EndCommand report at all.)
                 */
                EndCommand(completionTag, dest);
        }                                                       /* end loop 
over parsetrees */

        /*
         * Close down transaction statement, if one is open.
         */
        finish_xact_command();

        /*
         * If there were no parsetrees, return EmptyQueryResponse message.
         */
        if (!parsetree_list)
                NullCommand(dest);

        /*
         * Emit duration logging if appropriate.
         */
        switch (check_log_duration(msec_str, was_logged))
        {
                case 1:
                        ereport(LOG,
                                        (errmsg("duration: %s ms", msec_str),
                                         errhidestmt(true)));
                        break;
                case 2:
                        ereport(LOG,
                                        (errmsg("duration: %s ms  statement: 
%s",
                                                        msec_str, query_string),
                                         errhidestmt(true),
                                         errdetail_execute(parsetree_list)));
                        break;
        }

}


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to