From 25e8872ff50a6e076159bcf36112ca032c556706 Mon Sep 17 00:00:00 2001 From: Mark Dilger Date: Thu, 6 Feb 2020 11:41:44 -0800 Subject: [PATCH v4] Migrating commandTag from string to enum. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend was using strings to represent command tags and doing string comparisons in multiple places. Fixing that by creating a new CommandTag enum and using it instead. Replacing numerous occurrences of char *completionTag with a QueryCompletionData struct so that the code no longer stores information about completed queries in a cstring. Only at the last moment, in EndCommand(), does this get converted to a string. EventTriggerCacheItem no longer holds an array of palloc’d tag strings in sorted order, but rather just a Bitmapset over the CommandTags. --- .../pg_stat_statements/pg_stat_statements.c | 18 +- contrib/sepgsql/hooks.c | 6 +- src/backend/catalog/DataFile.pm | 400 ++++++++ src/backend/commands/createas.c | 14 +- src/backend/commands/event_trigger.c | 160 +-- src/backend/commands/matview.c | 2 +- src/backend/commands/portalcmds.c | 16 +- src/backend/commands/prepare.c | 4 +- src/backend/executor/execMain.c | 4 +- src/backend/executor/functions.c | 4 +- src/backend/executor/spi.c | 22 +- src/backend/replication/walsender.c | 18 +- src/backend/tcop/dest.c | 37 +- src/backend/tcop/postgres.c | 24 +- src/backend/tcop/pquery.c | 111 +-- src/backend/tcop/utility.c | 558 +++++------ src/backend/utils/.gitignore | 3 + src/backend/utils/Makefile | 26 +- src/backend/utils/cache/evtcache.c | 27 +- src/backend/utils/cache/plancache.c | 4 +- src/backend/utils/commandtag.c | 119 +++ src/backend/utils/gencommandtag.pl | 285 ++++++ src/backend/utils/misc/.gitignore | 1 + src/backend/utils/misc/Makefile | 4 + src/backend/utils/mmgr/portalmem.c | 6 +- src/include/Makefile | 4 +- src/include/commands/createas.h | 3 +- src/include/commands/event_trigger.h | 2 +- src/include/commands/matview.h | 2 +- src/include/commands/portalcmds.h | 3 +- src/include/commands/prepare.h | 2 +- src/include/nodes/parsenodes.h | 1 + src/include/tcop/dest.h | 7 +- src/include/tcop/pquery.h | 2 +- src/include/tcop/utility.h | 15 +- src/include/utils/.gitignore | 2 + src/include/utils/commandtag.dat | 938 ++++++++++++++++++ src/include/utils/commandtag.h | 60 ++ src/include/utils/evtcache.h | 3 +- src/include/utils/plancache.h | 7 +- src/include/utils/portal.h | 6 +- src/pl/plpgsql/src/pl_exec.c | 10 +- .../test_ddl_deparse/test_ddl_deparse.c | 2 +- src/tools/msvc/Solution.pm | 12 + 44 files changed, 2340 insertions(+), 614 deletions(-) create mode 100644 src/backend/catalog/DataFile.pm create mode 100644 src/backend/utils/commandtag.c create mode 100755 src/backend/utils/gencommandtag.pl create mode 100644 src/include/utils/commandtag.dat create mode 100644 src/include/utils/commandtag.h diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index e0dbeebde3..1f3e9c1041 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -307,7 +307,7 @@ static void pgss_ExecutorEnd(QueryDesc *queryDesc); static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, - DestReceiver *dest, char *completionTag); + DestReceiver *dest, QueryCompletion *qc); static uint64 pgss_hash_string(const char *str, int len); static void pgss_store(const char *query, uint64 queryId, int query_location, int query_len, @@ -960,7 +960,7 @@ static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, - DestReceiver *dest, char *completionTag) + DestReceiver *dest, QueryCompletion *qc) { Node *parsetree = pstmt->utilityStmt; @@ -998,11 +998,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, if (prev_ProcessUtility) prev_ProcessUtility(pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else standard_ProcessUtility(pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); } PG_FINALLY(); { @@ -1013,10 +1013,8 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, INSTR_TIME_SET_CURRENT(duration); INSTR_TIME_SUBTRACT(duration, start); - /* parse command tag to retrieve the number of affected rows. */ - if (completionTag && - strncmp(completionTag, "COPY ", 5) == 0) - rows = pg_strtouint64(completionTag + 5, NULL, 10); + if (qc && qc->commandTag == COMMANDTAG_COPY) + rows = qc->nprocessed; else rows = 0; @@ -1060,11 +1058,11 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, if (prev_ProcessUtility) prev_ProcessUtility(pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else standard_ProcessUtility(pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); } } diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c index 997a64c87e..853b5b04ab 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -317,7 +317,7 @@ sepgsql_utility_command(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, - char *completionTag) + QueryCompletion *qc) { Node *parsetree = pstmt->utilityStmt; sepgsql_context_info_t saved_context_info = sepgsql_context_info; @@ -380,11 +380,11 @@ sepgsql_utility_command(PlannedStmt *pstmt, if (next_ProcessUtility_hook) (*next_ProcessUtility_hook) (pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else standard_ProcessUtility(pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); } PG_FINALLY(); { diff --git a/src/backend/catalog/DataFile.pm b/src/backend/catalog/DataFile.pm new file mode 100644 index 0000000000..f7bf7656fc --- /dev/null +++ b/src/backend/catalog/DataFile.pm @@ -0,0 +1,400 @@ +#!/usr/bin/perl -w +#---------------------------------------------------------------------- +# DataFile.pm +# Perl module that safely converts data files into perl data. +# +# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/backend/catalog/DataFile.pm +# +#---------------------------------------------------------------------- + + +=pod + +=head1 NAME + +DataFile - module for safely parsing and vivifying perl data files + +=head1 SYNOPSIS + + use DataFile; + + my $array_reference = DataFile::ParseData("myfile.dat"); + +=head1 DESCRIPTION + +DataFile contains a parsing function, ParseData, for parsing various +data files. By convention, these data files are stored as a single array +of hashes of key/value pairs. + +=pod + +=head1 METHODS + +=cut + +package DataFile; + +use strict; +use warnings; +use Config; + +=pod + +=over + +=item slurp_file(filename) + +Return the full contents of the specified file. + +=cut + +sub slurp_file +{ + my ($filename) = @_; + local $/; + my $contents; + if ($Config{osname} ne 'MSWin32') + { + open(my $in, '<', $filename) + or die "could not read \"$filename\": $!"; + $contents = <$in>; + close $in; + } + else + { + my $fHandle = createFile($filename, "r", "rwd") + or die "could not open \"$filename\": $^E"; + OsFHandleOpen(my $fh = IO::Handle->new(), $fHandle, 'r') + or die "could not read \"$filename\": $^E\n"; + $contents = <$fh>; + CloseHandle($fHandle) + or die "could not close \"$filename\": $^E\n"; + } + $contents =~ s/\r//g if $Config{osname} eq 'msys'; + return $contents; +} + +=pod + +=item ParseData(filename) + +Parse a file which represents a valid perl array reference of hash references, +vivify the perl array reference, and return it. + +This function is paranoid about evaluation or interpolation of embedded perl +and will die with an error message if the file contains anything that looks +like it will result in code being executed. + +Mistakes in the data file making it an invalid perl object will draw errors +about the line number in the file where parsing failed and an attempt to +indicate what parsing problem was encountered there. + +=cut + +our (%looking, $pos); +sub update_search($$) +{ + my ($lookingfor, $atpos) = @_; + if ($atpos > $pos) + { + %looking = ($lookingfor => 1); + $pos = $atpos; + } + elsif ($atpos == $pos) + { + $looking{$lookingfor} = 1; + } +} + +sub ParseData +{ + my ($inputfile) = @_; + + # Vivify the input data, but be paranoid about eval'ing arbitrary code + my $data; + eval { + my $untrusted = slurp_file($inputfile); + our $noeval = undef; + our $noevalstr = undef; + our $noevalpos = -1; + %looking = (); + $pos = -1; + + # A quotable string is one that does not embed an unescaped end-quote + # or end with a backslash which would escape the quote that otherwise + # terminates the quoted string. + my $singlequotablere = qr/ + # Verify through look-behind that we're at the + # beginning of a single-quoted string + (?<=') + # Consume the single-quoted string contents + # without backtracking or givebacks. + (?> + # Any single character except single-quote + # or backslash + [^'\\] + | + # Any escaped character, including escaped + # single-quotes and escaped backslashes + \\. + )*+ + # Verify through look-ahead that we're at the + # end of a single-quoted string + (?=') + /msx; + + # Inside double-quoted strings, Perl interpolates substrings which look + # like variables with those variables' values. This seems a bit unsafe + # to allow in this package, so we check double-quoted strings for + # anything that Perl might interpret this way at 'eval' time. Such + # substrings still match our pattern, but we set a flag alerting us to + # not proceed to interpolate the substring. + my $doublequotablere = qr/ + # Verify through look-behind that we're at the + # beginning of a double-quoted string + (?<=") + # Begin capturing. If we encounter an illegal + # sequence, we can save the captured string for + # use later during error reporting + ( + # Consume the double-quoted string contents + # without backtracking or givebacks. + (?> + # Any character except double-quote, + # backslash, or variable indicators [$%@] + [^"\\%@\$] + | + # Any escaped character + \\. + | + # A variable indicator, but we set the flag + # that we must not eval this string + [%@\$](?{ local $noeval = 1; }) + )*+ + # verify through look-ahead that we're at the + # end of the double-quoted string + (?=") + # End capture + ) + # If we encountered a disallowed character + # during the capture, record the entire captured + # string for later + (?{ $noevalstr = $^N, $noevalpos = pos() + if ($noeval && !defined $noevalstr); + }) + /msx; + + my $quotedstringre = qr/ + (?: + ' + (?{ update_search("end of single quoted string", pos()) }) + $singlequotablere + ' + | + " + (?{ update_search("end of double quoted string", pos()) }) + $doublequotablere + " + ) + /msx; + + # A perl comment cannot occur inside a quoted string, but otherwise it can + # occur pretty much anywhere. So long as we're not inside a quotation, we + # can match a # to the end of the line + my $commentre = qr/ + (?: + (?{ update_search("comment", pos()) }) + # Beginning of a comment + \# + # Anything other than end-of-line, no backtracking, + # and no give-backs + (?>[^\r\n])*+ + # end-of-line + \r?\n + ) + /msx; + + # Comments and/or whitespace can occur just about anywhere. It is convenient + # to have a single regex that matches them so we don't have to think about + # comments much from here onward. + my $spacere = qr/ + (?: + # any number of comment lines, including none, including + # leading whitespace before the comment begins + (?: + (?{ update_search("whitespace", pos()) }) + (?>\s)*+ # whitespace without backtracking + (?{ update_search("comment", pos()) }) + $commentre + )*+ + # whitespace following the zero or more comment lines, + # without backtracking + (?{ update_search("whitespace", pos()) }) + (?>\s)*+ + ) + /msx; + + # Keys must be either quoted strings, numbers, or barewords if followed + # by an arrow operator. The arrow operator is not part of the key, however, + # so we check that with a look-ahead assertion + my $keyre = qr/ + (?: + $quotedstringre + | + \d+ + | + # bareword only allowed before arrow op, but there cannot + # be comments inbetween, only whitespace + \w+(?=\s*=>) + ) + /msx; + # Values must be either numbers or quoted strings + my $valuere = qr/ + (?: + $quotedstringre + | + \d+ + ) + /msx; + + # Key/Value pairs can use arrow or comma separators. We hid the complexity + # of arrow operator quoting in the keyre, above + my $keyvaluere = qr/ + (?{ update_search("key value pair", pos()) }) + (?: + $keyre + $spacere + (?: + => + | + , + ) + $spacere + $valuere + ) + /msx; + + # Key/Value pair lists + my $keyvaluelistre = qr/ + # Optional key-value pair + (?: + $keyvaluere + $spacere + + # Optionally followed by more + (?: + , # Required + $spacere + $keyvaluere + )* + # Optionally ending with a comma + (?: + $spacere + , + )? + )? + /msx; + + # Hash reference comprised of key/value pairs + my $hashrefre = qr/ + (?: + (?{ update_search("beginning of hash reference", pos()) }) + \{ # Open hash + $spacere + $keyvaluelistre + $spacere + (?{ update_search("ending of hash reference", pos()) }) + \} # Close hash + ) + /msx; + + # List of hash references + my $hashreflist = qr/ + # Optional hash ref + (?: + $hashrefre + $spacere + + # Optionally followed by more + (?: + , # Required + $spacere + $hashrefre + )*+ + # Optionally ending with a comma + (?: + $spacere + , + )?+ + )?+ + /msx; + + # Reference to list of hash references + my $aryofhashrefre = qr/ + (?: + (?{ update_search("beginning of array reference", pos()) }) + \[ # Open array + $spacere + $hashreflist + $spacere + (?{ update_search("ending of array reference", pos()) }) + \] # Close array + ) + /msx; + + + # After removing all comments, remaining structure must strictly + # match an array reference of hash references of key/value pairs + # with no code that perl's eval() could be tricked into running. + # + # This parser is stricter than necessary, but it should allow all + # data to be specified that we need it to allow, so instead of + # making this parser more complicated (or permissive), see if you + # can make the data file follow the expected format. + # + if ($untrusted =~ m/^ + $spacere + $aryofhashrefre + $spacere + $/msx) + { + # If we encountered anything that might cause eval() to perform + # variable interpolation, complain and do not eval(). This may + # be overly paranoid.... + if (defined $noevalstr) + { + my $lineno = scalar(split/\n/, substr($untrusted, 0, $noevalpos)); + die "\nPossible attempt at variable interpolation in double " . + "quoted string in $inputfile, line $lineno: \"$noevalstr\""; + } + + # We're treating the input file as a piece of Perl, so we + # need to use string eval here. Tell perlcritic we know what + # we're doing. + # + $data = eval $untrusted; ## no critic (ProhibitStringyEval) + die $@ if $@; + } + else + { + my $lineno = scalar(split/\n/, substr($untrusted, 0, $pos)); + my $looking = join(" or ", keys %looking); + die("\n$inputfile does not match our expected format;\n" . + "At line $lineno, looking for $looking\n"); + } + }; + die $@ if $@; + + return $data; +} + +=pod + +=back + +=cut + +1; diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index cc02cf824e..3bcd7a95af 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -10,7 +10,7 @@ * * Formerly, CTAS was implemented as a variant of SELECT, which led * to assorted legacy behaviors that we still try to preserve, notably that - * we must return a tuples-processed count in the completionTag. (We no + * we must return a tuples-processed count in the QueryCompletion. (We no * longer do that for CTAS ... WITH NO DATA, however.) * * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group @@ -225,7 +225,7 @@ create_ctas_nodata(List *tlist, IntoClause *into) ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, ParamListInfo params, QueryEnvironment *queryEnv, - char *completionTag) + QueryCompletion *qc) { Query *query = castNode(Query, stmt->query); IntoClause *into = stmt->into; @@ -270,7 +270,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, ExecuteStmt *estmt = castNode(ExecuteStmt, query->utilityStmt); Assert(!is_matview); /* excluded by syntax */ - ExecuteQuery(pstate, estmt, into, params, dest, completionTag); + ExecuteQuery(pstate, estmt, into, params, dest, qc); /* get object address that intorel_startup saved for us */ address = ((DR_intorel *) dest)->reladdr; @@ -352,11 +352,9 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, /* run the plan to completion */ ExecutorRun(queryDesc, ForwardScanDirection, 0L, true); - /* save the rowcount if we're given a completionTag to fill */ - if (completionTag) - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, - "SELECT " UINT64_FORMAT, - queryDesc->estate->es_processed); + /* save the rowcount if we're given a qc to fill */ + if (qc) + SetQueryCompletion(qc, COMMANDTAG_SELECT, queryDesc->estate->es_processed); /* get object address that intorel_startup saved for us */ address = ((DR_intorel *) dest)->reladdr; diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 71911d4067..7c8415475f 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -78,59 +78,6 @@ typedef struct bool supported; } event_trigger_support_data; -typedef enum -{ - EVENT_TRIGGER_COMMAND_TAG_OK, - EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED, - EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED -} event_trigger_command_tag_check_result; - -/* XXX merge this with ObjectTypeMap? */ -static const event_trigger_support_data event_trigger_support[] = { - {"ACCESS METHOD", true}, - {"AGGREGATE", true}, - {"CAST", true}, - {"CONSTRAINT", true}, - {"COLLATION", true}, - {"CONVERSION", true}, - {"DATABASE", false}, - {"DOMAIN", true}, - {"EXTENSION", true}, - {"EVENT TRIGGER", false}, - {"FOREIGN DATA WRAPPER", true}, - {"FOREIGN TABLE", true}, - {"FUNCTION", true}, - {"INDEX", true}, - {"LANGUAGE", true}, - {"MATERIALIZED VIEW", true}, - {"OPERATOR", true}, - {"OPERATOR CLASS", true}, - {"OPERATOR FAMILY", true}, - {"POLICY", true}, - {"PROCEDURE", true}, - {"PUBLICATION", true}, - {"ROLE", false}, - {"ROUTINE", true}, - {"RULE", true}, - {"SCHEMA", true}, - {"SEQUENCE", true}, - {"SERVER", true}, - {"STATISTICS", true}, - {"SUBSCRIPTION", true}, - {"TABLE", true}, - {"TABLESPACE", false}, - {"TRANSFORM", true}, - {"TRIGGER", true}, - {"TEXT SEARCH CONFIGURATION", true}, - {"TEXT SEARCH DICTIONARY", true}, - {"TEXT SEARCH PARSER", true}, - {"TEXT SEARCH TEMPLATE", true}, - {"TYPE", true}, - {"USER MAPPING", true}, - {"VIEW", true}, - {NULL, false} -}; - /* Support for dropped objects */ typedef struct SQLDropObject { @@ -150,8 +97,6 @@ typedef struct SQLDropObject static void AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId); -static event_trigger_command_tag_check_result check_ddl_tag(const char *tag); -static event_trigger_command_tag_check_result check_table_rewrite_ddl_tag(const char *tag); static void error_duplicate_filter_variable(const char *defname); static Datum filter_list_to_array(List *filterlist); static Oid insert_event_trigger_tuple(const char *trigname, const char *eventname, @@ -259,71 +204,23 @@ validate_ddl_tags(const char *filtervar, List *taglist) foreach(lc, taglist) { - const char *tag = strVal(lfirst(lc)); - event_trigger_command_tag_check_result result; + const char *tagstr = strVal(lfirst(lc)); + CommandTag commandTag = GetCommandTagEnum(tagstr); - result = check_ddl_tag(tag); - if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED) + if (commandTag == COMMANDTAG_UNKNOWN) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("filter value \"%s\" not recognized for filter variable \"%s\"", - tag, filtervar))); - if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED) + tagstr, filtervar))); + if ( ! command_tag_event_trigger_ok(commandTag)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s represents an SQL statement name */ errmsg("event triggers are not supported for %s", - tag))); + tagstr))); } } -static event_trigger_command_tag_check_result -check_ddl_tag(const char *tag) -{ - const char *obtypename; - const event_trigger_support_data *etsd; - - /* - * Handle some idiosyncratic special cases. - */ - if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 || - pg_strcasecmp(tag, "SELECT INTO") == 0 || - pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 || - pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 || - pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 || - pg_strcasecmp(tag, "COMMENT") == 0 || - pg_strcasecmp(tag, "GRANT") == 0 || - pg_strcasecmp(tag, "REVOKE") == 0 || - pg_strcasecmp(tag, "DROP OWNED") == 0 || - pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 || - pg_strcasecmp(tag, "SECURITY LABEL") == 0) - return EVENT_TRIGGER_COMMAND_TAG_OK; - - /* - * Otherwise, command should be CREATE, ALTER, or DROP. - */ - if (pg_strncasecmp(tag, "CREATE ", 7) == 0) - obtypename = tag + 7; - else if (pg_strncasecmp(tag, "ALTER ", 6) == 0) - obtypename = tag + 6; - else if (pg_strncasecmp(tag, "DROP ", 5) == 0) - obtypename = tag + 5; - else - return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED; - - /* - * ...and the object type should be something recognizable. - */ - for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++) - if (pg_strcasecmp(etsd->obtypename, obtypename) == 0) - break; - if (etsd->obtypename == NULL) - return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED; - if (!etsd->supported) - return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED; - return EVENT_TRIGGER_COMMAND_TAG_OK; -} - /* * Validate DDL command tags for event table_rewrite. */ @@ -334,29 +231,18 @@ validate_table_rewrite_tags(const char *filtervar, List *taglist) foreach(lc, taglist) { - const char *tag = strVal(lfirst(lc)); - event_trigger_command_tag_check_result result; + const char *tagstr = strVal(lfirst(lc)); + CommandTag commandTag = GetCommandTagEnum(tagstr); - result = check_table_rewrite_ddl_tag(tag); - if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED) + if (! command_tag_table_rewrite_ok(commandTag)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s represents an SQL statement name */ errmsg("event triggers are not supported for %s", - tag))); + tagstr))); } } -static event_trigger_command_tag_check_result -check_table_rewrite_ddl_tag(const char *tag) -{ - if (pg_strcasecmp(tag, "ALTER TABLE") == 0 || - pg_strcasecmp(tag, "ALTER TYPE") == 0) - return EVENT_TRIGGER_COMMAND_TAG_OK; - - return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED; -} - /* * Complain about a duplicate filter variable. */ @@ -663,7 +549,7 @@ get_event_trigger_oid(const char *trigname, bool missing_ok) * tags matching. */ static bool -filter_event_trigger(const char **tag, EventTriggerCacheItem *item) +filter_event_trigger(CommandTag tag, EventTriggerCacheItem *item) { /* * Filter by session replication role, knowing that we never see disabled @@ -681,9 +567,7 @@ filter_event_trigger(const char **tag, EventTriggerCacheItem *item) } /* Filter by tags, if any were specified. */ - if (item->ntags != 0 && bsearch(tag, item->tag, - item->ntags, sizeof(char *), - pg_qsort_strcmp) == NULL) + if (!bms_is_empty(item->tagset) && !bms_is_member(tag, item->tagset)) return false; /* if we reach that point, we're not filtering out this item */ @@ -700,7 +584,7 @@ EventTriggerCommonSetup(Node *parsetree, EventTriggerEvent event, const char *eventstr, EventTriggerData *trigdata) { - const char *tag; + CommandTag tag; List *cachelist; ListCell *lc; List *runlist = NIL; @@ -716,25 +600,25 @@ EventTriggerCommonSetup(Node *parsetree, * * If this cross-check fails for you, you probably need to either adjust * standard_ProcessUtility() not to invoke event triggers for the command - * type in question, or you need to adjust check_ddl_tag to accept the + * type in question, or you need to adjust event_trigger_ok to accept the * relevant command tag. */ #ifdef USE_ASSERT_CHECKING { - const char *dbgtag; + CommandTag dbgtag; dbgtag = CreateCommandTag(parsetree); if (event == EVT_DDLCommandStart || event == EVT_DDLCommandEnd || event == EVT_SQLDrop) { - if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK) - elog(ERROR, "unexpected command tag \"%s\"", dbgtag); + if (! command_tag_event_trigger_ok(dbgtag)) + elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag)); } else if (event == EVT_TableRewrite) { - if (check_table_rewrite_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK) - elog(ERROR, "unexpected command tag \"%s\"", dbgtag); + if (! command_tag_table_rewrite_ok(dbgtag)) + elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag)); } } #endif @@ -758,7 +642,7 @@ EventTriggerCommonSetup(Node *parsetree, { EventTriggerCacheItem *item = lfirst(lc); - if (filter_event_trigger(&tag, item)) + if (filter_event_trigger(tag, item)) { /* We must plan to fire this trigger. */ runlist = lappend_oid(runlist, item->fnoid); @@ -2136,7 +2020,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) /* objsubid */ values[i++] = Int32GetDatum(addr.objectSubId); /* command tag */ - values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); + values[i++] = CStringGetTextDatum(CreateCommandName(cmd->parsetree)); /* object_type */ values[i++] = CStringGetTextDatum(type); /* schema */ @@ -2161,7 +2045,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) /* objsubid */ nulls[i++] = true; /* command tag */ - values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); + values[i++] = CStringGetTextDatum(CreateCommandName(cmd->parsetree)); /* object_type */ values[i++] = CStringGetTextDatum(stringify_adefprivs_objtype(cmd->d.defprivs.objtype)); /* schema */ diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 1ee37c1aeb..c3954f3e24 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -136,7 +136,7 @@ SetMatViewPopulatedState(Relation relation, bool newstate) */ ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, - ParamListInfo params, char *completionTag) + ParamListInfo params, QueryCompletion *qc) { Oid matviewOid; Relation matviewRel; diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 7e5c805a1e..744d79a2e2 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -106,7 +106,8 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa PortalDefineQuery(portal, NULL, queryString, - "SELECT", /* cursor's query is always a SELECT */ + COMMANDTAG_SELECT, /* cursor's query is always a + * SELECT */ list_make1(plan), NULL); @@ -160,15 +161,14 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa * * stmt: parsetree node for command * dest: where to send results - * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE - * in which to store a command completion status string. + * qc: where to store a command completion status data. * - * completionTag may be NULL if caller doesn't want a status string. + * qc may be NULL if caller doesn't want status data. */ void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest, - char *completionTag) + QueryCompletion *qc) { Portal portal; uint64 nprocessed; @@ -203,10 +203,8 @@ PerformPortalFetch(FetchStmt *stmt, dest); /* Return command status if wanted */ - if (completionTag) - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s " UINT64_FORMAT, - stmt->ismove ? "MOVE" : "FETCH", - nprocessed); + if (qc) + SetQueryCompletion(qc, stmt->ismove ? COMMANDTAG_MOVE : COMMANDTAG_FETCH, nprocessed); } /* diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index c4e4b6eaec..f917fc9c7a 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -187,7 +187,7 @@ void ExecuteQuery(ParseState *pstate, ExecuteStmt *stmt, IntoClause *intoClause, ParamListInfo params, - DestReceiver *dest, char *completionTag) + DestReceiver *dest, QueryCompletion *qc) { PreparedStatement *entry; CachedPlan *cplan; @@ -288,7 +288,7 @@ ExecuteQuery(ParseState *pstate, */ PortalStart(portal, paramLI, eflags, GetActiveSnapshot()); - (void) PortalRun(portal, count, false, true, dest, dest, completionTag); + (void) PortalRun(portal, count, false, true, dest, dest, qc); PortalDrop(portal, false); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index ee5c3a60ff..751b36b6bf 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -787,11 +787,11 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) if (isTempNamespace(get_rel_namespace(rte->relid))) continue; - PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt)); + PreventCommandTagIfReadOnly(CreateCommandTag((Node *) plannedstmt)); } if (plannedstmt->commandType != CMD_SELECT || plannedstmt->hasModifyingCTE) - PreventCommandIfParallelMode(CreateCommandTag((Node *) plannedstmt)); + PreventCommandTagIfParallelMode(CreateCommandTag((Node *) plannedstmt)); } diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 5cff6c4321..9b45a8a9a0 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -530,7 +530,7 @@ init_execution_state(List *queryTree_list, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a SQL function", - CreateCommandTag(stmt->utilityStmt)))); + CreateCommandName(stmt->utilityStmt)))); } if (fcache->readonly_func && !CommandIsReadOnly(stmt)) @@ -538,7 +538,7 @@ init_execution_state(List *queryTree_list, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a non-volatile function", - CreateCommandTag((Node *) stmt)))); + CreateCommandName((Node *) stmt)))); /* OK, build the execution_state for this query */ newes = (execution_state *) palloc(sizeof(execution_state)); diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index c46764bf42..d0a73778ed 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -1338,7 +1338,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), /* translator: %s is name of a SQL command, eg INSERT */ errmsg("cannot open %s query as cursor", - plansource->commandTag))); + GetCommandTagName(plansource->commandTag)))); } Assert(list_length(plan->plancache_list) == 1); @@ -1469,7 +1469,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a non-volatile function", - CreateCommandTag((Node *) pstmt)))); + CreateCommandName((Node *) pstmt)))); } } @@ -2255,7 +2255,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a non-volatile function", - CreateCommandTag((Node *) stmt)))); + CreateCommandName((Node *) stmt)))); /* * If not read-only mode, advance the command counter before each @@ -2291,9 +2291,11 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, } else { - char completionTag[COMPLETION_TAG_BUFSIZE]; + QueryCompletion qc; ProcessUtilityContext context; + InitializeQueryCompletion(&qc); + /* * If the SPI context is atomic, or we are asked to manage * snapshots, then we are in an atomic execution context. @@ -2312,7 +2314,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, paramLI, _SPI_current->queryEnv, dest, - completionTag); + &qc); /* Update "processed" if stmt returned tuples */ if (_SPI_current->tuptable) @@ -2328,9 +2330,8 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, { CreateTableAsStmt *ctastmt = (CreateTableAsStmt *) stmt->utilityStmt; - if (strncmp(completionTag, "SELECT ", 7) == 0) - _SPI_current->processed = - pg_strtouint64(completionTag + 7, NULL, 10); + if (qc.commandTag == COMMANDTAG_SELECT) + _SPI_current->processed = qc.nprocessed; else { /* @@ -2351,9 +2352,8 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, } else if (IsA(stmt->utilityStmt, CopyStmt)) { - Assert(strncmp(completionTag, "COPY ", 5) == 0); - _SPI_current->processed = pg_strtouint64(completionTag + 5, - NULL, 10); + Assert(qc.commandTag == COMMANDTAG_COPY); + _SPI_current->processed = qc.nprocessed; } } diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index abb533b9d0..dc6c16101b 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1074,8 +1074,11 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) static void DropReplicationSlot(DropReplicationSlotCmd *cmd) { + QueryCompletion qc; + ReplicationSlotDrop(cmd->slotname, !cmd->wait); - EndCommand("DROP_REPLICATION_SLOT", DestRemote); + SetQueryCompletion(&qc, COMMANDTAG_DROP_REPLICATION_SLOT, 0); + EndCommand(&qc, DestRemote, false); } /* @@ -1086,6 +1089,7 @@ static void StartLogicalReplication(StartReplicationCmd *cmd) { StringInfoData buf; + QueryCompletion qc; /* make sure that our requirements are still fulfilled */ CheckLogicalDecodingRequirements(); @@ -1160,7 +1164,8 @@ StartLogicalReplication(StartReplicationCmd *cmd) WalSndSetState(WALSNDSTATE_STARTUP); /* Get out of COPY mode (CommandComplete). */ - EndCommand("COPY 0", DestRemote); + SetQueryCompletion(&qc, COMMANDTAG_COPY, 0); + EndCommand(&qc, DestRemote, false); } /* @@ -1464,6 +1469,7 @@ exec_replication_command(const char *cmd_string) Node *cmd_node; MemoryContext cmd_context; MemoryContext old_context; + QueryCompletion qc; /* * If WAL sender has been told that shutdown is getting close, switch its @@ -1614,7 +1620,8 @@ exec_replication_command(const char *cmd_string) MemoryContextDelete(cmd_context); /* Send CommandComplete message */ - EndCommand("SELECT", DestRemote); + SetQueryCompletion(&qc, COMMANDTAG_SELECT, 0); + EndCommand(&qc, DestRemote, true); /* Report to pgstat that this process is now idle */ pgstat_report_activity(STATE_IDLE, NULL); @@ -2867,8 +2874,11 @@ WalSndDone(WalSndSendDataCallback send_data) if (WalSndCaughtUp && sentPtr == replicatedPtr && !pq_is_send_pending()) { + QueryCompletion qc; + /* Inform the standby that XLOG streaming is done */ - EndCommand("COPY 0", DestRemote); + SetQueryCompletion(&qc, COMMANDTAG_COPY, 0); + EndCommand(&qc, DestRemote, false); pq_flush(); proc_exit(0); diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index 09c1dcbb53..324da752f4 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -100,7 +100,7 @@ DestReceiver *None_Receiver = (DestReceiver *) &donothingDR; * ---------------- */ void -BeginCommand(const char *commandTag, CommandDest dest) +BeginCommand(CommandTag commandTag, CommandDest dest) { /* Nothing to do at present */ } @@ -163,8 +163,12 @@ CreateDestReceiver(CommandDest dest) * ---------------- */ void -EndCommand(const char *commandTag, CommandDest dest) +EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_output) { + char completionTag[COMPLETION_TAG_BUFSIZE]; + CommandTag tag; + const char *tagname; + switch (dest) { case DestRemote: @@ -172,11 +176,32 @@ EndCommand(const char *commandTag, CommandDest dest) case DestRemoteSimple: /* - * We assume the commandTag is plain ASCII and therefore requires - * no encoding conversion. + * We assume the tagname is plain ASCII and therefore requires no + * encoding conversion. + * + * We no longer display LastOid, but to preserve the wire protocol, + * we write InvalidOid where the LastOid used to be written. For + * efficiency in the snprintf(), hard-code InvalidOid as zero. + * + * All cases where LastOid was written also write nprocessed count, + * so just Assert that rather than having an extra test. */ - pq_putmessage('C', commandTag, strlen(commandTag) + 1); - break; + tag = qc->commandTag; + tagname = GetCommandTagName(tag); + + if (command_tag_display_last_oid(tag) && !force_undecorated_output) + { + Assert(InvalidOid == 0); + Assert(command_tag_display_rowcount(tag)); + snprintf(completionTag, COMPLETION_TAG_BUFSIZE, + "%s 0 " UINT64_FORMAT, tagname, qc->nprocessed); + } + else if (command_tag_display_rowcount(tag) && !force_undecorated_output) + snprintf(completionTag, COMPLETION_TAG_BUFSIZE, + "%s " UINT64_FORMAT, tagname, qc->nprocessed); + else + snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s", tagname); + pq_putmessage('C', completionTag, strlen(completionTag) + 1); case DestNone: case DestDebug: diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 0a6f80963b..62c181b0a5 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1064,8 +1064,8 @@ exec_simple_query(const char *query_string) { RawStmt *parsetree = lfirst_node(RawStmt, parsetree_item); bool snapshot_set = false; - const char *commandTag; - char completionTag[COMPLETION_TAG_BUFSIZE]; + CommandTag commandTag; + QueryCompletion qc; MemoryContext per_parsetree_context = NULL; List *querytree_list, *plantree_list; @@ -1081,7 +1081,7 @@ exec_simple_query(const char *query_string) */ commandTag = CreateCommandTag(parsetree->stmt); - set_ps_display(commandTag, false); + set_ps_display(GetCommandTagName(commandTag), false); BeginCommand(commandTag, dest); @@ -1230,7 +1230,7 @@ exec_simple_query(const char *query_string) true, receiver, receiver, - completionTag); + &qc); receiver->rDestroy(receiver); @@ -1281,7 +1281,7 @@ exec_simple_query(const char *query_string) * command the client sent, regardless of rewriting. (But a command * aborted by error will not send an EndCommand report at all.) */ - EndCommand(completionTag, dest); + EndCommand(&qc, dest, false); /* Now we may drop the per-parsetree context, if one was created. */ if (per_parsetree_context) @@ -1343,7 +1343,7 @@ exec_parse_message(const char *query_string, /* string to execute */ MemoryContext oldcontext; List *parsetree_list; RawStmt *raw_parse_tree; - const char *commandTag; + CommandTag commandTag; List *querytree_list; CachedPlanSource *psrc; bool is_named; @@ -1505,7 +1505,7 @@ exec_parse_message(const char *query_string, /* string to execute */ { /* Empty input string. This is legal. */ raw_parse_tree = NULL; - commandTag = NULL; + commandTag = COMMANDTAG_UNKNOWN; psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag); querytree_list = NIL; } @@ -2022,7 +2022,7 @@ exec_execute_message(const char *portal_name, long max_rows) DestReceiver *receiver; Portal portal; bool completed; - char completionTag[COMPLETION_TAG_BUFSIZE]; + QueryCompletion qc; const char *sourceText; const char *prepStmtName; ParamListInfo portalParams; @@ -2049,7 +2049,7 @@ exec_execute_message(const char *portal_name, long max_rows) * If the original query was a null string, just return * EmptyQueryResponse. */ - if (portal->commandTag == NULL) + if (portal->commandTag == COMMANDTAG_UNKNOWN) { Assert(portal->stmts == NIL); NullCommand(dest); @@ -2095,7 +2095,7 @@ exec_execute_message(const char *portal_name, long max_rows) pgstat_report_activity(STATE_RUNNING, sourceText); - set_ps_display(portal->commandTag, false); + set_ps_display(GetCommandTagName(portal->commandTag), false); if (save_log_statement_stats) ResetUsage(); @@ -2176,7 +2176,7 @@ exec_execute_message(const char *portal_name, long max_rows) !execute_is_fetch && max_rows == FETCH_ALL, receiver, receiver, - completionTag); + &qc); receiver->rDestroy(receiver); @@ -2209,7 +2209,7 @@ exec_execute_message(const char *portal_name, long max_rows) } /* Send appropriate CommandComplete to client */ - EndCommand(completionTag, dest); + EndCommand(&qc, dest, false); } else { diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 0f5801e046..f2089f5c34 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -40,7 +40,7 @@ static void ProcessQuery(PlannedStmt *plan, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, - char *completionTag); + QueryCompletion *qc); static void FillPortalStore(Portal portal, bool isTopLevel); static uint64 RunFromStore(Portal portal, ScanDirection direction, uint64 count, DestReceiver *dest); @@ -48,11 +48,11 @@ static uint64 PortalRunSelect(Portal portal, bool forward, long count, DestReceiver *dest); static void PortalRunUtility(Portal portal, PlannedStmt *pstmt, bool isTopLevel, bool setHoldSnapshot, - DestReceiver *dest, char *completionTag); + DestReceiver *dest, QueryCompletion *qc); static void PortalRunMulti(Portal portal, bool isTopLevel, bool setHoldSnapshot, DestReceiver *dest, DestReceiver *altdest, - char *completionTag); + QueryCompletion *qc); static uint64 DoPortalRunFetch(Portal portal, FetchDirection fdirection, long count, @@ -125,10 +125,9 @@ FreeQueryDesc(QueryDesc *qdesc) * sourceText: the source text of the query * params: any parameters needed * dest: where to send results - * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE - * in which to store a command completion status string. + * qc: where to store the command completion status data. * - * completionTag may be NULL if caller doesn't want a status string. + * qc may be NULL if caller doesn't want a status string. * * Must be called in a memory context that will be reset or deleted on * error; otherwise the executor's memory usage will be leaked. @@ -139,7 +138,7 @@ ProcessQuery(PlannedStmt *plan, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, - char *completionTag) + QueryCompletion *qc) { QueryDesc *queryDesc; @@ -161,38 +160,26 @@ ProcessQuery(PlannedStmt *plan, ExecutorRun(queryDesc, ForwardScanDirection, 0L, true); /* - * Build command completion status string, if caller wants one. + * Build command completion status data, if caller wants one. */ - if (completionTag) + if (qc) { - Oid lastOid; - switch (queryDesc->operation) { case CMD_SELECT: - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, - "SELECT " UINT64_FORMAT, - queryDesc->estate->es_processed); + SetQueryCompletion(qc, COMMANDTAG_SELECT, queryDesc->estate->es_processed); break; case CMD_INSERT: - /* lastoid doesn't exist anymore */ - lastOid = InvalidOid; - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, - "INSERT %u " UINT64_FORMAT, - lastOid, queryDesc->estate->es_processed); + SetQueryCompletion(qc, COMMANDTAG_INSERT, queryDesc->estate->es_processed); break; case CMD_UPDATE: - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, - "UPDATE " UINT64_FORMAT, - queryDesc->estate->es_processed); + SetQueryCompletion(qc, COMMANDTAG_UPDATE, queryDesc->estate->es_processed); break; case CMD_DELETE: - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, - "DELETE " UINT64_FORMAT, - queryDesc->estate->es_processed); + SetQueryCompletion(qc, COMMANDTAG_DELETE, queryDesc->estate->es_processed); break; default: - strcpy(completionTag, "???"); + SetQueryCompletion(qc, COMMANDTAG_UNKNOWN, queryDesc->estate->es_processed); break; } } @@ -675,9 +662,8 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats) * * altdest: where to send output of non-primary queries * - * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE - * in which to store a command completion status string. - * May be NULL if caller doesn't want a status string. + * qc: where to store command completion status data. + * May be NULL if caller doesn't want status data. * * Returns true if the portal's execution is complete, false if it was * suspended due to exhaustion of the count parameter. @@ -685,7 +671,7 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats) bool PortalRun(Portal portal, long count, bool isTopLevel, bool run_once, DestReceiver *dest, DestReceiver *altdest, - char *completionTag) + QueryCompletion *qc) { bool result; uint64 nprocessed; @@ -700,9 +686,9 @@ PortalRun(Portal portal, long count, bool isTopLevel, bool run_once, TRACE_POSTGRESQL_QUERY_EXECUTE_START(); - /* Initialize completion tag to empty string */ - if (completionTag) - completionTag[0] = '\0'; + /* Initialize empty completion data */ + if (qc) + InitializeQueryCompletion(qc); if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY) { @@ -771,16 +757,12 @@ PortalRun(Portal portal, long count, bool isTopLevel, bool run_once, /* * If the portal result contains a command tag and the caller - * gave us a pointer to store it, copy it. Patch the "SELECT" - * tag to also provide the rowcount. + * gave us a pointer to store it, copy it and update the rowcount. */ - if (completionTag && portal->commandTag) + if (qc && portal->qc.commandTag != COMMANDTAG_UNKNOWN) { - if (strcmp(portal->commandTag, "SELECT") == 0) - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, - "SELECT " UINT64_FORMAT, nprocessed); - else - strcpy(completionTag, portal->commandTag); + CopyQueryCompletion(qc, &portal->qc); + qc->nprocessed = nprocessed; } /* Mark portal not active */ @@ -794,7 +776,7 @@ PortalRun(Portal portal, long count, bool isTopLevel, bool run_once, case PORTAL_MULTI_QUERY: PortalRunMulti(portal, isTopLevel, false, - dest, altdest, completionTag); + dest, altdest, qc); /* Prevent portal's commands from being re-executed */ MarkPortalDone(portal); @@ -1005,8 +987,9 @@ static void FillPortalStore(Portal portal, bool isTopLevel) { DestReceiver *treceiver; - char completionTag[COMPLETION_TAG_BUFSIZE]; + QueryCompletion qc; + InitializeQueryCompletion(&qc); PortalCreateHoldStore(portal); treceiver = CreateDestReceiver(DestTuplestore); SetTuplestoreDestReceiverParams(treceiver, @@ -1014,8 +997,6 @@ FillPortalStore(Portal portal, bool isTopLevel) portal->holdContext, false); - completionTag[0] = '\0'; - switch (portal->strategy) { case PORTAL_ONE_RETURNING: @@ -1028,12 +1009,12 @@ FillPortalStore(Portal portal, bool isTopLevel) * portal's holdSnapshot to the snapshot used (or a copy of it). */ PortalRunMulti(portal, isTopLevel, true, - treceiver, None_Receiver, completionTag); + treceiver, None_Receiver, &qc); break; case PORTAL_UTIL_SELECT: PortalRunUtility(portal, linitial_node(PlannedStmt, portal->stmts), - isTopLevel, true, treceiver, completionTag); + isTopLevel, true, treceiver, &qc); break; default: @@ -1042,9 +1023,9 @@ FillPortalStore(Portal portal, bool isTopLevel) break; } - /* Override default completion tag with actual command result */ - if (completionTag[0] != '\0') - portal->commandTag = pstrdup(completionTag); + /* Override portal completion data with actual command results */ + if (qc.commandTag != COMMANDTAG_UNKNOWN) + CopyQueryCompletion(&portal->qc, &qc); treceiver->rDestroy(treceiver); } @@ -1130,7 +1111,7 @@ RunFromStore(Portal portal, ScanDirection direction, uint64 count, static void PortalRunUtility(Portal portal, PlannedStmt *pstmt, bool isTopLevel, bool setHoldSnapshot, - DestReceiver *dest, char *completionTag) + DestReceiver *dest, QueryCompletion *qc) { Node *utilityStmt = pstmt->utilityStmt; Snapshot snapshot; @@ -1178,7 +1159,7 @@ PortalRunUtility(Portal portal, PlannedStmt *pstmt, portal->portalParams, portal->queryEnv, dest, - completionTag); + qc); /* Some utility statements may change context on us */ MemoryContextSwitchTo(portal->portalContext); @@ -1202,7 +1183,7 @@ static void PortalRunMulti(Portal portal, bool isTopLevel, bool setHoldSnapshot, DestReceiver *dest, DestReceiver *altdest, - char *completionTag) + QueryCompletion *qc) { bool active_snapshot_set = false; ListCell *stmtlist_item; @@ -1284,7 +1265,7 @@ PortalRunMulti(Portal portal, portal->sourceText, portal->portalParams, portal->queryEnv, - dest, completionTag); + dest, qc); } else { @@ -1319,7 +1300,7 @@ PortalRunMulti(Portal portal, Assert(!active_snapshot_set); /* statement can set tag string */ PortalRunUtility(portal, pstmt, isTopLevel, false, - dest, completionTag); + dest, qc); } else { @@ -1350,8 +1331,8 @@ PortalRunMulti(Portal portal, PopActiveSnapshot(); /* - * If a command completion tag was supplied, use it. Otherwise use the - * portal's commandTag as the default completion tag. + * If a query completion data was supplied, use it. Otherwise use the + * portal's query completion data. * * Exception: Clients expect INSERT/UPDATE/DELETE tags to have counts, so * fake them with zeros. This can happen with DO INSTEAD rules if there @@ -1361,18 +1342,12 @@ PortalRunMulti(Portal portal, * e.g. an INSERT that does an UPDATE instead should not print "0 1" if * one row was updated. See QueryRewrite(), step 3, for details. */ - if (completionTag && completionTag[0] == '\0') + if (qc && qc->commandTag == COMMANDTAG_UNKNOWN) { - if (portal->commandTag) - strcpy(completionTag, portal->commandTag); - if (strcmp(completionTag, "SELECT") == 0) - sprintf(completionTag, "SELECT 0 0"); - else if (strcmp(completionTag, "INSERT") == 0) - strcpy(completionTag, "INSERT 0 0"); - else if (strcmp(completionTag, "UPDATE") == 0) - strcpy(completionTag, "UPDATE 0"); - else if (strcmp(completionTag, "DELETE") == 0) - strcpy(completionTag, "DELETE 0"); + if (portal->qc.commandTag != COMMANDTAG_UNKNOWN) + CopyQueryCompletion(qc, &portal->qc); + /* If the caller supplied a qc, we should have set it by now. */ + Assert(qc->commandTag != COMMANDTAG_UNKNOWN); } } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index bb85b5e52a..b7b7c952a7 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -83,10 +83,9 @@ static void ProcessUtilitySlow(ParseState *pstate, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, - char *completionTag); + QueryCompletion *qc); static void ExecDropStmt(DropStmt *stmt, bool isTopLevel); - /* * CommandIsReadOnly: is an executable query read-only? * @@ -467,7 +466,6 @@ CheckRestrictedOperation(const char *cmdname) cmdname))); } - /* * ProcessUtility * general utility function invoker @@ -480,16 +478,13 @@ CheckRestrictedOperation(const char *cmdname) * queryEnv: environment for parse through execution (e.g., ephemeral named * tables like trigger transition tables). May be NULL. * dest: where to send results - * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE - * in which to store a command completion status string. + * qc: where to store command completion status data. * * Caller MUST supply a queryString; it is not allowed (anymore) to pass NULL. * If you really don't have source text, you can pass a constant string, * perhaps "(query not available)". * - * completionTag is only set nonempty if we want to return a nondefault status. - * - * completionTag may be NULL if caller doesn't want a status string. + * qc may be NULL if caller doesn't want status data. * * Note for users of ProcessUtility_hook: the same queryString may be passed * to multiple invocations of ProcessUtility when processing a query string @@ -507,7 +502,7 @@ ProcessUtility(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, - char *completionTag) + QueryCompletion *qc) { Assert(IsA(pstmt, PlannedStmt)); Assert(pstmt->commandType == CMD_UTILITY); @@ -521,11 +516,11 @@ ProcessUtility(PlannedStmt *pstmt, if (ProcessUtility_hook) (*ProcessUtility_hook) (pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else standard_ProcessUtility(pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); } /* @@ -546,7 +541,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, - char *completionTag) + QueryCompletion *qc) { Node *parsetree = pstmt->utilityStmt; bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); @@ -562,18 +557,18 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY && (XactReadOnly || IsInParallelMode())) { - const char *commandtag = CreateCommandTag(parsetree); + CommandTag commandtag = CreateCommandTag(parsetree); if ((readonly_flags & COMMAND_OK_IN_READ_ONLY_TXN) == 0) - PreventCommandIfReadOnly(commandtag); + PreventCommandTagIfReadOnly(commandtag); if ((readonly_flags & COMMAND_OK_IN_PARALLEL_MODE) == 0) - PreventCommandIfParallelMode(commandtag); + PreventCommandTagIfParallelMode(commandtag); if ((readonly_flags & COMMAND_OK_IN_RECOVERY) == 0) - PreventCommandDuringRecovery(commandtag); + PreventCommandTagDuringRecovery(commandtag); } - if (completionTag) - completionTag[0] = '\0'; + if (qc) + InitializeQueryCompletion(qc); pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -623,18 +618,18 @@ standard_ProcessUtility(PlannedStmt *pstmt, case TRANS_STMT_COMMIT: if (!EndTransactionBlock(stmt->chain)) { - /* report unsuccessful commit in completionTag */ - if (completionTag) - strcpy(completionTag, "ROLLBACK"); + /* report unsuccessful commit in qc */ + if (qc) + SetQueryCompletion(qc, COMMANDTAG_ROLLBACK, 0); } break; case TRANS_STMT_PREPARE: if (!PrepareTransactionBlock(stmt->gid)) { - /* report unsuccessful commit in completionTag */ - if (completionTag) - strcpy(completionTag, "ROLLBACK"); + /* report unsuccessful commit in qc */ + if (qc) + SetQueryCompletion(qc, COMMANDTAG_ROLLBACK, 0); } break; @@ -693,8 +688,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, break; case T_FetchStmt: - PerformPortalFetch((FetchStmt *) parsetree, dest, - completionTag); + PerformPortalFetch((FetchStmt *) parsetree, dest, qc); break; case T_DoStmt: @@ -729,9 +723,8 @@ standard_ProcessUtility(PlannedStmt *pstmt, DoCopy(pstate, (CopyStmt *) parsetree, pstmt->stmt_location, pstmt->stmt_len, &processed); - if (completionTag) - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, - "COPY " UINT64_FORMAT, processed); + if (qc) + SetQueryCompletion(qc, COMMANDTAG_COPY, processed); } break; @@ -745,7 +738,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, ExecuteQuery(pstate, (ExecuteStmt *) parsetree, NULL, params, - dest, completionTag); + dest, qc); break; case T_DeallocateStmt: @@ -974,7 +967,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->objtype)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else ExecuteGrantStmt(stmt); } @@ -987,7 +980,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->removeType)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else ExecDropStmt(stmt, isTopLevel); } @@ -1000,7 +993,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->renameType)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else ExecRenameStmt(stmt); } @@ -1013,7 +1006,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->objectType)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else ExecAlterObjectDependsStmt(stmt, NULL); } @@ -1026,7 +1019,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->objectType)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else ExecAlterObjectSchemaStmt(stmt, NULL); } @@ -1039,7 +1032,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->objectType)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else ExecAlterOwnerStmt(stmt); } @@ -1052,7 +1045,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->objtype)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else CommentObject(stmt); break; @@ -1065,7 +1058,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, if (EventTriggerSupportsObjectType(stmt->objtype)) ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); else ExecSecLabelStmt(stmt); break; @@ -1075,7 +1068,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, /* All other statement types have event trigger support */ ProcessUtilitySlow(pstate, pstmt, queryString, context, params, queryEnv, - dest, completionTag); + dest, qc); break; } @@ -1102,7 +1095,7 @@ ProcessUtilitySlow(ParseState *pstate, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, - char *completionTag) + QueryCompletion *qc) { Node *parsetree = pstmt->utilityStmt; bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); @@ -1605,7 +1598,7 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateTableAsStmt: address = ExecCreateTableAs(pstate, (CreateTableAsStmt *) parsetree, - params, queryEnv, completionTag); + params, queryEnv, qc); break; case T_RefreshMatViewStmt: @@ -1620,7 +1613,7 @@ ProcessUtilitySlow(ParseState *pstate, PG_TRY(); { address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, - queryString, params, completionTag); + queryString, params, qc); } PG_FINALLY(); { @@ -2099,137 +2092,137 @@ UtilityContainsQuery(Node *parsetree) * * This covers most cases where ALTER is used with an ObjectType enum. */ -static const char * +static CommandTag AlterObjectTypeCommandTag(ObjectType objtype) { - const char *tag; + CommandTag tag; switch (objtype) { case OBJECT_AGGREGATE: - tag = "ALTER AGGREGATE"; + tag = COMMANDTAG_ALTER_AGGREGATE; break; case OBJECT_ATTRIBUTE: - tag = "ALTER TYPE"; + tag = COMMANDTAG_ALTER_TYPE; break; case OBJECT_CAST: - tag = "ALTER CAST"; + tag = COMMANDTAG_ALTER_CAST; break; case OBJECT_COLLATION: - tag = "ALTER COLLATION"; + tag = COMMANDTAG_ALTER_COLLATION; break; case OBJECT_COLUMN: - tag = "ALTER TABLE"; + tag = COMMANDTAG_ALTER_TABLE; break; case OBJECT_CONVERSION: - tag = "ALTER CONVERSION"; + tag = COMMANDTAG_ALTER_CONVERSION; break; case OBJECT_DATABASE: - tag = "ALTER DATABASE"; + tag = COMMANDTAG_ALTER_DATABASE; break; case OBJECT_DOMAIN: case OBJECT_DOMCONSTRAINT: - tag = "ALTER DOMAIN"; + tag = COMMANDTAG_ALTER_DOMAIN; break; case OBJECT_EXTENSION: - tag = "ALTER EXTENSION"; + tag = COMMANDTAG_ALTER_EXTENSION; break; case OBJECT_FDW: - tag = "ALTER FOREIGN DATA WRAPPER"; + tag = COMMANDTAG_ALTER_FOREIGN_DATA_WRAPPER; break; case OBJECT_FOREIGN_SERVER: - tag = "ALTER SERVER"; + tag = COMMANDTAG_ALTER_SERVER; break; case OBJECT_FOREIGN_TABLE: - tag = "ALTER FOREIGN TABLE"; + tag = COMMANDTAG_ALTER_FOREIGN_TABLE; break; case OBJECT_FUNCTION: - tag = "ALTER FUNCTION"; + tag = COMMANDTAG_ALTER_FUNCTION; break; case OBJECT_INDEX: - tag = "ALTER INDEX"; + tag = COMMANDTAG_ALTER_INDEX; break; case OBJECT_LANGUAGE: - tag = "ALTER LANGUAGE"; + tag = COMMANDTAG_ALTER_LANGUAGE; break; case OBJECT_LARGEOBJECT: - tag = "ALTER LARGE OBJECT"; + tag = COMMANDTAG_ALTER_LARGE_OBJECT; break; case OBJECT_OPCLASS: - tag = "ALTER OPERATOR CLASS"; + tag = COMMANDTAG_ALTER_OPERATOR_CLASS; break; case OBJECT_OPERATOR: - tag = "ALTER OPERATOR"; + tag = COMMANDTAG_ALTER_OPERATOR; break; case OBJECT_OPFAMILY: - tag = "ALTER OPERATOR FAMILY"; + tag = COMMANDTAG_ALTER_OPERATOR_FAMILY; break; case OBJECT_POLICY: - tag = "ALTER POLICY"; + tag = COMMANDTAG_ALTER_POLICY; break; case OBJECT_PROCEDURE: - tag = "ALTER PROCEDURE"; + tag = COMMANDTAG_ALTER_PROCEDURE; break; case OBJECT_ROLE: - tag = "ALTER ROLE"; + tag = COMMANDTAG_ALTER_ROLE; break; case OBJECT_ROUTINE: - tag = "ALTER ROUTINE"; + tag = COMMANDTAG_ALTER_ROUTINE; break; case OBJECT_RULE: - tag = "ALTER RULE"; + tag = COMMANDTAG_ALTER_RULE; break; case OBJECT_SCHEMA: - tag = "ALTER SCHEMA"; + tag = COMMANDTAG_ALTER_SCHEMA; break; case OBJECT_SEQUENCE: - tag = "ALTER SEQUENCE"; + tag = COMMANDTAG_ALTER_SEQUENCE; break; case OBJECT_TABLE: case OBJECT_TABCONSTRAINT: - tag = "ALTER TABLE"; + tag = COMMANDTAG_ALTER_TABLE; break; case OBJECT_TABLESPACE: - tag = "ALTER TABLESPACE"; + tag = COMMANDTAG_ALTER_TABLESPACE; break; case OBJECT_TRIGGER: - tag = "ALTER TRIGGER"; + tag = COMMANDTAG_ALTER_TRIGGER; break; case OBJECT_EVENT_TRIGGER: - tag = "ALTER EVENT TRIGGER"; + tag = COMMANDTAG_ALTER_EVENT_TRIGGER; break; case OBJECT_TSCONFIGURATION: - tag = "ALTER TEXT SEARCH CONFIGURATION"; + tag = COMMANDTAG_ALTER_TEXT_SEARCH_CONFIGURATION; break; case OBJECT_TSDICTIONARY: - tag = "ALTER TEXT SEARCH DICTIONARY"; + tag = COMMANDTAG_ALTER_TEXT_SEARCH_DICTIONARY; break; case OBJECT_TSPARSER: - tag = "ALTER TEXT SEARCH PARSER"; + tag = COMMANDTAG_ALTER_TEXT_SEARCH_PARSER; break; case OBJECT_TSTEMPLATE: - tag = "ALTER TEXT SEARCH TEMPLATE"; + tag = COMMANDTAG_ALTER_TEXT_SEARCH_TEMPLATE; break; case OBJECT_TYPE: - tag = "ALTER TYPE"; + tag = COMMANDTAG_ALTER_TYPE; break; case OBJECT_VIEW: - tag = "ALTER VIEW"; + tag = COMMANDTAG_ALTER_VIEW; break; case OBJECT_MATVIEW: - tag = "ALTER MATERIALIZED VIEW"; + tag = COMMANDTAG_ALTER_MATERIALIZED_VIEW; break; case OBJECT_PUBLICATION: - tag = "ALTER PUBLICATION"; + tag = COMMANDTAG_ALTER_PUBLICATION; break; case OBJECT_SUBSCRIPTION: - tag = "ALTER SUBSCRIPTION"; + tag = COMMANDTAG_ALTER_SUBSCRIPTION; break; case OBJECT_STATISTIC_EXT: - tag = "ALTER STATISTICS"; + tag = COMMANDTAG_ALTER_STATISTICS; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; break; } @@ -2238,20 +2231,17 @@ AlterObjectTypeCommandTag(ObjectType objtype) /* * CreateCommandTag - * utility to get a string representation of the command operation, + * utility to get a CommandTag for the command operation, * given either a raw (un-analyzed) parsetree, an analyzed Query, * or a PlannedStmt. * * This must handle all command types, but since the vast majority * of 'em are utility commands, it seems sensible to keep it here. - * - * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE. - * Also, the result must point at a true constant (permanent storage). */ -const char * +CommandTag CreateCommandTag(Node *parsetree) { - const char *tag; + CommandTag tag; switch (nodeTag(parsetree)) { @@ -2262,19 +2252,19 @@ CreateCommandTag(Node *parsetree) /* raw plannable queries */ case T_InsertStmt: - tag = "INSERT"; + tag = COMMANDTAG_INSERT; break; case T_DeleteStmt: - tag = "DELETE"; + tag = COMMANDTAG_DELETE; break; case T_UpdateStmt: - tag = "UPDATE"; + tag = COMMANDTAG_UPDATE; break; case T_SelectStmt: - tag = "SELECT"; + tag = COMMANDTAG_SELECT; break; /* utility statements --- same whether raw or cooked */ @@ -2285,51 +2275,51 @@ CreateCommandTag(Node *parsetree) switch (stmt->kind) { case TRANS_STMT_BEGIN: - tag = "BEGIN"; + tag = COMMANDTAG_BEGIN; break; case TRANS_STMT_START: - tag = "START TRANSACTION"; + tag = COMMANDTAG_START_TRANSACTION; break; case TRANS_STMT_COMMIT: - tag = "COMMIT"; + tag = COMMANDTAG_COMMIT; break; case TRANS_STMT_ROLLBACK: case TRANS_STMT_ROLLBACK_TO: - tag = "ROLLBACK"; + tag = COMMANDTAG_ROLLBACK; break; case TRANS_STMT_SAVEPOINT: - tag = "SAVEPOINT"; + tag = COMMANDTAG_SAVEPOINT; break; case TRANS_STMT_RELEASE: - tag = "RELEASE"; + tag = COMMANDTAG_RELEASE; break; case TRANS_STMT_PREPARE: - tag = "PREPARE TRANSACTION"; + tag = COMMANDTAG_PREPARE_TRANSACTION; break; case TRANS_STMT_COMMIT_PREPARED: - tag = "COMMIT PREPARED"; + tag = COMMANDTAG_COMMIT_PREPARED; break; case TRANS_STMT_ROLLBACK_PREPARED: - tag = "ROLLBACK PREPARED"; + tag = COMMANDTAG_ROLLBACK_PREPARED; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; break; } } break; case T_DeclareCursorStmt: - tag = "DECLARE CURSOR"; + tag = COMMANDTAG_DECLARE_CURSOR; break; case T_ClosePortalStmt: @@ -2337,9 +2327,9 @@ CreateCommandTag(Node *parsetree) ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree; if (stmt->portalname == NULL) - tag = "CLOSE CURSOR ALL"; + tag = COMMANDTAG_CLOSE_CURSOR_ALL; else - tag = "CLOSE CURSOR"; + tag = COMMANDTAG_CLOSE_CURSOR; } break; @@ -2347,209 +2337,209 @@ CreateCommandTag(Node *parsetree) { FetchStmt *stmt = (FetchStmt *) parsetree; - tag = (stmt->ismove) ? "MOVE" : "FETCH"; + tag = (stmt->ismove) ? COMMANDTAG_MOVE : COMMANDTAG_FETCH; } break; case T_CreateDomainStmt: - tag = "CREATE DOMAIN"; + tag = COMMANDTAG_CREATE_DOMAIN; break; case T_CreateSchemaStmt: - tag = "CREATE SCHEMA"; + tag = COMMANDTAG_CREATE_SCHEMA; break; case T_CreateStmt: - tag = "CREATE TABLE"; + tag = COMMANDTAG_CREATE_TABLE; break; case T_CreateTableSpaceStmt: - tag = "CREATE TABLESPACE"; + tag = COMMANDTAG_CREATE_TABLESPACE; break; case T_DropTableSpaceStmt: - tag = "DROP TABLESPACE"; + tag = COMMANDTAG_DROP_TABLESPACE; break; case T_AlterTableSpaceOptionsStmt: - tag = "ALTER TABLESPACE"; + tag = COMMANDTAG_ALTER_TABLESPACE; break; case T_CreateExtensionStmt: - tag = "CREATE EXTENSION"; + tag = COMMANDTAG_CREATE_EXTENSION; break; case T_AlterExtensionStmt: - tag = "ALTER EXTENSION"; + tag = COMMANDTAG_ALTER_EXTENSION; break; case T_AlterExtensionContentsStmt: - tag = "ALTER EXTENSION"; + tag = COMMANDTAG_ALTER_EXTENSION; break; case T_CreateFdwStmt: - tag = "CREATE FOREIGN DATA WRAPPER"; + tag = COMMANDTAG_CREATE_FOREIGN_DATA_WRAPPER; break; case T_AlterFdwStmt: - tag = "ALTER FOREIGN DATA WRAPPER"; + tag = COMMANDTAG_ALTER_FOREIGN_DATA_WRAPPER; break; case T_CreateForeignServerStmt: - tag = "CREATE SERVER"; + tag = COMMANDTAG_CREATE_SERVER; break; case T_AlterForeignServerStmt: - tag = "ALTER SERVER"; + tag = COMMANDTAG_ALTER_SERVER; break; case T_CreateUserMappingStmt: - tag = "CREATE USER MAPPING"; + tag = COMMANDTAG_CREATE_USER_MAPPING; break; case T_AlterUserMappingStmt: - tag = "ALTER USER MAPPING"; + tag = COMMANDTAG_ALTER_USER_MAPPING; break; case T_DropUserMappingStmt: - tag = "DROP USER MAPPING"; + tag = COMMANDTAG_DROP_USER_MAPPING; break; case T_CreateForeignTableStmt: - tag = "CREATE FOREIGN TABLE"; + tag = COMMANDTAG_CREATE_FOREIGN_TABLE; break; case T_ImportForeignSchemaStmt: - tag = "IMPORT FOREIGN SCHEMA"; + tag = COMMANDTAG_IMPORT_FOREIGN_SCHEMA; break; case T_DropStmt: switch (((DropStmt *) parsetree)->removeType) { case OBJECT_TABLE: - tag = "DROP TABLE"; + tag = COMMANDTAG_DROP_TABLE; break; case OBJECT_SEQUENCE: - tag = "DROP SEQUENCE"; + tag = COMMANDTAG_DROP_SEQUENCE; break; case OBJECT_VIEW: - tag = "DROP VIEW"; + tag = COMMANDTAG_DROP_VIEW; break; case OBJECT_MATVIEW: - tag = "DROP MATERIALIZED VIEW"; + tag = COMMANDTAG_DROP_MATERIALIZED_VIEW; break; case OBJECT_INDEX: - tag = "DROP INDEX"; + tag = COMMANDTAG_DROP_INDEX; break; case OBJECT_TYPE: - tag = "DROP TYPE"; + tag = COMMANDTAG_DROP_TYPE; break; case OBJECT_DOMAIN: - tag = "DROP DOMAIN"; + tag = COMMANDTAG_DROP_DOMAIN; break; case OBJECT_COLLATION: - tag = "DROP COLLATION"; + tag = COMMANDTAG_DROP_COLLATION; break; case OBJECT_CONVERSION: - tag = "DROP CONVERSION"; + tag = COMMANDTAG_DROP_CONVERSION; break; case OBJECT_SCHEMA: - tag = "DROP SCHEMA"; + tag = COMMANDTAG_DROP_SCHEMA; break; case OBJECT_TSPARSER: - tag = "DROP TEXT SEARCH PARSER"; + tag = COMMANDTAG_DROP_TEXT_SEARCH_PARSER; break; case OBJECT_TSDICTIONARY: - tag = "DROP TEXT SEARCH DICTIONARY"; + tag = COMMANDTAG_DROP_TEXT_SEARCH_DICTIONARY; break; case OBJECT_TSTEMPLATE: - tag = "DROP TEXT SEARCH TEMPLATE"; + tag = COMMANDTAG_DROP_TEXT_SEARCH_TEMPLATE; break; case OBJECT_TSCONFIGURATION: - tag = "DROP TEXT SEARCH CONFIGURATION"; + tag = COMMANDTAG_DROP_TEXT_SEARCH_CONFIGURATION; break; case OBJECT_FOREIGN_TABLE: - tag = "DROP FOREIGN TABLE"; + tag = COMMANDTAG_DROP_FOREIGN_TABLE; break; case OBJECT_EXTENSION: - tag = "DROP EXTENSION"; + tag = COMMANDTAG_DROP_EXTENSION; break; case OBJECT_FUNCTION: - tag = "DROP FUNCTION"; + tag = COMMANDTAG_DROP_FUNCTION; break; case OBJECT_PROCEDURE: - tag = "DROP PROCEDURE"; + tag = COMMANDTAG_DROP_PROCEDURE; break; case OBJECT_ROUTINE: - tag = "DROP ROUTINE"; + tag = COMMANDTAG_DROP_ROUTINE; break; case OBJECT_AGGREGATE: - tag = "DROP AGGREGATE"; + tag = COMMANDTAG_DROP_AGGREGATE; break; case OBJECT_OPERATOR: - tag = "DROP OPERATOR"; + tag = COMMANDTAG_DROP_OPERATOR; break; case OBJECT_LANGUAGE: - tag = "DROP LANGUAGE"; + tag = COMMANDTAG_DROP_LANGUAGE; break; case OBJECT_CAST: - tag = "DROP CAST"; + tag = COMMANDTAG_DROP_CAST; break; case OBJECT_TRIGGER: - tag = "DROP TRIGGER"; + tag = COMMANDTAG_DROP_TRIGGER; break; case OBJECT_EVENT_TRIGGER: - tag = "DROP EVENT TRIGGER"; + tag = COMMANDTAG_DROP_EVENT_TRIGGER; break; case OBJECT_RULE: - tag = "DROP RULE"; + tag = COMMANDTAG_DROP_RULE; break; case OBJECT_FDW: - tag = "DROP FOREIGN DATA WRAPPER"; + tag = COMMANDTAG_DROP_FOREIGN_DATA_WRAPPER; break; case OBJECT_FOREIGN_SERVER: - tag = "DROP SERVER"; + tag = COMMANDTAG_DROP_SERVER; break; case OBJECT_OPCLASS: - tag = "DROP OPERATOR CLASS"; + tag = COMMANDTAG_DROP_OPERATOR_CLASS; break; case OBJECT_OPFAMILY: - tag = "DROP OPERATOR FAMILY"; + tag = COMMANDTAG_DROP_OPERATOR_FAMILY; break; case OBJECT_POLICY: - tag = "DROP POLICY"; + tag = COMMANDTAG_DROP_POLICY; break; case OBJECT_TRANSFORM: - tag = "DROP TRANSFORM"; + tag = COMMANDTAG_DROP_TRANSFORM; break; case OBJECT_ACCESS_METHOD: - tag = "DROP ACCESS METHOD"; + tag = COMMANDTAG_DROP_ACCESS_METHOD; break; case OBJECT_PUBLICATION: - tag = "DROP PUBLICATION"; + tag = COMMANDTAG_DROP_PUBLICATION; break; case OBJECT_STATISTIC_EXT: - tag = "DROP STATISTICS"; + tag = COMMANDTAG_DROP_STATISTICS; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; } break; case T_TruncateStmt: - tag = "TRUNCATE TABLE"; + tag = COMMANDTAG_TRUNCATE_TABLE; break; case T_CommentStmt: - tag = "COMMENT"; + tag = COMMANDTAG_COMMENT; break; case T_SecLabelStmt: - tag = "SECURITY LABEL"; + tag = COMMANDTAG_SECURITY_LABEL; break; case T_CopyStmt: - tag = "COPY"; + tag = COMMANDTAG_COPY; break; case T_RenameStmt: @@ -2584,23 +2574,23 @@ CreateCommandTag(Node *parsetree) break; case T_AlterDomainStmt: - tag = "ALTER DOMAIN"; + tag = COMMANDTAG_ALTER_DOMAIN; break; case T_AlterFunctionStmt: switch (((AlterFunctionStmt *) parsetree)->objtype) { case OBJECT_FUNCTION: - tag = "ALTER FUNCTION"; + tag = COMMANDTAG_ALTER_FUNCTION; break; case OBJECT_PROCEDURE: - tag = "ALTER PROCEDURE"; + tag = COMMANDTAG_ALTER_PROCEDURE; break; case OBJECT_ROUTINE: - tag = "ALTER ROUTINE"; + tag = COMMANDTAG_ALTER_ROUTINE; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; } break; @@ -2608,7 +2598,7 @@ CreateCommandTag(Node *parsetree) { GrantStmt *stmt = (GrantStmt *) parsetree; - tag = (stmt->is_grant) ? "GRANT" : "REVOKE"; + tag = (stmt->is_grant) ? COMMANDTAG_GRANT : COMMANDTAG_REVOKE; } break; @@ -2616,145 +2606,145 @@ CreateCommandTag(Node *parsetree) { GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree; - tag = (stmt->is_grant) ? "GRANT ROLE" : "REVOKE ROLE"; + tag = (stmt->is_grant) ? COMMANDTAG_GRANT_ROLE : COMMANDTAG_REVOKE_ROLE; } break; case T_AlterDefaultPrivilegesStmt: - tag = "ALTER DEFAULT PRIVILEGES"; + tag = COMMANDTAG_ALTER_DEFAULT_PRIVILEGES; break; case T_DefineStmt: switch (((DefineStmt *) parsetree)->kind) { case OBJECT_AGGREGATE: - tag = "CREATE AGGREGATE"; + tag = COMMANDTAG_CREATE_AGGREGATE; break; case OBJECT_OPERATOR: - tag = "CREATE OPERATOR"; + tag = COMMANDTAG_CREATE_OPERATOR; break; case OBJECT_TYPE: - tag = "CREATE TYPE"; + tag = COMMANDTAG_CREATE_TYPE; break; case OBJECT_TSPARSER: - tag = "CREATE TEXT SEARCH PARSER"; + tag = COMMANDTAG_CREATE_TEXT_SEARCH_PARSER; break; case OBJECT_TSDICTIONARY: - tag = "CREATE TEXT SEARCH DICTIONARY"; + tag = COMMANDTAG_CREATE_TEXT_SEARCH_DICTIONARY; break; case OBJECT_TSTEMPLATE: - tag = "CREATE TEXT SEARCH TEMPLATE"; + tag = COMMANDTAG_CREATE_TEXT_SEARCH_TEMPLATE; break; case OBJECT_TSCONFIGURATION: - tag = "CREATE TEXT SEARCH CONFIGURATION"; + tag = COMMANDTAG_CREATE_TEXT_SEARCH_CONFIGURATION; break; case OBJECT_COLLATION: - tag = "CREATE COLLATION"; + tag = COMMANDTAG_CREATE_COLLATION; break; case OBJECT_ACCESS_METHOD: - tag = "CREATE ACCESS METHOD"; + tag = COMMANDTAG_CREATE_ACCESS_METHOD; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; } break; case T_CompositeTypeStmt: - tag = "CREATE TYPE"; + tag = COMMANDTAG_CREATE_TYPE; break; case T_CreateEnumStmt: - tag = "CREATE TYPE"; + tag = COMMANDTAG_CREATE_TYPE; break; case T_CreateRangeStmt: - tag = "CREATE TYPE"; + tag = COMMANDTAG_CREATE_TYPE; break; case T_AlterEnumStmt: - tag = "ALTER TYPE"; + tag = COMMANDTAG_ALTER_TYPE; break; case T_ViewStmt: - tag = "CREATE VIEW"; + tag = COMMANDTAG_CREATE_VIEW; break; case T_CreateFunctionStmt: if (((CreateFunctionStmt *) parsetree)->is_procedure) - tag = "CREATE PROCEDURE"; + tag = COMMANDTAG_CREATE_PROCEDURE; else - tag = "CREATE FUNCTION"; + tag = COMMANDTAG_CREATE_FUNCTION; break; case T_IndexStmt: - tag = "CREATE INDEX"; + tag = COMMANDTAG_CREATE_INDEX; break; case T_RuleStmt: - tag = "CREATE RULE"; + tag = COMMANDTAG_CREATE_RULE; break; case T_CreateSeqStmt: - tag = "CREATE SEQUENCE"; + tag = COMMANDTAG_CREATE_SEQUENCE; break; case T_AlterSeqStmt: - tag = "ALTER SEQUENCE"; + tag = COMMANDTAG_ALTER_SEQUENCE; break; case T_DoStmt: - tag = "DO"; + tag = COMMANDTAG_DO; break; case T_CreatedbStmt: - tag = "CREATE DATABASE"; + tag = COMMANDTAG_CREATE_DATABASE; break; case T_AlterDatabaseStmt: - tag = "ALTER DATABASE"; + tag = COMMANDTAG_ALTER_DATABASE; break; case T_AlterDatabaseSetStmt: - tag = "ALTER DATABASE"; + tag = COMMANDTAG_ALTER_DATABASE; break; case T_DropdbStmt: - tag = "DROP DATABASE"; + tag = COMMANDTAG_DROP_DATABASE; break; case T_NotifyStmt: - tag = "NOTIFY"; + tag = COMMANDTAG_NOTIFY; break; case T_ListenStmt: - tag = "LISTEN"; + tag = COMMANDTAG_LISTEN; break; case T_UnlistenStmt: - tag = "UNLISTEN"; + tag = COMMANDTAG_UNLISTEN; break; case T_LoadStmt: - tag = "LOAD"; + tag = COMMANDTAG_LOAD; break; case T_CallStmt: - tag = "CALL"; + tag = COMMANDTAG_CALL; break; case T_ClusterStmt: - tag = "CLUSTER"; + tag = COMMANDTAG_CLUSTER; break; case T_VacuumStmt: if (((VacuumStmt *) parsetree)->is_vacuumcmd) - tag = "VACUUM"; + tag = COMMANDTAG_VACUUM; else - tag = "ANALYZE"; + tag = COMMANDTAG_ANALYZE; break; case T_ExplainStmt: - tag = "EXPLAIN"; + tag = COMMANDTAG_EXPLAIN; break; case T_CreateTableAsStmt: @@ -2762,24 +2752,24 @@ CreateCommandTag(Node *parsetree) { case OBJECT_TABLE: if (((CreateTableAsStmt *) parsetree)->is_select_into) - tag = "SELECT INTO"; + tag = COMMANDTAG_SELECT_INTO; else - tag = "CREATE TABLE AS"; + tag = COMMANDTAG_CREATE_TABLE_AS; break; case OBJECT_MATVIEW: - tag = "CREATE MATERIALIZED VIEW"; + tag = COMMANDTAG_CREATE_MATERIALIZED_VIEW; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; } break; case T_RefreshMatViewStmt: - tag = "REFRESH MATERIALIZED VIEW"; + tag = COMMANDTAG_REFRESH_MATERIALIZED_VIEW; break; case T_AlterSystemStmt: - tag = "ALTER SYSTEM"; + tag = COMMANDTAG_ALTER_SYSTEM; break; case T_VariableSetStmt: @@ -2789,183 +2779,183 @@ CreateCommandTag(Node *parsetree) case VAR_SET_CURRENT: case VAR_SET_DEFAULT: case VAR_SET_MULTI: - tag = "SET"; + tag = COMMANDTAG_SET; break; case VAR_RESET: case VAR_RESET_ALL: - tag = "RESET"; + tag = COMMANDTAG_RESET; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; } break; case T_VariableShowStmt: - tag = "SHOW"; + tag = COMMANDTAG_SHOW; break; case T_DiscardStmt: switch (((DiscardStmt *) parsetree)->target) { case DISCARD_ALL: - tag = "DISCARD ALL"; + tag = COMMANDTAG_DISCARD_ALL; break; case DISCARD_PLANS: - tag = "DISCARD PLANS"; + tag = COMMANDTAG_DISCARD_PLANS; break; case DISCARD_TEMP: - tag = "DISCARD TEMP"; + tag = COMMANDTAG_DISCARD_TEMP; break; case DISCARD_SEQUENCES: - tag = "DISCARD SEQUENCES"; + tag = COMMANDTAG_DISCARD_SEQUENCES; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; } break; case T_CreateTransformStmt: - tag = "CREATE TRANSFORM"; + tag = COMMANDTAG_CREATE_TRANSFORM; break; case T_CreateTrigStmt: - tag = "CREATE TRIGGER"; + tag = COMMANDTAG_CREATE_TRIGGER; break; case T_CreateEventTrigStmt: - tag = "CREATE EVENT TRIGGER"; + tag = COMMANDTAG_CREATE_EVENT_TRIGGER; break; case T_AlterEventTrigStmt: - tag = "ALTER EVENT TRIGGER"; + tag = COMMANDTAG_ALTER_EVENT_TRIGGER; break; case T_CreatePLangStmt: - tag = "CREATE LANGUAGE"; + tag = COMMANDTAG_CREATE_LANGUAGE; break; case T_CreateRoleStmt: - tag = "CREATE ROLE"; + tag = COMMANDTAG_CREATE_ROLE; break; case T_AlterRoleStmt: - tag = "ALTER ROLE"; + tag = COMMANDTAG_ALTER_ROLE; break; case T_AlterRoleSetStmt: - tag = "ALTER ROLE"; + tag = COMMANDTAG_ALTER_ROLE; break; case T_DropRoleStmt: - tag = "DROP ROLE"; + tag = COMMANDTAG_DROP_ROLE; break; case T_DropOwnedStmt: - tag = "DROP OWNED"; + tag = COMMANDTAG_DROP_OWNED; break; case T_ReassignOwnedStmt: - tag = "REASSIGN OWNED"; + tag = COMMANDTAG_REASSIGN_OWNED; break; case T_LockStmt: - tag = "LOCK TABLE"; + tag = COMMANDTAG_LOCK_TABLE; break; case T_ConstraintsSetStmt: - tag = "SET CONSTRAINTS"; + tag = COMMANDTAG_SET_CONSTRAINTS; break; case T_CheckPointStmt: - tag = "CHECKPOINT"; + tag = COMMANDTAG_CHECKPOINT; break; case T_ReindexStmt: - tag = "REINDEX"; + tag = COMMANDTAG_REINDEX; break; case T_CreateConversionStmt: - tag = "CREATE CONVERSION"; + tag = COMMANDTAG_CREATE_CONVERSION; break; case T_CreateCastStmt: - tag = "CREATE CAST"; + tag = COMMANDTAG_CREATE_CAST; break; case T_CreateOpClassStmt: - tag = "CREATE OPERATOR CLASS"; + tag = COMMANDTAG_CREATE_OPERATOR_CLASS; break; case T_CreateOpFamilyStmt: - tag = "CREATE OPERATOR FAMILY"; + tag = COMMANDTAG_CREATE_OPERATOR_FAMILY; break; case T_AlterOpFamilyStmt: - tag = "ALTER OPERATOR FAMILY"; + tag = COMMANDTAG_ALTER_OPERATOR_FAMILY; break; case T_AlterOperatorStmt: - tag = "ALTER OPERATOR"; + tag = COMMANDTAG_ALTER_OPERATOR; break; case T_AlterTSDictionaryStmt: - tag = "ALTER TEXT SEARCH DICTIONARY"; + tag = COMMANDTAG_ALTER_TEXT_SEARCH_DICTIONARY; break; case T_AlterTSConfigurationStmt: - tag = "ALTER TEXT SEARCH CONFIGURATION"; + tag = COMMANDTAG_ALTER_TEXT_SEARCH_CONFIGURATION; break; case T_CreatePolicyStmt: - tag = "CREATE POLICY"; + tag = COMMANDTAG_CREATE_POLICY; break; case T_AlterPolicyStmt: - tag = "ALTER POLICY"; + tag = COMMANDTAG_ALTER_POLICY; break; case T_CreateAmStmt: - tag = "CREATE ACCESS METHOD"; + tag = COMMANDTAG_CREATE_ACCESS_METHOD; break; case T_CreatePublicationStmt: - tag = "CREATE PUBLICATION"; + tag = COMMANDTAG_CREATE_PUBLICATION; break; case T_AlterPublicationStmt: - tag = "ALTER PUBLICATION"; + tag = COMMANDTAG_ALTER_PUBLICATION; break; case T_CreateSubscriptionStmt: - tag = "CREATE SUBSCRIPTION"; + tag = COMMANDTAG_CREATE_SUBSCRIPTION; break; case T_AlterSubscriptionStmt: - tag = "ALTER SUBSCRIPTION"; + tag = COMMANDTAG_ALTER_SUBSCRIPTION; break; case T_DropSubscriptionStmt: - tag = "DROP SUBSCRIPTION"; + tag = COMMANDTAG_DROP_SUBSCRIPTION; break; case T_AlterCollationStmt: - tag = "ALTER COLLATION"; + tag = COMMANDTAG_ALTER_COLLATION; break; case T_PrepareStmt: - tag = "PREPARE"; + tag = COMMANDTAG_PREPARE; break; case T_ExecuteStmt: - tag = "EXECUTE"; + tag = COMMANDTAG_EXECUTE; break; case T_CreateStatsStmt: - tag = "CREATE STATISTICS"; + tag = COMMANDTAG_CREATE_STATISTICS; break; case T_AlterStatsStmt: - tag = "ALTER STATISTICS"; + tag = COMMANDTAG_ALTER_STATISTICS; break; case T_DeallocateStmt: @@ -2973,9 +2963,9 @@ CreateCommandTag(Node *parsetree) DeallocateStmt *stmt = (DeallocateStmt *) parsetree; if (stmt->name == NULL) - tag = "DEALLOCATE ALL"; + tag = COMMANDTAG_DEALLOCATE_ALL; else - tag = "DEALLOCATE"; + tag = COMMANDTAG_DEALLOCATE; } break; @@ -2999,33 +2989,33 @@ CreateCommandTag(Node *parsetree) switch (((PlanRowMark *) linitial(stmt->rowMarks))->strength) { case LCS_FORKEYSHARE: - tag = "SELECT FOR KEY SHARE"; + tag = COMMANDTAG_SELECT_FOR_KEY_SHARE; break; case LCS_FORSHARE: - tag = "SELECT FOR SHARE"; + tag = COMMANDTAG_SELECT_FOR_SHARE; break; case LCS_FORNOKEYUPDATE: - tag = "SELECT FOR NO KEY UPDATE"; + tag = COMMANDTAG_SELECT_FOR_NO_KEY_UPDATE; break; case LCS_FORUPDATE: - tag = "SELECT FOR UPDATE"; + tag = COMMANDTAG_SELECT_FOR_UPDATE; break; default: - tag = "SELECT"; + tag = COMMANDTAG_SELECT; break; } } else - tag = "SELECT"; + tag = COMMANDTAG_SELECT; break; case CMD_UPDATE: - tag = "UPDATE"; + tag = COMMANDTAG_UPDATE; break; case CMD_INSERT: - tag = "INSERT"; + tag = COMMANDTAG_INSERT; break; case CMD_DELETE: - tag = "DELETE"; + tag = COMMANDTAG_DELETE; break; case CMD_UTILITY: tag = CreateCommandTag(stmt->utilityStmt); @@ -3033,7 +3023,7 @@ CreateCommandTag(Node *parsetree) default: elog(WARNING, "unrecognized commandType: %d", (int) stmt->commandType); - tag = "???"; + tag = COMMANDTAG_UNKNOWN; break; } } @@ -3059,33 +3049,33 @@ CreateCommandTag(Node *parsetree) switch (((RowMarkClause *) linitial(stmt->rowMarks))->strength) { case LCS_FORKEYSHARE: - tag = "SELECT FOR KEY SHARE"; + tag = COMMANDTAG_SELECT_FOR_KEY_SHARE; break; case LCS_FORSHARE: - tag = "SELECT FOR SHARE"; + tag = COMMANDTAG_SELECT_FOR_SHARE; break; case LCS_FORNOKEYUPDATE: - tag = "SELECT FOR NO KEY UPDATE"; + tag = COMMANDTAG_SELECT_FOR_NO_KEY_UPDATE; break; case LCS_FORUPDATE: - tag = "SELECT FOR UPDATE"; + tag = COMMANDTAG_SELECT_FOR_UPDATE; break; default: - tag = "???"; + tag = COMMANDTAG_UNKNOWN; break; } } else - tag = "SELECT"; + tag = COMMANDTAG_SELECT; break; case CMD_UPDATE: - tag = "UPDATE"; + tag = COMMANDTAG_UPDATE; break; case CMD_INSERT: - tag = "INSERT"; + tag = COMMANDTAG_INSERT; break; case CMD_DELETE: - tag = "DELETE"; + tag = COMMANDTAG_DELETE; break; case CMD_UTILITY: tag = CreateCommandTag(stmt->utilityStmt); @@ -3093,7 +3083,7 @@ CreateCommandTag(Node *parsetree) default: elog(WARNING, "unrecognized commandType: %d", (int) stmt->commandType); - tag = "???"; + tag = COMMANDTAG_UNKNOWN; break; } } @@ -3102,7 +3092,7 @@ CreateCommandTag(Node *parsetree) default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); - tag = "???"; + tag = COMMANDTAG_UNKNOWN; break; } diff --git a/src/backend/utils/.gitignore b/src/backend/utils/.gitignore index 0685556959..7d6dfa45a5 100644 --- a/src/backend/utils/.gitignore +++ b/src/backend/utils/.gitignore @@ -1,6 +1,9 @@ +/commandtag_behavior.h +/commandtag_enum.h /fmgrtab.c /fmgroids.h /fmgrprotos.h /fmgr-stamp +/commandtag-stamp /probes.h /errcodes.h diff --git a/src/backend/utils/Makefile b/src/backend/utils/Makefile index b91028ddfd..4e27b2d80c 100644 --- a/src/backend/utils/Makefile +++ b/src/backend/utils/Makefile @@ -13,23 +13,29 @@ subdir = src/backend/utils top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS = fmgrtab.o +OBJS = commandtag.o fmgrtab.o SUBDIRS = adt cache error fmgr hash init mb misc mmgr resowner sort time # location of Catalog.pm catalogdir = $(top_srcdir)/src/backend/catalog +# location of commandtag.dat +headerdir = $(top_srcdir)/src/include/utils + +# location to write generated headers +sourcedir = $(top_srcdir)/src/backend/utils + include $(top_srcdir)/src/backend/common.mk all: distprep probes.h generated-header-symlinks -distprep: fmgr-stamp errcodes.h +distprep: commandtag-stamp fmgr-stamp errcodes.h .PHONY: generated-header-symlinks generated-header-symlinks: $(top_builddir)/src/include/utils/header-stamp $(top_builddir)/src/include/utils/probes.h -$(SUBDIRS:%=%-recursive): fmgr-stamp errcodes.h +$(SUBDIRS:%=%-recursive): commandtag-stamp fmgr-stamp errcodes.h # fmgr-stamp records the last time we ran Gen_fmgrtab.pl. We don't rely on # the timestamps of the individual output files, because the Perl script @@ -38,6 +44,10 @@ fmgr-stamp: Gen_fmgrtab.pl $(catalogdir)/Catalog.pm $(top_srcdir)/src/include/ca $(PERL) -I $(catalogdir) $< --include-path=$(top_srcdir)/src/include/ $(top_srcdir)/src/include/catalog/pg_proc.dat touch $@ +commandtag-stamp: gencommandtag.pl $(headerdir)/commandtag.dat + $(PERL) -I $(top_srcdir)/src/include/utils $< --headerdir=$(headerdir) --sourcedir=$(sourcedir) --inputfile=$(headerdir)/commandtag.dat + touch $@ + errcodes.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-errcodes.pl $(PERL) $(srcdir)/generate-errcodes.pl $< > $@ @@ -62,10 +72,10 @@ endif # These generated headers must be symlinked into builddir/src/include/, # using absolute links for the reasons explained in src/backend/Makefile. # We use header-stamp to record that we've done this because the symlinks -# themselves may appear older than fmgr-stamp. -$(top_builddir)/src/include/utils/header-stamp: fmgr-stamp errcodes.h +# themselves may appear older than commandtag-stamp and fmgr-stamp. +$(top_builddir)/src/include/utils/header-stamp: commandtag-stamp fmgr-stamp errcodes.h prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \ - cd '$(dir $@)' && for file in fmgroids.h fmgrprotos.h errcodes.h; do \ + cd '$(dir $@)' && for file in commandtag_enum.h commandtag_behavior.h fmgroids.h fmgrprotos.h errcodes.h; do \ rm -f $$file && $(LN_S) "$$prereqdir/$$file" . ; \ done touch $@ @@ -87,10 +97,10 @@ installdirs: uninstall-data: rm -f $(addprefix '$(DESTDIR)$(datadir)'/, errcodes.txt) -# fmgroids.h, fmgrprotos.h, fmgrtab.c, fmgr-stamp, and errcodes.h are in the +# fmgroids.h, fmgrprotos.h, fmgrtab.c, commandtag-stamp, fmgr-stamp, and errcodes.h are in the # distribution tarball, so they are not cleaned here. clean: rm -f probes.h maintainer-clean: clean - rm -f fmgroids.h fmgrprotos.h fmgrtab.c fmgr-stamp errcodes.h + rm -f commandtag_enum.h commandtag_behavior.h commandtag-stamp fmgroids.h fmgrprotos.h fmgrtab.c fmgr-stamp errcodes.h diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c index 1b63048a77..a960d7ca80 100644 --- a/src/backend/utils/cache/evtcache.c +++ b/src/backend/utils/cache/evtcache.c @@ -51,7 +51,7 @@ static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD; static void BuildEventTriggerCache(void); static void InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue); -static int DecodeTextArrayToCString(Datum array, char ***cstringp); +static void DecodeTextArrayToBitmapset(Datum array, Bitmapset **bms); /* * Search the event cache by trigger event. @@ -180,10 +180,7 @@ BuildEventTriggerCache(void) evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags, RelationGetDescr(rel), &evttags_isnull); if (!evttags_isnull) - { - item->ntags = DecodeTextArrayToCString(evttags, &item->tag); - qsort(item->tag, item->ntags, sizeof(char *), pg_qsort_strcmp); - } + DecodeTextArrayToBitmapset(evttags, &item->tagset); /* Add to cache entry. */ entry = hash_search(cache, &event, HASH_ENTER, &found); @@ -215,18 +212,18 @@ BuildEventTriggerCache(void) } /* - * Decode text[] to an array of C strings. + * Decode text[] to a Bitmapset of CommandTags. * * We could avoid a bit of overhead here if we were willing to duplicate some * of the logic from deconstruct_array, but it doesn't seem worth the code * complexity. */ -static int -DecodeTextArrayToCString(Datum array, char ***cstringp) +static void +DecodeTextArrayToBitmapset(Datum array, Bitmapset **tagset) { ArrayType *arr = DatumGetArrayTypeP(array); Datum *elems; - char **cstring; + Bitmapset *bms; int i; int nelems; @@ -234,13 +231,15 @@ DecodeTextArrayToCString(Datum array, char ***cstringp) elog(ERROR, "expected 1-D text array"); deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems); - cstring = palloc(nelems * sizeof(char *)); - for (i = 0; i < nelems; ++i) - cstring[i] = TextDatumGetCString(elems[i]); + for (bms = NULL, i = 0; i < nelems; ++i) + { + char *str = TextDatumGetCString(elems[i]); + bms = bms_add_member(bms, GetCommandTagEnum(str)); + pfree(str); + } pfree(elems); - *cstringp = cstring; - return nelems; + *tagset = bms; } /* diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index c47be0ba4c..53401bd4e9 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -163,7 +163,7 @@ InitPlanCache(void) CachedPlanSource * CreateCachedPlan(RawStmt *raw_parse_tree, const char *query_string, - const char *commandTag) + CommandTag commandTag) { CachedPlanSource *plansource; MemoryContext source_context; @@ -246,7 +246,7 @@ CreateCachedPlan(RawStmt *raw_parse_tree, CachedPlanSource * CreateOneShotCachedPlan(RawStmt *raw_parse_tree, const char *query_string, - const char *commandTag) + CommandTag commandTag) { CachedPlanSource *plansource; diff --git a/src/backend/utils/commandtag.c b/src/backend/utils/commandtag.c new file mode 100644 index 0000000000..c629eca608 --- /dev/null +++ b/src/backend/utils/commandtag.c @@ -0,0 +1,119 @@ +/*------------------------------------------------------------------------- + * + * commandtag.c + * Data and routines for commandtag names and enumeration. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/commandtag.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "c.h" +#include "miscadmin.h" +#include "utils/commandtag.h" +#include "utils/commandtag_enum.h" +#include "utils/commandtag_behavior.h" + +static const unsigned int tag_behavior_length = lengthof(tag_behavior); + +static inline bool +PG_VALID_COMMANDTAG(CommandTag commandTag) +{ + return (commandTag >= FIRST_COMMANDTAG && + commandTag <= LAST_COMMANDTAG); +} + +void +InitializeQueryCompletion(QueryCompletion *qc) +{ + qc->commandTag = COMMANDTAG_UNKNOWN; + qc->nprocessed = 0; +} + +const char * +GetCommandTagName(CommandTag commandTag) +{ + Assert(PG_VALID_COMMANDTAG(commandTag)); + return tag_behavior[commandTag].name; +} + +bool +command_tag_display_last_oid(CommandTag commandTag) +{ + Assert(PG_VALID_COMMANDTAG(commandTag)); + return tag_behavior[commandTag].display_last_oid; +} + +bool +command_tag_display_rowcount(CommandTag commandTag) +{ + Assert(PG_VALID_COMMANDTAG(commandTag)); + return tag_behavior[commandTag].display_rowcount; +} + +bool +command_tag_event_trigger_ok(CommandTag commandTag) +{ + Assert(PG_VALID_COMMANDTAG(commandTag)); + return tag_behavior[commandTag].event_trigger_ok; +} + +bool +command_tag_table_rewrite_ok(CommandTag commandTag) +{ + Assert(PG_VALID_COMMANDTAG(commandTag)); + return tag_behavior[commandTag].table_rewrite_ok; +} + +/* + * Search CommandTag by name + * + * Returns CommandTag, or COMMANDTAG_UNKNOWN if not recognized + */ +CommandTag +GetCommandTagEnum(const char *commandname) +{ + const CommandTagBehavior *base, *last, *position; + int result; + + if (commandname == NULL || *commandname == '\0') + return COMMANDTAG_UNKNOWN; + + base = tag_behavior; + last = tag_behavior + tag_behavior_length - 1; + while (last >= base) + { + position = base + ((last - base) >> 1); + result = pg_strcasecmp(commandname, position->name); + if (result == 0) + return (CommandTag) (position - tag_behavior); + else if (result < 0) + last = position - 1; + else + base = position + 1; + } + return COMMANDTAG_UNKNOWN; +} + +void +PreventCommandTagIfReadOnly(CommandTag commandTag) +{ + PreventCommandIfReadOnly(GetCommandTagName(commandTag)); +} + +void +PreventCommandTagIfParallelMode(CommandTag commandTag) +{ + PreventCommandIfParallelMode(GetCommandTagName(commandTag)); +} + +void +PreventCommandTagDuringRecovery(CommandTag commandTag) +{ + PreventCommandDuringRecovery(GetCommandTagName(commandTag)); +} diff --git a/src/backend/utils/gencommandtag.pl b/src/backend/utils/gencommandtag.pl new file mode 100755 index 0000000000..2f2d37ef1f --- /dev/null +++ b/src/backend/utils/gencommandtag.pl @@ -0,0 +1,285 @@ +#!/usr/bin/perl -w +#---------------------------------------------------------------------- +# +# gencommandtag.pl +# Perl script that generates the commandtag data headers from the +# specially formatted commandtag.dat data file. +# +# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/backend/utils/gencommandtag.pl +# +#---------------------------------------------------------------------- + +use strict; +use warnings; +use Getopt::Long; + +use File::Basename; +use File::Spec; +BEGIN { use lib File::Spec->rel2abs(dirname(__FILE__)); } + +use FindBin; +use lib "$FindBin::RealBin/../catalog/"; +use DataFile; + +my $headerdir; +my $sourcedir; +my $inputfile; +my $errorcnt = 0; + +GetOptions( + 'inputfile=s' => \$inputfile, + 'sourcedir=s' => \$sourcedir, + 'headerdir=s' => \$headerdir) || usage(); + +# Sanity check arguments. +die "input file $inputfile does not exist" unless(-f $inputfile); +die "header directory $headerdir does not exist" unless(-d $headerdir); +die "source directory $headerdir does not exist" unless(-d $headerdir); + +sub dataerror +{ + my ($errmsg, $errhint) = @_; + print STDERR "Error encountered while verifying $inputfile:\n"; + print STDERR $errmsg, "\n"; + if (defined $errhint) + { + print STDERR "HINT: ", $errhint, "\n"; + } + print STDERR "Please fix $inputfile\n"; + exit 1; +} + +# Make sure paths end with a slash. +$sourcedir .= '/' if (substr($sourcedir, -1) ne '/'); +$headerdir .= '/' if (substr($headerdir, -1) ne '/'); + +my $enumfile = join('', $sourcedir, "commandtag_enum.h"); +my $arrayfile = join('', $sourcedir, "commandtag_behavior.h"); + +# Vivify the input data, but be paranoid about eval'ing arbitrary code +my $data = DataFile::ParseData($inputfile); +sanity_check_data($data); + +# Sort the commandtags based on their stringified representation, +# not their labels. +my @sorted = sort { $a->{tagname} cmp $b->{tagname} } @$data; + +# Generate all output internally before outputting anything, to avoid +# partially overwriting generated files under error conditions +my (@enum_chunks, @behavior_chunks); +push(@enum_chunks, qq( +/*------------------------------------------------------------------------- + * + * commandtag_enum.h + * Enumeration of all PostgreSQL command tags. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * ****************************** + * *** DO NOT EDIT THIS FILE! *** + * ****************************** + * + * It has been generated by src/backend/utils/gencommandtag.pl + * + * IDENTIFICATION + * src/include/utils/commandtag_enum.h + * + *------------------------------------------------------------------------- + */ +#ifndef COMMANDTAG_ENUM_H +#define COMMANDTAG_ENUM_H +typedef enum CommandTag +{ +#define FIRST_COMMANDTAG COMMANDTAG_$sorted[0]->{taglabel}) +); + +push(@behavior_chunks, qq( +/*------------------------------------------------------------------------- + * + * commandtag_behavior.h + * Array of records defining the behavior of each command tag. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * ****************************** + * *** DO NOT EDIT THIS FILE! *** + * ****************************** + * + * It has been generated by src/backend/utils/gencommandtag.pl + * + * IDENTIFICATION + * src/include/utils/commandtag_behavior.h + * + *------------------------------------------------------------------------- + */ +#ifndef COMMANDTAG_BEHAVIOR_H +#define COMMANDTAG_BEHAVIOR_H +const CommandTagBehavior tag_behavior[] = {) +); + +for(my $i = 0; $i < scalar(@sorted); $i++) +{ + my $rec = $sorted[$i]; + my $comma = ($i < $#sorted) ? "," : ""; + my $enumrow = sprintf("\tCOMMANDTAG_%s%s", $rec->{taglabel}, $comma); + push(@enum_chunks, $enumrow); + + my $behaviorrow = sprintf("\t[COMMANDTAG_%s] = {", $rec->{taglabel}); + $behaviorrow .= sprintf("\n\t\t.name = \"%s\"", $rec->{tagname}); + $behaviorrow .= ",\n\t\t.event_trigger_ok = true" if ($rec->{event_trigger_ok}); + $behaviorrow .= ",\n\t\t.table_rewrite_ok = true" if ($rec->{table_rewrite_ok}); + $behaviorrow .= ",\n\t\t.display_rowcount = true" if ($rec->{display_rowcount}); + $behaviorrow .= ",\n\t\t.display_last_oid = true" if ($rec->{display_last_oid}); + $behaviorrow .= "\n\t}$comma"; + push (@behavior_chunks, $behaviorrow); +} + +push(@enum_chunks, +qq(#define LAST_COMMANDTAG COMMANDTAG_$sorted[$#sorted]->{taglabel} +} CommandTag; +#endif /* COMMANDTAG_ENUM_H */) +); + +push(@behavior_chunks, +qq(}; +StaticAssertDecl(FIRST_COMMANDTAG == (CommandTag)0, "first command tag"); +StaticAssertDecl(lengthof(tag_behavior) == (LAST_COMMANDTAG + 1), + "array length mismatch"); +#endif /* COMMANDTAG_BEHAVIOR_H */) +); + +my $enum_out = IO::File->new(">$enumfile") + or die "Cannot write $enumfile: $!"; +$enum_out->print(join("\n", @enum_chunks)); +$enum_out->close(); + +my $array_out = IO::File->new(">$arrayfile") + or die "Cannot write $arrayfile: $!"; +$array_out->print(join("\n", @behavior_chunks)); +$array_out->close(); + +# Check that the records from the data file look reasonable. +# +# Some of the checks here cannot fail, because they check things that were +# already checked before eval'ing the inputfile data. We check anyway, for +# future-proofing and because the prior checks were against the stringified +# form of the data and these checks are against the perl data structures that +# we revivified, and the possibility of subtle corner cases that entails. +# +sub sanity_check_data +{ + my ($data) = @_; + + # Verify that at the outermost layer the data file is an array reference + dataerror("Malformed input data: expected an array reference", + "The input file should start with a '[' and end with a ']'") + unless defined $data and ref($data) and ref($data) =~ m/ARRAY/; + + # Verify that the data array contains only hash references + dataerror("Malformed input data: expected an array of hash references", + "The input file array should contain only records in perl hash " . + "reference '{...}' format") + if (grep { !ref($_) || ref($_) !~ m/HASH/ } @$data); + + my @required_keys = qw(tagname taglabel); + my @flag_keys = qw(event_trigger_ok table_rewrite_ok display_rowcount + display_last_oid); + my @expected_keys = (@required_keys, @flag_keys); + my %expected_keys = map { $_ => 1 } @expected_keys; + + # Check that each record in the data file contains the required + # fields. If we have only one required field (name or label) but + # not the other, we can use the one we have in the error message + # to help direct the user to the record which needs correcting. + foreach my $href (@$data) + { + my $preamble = "Malformed input data:"; + if (defined $href->{taglabel} and length $href->{taglabel}) + { + $preamble .= " Record for commandtag label '$href->{taglabel}':"; + } + elsif (defined $href->{tagname} and length $href->{tagname}) + { + $preamble .= " Record for commandtag name '$href->{tagname}':"; + } + + foreach my $key (@required_keys) + { + dataerror("$preamble missing '$key'", + "Each record in the data file should fields for @required_keys") + unless(exists $href->{$key}); + dataerror("$preamble trivial value for '$key'") + unless(defined $href->{$key} and length $href->{$key}); + dataerror("$preamble value of '$key' not uppercase: $href->{$key}") + unless($href->{$key} eq uc($href->{$key})); + } + + foreach my $key (keys %$href) + { + dataerror("$preamble non-printable value for '$key'", + "Do not embed non-printable characters such as tabs, form-feeds or newlines") + if (grep { ord($_) < 0x20 } split(//, $href->{$key})); + dataerror("$preamble non-ASCII value for '$key'", + "Only printable 7-bit ASCII characters are allowed as commandtag data") + if (grep { ord($_) > 0x7F } split(//, $href->{$key})); + dataerror("$preamble unrecognized field '$key'", + "Recognized keys are @expected_keys") + unless $expected_keys{$key}; + } + + foreach my $flag (grep { exists $href->{$_} } @flag_keys) + { + my $val = $href->{$flag}; + dataerror("$preamble empty or undefined value for '$flag'", + "Fields with no value should simply be omitted") + unless defined $val and length $val; + dataerror("$preamble false value need not be specified for '$flag'", + "Fields with false values should simply be omitted") + if ($val =~ m/^(?:false|f|0|off)$/i); + dataerror("$preamble unrecognized value for '$flag': '$val'") + unless ($val =~ m/^(?:true|t|1|on)$/i); + } + } +} + +sub slurp_without_comments +{ + my ($path) = @_; + my @contents; + my $fh = IO::File->new($path) or die "Cannot read $path: $!"; + { + while (my $line = <$fh>) + { + chomp($line); # Remove \n, if any + chomp($line); # Remove \r, if any + $line =~ s/#.*$//; # Remove comments + $line =~ s/\s+$//; # Remove trailing whitespace + push (@contents, $line) if length $line + } + } + $fh->close(); + return join("\n", @contents); +} + +sub usage +{ + die <] + +Options: + --inputfile Input commandtag data file (default /commandtag.dat) + --headerdir Output directory for commandtag.h (default '../../../include/utils') + +gencommandtag.pl generates the commandtag_enum.h and commandtag_behavior.h files +from the specially formatted commandtag.dat data file. + +Report bugs to . +EOM +} diff --git a/src/backend/utils/misc/.gitignore b/src/backend/utils/misc/.gitignore index 495b1aec76..e9cb531f4f 100644 --- a/src/backend/utils/misc/.gitignore +++ b/src/backend/utils/misc/.gitignore @@ -1 +1,2 @@ /guc-file.c +/commandtag-stamp diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile index 2397fc2453..389f71dd97 100644 --- a/src/backend/utils/misc/Makefile +++ b/src/backend/utils/misc/Makefile @@ -39,7 +39,11 @@ include $(top_srcdir)/src/backend/common.mk # guc-file is compiled as part of guc guc.o: guc-file.c +all: distprep + # Note: guc-file.c is not deleted by 'make clean', # since we want to ship it in distribution tarballs. clean: @rm -f lex.yy.c + +maintainer-clean: clean diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index b675575c31..00e8065342 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -281,7 +281,7 @@ void PortalDefineQuery(Portal portal, const char *prepStmtName, const char *sourceText, - const char *commandTag, + CommandTag commandTag, List *stmts, CachedPlan *cplan) { @@ -289,10 +289,12 @@ PortalDefineQuery(Portal portal, AssertState(portal->status == PORTAL_NEW); AssertArg(sourceText != NULL); - AssertArg(commandTag != NULL || stmts == NIL); + AssertArg(commandTag != COMMANDTAG_UNKNOWN || stmts == NIL); portal->prepStmtName = prepStmtName; portal->sourceText = sourceText; + portal->qc.commandTag = commandTag; + portal->qc.nprocessed = 0; portal->commandTag = commandTag; portal->stmts = stmts; portal->cplan = cplan; diff --git a/src/include/Makefile b/src/include/Makefile index c557375ae3..fadbd737c9 100644 --- a/src/include/Makefile +++ b/src/include/Makefile @@ -44,6 +44,8 @@ install: all installdirs $(INSTALL_DATA) pg_config.h '$(DESTDIR)$(includedir_server)' $(INSTALL_DATA) pg_config_ext.h '$(DESTDIR)$(includedir_server)' $(INSTALL_DATA) pg_config_os.h '$(DESTDIR)$(includedir_server)' + $(INSTALL_DATA) utils/commandtag_enum.h '$(DESTDIR)$(includedir_server)/utils' + $(INSTALL_DATA) utils/commandtag_behavior.h '$(DESTDIR)$(includedir_server)/utils' $(INSTALL_DATA) utils/errcodes.h '$(DESTDIR)$(includedir_server)/utils' $(INSTALL_DATA) utils/fmgroids.h '$(DESTDIR)$(includedir_server)/utils' $(INSTALL_DATA) utils/fmgrprotos.h '$(DESTDIR)$(includedir_server)/utils' @@ -77,7 +79,7 @@ uninstall: clean: - rm -f utils/fmgroids.h utils/fmgrprotos.h utils/errcodes.h utils/header-stamp + rm -f utils/commandtag_enum.h utils/commandtag_behavior.h utils/fmgroids.h utils/fmgrprotos.h utils/errcodes.h utils/header-stamp rm -f parser/gram.h storage/lwlocknames.h utils/probes.h rm -f catalog/schemapg.h catalog/pg_*_d.h catalog/header-stamp diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h index 7743851a38..5615b5ecac 100644 --- a/src/include/commands/createas.h +++ b/src/include/commands/createas.h @@ -22,7 +22,8 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, - ParamListInfo params, QueryEnvironment *queryEnv, char *completionTag); + ParamListInfo params, QueryEnvironment *queryEnv, + QueryCompletion *qc); extern int GetIntoRelEFlags(IntoClause *intoClause); diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index faa2958b89..5123c4b850 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -25,7 +25,7 @@ typedef struct EventTriggerData NodeTag type; const char *event; /* event name */ Node *parsetree; /* parse tree */ - const char *tag; /* command tag */ + CommandTag tag; } EventTriggerData; #define AT_REWRITE_ALTER_PERSISTENCE 0x01 diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h index 6bdb7ca258..3ea4f5c80b 100644 --- a/src/include/commands/matview.h +++ b/src/include/commands/matview.h @@ -24,7 +24,7 @@ extern void SetMatViewPopulatedState(Relation relation, bool newstate); extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, - ParamListInfo params, char *completionTag); + ParamListInfo params, QueryCompletion *qc); extern DestReceiver *CreateTransientRelDestReceiver(Oid oid); diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h index 4ecc1a2ecd..41bb7c395b 100644 --- a/src/include/commands/portalcmds.h +++ b/src/include/commands/portalcmds.h @@ -22,8 +22,7 @@ extern void PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params, bool isTopLevel); -extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest, - char *completionTag); +extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest, QueryCompletion *qc); extern void PerformPortalClose(const char *name); diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index a0509e1f33..4fcf2406c1 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -40,7 +40,7 @@ extern void PrepareQuery(ParseState *pstate, PrepareStmt *stmt, extern void ExecuteQuery(ParseState *pstate, ExecuteStmt *stmt, IntoClause *intoClause, ParamListInfo params, - DestReceiver *dest, char *completionTag); + DestReceiver *dest, QueryCompletion *qc); extern void DeallocateQuery(DeallocateStmt *stmt); extern void ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, const char *queryString, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index da0706add5..2c3ae6b603 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -27,6 +27,7 @@ #include "nodes/primnodes.h" #include "nodes/value.h" #include "partitioning/partdefs.h" +#include "utils/commandtag.h" typedef enum OverridingKind diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h index 35bce731a1..dd699b0993 100644 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -68,7 +68,7 @@ #define DEST_H #include "executor/tuptable.h" - +#include "utils/commandtag.h" /* buffer size to use for command completion tags */ #define COMPLETION_TAG_BUFSIZE 64 @@ -134,9 +134,10 @@ extern PGDLLIMPORT DestReceiver *None_Receiver; /* permanent receiver for /* The primary destination management functions */ -extern void BeginCommand(const char *commandTag, CommandDest dest); +extern void BeginCommand(CommandTag commandTag, CommandDest dest); extern DestReceiver *CreateDestReceiver(CommandDest dest); -extern void EndCommand(const char *commandTag, CommandDest dest); +extern void EndCommand(const QueryCompletion *qc, CommandDest dest, + bool force_undecorated_output); /* Additional functions that go with destination management, more or less. */ diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index 4ad6324e2d..437642cc72 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -35,7 +35,7 @@ extern void PortalSetResultFormat(Portal portal, int nFormats, extern bool PortalRun(Portal portal, long count, bool isTopLevel, bool run_once, DestReceiver *dest, DestReceiver *altdest, - char *completionTag); + QueryCompletion *qc); extern uint64 PortalRunFetch(Portal portal, FetchDirection fdirection, diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index a551e08cb8..8781ac7d38 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -15,6 +15,7 @@ #define UTILITY_H #include "tcop/tcopprot.h" +#include "utils/commandtag.h" typedef enum { @@ -71,17 +72,17 @@ typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, - DestReceiver *dest, char *completionTag); + DestReceiver *dest, QueryCompletion *qc); extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook; extern void ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, - DestReceiver *dest, char *completionTag); + DestReceiver *dest, QueryCompletion *qc); extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, - DestReceiver *dest, char *completionTag); + DestReceiver *dest, QueryCompletion *qc); extern void ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context); @@ -92,7 +93,13 @@ extern TupleDesc UtilityTupleDescriptor(Node *parsetree); extern Query *UtilityContainsQuery(Node *parsetree); -extern const char *CreateCommandTag(Node *parsetree); +extern CommandTag CreateCommandTag(Node *parsetree); + +static inline const char * +CreateCommandName(Node *parsetree) +{ + return GetCommandTagName(CreateCommandTag(parsetree)); +} extern LogStmtLevel GetCommandLogLevel(Node *parsetree); diff --git a/src/include/utils/.gitignore b/src/include/utils/.gitignore index 05cfa7a8d6..9bc600aacc 100644 --- a/src/include/utils/.gitignore +++ b/src/include/utils/.gitignore @@ -1,3 +1,5 @@ +/commandtag_behavior.h +/commandtag_enum.h /fmgroids.h /fmgrprotos.h /probes.h diff --git a/src/include/utils/commandtag.dat b/src/include/utils/commandtag.dat new file mode 100644 index 0000000000..a79ee50e00 --- /dev/null +++ b/src/include/utils/commandtag.dat @@ -0,0 +1,938 @@ +#---------------------------------------------------------------------- +# +# commandtag.dat +# Data file from which commandtag_enum.h and commandtag_behavior.h +# are generated +# +# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group +# Portions Copyright (c) 1994, Regents of the University of California +# +# src/backend/utils/commandtag.dat +# +#---------------------------------------------------------------------- + +# gencommandtag.pl will sort these records, so there is no requirement +# that they be correctly sorted here, but by convention, we keep these +# in sorted order by tagname. +# +# The 'taglabel' field is typically just a repeat of the tagname but +# with spaces converted to underscores. It serves as the label within +# the generated CommandTag enum. For weirder names, like ???, the +# label does not match, for obvious reasons. +# +# The following boolean fields specify whether event trigger support +# exists for the given command tag: +# +# event_trigger_ok +# table_rewrite_ok +# +# And these two boolean fields specify the formatting that EndCommand +# should send back to the client for the command tag: +# +# display_rowcount +# display_last_oid +# +# For these four boolean fields, the default is false. Only specify +# a value for records where they are true. +[ +{ + taglabel => 'UNKNOWN', + tagname => '???', +}, +{ + taglabel => 'ALTER_ACCESS_METHOD', + tagname => 'ALTER ACCESS METHOD', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_AGGREGATE', + tagname => 'ALTER AGGREGATE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_CAST', + tagname => 'ALTER CAST', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_COLLATION', + tagname => 'ALTER COLLATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_CONSTRAINT', + tagname => 'ALTER CONSTRAINT', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_CONVERSION', + tagname => 'ALTER CONVERSION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_DATABASE', + tagname => 'ALTER DATABASE', +}, +{ + taglabel => 'ALTER_DEFAULT_PRIVILEGES', + tagname => 'ALTER DEFAULT PRIVILEGES', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_DOMAIN', + tagname => 'ALTER DOMAIN', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_EVENT_TRIGGER', + tagname => 'ALTER EVENT TRIGGER', +}, +{ + taglabel => 'ALTER_EXTENSION', + tagname => 'ALTER EXTENSION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_FOREIGN_DATA_WRAPPER', + tagname => 'ALTER FOREIGN DATA WRAPPER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_FOREIGN_TABLE', + tagname => 'ALTER FOREIGN TABLE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_FUNCTION', + tagname => 'ALTER FUNCTION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_INDEX', + tagname => 'ALTER INDEX', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_LANGUAGE', + tagname => 'ALTER LANGUAGE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_LARGE_OBJECT', + tagname => 'ALTER LARGE OBJECT', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_MATERIALIZED_VIEW', + tagname => 'ALTER MATERIALIZED VIEW', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_OPERATOR', + tagname => 'ALTER OPERATOR', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_OPERATOR_CLASS', + tagname => 'ALTER OPERATOR CLASS', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_OPERATOR_FAMILY', + tagname => 'ALTER OPERATOR FAMILY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_POLICY', + tagname => 'ALTER POLICY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_PROCEDURE', + tagname => 'ALTER PROCEDURE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_PUBLICATION', + tagname => 'ALTER PUBLICATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_ROLE', + tagname => 'ALTER ROLE', +}, +{ + taglabel => 'ALTER_ROUTINE', + tagname => 'ALTER ROUTINE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_RULE', + tagname => 'ALTER RULE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_SCHEMA', + tagname => 'ALTER SCHEMA', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_SEQUENCE', + tagname => 'ALTER SEQUENCE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_SERVER', + tagname => 'ALTER SERVER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_STATISTICS', + tagname => 'ALTER STATISTICS', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_SUBSCRIPTION', + tagname => 'ALTER SUBSCRIPTION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_SYSTEM', + tagname => 'ALTER SYSTEM', +}, +{ + taglabel => 'ALTER_TABLE', + tagname => 'ALTER TABLE', + event_trigger_ok => 'true', + table_rewrite_ok => 'true', +}, +{ + taglabel => 'ALTER_TABLESPACE', + tagname => 'ALTER TABLESPACE', +}, +{ + taglabel => 'ALTER_TEXT_SEARCH_CONFIGURATION', + tagname => 'ALTER TEXT SEARCH CONFIGURATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_TEXT_SEARCH_DICTIONARY', + tagname => 'ALTER TEXT SEARCH DICTIONARY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_TEXT_SEARCH_PARSER', + tagname => 'ALTER TEXT SEARCH PARSER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_TEXT_SEARCH_TEMPLATE', + tagname => 'ALTER TEXT SEARCH TEMPLATE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_TRANSFORM', + tagname => 'ALTER TRANSFORM', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_TRIGGER', + tagname => 'ALTER TRIGGER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_TYPE', + tagname => 'ALTER TYPE', + event_trigger_ok => 'true', + table_rewrite_ok => 'true', +}, +{ + taglabel => 'ALTER_USER_MAPPING', + tagname => 'ALTER USER MAPPING', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ALTER_VIEW', + tagname => 'ALTER VIEW', + event_trigger_ok => 'true', +}, +{ + taglabel => 'ANALYZE', + tagname => 'ANALYZE', +}, +{ + taglabel => 'BEGIN', + tagname => 'BEGIN', +}, +{ + taglabel => 'CALL', + tagname => 'CALL', +}, +{ + taglabel => 'CHECKPOINT', + tagname => 'CHECKPOINT', +}, +{ + taglabel => 'CLOSE', + tagname => 'CLOSE', +}, +{ + taglabel => 'CLOSE_CURSOR', + tagname => 'CLOSE CURSOR', +}, +{ + taglabel => 'CLOSE_CURSOR_ALL', + tagname => 'CLOSE CURSOR ALL', +}, +{ + taglabel => 'CLUSTER', + tagname => 'CLUSTER', +}, +{ + taglabel => 'COMMENT', + tagname => 'COMMENT', + event_trigger_ok => 'true', +}, +{ + taglabel => 'COMMIT', + tagname => 'COMMIT', +}, +{ + taglabel => 'COMMIT_PREPARED', + tagname => 'COMMIT PREPARED', +}, +{ + taglabel => 'COPY', + tagname => 'COPY', + display_rowcount => 'true', +}, +{ + taglabel => 'COPY_FROM', + tagname => 'COPY FROM', +}, +{ + taglabel => 'CREATE_ACCESS_METHOD', + tagname => 'CREATE ACCESS METHOD', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_AGGREGATE', + tagname => 'CREATE AGGREGATE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_CAST', + tagname => 'CREATE CAST', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_COLLATION', + tagname => 'CREATE COLLATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_CONSTRAINT', + tagname => 'CREATE CONSTRAINT', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_CONVERSION', + tagname => 'CREATE CONVERSION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_DATABASE', + tagname => 'CREATE DATABASE', +}, +{ + taglabel => 'CREATE_DOMAIN', + tagname => 'CREATE DOMAIN', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_EVENT_TRIGGER', + tagname => 'CREATE EVENT TRIGGER', +}, +{ + taglabel => 'CREATE_EXTENSION', + tagname => 'CREATE EXTENSION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_FOREIGN_DATA_WRAPPER', + tagname => 'CREATE FOREIGN DATA WRAPPER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_FOREIGN_TABLE', + tagname => 'CREATE FOREIGN TABLE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_FUNCTION', + tagname => 'CREATE FUNCTION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_INDEX', + tagname => 'CREATE INDEX', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_LANGUAGE', + tagname => 'CREATE LANGUAGE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_MATERIALIZED_VIEW', + tagname => 'CREATE MATERIALIZED VIEW', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_OPERATOR', + tagname => 'CREATE OPERATOR', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_OPERATOR_CLASS', + tagname => 'CREATE OPERATOR CLASS', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_OPERATOR_FAMILY', + tagname => 'CREATE OPERATOR FAMILY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_POLICY', + tagname => 'CREATE POLICY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_PROCEDURE', + tagname => 'CREATE PROCEDURE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_PUBLICATION', + tagname => 'CREATE PUBLICATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_ROLE', + tagname => 'CREATE ROLE', +}, +{ + taglabel => 'CREATE_ROUTINE', + tagname => 'CREATE ROUTINE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_RULE', + tagname => 'CREATE RULE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_SCHEMA', + tagname => 'CREATE SCHEMA', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_SEQUENCE', + tagname => 'CREATE SEQUENCE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_SERVER', + tagname => 'CREATE SERVER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_STATISTICS', + tagname => 'CREATE STATISTICS', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_SUBSCRIPTION', + tagname => 'CREATE SUBSCRIPTION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TABLE', + tagname => 'CREATE TABLE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TABLE_AS', + tagname => 'CREATE TABLE AS', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TABLESPACE', + tagname => 'CREATE TABLESPACE', +}, +{ + taglabel => 'CREATE_TEXT_SEARCH_CONFIGURATION', + tagname => 'CREATE TEXT SEARCH CONFIGURATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TEXT_SEARCH_DICTIONARY', + tagname => 'CREATE TEXT SEARCH DICTIONARY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TEXT_SEARCH_PARSER', + tagname => 'CREATE TEXT SEARCH PARSER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TEXT_SEARCH_TEMPLATE', + tagname => 'CREATE TEXT SEARCH TEMPLATE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TRANSFORM', + tagname => 'CREATE TRANSFORM', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TRIGGER', + tagname => 'CREATE TRIGGER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_TYPE', + tagname => 'CREATE TYPE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_USER_MAPPING', + tagname => 'CREATE USER MAPPING', + event_trigger_ok => 'true', +}, +{ + taglabel => 'CREATE_VIEW', + tagname => 'CREATE VIEW', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DEALLOCATE', + tagname => 'DEALLOCATE', +}, +{ + taglabel => 'DEALLOCATE_ALL', + tagname => 'DEALLOCATE ALL', +}, +{ + taglabel => 'DECLARE_CURSOR', + tagname => 'DECLARE CURSOR', +}, +{ + taglabel => 'DELETE', + tagname => 'DELETE', + display_rowcount => 'true', +}, +{ + taglabel => 'DISCARD', + tagname => 'DISCARD', +}, +{ + taglabel => 'DISCARD_ALL', + tagname => 'DISCARD ALL', +}, +{ + taglabel => 'DISCARD_PLANS', + tagname => 'DISCARD PLANS', +}, +{ + taglabel => 'DISCARD_SEQUENCES', + tagname => 'DISCARD SEQUENCES', +}, +{ + taglabel => 'DISCARD_TEMP', + tagname => 'DISCARD TEMP', +}, +{ + taglabel => 'DO', + tagname => 'DO', +}, +{ + taglabel => 'DROP_ACCESS_METHOD', + tagname => 'DROP ACCESS METHOD', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_AGGREGATE', + tagname => 'DROP AGGREGATE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_CAST', + tagname => 'DROP CAST', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_COLLATION', + tagname => 'DROP COLLATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_CONSTRAINT', + tagname => 'DROP CONSTRAINT', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_CONVERSION', + tagname => 'DROP CONVERSION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_DATABASE', + tagname => 'DROP DATABASE', +}, +{ + taglabel => 'DROP_DOMAIN', + tagname => 'DROP DOMAIN', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_EVENT_TRIGGER', + tagname => 'DROP EVENT TRIGGER', +}, +{ + taglabel => 'DROP_EXTENSION', + tagname => 'DROP EXTENSION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_FOREIGN_DATA_WRAPPER', + tagname => 'DROP FOREIGN DATA WRAPPER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_FOREIGN_TABLE', + tagname => 'DROP FOREIGN TABLE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_FUNCTION', + tagname => 'DROP FUNCTION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_INDEX', + tagname => 'DROP INDEX', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_LANGUAGE', + tagname => 'DROP LANGUAGE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_MATERIALIZED_VIEW', + tagname => 'DROP MATERIALIZED VIEW', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_OPERATOR', + tagname => 'DROP OPERATOR', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_OPERATOR_CLASS', + tagname => 'DROP OPERATOR CLASS', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_OPERATOR_FAMILY', + tagname => 'DROP OPERATOR FAMILY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_OWNED', + tagname => 'DROP OWNED', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_POLICY', + tagname => 'DROP POLICY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_PROCEDURE', + tagname => 'DROP PROCEDURE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_PUBLICATION', + tagname => 'DROP PUBLICATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_REPLICATION_SLOT', + tagname => 'DROP REPLICATION SLOT', +}, +{ + taglabel => 'DROP_ROLE', + tagname => 'DROP ROLE', +}, +{ + taglabel => 'DROP_ROUTINE', + tagname => 'DROP ROUTINE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_RULE', + tagname => 'DROP RULE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_SCHEMA', + tagname => 'DROP SCHEMA', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_SEQUENCE', + tagname => 'DROP SEQUENCE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_SERVER', + tagname => 'DROP SERVER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_STATISTICS', + tagname => 'DROP STATISTICS', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_SUBSCRIPTION', + tagname => 'DROP SUBSCRIPTION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TABLE', + tagname => 'DROP TABLE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TABLESPACE', + tagname => 'DROP TABLESPACE', +}, +{ + taglabel => 'DROP_TEXT_SEARCH_CONFIGURATION', + tagname => 'DROP TEXT SEARCH CONFIGURATION', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TEXT_SEARCH_DICTIONARY', + tagname => 'DROP TEXT SEARCH DICTIONARY', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TEXT_SEARCH_PARSER', + tagname => 'DROP TEXT SEARCH PARSER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TEXT_SEARCH_TEMPLATE', + tagname => 'DROP TEXT SEARCH TEMPLATE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TRANSFORM', + tagname => 'DROP TRANSFORM', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TRIGGER', + tagname => 'DROP TRIGGER', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_TYPE', + tagname => 'DROP TYPE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_USER_MAPPING', + tagname => 'DROP USER MAPPING', + event_trigger_ok => 'true', +}, +{ + taglabel => 'DROP_VIEW', + tagname => 'DROP VIEW', + event_trigger_ok => 'true', +}, +{ + taglabel => 'EXECUTE', + tagname => 'EXECUTE', +}, +{ + taglabel => 'EXPLAIN', + tagname => 'EXPLAIN', +}, +{ + taglabel => 'FETCH', + tagname => 'FETCH', + display_rowcount => 'true', +}, +{ + taglabel => 'GRANT', + tagname => 'GRANT', + event_trigger_ok => 'true', +}, +{ + taglabel => 'GRANT_ROLE', + tagname => 'GRANT ROLE', +}, +{ + taglabel => 'IMPORT_FOREIGN_SCHEMA', + tagname => 'IMPORT FOREIGN SCHEMA', + event_trigger_ok => 'true', +}, +{ + taglabel => 'INSERT', + tagname => 'INSERT', + display_last_oid => 'true', + display_rowcount => 'true', +}, +{ + taglabel => 'LISTEN', + tagname => 'LISTEN', +}, +{ + taglabel => 'LOAD', + tagname => 'LOAD', +}, +{ + taglabel => 'LOCK_TABLE', + tagname => 'LOCK TABLE', +}, +{ + taglabel => 'MOVE', + tagname => 'MOVE', + display_rowcount => 'true', +}, +{ + taglabel => 'NOTIFY', + tagname => 'NOTIFY', +}, +{ + taglabel => 'PREPARE', + tagname => 'PREPARE', +}, +{ + taglabel => 'PREPARE_TRANSACTION', + tagname => 'PREPARE TRANSACTION', +}, +{ + taglabel => 'REASSIGN_OWNED', + tagname => 'REASSIGN OWNED', +}, +{ + taglabel => 'REFRESH_MATERIALIZED_VIEW', + tagname => 'REFRESH MATERIALIZED VIEW', + event_trigger_ok => 'true', +}, +{ + taglabel => 'REINDEX', + tagname => 'REINDEX', +}, +{ + taglabel => 'RELEASE', + tagname => 'RELEASE', +}, +{ + taglabel => 'RESET', + tagname => 'RESET', +}, +{ + taglabel => 'REVOKE', + tagname => 'REVOKE', + event_trigger_ok => 'true', +}, +{ + taglabel => 'REVOKE_ROLE', + tagname => 'REVOKE ROLE', +}, +{ + taglabel => 'ROLLBACK', + tagname => 'ROLLBACK', +}, +{ + taglabel => 'ROLLBACK_PREPARED', + tagname => 'ROLLBACK PREPARED', +}, +{ + taglabel => 'SAVEPOINT', + tagname => 'SAVEPOINT', +}, +{ + taglabel => 'SECURITY_LABEL', + tagname => 'SECURITY LABEL', + event_trigger_ok => 'true', +}, +{ + taglabel => 'SELECT', + tagname => 'SELECT', + display_rowcount => 'true', +}, +{ + taglabel => 'SELECT_FOR_KEY_SHARE', + tagname => 'SELECT FOR KEY SHARE', +}, +{ + taglabel => 'SELECT_FOR_NO_KEY_UPDATE', + tagname => 'SELECT FOR NO KEY UPDATE', +}, +{ + taglabel => 'SELECT_FOR_SHARE', + tagname => 'SELECT FOR SHARE', +}, +{ + taglabel => 'SELECT_FOR_UPDATE', + tagname => 'SELECT FOR UPDATE', +}, +{ + taglabel => 'SELECT_INTO', + tagname => 'SELECT INTO', + event_trigger_ok => 'true', +}, +{ + taglabel => 'SET', + tagname => 'SET', +}, +{ + taglabel => 'SET_CONSTRAINTS', + tagname => 'SET CONSTRAINTS', +}, +{ + taglabel => 'SHOW', + tagname => 'SHOW', +}, +{ + taglabel => 'START_TRANSACTION', + tagname => 'START TRANSACTION', +}, +{ + taglabel => 'TRUNCATE_TABLE', + tagname => 'TRUNCATE TABLE', +}, +{ + taglabel => 'UNLISTEN', + tagname => 'UNLISTEN', +}, +{ + taglabel => 'UPDATE', + tagname => 'UPDATE', + display_rowcount => 'true', +}, +{ + taglabel => 'VACUUM', + tagname => 'VACUUM', +}, +] diff --git a/src/include/utils/commandtag.h b/src/include/utils/commandtag.h new file mode 100644 index 0000000000..ea9b2c682a --- /dev/null +++ b/src/include/utils/commandtag.h @@ -0,0 +1,60 @@ +/*------------------------------------------------------------------------- + * + * commandtag.h + * Declarations for commandtag names and enumeration. + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/commandtag.h + * + *------------------------------------------------------------------------- + */ + +#ifndef COMMANDTAG_H +#define COMMANDTAG_H + +#include "utils/commandtag_enum.h" + +typedef struct CommandTagBehavior { + const char *name; + const bool display_last_oid; + const bool display_rowcount; + const bool event_trigger_ok; + const bool table_rewrite_ok; +} CommandTagBehavior; + +extern const CommandTagBehavior tag_behavior[]; + +typedef struct QueryCompletion +{ + CommandTag commandTag; + uint64 nprocessed; +} QueryCompletion; + +static inline void +SetQueryCompletion(QueryCompletion *qc, CommandTag commandTag, uint64 nprocessed) +{ + qc->commandTag = commandTag; + qc->nprocessed = nprocessed; +} + +static inline void +CopyQueryCompletion(QueryCompletion *dst, const QueryCompletion *src) +{ + dst->commandTag = src->commandTag; + dst->nprocessed = src->nprocessed; +} + +extern void InitializeQueryCompletion(QueryCompletion *qc); +extern const char *GetCommandTagName(CommandTag commandTag); +extern bool command_tag_display_last_oid(CommandTag commandTag); +extern bool command_tag_display_rowcount(CommandTag commandTag); +extern bool command_tag_event_trigger_ok(CommandTag commandTag); +extern bool command_tag_table_rewrite_ok(CommandTag commandTag); +extern CommandTag GetCommandTagEnum(const char *tagname); +extern void PreventCommandTagIfReadOnly(CommandTag commandTag); +extern void PreventCommandTagIfParallelMode(CommandTag commandTag); +extern void PreventCommandTagDuringRecovery(CommandTag commandTag); + +#endif /* COMMANDTAG_H */ diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h index 6c3ff81ba3..bc8ce48061 100644 --- a/src/include/utils/evtcache.h +++ b/src/include/utils/evtcache.h @@ -28,8 +28,7 @@ typedef struct { Oid fnoid; /* function to be called */ char enabled; /* as SESSION_REPLICATION_ROLE_* */ - int ntags; /* number of command tags */ - char **tag; /* command tags in SORTED order */ + Bitmapset *tagset; /* command tags, or NULL if empty */ } EventTriggerCacheItem; extern List *EventCacheLookup(EventTriggerEvent event); diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index e48661ebec..b2900307ad 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -18,6 +18,7 @@ #include "access/tupdesc.h" #include "lib/ilist.h" #include "nodes/params.h" +#include "utils/commandtag.h" #include "utils/queryenvironment.h" /* Forward declaration, to avoid including parsenodes.h here */ @@ -95,7 +96,7 @@ typedef struct CachedPlanSource int magic; /* should equal CACHEDPLANSOURCE_MAGIC */ struct RawStmt *raw_parse_tree; /* output of raw_parser(), or NULL */ const char *query_string; /* source text of query */ - const char *commandTag; /* command tag (a constant!), or NULL */ + CommandTag commandTag; Oid *param_types; /* array of parameter type OIDs, or NULL */ int num_params; /* length of param_types array */ ParserSetupHook parserSetup; /* alternative parameter spec method */ @@ -186,10 +187,10 @@ extern void ResetPlanCache(void); extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree, const char *query_string, - const char *commandTag); + CommandTag commandTag); extern CachedPlanSource *CreateOneShotCachedPlan(struct RawStmt *raw_parse_tree, const char *query_string, - const char *commandTag); + CommandTag commandTag); extern void CompleteCachedPlan(CachedPlanSource *plansource, List *querytree_list, MemoryContext querytree_context, diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index 0b69433722..b398ae45a3 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -48,6 +48,7 @@ #include "datatype/timestamp.h" #include "executor/execdesc.h" +#include "utils/commandtag.h" #include "utils/plancache.h" #include "utils/resowner.h" @@ -132,7 +133,8 @@ typedef struct PortalData /* The query or queries the portal will execute */ const char *sourceText; /* text of query (as of 8.4, never NULL) */ - const char *commandTag; /* command tag for original query */ + CommandTag commandTag; /* command tag for original query */ + QueryCompletion qc; /* command completion data for executed query */ List *stmts; /* list of PlannedStmts */ CachedPlan *cplan; /* CachedPlan, if stmts are from one */ @@ -227,7 +229,7 @@ extern Portal GetPortalByName(const char *name); extern void PortalDefineQuery(Portal portal, const char *prepStmtName, const char *sourceText, - const char *commandTag, + CommandTag commandTag, List *stmts, CachedPlan *cplan); extern PlannedStmt *PortalGetPrimaryStmt(Portal portal); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 5acf604f63..e165790d66 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -41,6 +41,7 @@ #include "tcop/utility.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/commandtag.h" #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -1473,7 +1474,7 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate, case PLPGSQL_PROMISE_TG_TAG: if (estate->evtrigdata == NULL) elog(ERROR, "event trigger promise is not in an event trigger function"); - assign_text_var(estate, var, estate->evtrigdata->tag); + assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag)); break; default: @@ -4115,10 +4116,9 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, * tree(s), since those are the result of rewriting and could have * been transmogrified into something else entirely. */ - if (plansource->commandTag && - (strcmp(plansource->commandTag, "INSERT") == 0 || - strcmp(plansource->commandTag, "UPDATE") == 0 || - strcmp(plansource->commandTag, "DELETE") == 0)) + if (plansource->commandTag == COMMANDTAG_INSERT || + plansource->commandTag == COMMANDTAG_UPDATE || + plansource->commandTag == COMMANDTAG_DELETE) { stmt->mod_stmt = true; break; diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index e1629ec618..b7bdb88ce7 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -74,7 +74,7 @@ get_command_tag(PG_FUNCTION_ARGS) if (!cmd->parsetree) PG_RETURN_NULL(); - PG_RETURN_TEXT_P(cstring_to_text(CreateCommandTag(cmd->parsetree))); + PG_RETURN_TEXT_P(cstring_to_text(CreateCommandName(cmd->parsetree))); } /* diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index 8412ef298e..5afc83498f 100644 --- a/src/tools/msvc/Solution.pm +++ b/src/tools/msvc/Solution.pm @@ -558,6 +558,7 @@ sub GenerateFiles chdir('src/backend/utils'); my $pg_proc_dat = '../../../src/include/catalog/pg_proc.dat'; + my $commandtag_dat = '../../../src/include/utils/commandtag.dat'; if ( IsNewer('fmgr-stamp', 'Gen_fmgrtab.pl') || IsNewer('fmgr-stamp', '../catalog/Catalog.pm') || IsNewer('fmgr-stamp', $pg_proc_dat) @@ -570,6 +571,17 @@ sub GenerateFiles || confess "Could not touch fmgr-stamp"; close($f); } + if ( IsNewer('commandtag-stamp', 'gencommandtag.pl') + || IsNewer('commandtag-stamp', 'DataFile.pm') + || IsNewer('commandtag-stamp', $commandtag_dat)) + { + system( + "perl -I ../../../src/include/utils gencommandtag.pl --headerdir=../../../src/include/utils --sourcedir=. --inputfile=$commandtag_dat" + ); + open(my $f, '>', 'commandtag-stamp') + || confess "Could not touch commandtag-stamp"; + close($f); + } chdir('../../..'); if (IsNewer( -- 2.21.1 (Apple Git-122.3)