Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nAnUP-0001xY-Sf for pgsql-hackers@arkaria.postgresql.org; Fri, 21 Jan 2022 06:33:39 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1nAnUN-0003nJ-QV for pgsql-hackers@arkaria.postgresql.org; Fri, 21 Jan 2022 06:33:35 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nAnUM-0003m4-Vi for pgsql-hackers@lists.postgresql.org; Fri, 21 Jan 2022 06:33:35 +0000 Received: from mail.postgrespro.ru ([93.174.131.139]) by makus.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1nAnUE-0007WG-9S for pgsql-hackers@lists.postgresql.org; Fri, 21 Jan 2022 06:33:33 +0000 Received: from [192.168.28.53] (cyclops.postgrespro.ru [93.174.131.138]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (Client did not present a certificate) by mail.postgrespro.ru (Postfix) with ESMTPSA id 9938721C34F6; Fri, 21 Jan 2022 09:33:23 +0300 (MSK) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=postgrespro.ru; s=mail; t=1642746803; bh=1+xdvbowkkV/N7EmGCdBltXT+Ve7PZrF/250MwBuEMI=; h=Subject:To:Cc:References:From:Date:In-Reply-To; b=pxiYNIRAxt7e7eCT321+cgN4Ho9wxWDbqv2Ycm4wKUIIlfc5876k+Y5nPhR8r/vQb wLIauZP4DPR7Ma8dYsUaSNvwFbm5gF8vjZKqsOspVk6Lqoq7lFODyB7BhvQxM3eHKL R4gb+EdCDARCLqnlW/tReD/Gv6PNq/bucXK59f9Y= Subject: Re: Multiple Query IDs for a rewritten parse tree To: Tom Lane Cc: PostgreSQL Hackers References: <1606733.1641689399@sss.pgh.pa.us> From: "Andrey V. Lepikhov" Organization: Postgres Professional Message-ID: <54863479-1144-43fb-853e-fa7d279e2d37@postgrespro.ru> Date: Fri, 21 Jan 2022 11:33:22 +0500 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.14.0 MIME-Version: 1.0 In-Reply-To: <1606733.1641689399@sss.pgh.pa.us> Content-Type: multipart/mixed; boundary="------------33708CB72BF31B9DD01BF366" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --------------33708CB72BF31B9DD01BF366 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit On 1/9/22 5:49 AM, Tom Lane wrote: > The idea I'd been vaguely thinking about is to allow attaching a list > of query-hash nodes to a Query, where each node would contain a "tag" > identifying the specific hash calculation method, and also the value > of the query's hash calculated according to that method. We could > probably get away with saying that all such hash values must be uint64. > The main difference from your function-OID idea, I think, is that > I'm envisioning the tags as being small integers with well-known > values, similarly to the way we manage stakind values in pg_statistic. > In this way, an extension that wants a hash that the core knows how > to calculate doesn't need its own copy of the code, and similarly > one extension could publish a calculation method for use by other > extensions. To move forward, I have made a patch that implements this idea (see attachment). It is a POC, but passes all regression tests. Registration of an queryId generator implemented by analogy with extensible methods machinery. Also, I switched queryId to int64 type and renamed to 'label'. Some lessons learned: 1. Single queryId implementation is deeply tangled with the core code (stat reporting machinery and parallel workers as an example). 2. We need a custom queryId, that is based on a generated queryId (according to the logic of pg_stat_statements). 3. We should think about safety of de-registering procedure. 4. We should reserve position of default in-core generator and think on logic of enabling/disabling it. 5. We should add an EXPLAIN hook, to allow an extension to print this custom queryId. -- regards, Andrey Lepikhov Postgres Professional --------------33708CB72BF31B9DD01BF366 Content-Type: text/x-patch; charset=UTF-8; name="POC-multiple-query_id.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="POC-multiple-query_id.patch" From f54bb60bd71b49ac8e1b85cd2ad86332a8a81e84 Mon Sep 17 00:00:00 2001 From: Andrey Lepikhov Date: Thu, 20 Jan 2022 16:05:17 +0500 Subject: [PATCH] Initial commit --- .../pg_stat_statements/pg_stat_statements.c | 57 ++++-- src/backend/commands/explain.c | 11 +- src/backend/executor/execMain.c | 4 +- src/backend/executor/execParallel.c | 3 +- src/backend/nodes/copyfuncs.c | 21 ++- src/backend/nodes/outfuncs.c | 17 +- src/backend/nodes/readfuncs.c | 17 +- src/backend/optimizer/plan/planner.c | 2 +- src/backend/parser/analyze.c | 15 +- src/backend/rewrite/rewriteHandler.c | 4 +- src/backend/tcop/postgres.c | 11 +- src/backend/utils/misc/guc.c | 2 +- src/backend/utils/misc/queryjumble.c | 164 ++++++++++++++++-- src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 10 +- src/include/nodes/plannodes.h | 2 +- src/include/parser/analyze.h | 3 +- src/include/utils/queryjumble.h | 12 +- 18 files changed, 294 insertions(+), 62 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 082bfa8f77..ebfc3331df 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -307,8 +307,7 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_info); static void pgss_shmem_startup(void); static void pgss_shmem_shutdown(int code, Datum arg); -static void pgss_post_parse_analyze(ParseState *pstate, Query *query, - JumbleState *jstate); +static void pgss_post_parse_analyze(ParseState *pstate, Query *query); static PlannedStmt *pgss_planner(Query *parse, const char *query_string, int cursorOptions, @@ -813,13 +812,29 @@ error: } /* - * Post-parse-analysis hook: mark query with a queryId + * Post-parse-analysis hook: create a label for the query. */ static void -pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) +pgss_post_parse_analyze(ParseState *pstate, Query *query) { + JumbleState *jstate; + QueryLabel *label = get_query_label(query->queryIds, 0); + if (prev_post_parse_analyze_hook) - prev_post_parse_analyze_hook(pstate, query, jstate); + prev_post_parse_analyze_hook(pstate, query); + + if (label) + { + add_custom_query_label(&query->queryIds, -1, label->hash); + jstate = (JumbleState *) label->context; + } + else + { + add_custom_query_label(&query->queryIds, -1, UINT64CONST(0)); + jstate = NULL; + } + + label = get_query_label(query->queryIds, -1); /* Safety check... */ if (!pgss || !pgss_hash || !pgss_enabled(exec_nested_level)) @@ -833,7 +848,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) if (query->utilityStmt) { if (pgss_track_utility && !PGSS_HANDLED_UTILITY(query->utilityStmt)) - query->queryId = UINT64CONST(0); + label->hash = UINT64CONST(0); return; } @@ -846,7 +861,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) */ if (jstate && jstate->clocations_count > 0) pgss_store(pstate->p_sourcetext, - query->queryId, + label->hash, query->stmt_location, query->stmt_len, PGSS_INVALID, @@ -868,6 +883,9 @@ pgss_planner(Query *parse, ParamListInfo boundParams) { PlannedStmt *result; + int64 queryId; + + queryId = get_query_label_hash(parse->queryIds, -1); /* * We can't process the query if no query_string is provided, as @@ -883,7 +901,7 @@ pgss_planner(Query *parse, */ if (pgss_enabled(plan_nested_level + exec_nested_level) && pgss_track_planning && query_string - && parse->queryId != UINT64CONST(0)) + && queryId != UINT64CONST(0)) { instr_time start; instr_time duration; @@ -930,7 +948,7 @@ pgss_planner(Query *parse, WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start); pgss_store(query_string, - parse->queryId, + queryId, parse->stmt_location, parse->stmt_len, PGSS_PLAN, @@ -959,17 +977,21 @@ pgss_planner(Query *parse, static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) { + int64 queryId; + if (prev_ExecutorStart) prev_ExecutorStart(queryDesc, eflags); else standard_ExecutorStart(queryDesc, eflags); + queryId = get_query_label_hash(queryDesc->plannedstmt->queryIds, -1); + /* * If query has queryId zero, don't track it. This prevents double * counting of optimizable statements that are directly contained in * utility statements. */ - if (pgss_enabled(exec_nested_level) && queryDesc->plannedstmt->queryId != UINT64CONST(0)) + if (pgss_enabled(exec_nested_level) && queryId != UINT64CONST(0)) { /* * Set up to track total elapsed time in ExecutorRun. Make sure the @@ -1036,7 +1058,7 @@ pgss_ExecutorFinish(QueryDesc *queryDesc) static void pgss_ExecutorEnd(QueryDesc *queryDesc) { - uint64 queryId = queryDesc->plannedstmt->queryId; + uint64 queryId = get_query_label_hash(queryDesc->plannedstmt->queryIds, -1); if (queryId != UINT64CONST(0) && queryDesc->totaltime && pgss_enabled(exec_nested_level)) @@ -1076,7 +1098,16 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, DestReceiver *dest, QueryCompletion *qc) { Node *parsetree = pstmt->utilityStmt; - uint64 saved_queryId = pstmt->queryId; + QueryLabel *label = get_query_label(pstmt->queryIds, -1); + uint64 saved_queryId; + + if (!label) + { + add_custom_query_label(&pstmt->queryIds, -1, UINT64CONST(0)); + label = get_query_label(pstmt->queryIds, -1); + } + + saved_queryId = label->hash; /* * Force utility statements to get queryId zero. We do this even in cases @@ -1093,7 +1124,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, * only. */ if (pgss_enabled(exec_nested_level) && pgss_track_utility) - pstmt->queryId = UINT64CONST(0); + label->hash = UINT64CONST(0); /* * If it's an EXECUTE statement, we don't track it and don't increment the diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index b970997c34..b31a870156 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -166,7 +166,6 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, { ExplainState *es = NewExplainState(); TupOutputState *tstate; - JumbleState *jstate = NULL; Query *query; List *rewritten; ListCell *lc; @@ -246,10 +245,10 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, query = castNode(Query, stmt->query); if (IsQueryIdEnabled()) - jstate = JumbleQuery(query, pstate->p_sourcetext); + GenerateQueryLabels(query, pstate->p_sourcetext); if (post_parse_analyze_hook) - (*post_parse_analyze_hook) (pstate, query, jstate); + (*post_parse_analyze_hook) (pstate, query); /* * Parse analysis was done already, but we still have to run the rule @@ -604,14 +603,14 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, /* Create textual dump of plan tree */ ExplainPrintPlan(es, queryDesc); - if (es->verbose && plannedstmt->queryId != UINT64CONST(0)) + if (es->verbose && plannedstmt->queryIds != NIL) { /* * Output the queryid as an int64 rather than a uint64 so we match * what would be seen in the BIGINT pg_stat_statements.queryid column. */ - ExplainPropertyInteger("Query Identifier", NULL, (int64) - plannedstmt->queryId, es); + ExplainPropertyInteger("Query Identifier", NULL, + get_query_label_hash(plannedstmt->queryIds, 0), es); } /* Show buffer usage in planning */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 549d9eb696..cbb97cf91f 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -62,6 +62,7 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/partcache.h" +#include "utils/queryjumble.h" #include "utils/rls.h" #include "utils/ruleutils.h" #include "utils/snapmgr.h" @@ -135,7 +136,8 @@ ExecutorStart(QueryDesc *queryDesc, int eflags) * that it's harmless to report the query_id multiple time, as the call * will be ignored if the top level query_id has already been reported. */ - pgstat_report_query_id(queryDesc->plannedstmt->queryId, false); + pgstat_report_query_id( + get_query_label_hash(queryDesc->plannedstmt->queryIds, 0), false); if (ExecutorStart_hook) (*ExecutorStart_hook) (queryDesc, eflags); diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 5dd8ab7db2..d025860f45 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -49,6 +49,7 @@ #include "utils/dsa.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/queryjumble.h" #include "utils/snapmgr.h" /* @@ -175,7 +176,7 @@ ExecSerializePlan(Plan *plan, EState *estate) */ pstmt = makeNode(PlannedStmt); pstmt->commandType = CMD_SELECT; - pstmt->queryId = pgstat_get_my_query_id(); + add_custom_query_label(&pstmt->queryIds, 0, pgstat_get_my_query_id()); pstmt->hasReturning = false; pstmt->hasModifyingCTE = false; pstmt->canSetTag = true; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 90b5da51c9..6f159e0cbd 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -87,7 +87,7 @@ _copyPlannedStmt(const PlannedStmt *from) PlannedStmt *newnode = makeNode(PlannedStmt); COPY_SCALAR_FIELD(commandType); - COPY_SCALAR_FIELD(queryId); + COPY_NODE_FIELD(queryIds); COPY_SCALAR_FIELD(hasReturning); COPY_SCALAR_FIELD(hasModifyingCTE); COPY_SCALAR_FIELD(canSetTag); @@ -3159,6 +3159,20 @@ _copyTriggerTransition(const TriggerTransition *from) return newnode; } +static QueryLabel * +_copyQueryLabel(const QueryLabel *from) +{ + QueryLabel *newnode = makeNode(QueryLabel); + + COPY_SCALAR_FIELD(kind); + COPY_SCALAR_FIELD(hash); + + /* Caller should re-generate labels if it want to use it. */ + newnode->context = NULL; + + return newnode; +} + static Query * _copyQuery(const Query *from) { @@ -3166,7 +3180,7 @@ _copyQuery(const Query *from) COPY_SCALAR_FIELD(commandType); COPY_SCALAR_FIELD(querySource); - COPY_SCALAR_FIELD(queryId); + COPY_NODE_FIELD(queryIds); COPY_SCALAR_FIELD(canSetTag); COPY_NODE_FIELD(utilityStmt); COPY_SCALAR_FIELD(resultRelation); @@ -5405,6 +5419,9 @@ copyObjectImpl(const void *from) /* * PARSE NODES */ + case T_QueryLabel: + retval = _copyQueryLabel(from); + break; case T_Query: retval = _copyQuery(from); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 2b0236937a..c86eb636b6 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -305,7 +305,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_NODE_TYPE("PLANNEDSTMT"); WRITE_ENUM_FIELD(commandType, CmdType); - WRITE_UINT64_FIELD(queryId); + WRITE_NODE_FIELD(queryIds); WRITE_BOOL_FIELD(hasReturning); WRITE_BOOL_FIELD(hasModifyingCTE); WRITE_BOOL_FIELD(canSetTag); @@ -3030,6 +3030,16 @@ _outStatsElem(StringInfo str, const StatsElem *node) WRITE_NODE_FIELD(expr); } +/* Needed for dump of a PlannedStmt node */ +static void +_outQueryLabel(StringInfo str, const QueryLabel *node) +{ + WRITE_NODE_TYPE("QUERYLABEL"); + + WRITE_INT_FIELD(kind); + WRITE_UINT64_FIELD(hash); +} + static void _outQuery(StringInfo str, const Query *node) { @@ -3037,7 +3047,7 @@ _outQuery(StringInfo str, const Query *node) WRITE_ENUM_FIELD(commandType, CmdType); WRITE_ENUM_FIELD(querySource, QuerySource); - /* we intentionally do not print the queryId field */ + /* we intentionally do not print the queryId fields */ WRITE_BOOL_FIELD(canSetTag); /* @@ -4403,6 +4413,9 @@ outNode(StringInfo str, const void *obj) case T_StatsElem: _outStatsElem(str, obj); break; + case T_QueryLabel: + _outQueryLabel(str, obj); + break; case T_Query: _outQuery(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 3f68f7c18d..47c35707d4 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -238,6 +238,17 @@ readBitmapset(void) return _readBitmapset(); } +static QueryLabel * +_readQueryLabel(void) +{ + READ_LOCALS(QueryLabel); + READ_INT_FIELD(kind); + READ_UINT64_FIELD(hash); + local_node->context = NULL; + + READ_DONE(); +} + /* * _readQuery */ @@ -248,7 +259,7 @@ _readQuery(void) READ_ENUM_FIELD(commandType, CmdType); READ_ENUM_FIELD(querySource, QuerySource); - local_node->queryId = UINT64CONST(0); /* not saved in output format */ + local_node->queryIds = NIL; /* not saved in output format */ READ_BOOL_FIELD(canSetTag); READ_NODE_FIELD(utilityStmt); READ_INT_FIELD(resultRelation); @@ -1578,7 +1589,7 @@ _readPlannedStmt(void) READ_LOCALS(PlannedStmt); READ_ENUM_FIELD(commandType, CmdType); - READ_UINT64_FIELD(queryId); + READ_NODE_FIELD(queryIds); READ_BOOL_FIELD(hasReturning); READ_BOOL_FIELD(hasModifyingCTE); READ_BOOL_FIELD(canSetTag); @@ -2728,6 +2739,8 @@ parseNodeString(void) if (MATCH("QUERY", 5)) return_value = _readQuery(); + else if (MATCH("QUERYLABEL", 10)) + return_value = _readQueryLabel(); else if (MATCH("WITHCHECKOPTION", 15)) return_value = _readWithCheckOption(); else if (MATCH("SORTGROUPCLAUSE", 15)) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bd09f85aea..2ef8a97997 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -510,7 +510,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, result = makeNode(PlannedStmt); result->commandType = parse->commandType; - result->queryId = parse->queryId; + result->queryIds = list_copy(parse->queryIds); result->hasReturning = (parse->returningList != NIL); result->hasModifyingCTE = parse->hasModifyingCTE; result->canSetTag = parse->canSetTag; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 6ac2e9ce23..5925641795 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -113,7 +113,6 @@ parse_analyze(RawStmt *parseTree, const char *sourceText, { ParseState *pstate = make_parsestate(NULL); Query *query; - JumbleState *jstate = NULL; Assert(sourceText != NULL); /* required as of 8.4 */ @@ -127,14 +126,15 @@ parse_analyze(RawStmt *parseTree, const char *sourceText, query = transformTopLevelStmt(pstate, parseTree); if (IsQueryIdEnabled()) - jstate = JumbleQuery(query, sourceText); + GenerateQueryLabels(query, sourceText); if (post_parse_analyze_hook) - (*post_parse_analyze_hook) (pstate, query, jstate); + (*post_parse_analyze_hook) (pstate, query); free_parsestate(pstate); - pgstat_report_query_id(query->queryId, false); + /* Report id of default generator. */ + pgstat_report_query_id(get_query_label_hash(query->queryIds, 0), false); return query; } @@ -152,7 +152,6 @@ parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, { ParseState *pstate = make_parsestate(NULL); Query *query; - JumbleState *jstate = NULL; Assert(sourceText != NULL); /* required as of 8.4 */ @@ -166,14 +165,14 @@ parse_analyze_varparams(RawStmt *parseTree, const char *sourceText, check_variable_parameters(pstate, query); if (IsQueryIdEnabled()) - jstate = JumbleQuery(query, sourceText); + GenerateQueryLabels(query, sourceText); if (post_parse_analyze_hook) - (*post_parse_analyze_hook) (pstate, query, jstate); + (*post_parse_analyze_hook) (pstate, query); free_parsestate(pstate); - pgstat_report_query_id(query->queryId, false); + pgstat_report_query_id(get_query_label_hash(query->queryIds, 0), false); return query; } diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 3d82138cb3..1cce0949b9 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -4079,7 +4079,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) List * QueryRewrite(Query *parsetree) { - uint64 input_query_id = parsetree->queryId; + List *input_query_ids = parsetree->queryIds; List *querylist; List *results; ListCell *l; @@ -4114,7 +4114,7 @@ QueryRewrite(Query *parsetree) query = fireRIRrules(query, NIL); - query->queryId = input_query_id; + query->queryIds = input_query_ids; results = lappend(results, query); } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index fda2e9360e..76a8312b47 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -686,7 +686,6 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree, ParseState *pstate; Query *query; List *querytree_list; - JumbleState *jstate = NULL; Assert(query_string != NULL); /* required as of 8.4 */ @@ -706,14 +705,14 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree, query = transformTopLevelStmt(pstate, parsetree); if (IsQueryIdEnabled()) - jstate = JumbleQuery(query, query_string); + GenerateQueryLabels(query, query_string); if (post_parse_analyze_hook) - (*post_parse_analyze_hook) (pstate, query, jstate); + (*post_parse_analyze_hook) (pstate, query); free_parsestate(pstate); - pgstat_report_query_id(query->queryId, false); + pgstat_report_query_id(get_query_label_hash(query->queryIds, 0), false); if (log_parser_stats) ShowUsage("PARSE ANALYSIS STATISTICS"); @@ -797,7 +796,7 @@ pg_rewrite_query(Query *query) * queryId is not saved in stored rules, but we must preserve * it here to avoid breaking pg_stat_statements. */ - new_query->queryId = query->queryId; + new_query->queryIds = list_copy(query->queryIds); new_list = lappend(new_list, new_query); pfree(str); @@ -933,7 +932,7 @@ pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions, stmt->utilityStmt = query->utilityStmt; stmt->stmt_location = query->stmt_location; stmt->stmt_len = query->stmt_len; - stmt->queryId = query->queryId; + stmt->queryIds = list_copy(query->queryIds); } else { diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 4c94f09c64..fb5dd1cdce 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -4695,7 +4695,7 @@ static struct config_enum ConfigureNamesEnum[] = }, &compute_query_id, COMPUTE_QUERY_ID_AUTO, compute_query_id_options, - NULL, NULL, NULL + NULL, assign_query_id, NULL }, { diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c index a67487e5fe..59bf53bc46 100644 --- a/src/backend/utils/misc/queryjumble.c +++ b/src/backend/utils/misc/queryjumble.c @@ -97,18 +97,18 @@ CleanQuerytext(const char *query, int *location, int *len) return query; } -JumbleState * -JumbleQuery(Query *query, const char *querytext) +int64 +JumbleQuery(Query *query, const char *querytext, void **context) { - JumbleState *jstate = NULL; + JumbleState *jstate = NULL; + int64 queryId; Assert(IsQueryIdEnabled()); if (query->utilityStmt) { - query->queryId = compute_utility_query_id(querytext, - query->stmt_location, - query->stmt_len); + queryId = compute_utility_query_id(querytext, query->stmt_location, + query->stmt_len); } else { @@ -125,19 +125,20 @@ JumbleQuery(Query *query, const char *querytext) /* Compute query ID and mark the Query node with it */ JumbleQueryInternal(jstate, query); - query->queryId = DatumGetUInt64(hash_any_extended(jstate->jumble, - jstate->jumble_len, - 0)); + queryId = DatumGetUInt64(hash_any_extended(jstate->jumble, + jstate->jumble_len, + 0)); /* * If we are unlucky enough to get a hash of zero, use 1 instead, to * prevent confusion with the utility-statement case. */ - if (query->queryId == UINT64CONST(0)) - query->queryId = UINT64CONST(1); + if (queryId == UINT64CONST(0)) + queryId = UINT64CONST(1); } - return jstate; + *context = jstate; + return queryId; } /* @@ -150,7 +151,29 @@ void EnableQueryId(void) { if (compute_query_id != COMPUTE_QUERY_ID_OFF) + { + (void) RegisterQueryIdGen(0, JumbleQuery); query_id_enabled = true; + } +} + +void +assign_query_id(int newval, void *extra) +{ + switch (newval) + { + case COMPUTE_QUERY_ID_OFF: + /* De-register routine should be implemented */ + break; + case COMPUTE_QUERY_ID_AUTO: + break; + case COMPUTE_QUERY_ID_ON: + (void) RegisterQueryIdGen(0, JumbleQuery); + break; + default: + elog(ERROR, "Unknown value"); + break; + } } /* @@ -856,3 +879,120 @@ RecordConstLocation(JumbleState *jstate, int location) jstate->clocations_count++; } } + +/* ******************************************************************************* + * + * Query label custom generation machinery. + * + * ******************************************************************************/ + +#include "utils/hsearch.h" + +static HTAB *QueryLabelGens = NULL; + +typedef struct QLGeneratorEntry +{ + int16 kind; + query_generator_type callback; +} QLGeneratorEntry; + +/* + * An internal function to register a new callback structure + * Use a positive value for the 'kind' field to register an query label generator. + * Caller should worry about intersections by this value with other extensions. + */ +bool +RegisterQueryIdGen(const int16 kind, query_generator_type callback) +{ + QLGeneratorEntry *entry; + bool found; + + if (QueryLabelGens == NULL) + { + HASHCTL ctl; + + ctl.keysize = sizeof(int16); + ctl.entrysize = sizeof(QLGeneratorEntry); + + QueryLabelGens = hash_create("Query Label Generators", 8, &ctl, + HASH_ELEM | HASH_BLOBS); + } + + if (kind < 0) + elog(ERROR, "Can't use value %d as an query generator kind.", kind); + + entry = (QLGeneratorEntry *) hash_search(QueryLabelGens, (void *) &kind, + HASH_ENTER, &found); + if (found) + /* Give caller a chance to process the problem. */ + return false; + + entry->callback = callback; + return true; +} + +/* + * Create QueryLabel entry for each registered generator. + * All memory allocations is made in current memory context. + */ +void +GenerateQueryLabels(Query *query, const char *querytext) +{ + HASH_SEQ_STATUS hash_seq; + QLGeneratorEntry *entry; + + /* Newly generated query haven't any labels. */ + Assert(query->queryIds == NIL); + + if (QueryLabelGens == NULL || hash_get_num_entries(QueryLabelGens) == 0) + return; + + hash_seq_init(&hash_seq, QueryLabelGens); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + QueryLabel *qlabel = makeNode(QueryLabel); + + qlabel->kind = entry->kind; + qlabel->hash = entry->callback(query, querytext, &qlabel->context); + query->queryIds = lappend(query->queryIds, qlabel); + } +} + +int64 +get_query_label_hash(List *queryIds, const int16 kind) +{ + QueryLabel *label; + + label = get_query_label(queryIds, kind); + return (label) ? label->hash : 0; +} + +QueryLabel * +get_query_label(List *queryIds, const int16 kind) +{ + ListCell *lc; + + foreach(lc, queryIds) + { + QueryLabel *label = lfirst_node(QueryLabel, lc); + if (label->kind == kind) + return label; + } + return NULL; +} + +bool +add_custom_query_label(List **queryIds, int16 kind, int64 hash) +{ + QueryLabel *label; + + if (get_query_label_hash(*queryIds, kind) != 0) + elog(ERROR, "Duplicated custom label %ld for the kind %d.", hash, kind); + + label = makeNode(QueryLabel); + label->kind = kind; + label->hash = hash; + label->context = NULL; + *queryIds = lappend(*queryIds, label); + return true; +} diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index f9ddafd345..0835034c2d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -314,6 +314,7 @@ typedef enum NodeTag T_RawStmt, T_Query, T_PlannedStmt, + T_QueryLabel, T_InsertStmt, T_DeleteStmt, T_UpdateStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 3e9bdc781f..bc9ca32273 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -70,6 +70,14 @@ typedef enum SetQuantifier SET_QUANTIFIER_DISTINCT } SetQuantifier; +typedef struct QueryLabel +{ + NodeTag type; + int16 kind; /* unique ID of generator. 0 reserved for in-core jumbling routine. */ + int64 hash; /* generated stamp. */ + void *context; /* internal generator-related data. */ +} QueryLabel; + /* * Grantable rights are encoded so that we can OR them together in a bitmask. * The present representation of AclItem limits us to 16 distinct rights, @@ -121,7 +129,7 @@ typedef struct Query QuerySource querySource; /* where did I come from? */ - uint64 queryId; /* query identifier (can be set by plugins) */ + List *queryIds; /* query identifiers (can be added by plugins) */ bool canSetTag; /* do I set the command result tag? */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 0b518ce6b2..f588311de2 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -45,7 +45,7 @@ typedef struct PlannedStmt CmdType commandType; /* select|insert|update|delete|utility */ - uint64 queryId; /* query identifier (copied from Query) */ + List *queryIds; /* query identifiers (copied from Query) */ bool hasReturning; /* is it insert|update|delete RETURNING? */ diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 0022184de0..455396c054 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -19,8 +19,7 @@ /* Hook for plugins to get control at end of parse analysis */ typedef void (*post_parse_analyze_hook_type) (ParseState *pstate, - Query *query, - JumbleState *jstate); + Query *query); extern PGDLLIMPORT post_parse_analyze_hook_type post_parse_analyze_hook; diff --git a/src/include/utils/queryjumble.h b/src/include/utils/queryjumble.h index a4c277269e..f09b88e3ec 100644 --- a/src/include/utils/queryjumble.h +++ b/src/include/utils/queryjumble.h @@ -65,7 +65,7 @@ extern int compute_query_id; extern const char *CleanQuerytext(const char *query, int *location, int *len); -extern JumbleState *JumbleQuery(Query *query, const char *querytext); +extern int64 JumbleQuery(Query *query, const char *querytext, void **context); extern void EnableQueryId(void); extern bool query_id_enabled; @@ -84,4 +84,14 @@ IsQueryIdEnabled(void) return query_id_enabled; } +typedef int64 (*query_generator_type) (Query *query, const char *querytext, + void **context); + +extern void assign_query_id(int newval, void *extra); +extern bool RegisterQueryIdGen(const int16 kind, query_generator_type callback); +extern void GenerateQueryLabels(Query *query, const char *querytext); +extern int64 get_query_label_hash(List *queryIds, const int16 kind); +extern QueryLabel *get_query_label(List *queryIds, const int16 kind); +extern bool add_custom_query_label(List **queryIds, int16 kind, int64 hash); + #endif /* QUERYJUMBLE_H */ -- 2.25.1 --------------33708CB72BF31B9DD01BF366--