mirror of https://github.com/postgres/postgres
Add compute_query_id GUC to control whether a query identifier should be computed by the core (off by default). It's thefore now possible to disable core queryid computation and use pg_stat_statements with a different algorithm to compute the query identifier by using a third-party module. To ensure that a single source of query identifier can be used and is well defined, modules that calculate a query identifier should throw an error if compute_query_id specified to compute a query id and if a query idenfitier was already calculated. Discussion: https://postgr.es/m/20210407125726.tkvjdbw76hxnpwfi@nol Author: Julien Rouhaud Reviewed-by: Alvaro Herrera, Nitin Jadhav, Zhihong Yupull/64/head
parent
a282ee68a0
commit
5fd9dfa5f5
@ -1 +1,2 @@ |
|||||||
shared_preload_libraries = 'pg_stat_statements' |
shared_preload_libraries = 'pg_stat_statements' |
||||||
|
compute_query_id = on |
||||||
|
|||||||
@ -0,0 +1,834 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* queryjumble.c |
||||||
|
* Query normalization and fingerprinting. |
||||||
|
* |
||||||
|
* Normalization is a process whereby similar queries, typically differing only |
||||||
|
* in their constants (though the exact rules are somewhat more subtle than |
||||||
|
* that) are recognized as equivalent, and are tracked as a single entry. This |
||||||
|
* is particularly useful for non-prepared queries. |
||||||
|
* |
||||||
|
* Normalization is implemented by fingerprinting queries, selectively |
||||||
|
* serializing those fields of each query tree's nodes that are judged to be |
||||||
|
* essential to the query. This is referred to as a query jumble. This is |
||||||
|
* distinct from a regular serialization in that various extraneous |
||||||
|
* information is ignored as irrelevant or not essential to the query, such |
||||||
|
* as the collations of Vars and, most notably, the values of constants. |
||||||
|
* |
||||||
|
* This jumble is acquired at the end of parse analysis of each query, and |
||||||
|
* a 64-bit hash of it is stored into the query's Query.queryId field. |
||||||
|
* The server then copies this value around, making it available in plan |
||||||
|
* tree(s) generated from the query. The executor can then use this value |
||||||
|
* to blame query costs on the proper queryId. |
||||||
|
* |
||||||
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California |
||||||
|
* |
||||||
|
* |
||||||
|
* IDENTIFICATION |
||||||
|
* src/backend/utils/misc/queryjumble.c |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
#include "postgres.h" |
||||||
|
|
||||||
|
#include "common/hashfn.h" |
||||||
|
#include "miscadmin.h" |
||||||
|
#include "parser/scansup.h" |
||||||
|
#include "utils/queryjumble.h" |
||||||
|
|
||||||
|
#define JUMBLE_SIZE 1024 /* query serialization buffer size */ |
||||||
|
|
||||||
|
static uint64 compute_utility_queryid(const char *str, int query_len); |
||||||
|
static void AppendJumble(JumbleState *jstate, |
||||||
|
const unsigned char *item, Size size); |
||||||
|
static void JumbleQueryInternal(JumbleState *jstate, Query *query); |
||||||
|
static void JumbleRangeTable(JumbleState *jstate, List *rtable); |
||||||
|
static void JumbleRowMarks(JumbleState *jstate, List *rowMarks); |
||||||
|
static void JumbleExpr(JumbleState *jstate, Node *node); |
||||||
|
static void RecordConstLocation(JumbleState *jstate, int location); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a possibly multi-statement source string, confine our attention to the |
||||||
|
* relevant part of the string. |
||||||
|
*/ |
||||||
|
const char * |
||||||
|
CleanQuerytext(const char *query, int *location, int *len) |
||||||
|
{ |
||||||
|
int query_location = *location; |
||||||
|
int query_len = *len; |
||||||
|
|
||||||
|
/* First apply starting offset, unless it's -1 (unknown). */ |
||||||
|
if (query_location >= 0) |
||||||
|
{ |
||||||
|
Assert(query_location <= strlen(query)); |
||||||
|
query += query_location; |
||||||
|
/* Length of 0 (or -1) means "rest of string" */ |
||||||
|
if (query_len <= 0) |
||||||
|
query_len = strlen(query); |
||||||
|
else |
||||||
|
Assert(query_len <= strlen(query)); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
/* If query location is unknown, distrust query_len as well */ |
||||||
|
query_location = 0; |
||||||
|
query_len = strlen(query); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Discard leading and trailing whitespace, too. Use scanner_isspace() |
||||||
|
* not libc's isspace(), because we want to match the lexer's behavior. |
||||||
|
*/ |
||||||
|
while (query_len > 0 && scanner_isspace(query[0])) |
||||||
|
query++, query_location++, query_len--; |
||||||
|
while (query_len > 0 && scanner_isspace(query[query_len - 1])) |
||||||
|
query_len--; |
||||||
|
|
||||||
|
*location = query_location; |
||||||
|
*len = query_len; |
||||||
|
|
||||||
|
return query; |
||||||
|
} |
||||||
|
|
||||||
|
JumbleState * |
||||||
|
JumbleQuery(Query *query, const char *querytext) |
||||||
|
{ |
||||||
|
JumbleState *jstate = NULL; |
||||||
|
if (query->utilityStmt) |
||||||
|
{ |
||||||
|
const char *sql; |
||||||
|
int query_location = query->stmt_location; |
||||||
|
int query_len = query->stmt_len; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Confine our attention to the relevant part of the string, if the |
||||||
|
* query is a portion of a multi-statement source string. |
||||||
|
*/ |
||||||
|
sql = CleanQuerytext(querytext, &query_location, &query_len); |
||||||
|
|
||||||
|
query->queryId = compute_utility_queryid(sql, query_len); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
jstate = (JumbleState *) palloc(sizeof(JumbleState)); |
||||||
|
|
||||||
|
/* Set up workspace for query jumbling */ |
||||||
|
jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE); |
||||||
|
jstate->jumble_len = 0; |
||||||
|
jstate->clocations_buf_size = 32; |
||||||
|
jstate->clocations = (LocationLen *) |
||||||
|
palloc(jstate->clocations_buf_size * sizeof(LocationLen)); |
||||||
|
jstate->clocations_count = 0; |
||||||
|
jstate->highest_extern_param_id = 0; |
||||||
|
|
||||||
|
/* 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)); |
||||||
|
|
||||||
|
/*
|
||||||
|
* 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); |
||||||
|
} |
||||||
|
|
||||||
|
return jstate; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute a query identifier for the given utility query string. |
||||||
|
*/ |
||||||
|
static uint64 |
||||||
|
compute_utility_queryid(const char *str, int query_len) |
||||||
|
{ |
||||||
|
uint64 queryId; |
||||||
|
|
||||||
|
queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) str, |
||||||
|
query_len, 0)); |
||||||
|
|
||||||
|
/*
|
||||||
|
* If we are unlucky enough to get a hash of zero(invalid), use |
||||||
|
* queryID as 2 instead, queryID 1 is already in use for normal |
||||||
|
* statements. |
||||||
|
*/ |
||||||
|
if (queryId == UINT64CONST(0)) |
||||||
|
queryId = UINT64CONST(2); |
||||||
|
|
||||||
|
return queryId; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* AppendJumble: Append a value that is substantive in a given query to |
||||||
|
* the current jumble. |
||||||
|
*/ |
||||||
|
static void |
||||||
|
AppendJumble(JumbleState *jstate, const unsigned char *item, Size size) |
||||||
|
{ |
||||||
|
unsigned char *jumble = jstate->jumble; |
||||||
|
Size jumble_len = jstate->jumble_len; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Whenever the jumble buffer is full, we hash the current contents and |
||||||
|
* reset the buffer to contain just that hash value, thus relying on the |
||||||
|
* hash to summarize everything so far. |
||||||
|
*/ |
||||||
|
while (size > 0) |
||||||
|
{ |
||||||
|
Size part_size; |
||||||
|
|
||||||
|
if (jumble_len >= JUMBLE_SIZE) |
||||||
|
{ |
||||||
|
uint64 start_hash; |
||||||
|
|
||||||
|
start_hash = DatumGetUInt64(hash_any_extended(jumble, |
||||||
|
JUMBLE_SIZE, 0)); |
||||||
|
memcpy(jumble, &start_hash, sizeof(start_hash)); |
||||||
|
jumble_len = sizeof(start_hash); |
||||||
|
} |
||||||
|
part_size = Min(size, JUMBLE_SIZE - jumble_len); |
||||||
|
memcpy(jumble + jumble_len, item, part_size); |
||||||
|
jumble_len += part_size; |
||||||
|
item += part_size; |
||||||
|
size -= part_size; |
||||||
|
} |
||||||
|
jstate->jumble_len = jumble_len; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Wrappers around AppendJumble to encapsulate details of serialization |
||||||
|
* of individual local variable elements. |
||||||
|
*/ |
||||||
|
#define APP_JUMB(item) \ |
||||||
|
AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item)) |
||||||
|
#define APP_JUMB_STRING(str) \ |
||||||
|
AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1) |
||||||
|
|
||||||
|
/*
|
||||||
|
* JumbleQueryInternal: Selectively serialize the query tree, appending |
||||||
|
* significant data to the "query jumble" while ignoring nonsignificant data. |
||||||
|
* |
||||||
|
* Rule of thumb for what to include is that we should ignore anything not |
||||||
|
* semantically significant (such as alias names) as well as anything that can |
||||||
|
* be deduced from child nodes (else we'd just be double-hashing that piece |
||||||
|
* of information). |
||||||
|
*/ |
||||||
|
static void |
||||||
|
JumbleQueryInternal(JumbleState *jstate, Query *query) |
||||||
|
{ |
||||||
|
Assert(IsA(query, Query)); |
||||||
|
Assert(query->utilityStmt == NULL); |
||||||
|
|
||||||
|
APP_JUMB(query->commandType); |
||||||
|
/* resultRelation is usually predictable from commandType */ |
||||||
|
JumbleExpr(jstate, (Node *) query->cteList); |
||||||
|
JumbleRangeTable(jstate, query->rtable); |
||||||
|
JumbleExpr(jstate, (Node *) query->jointree); |
||||||
|
JumbleExpr(jstate, (Node *) query->targetList); |
||||||
|
JumbleExpr(jstate, (Node *) query->onConflict); |
||||||
|
JumbleExpr(jstate, (Node *) query->returningList); |
||||||
|
JumbleExpr(jstate, (Node *) query->groupClause); |
||||||
|
JumbleExpr(jstate, (Node *) query->groupingSets); |
||||||
|
JumbleExpr(jstate, query->havingQual); |
||||||
|
JumbleExpr(jstate, (Node *) query->windowClause); |
||||||
|
JumbleExpr(jstate, (Node *) query->distinctClause); |
||||||
|
JumbleExpr(jstate, (Node *) query->sortClause); |
||||||
|
JumbleExpr(jstate, query->limitOffset); |
||||||
|
JumbleExpr(jstate, query->limitCount); |
||||||
|
JumbleRowMarks(jstate, query->rowMarks); |
||||||
|
JumbleExpr(jstate, query->setOperations); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Jumble a range table |
||||||
|
*/ |
||||||
|
static void |
||||||
|
JumbleRangeTable(JumbleState *jstate, List *rtable) |
||||||
|
{ |
||||||
|
ListCell *lc; |
||||||
|
|
||||||
|
foreach(lc, rtable) |
||||||
|
{ |
||||||
|
RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); |
||||||
|
|
||||||
|
APP_JUMB(rte->rtekind); |
||||||
|
switch (rte->rtekind) |
||||||
|
{ |
||||||
|
case RTE_RELATION: |
||||||
|
APP_JUMB(rte->relid); |
||||||
|
JumbleExpr(jstate, (Node *) rte->tablesample); |
||||||
|
break; |
||||||
|
case RTE_SUBQUERY: |
||||||
|
JumbleQueryInternal(jstate, rte->subquery); |
||||||
|
break; |
||||||
|
case RTE_JOIN: |
||||||
|
APP_JUMB(rte->jointype); |
||||||
|
break; |
||||||
|
case RTE_FUNCTION: |
||||||
|
JumbleExpr(jstate, (Node *) rte->functions); |
||||||
|
break; |
||||||
|
case RTE_TABLEFUNC: |
||||||
|
JumbleExpr(jstate, (Node *) rte->tablefunc); |
||||||
|
break; |
||||||
|
case RTE_VALUES: |
||||||
|
JumbleExpr(jstate, (Node *) rte->values_lists); |
||||||
|
break; |
||||||
|
case RTE_CTE: |
||||||
|
|
||||||
|
/*
|
||||||
|
* Depending on the CTE name here isn't ideal, but it's the |
||||||
|
* only info we have to identify the referenced WITH item. |
||||||
|
*/ |
||||||
|
APP_JUMB_STRING(rte->ctename); |
||||||
|
APP_JUMB(rte->ctelevelsup); |
||||||
|
break; |
||||||
|
case RTE_NAMEDTUPLESTORE: |
||||||
|
APP_JUMB_STRING(rte->enrname); |
||||||
|
break; |
||||||
|
case RTE_RESULT: |
||||||
|
break; |
||||||
|
default: |
||||||
|
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Jumble a rowMarks list |
||||||
|
*/ |
||||||
|
static void |
||||||
|
JumbleRowMarks(JumbleState *jstate, List *rowMarks) |
||||||
|
{ |
||||||
|
ListCell *lc; |
||||||
|
|
||||||
|
foreach(lc, rowMarks) |
||||||
|
{ |
||||||
|
RowMarkClause *rowmark = lfirst_node(RowMarkClause, lc); |
||||||
|
|
||||||
|
if (!rowmark->pushedDown) |
||||||
|
{ |
||||||
|
APP_JUMB(rowmark->rti); |
||||||
|
APP_JUMB(rowmark->strength); |
||||||
|
APP_JUMB(rowmark->waitPolicy); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Jumble an expression tree |
||||||
|
* |
||||||
|
* In general this function should handle all the same node types that |
||||||
|
* expression_tree_walker() does, and therefore it's coded to be as parallel |
||||||
|
* to that function as possible. However, since we are only invoked on |
||||||
|
* queries immediately post-parse-analysis, we need not handle node types |
||||||
|
* that only appear in planning. |
||||||
|
* |
||||||
|
* Note: the reason we don't simply use expression_tree_walker() is that the |
||||||
|
* point of that function is to support tree walkers that don't care about |
||||||
|
* most tree node types, but here we care about all types. We should complain |
||||||
|
* about any unrecognized node type. |
||||||
|
*/ |
||||||
|
static void |
||||||
|
JumbleExpr(JumbleState *jstate, Node *node) |
||||||
|
{ |
||||||
|
ListCell *temp; |
||||||
|
|
||||||
|
if (node == NULL) |
||||||
|
return; |
||||||
|
|
||||||
|
/* Guard against stack overflow due to overly complex expressions */ |
||||||
|
check_stack_depth(); |
||||||
|
|
||||||
|
/*
|
||||||
|
* We always emit the node's NodeTag, then any additional fields that are |
||||||
|
* considered significant, and then we recurse to any child nodes. |
||||||
|
*/ |
||||||
|
APP_JUMB(node->type); |
||||||
|
|
||||||
|
switch (nodeTag(node)) |
||||||
|
{ |
||||||
|
case T_Var: |
||||||
|
{ |
||||||
|
Var *var = (Var *) node; |
||||||
|
|
||||||
|
APP_JUMB(var->varno); |
||||||
|
APP_JUMB(var->varattno); |
||||||
|
APP_JUMB(var->varlevelsup); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_Const: |
||||||
|
{ |
||||||
|
Const *c = (Const *) node; |
||||||
|
|
||||||
|
/* We jumble only the constant's type, not its value */ |
||||||
|
APP_JUMB(c->consttype); |
||||||
|
/* Also, record its parse location for query normalization */ |
||||||
|
RecordConstLocation(jstate, c->location); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_Param: |
||||||
|
{ |
||||||
|
Param *p = (Param *) node; |
||||||
|
|
||||||
|
APP_JUMB(p->paramkind); |
||||||
|
APP_JUMB(p->paramid); |
||||||
|
APP_JUMB(p->paramtype); |
||||||
|
/* Also, track the highest external Param id */ |
||||||
|
if (p->paramkind == PARAM_EXTERN && |
||||||
|
p->paramid > jstate->highest_extern_param_id) |
||||||
|
jstate->highest_extern_param_id = p->paramid; |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_Aggref: |
||||||
|
{ |
||||||
|
Aggref *expr = (Aggref *) node; |
||||||
|
|
||||||
|
APP_JUMB(expr->aggfnoid); |
||||||
|
JumbleExpr(jstate, (Node *) expr->aggdirectargs); |
||||||
|
JumbleExpr(jstate, (Node *) expr->args); |
||||||
|
JumbleExpr(jstate, (Node *) expr->aggorder); |
||||||
|
JumbleExpr(jstate, (Node *) expr->aggdistinct); |
||||||
|
JumbleExpr(jstate, (Node *) expr->aggfilter); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_GroupingFunc: |
||||||
|
{ |
||||||
|
GroupingFunc *grpnode = (GroupingFunc *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, (Node *) grpnode->refs); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_WindowFunc: |
||||||
|
{ |
||||||
|
WindowFunc *expr = (WindowFunc *) node; |
||||||
|
|
||||||
|
APP_JUMB(expr->winfnoid); |
||||||
|
APP_JUMB(expr->winref); |
||||||
|
JumbleExpr(jstate, (Node *) expr->args); |
||||||
|
JumbleExpr(jstate, (Node *) expr->aggfilter); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_SubscriptingRef: |
||||||
|
{ |
||||||
|
SubscriptingRef *sbsref = (SubscriptingRef *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, (Node *) sbsref->refupperindexpr); |
||||||
|
JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr); |
||||||
|
JumbleExpr(jstate, (Node *) sbsref->refexpr); |
||||||
|
JumbleExpr(jstate, (Node *) sbsref->refassgnexpr); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_FuncExpr: |
||||||
|
{ |
||||||
|
FuncExpr *expr = (FuncExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(expr->funcid); |
||||||
|
JumbleExpr(jstate, (Node *) expr->args); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_NamedArgExpr: |
||||||
|
{ |
||||||
|
NamedArgExpr *nae = (NamedArgExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(nae->argnumber); |
||||||
|
JumbleExpr(jstate, (Node *) nae->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_OpExpr: |
||||||
|
case T_DistinctExpr: /* struct-equivalent to OpExpr */ |
||||||
|
case T_NullIfExpr: /* struct-equivalent to OpExpr */ |
||||||
|
{ |
||||||
|
OpExpr *expr = (OpExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(expr->opno); |
||||||
|
JumbleExpr(jstate, (Node *) expr->args); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_ScalarArrayOpExpr: |
||||||
|
{ |
||||||
|
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(expr->opno); |
||||||
|
APP_JUMB(expr->useOr); |
||||||
|
JumbleExpr(jstate, (Node *) expr->args); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_BoolExpr: |
||||||
|
{ |
||||||
|
BoolExpr *expr = (BoolExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(expr->boolop); |
||||||
|
JumbleExpr(jstate, (Node *) expr->args); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_SubLink: |
||||||
|
{ |
||||||
|
SubLink *sublink = (SubLink *) node; |
||||||
|
|
||||||
|
APP_JUMB(sublink->subLinkType); |
||||||
|
APP_JUMB(sublink->subLinkId); |
||||||
|
JumbleExpr(jstate, (Node *) sublink->testexpr); |
||||||
|
JumbleQueryInternal(jstate, castNode(Query, sublink->subselect)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_FieldSelect: |
||||||
|
{ |
||||||
|
FieldSelect *fs = (FieldSelect *) node; |
||||||
|
|
||||||
|
APP_JUMB(fs->fieldnum); |
||||||
|
JumbleExpr(jstate, (Node *) fs->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_FieldStore: |
||||||
|
{ |
||||||
|
FieldStore *fstore = (FieldStore *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, (Node *) fstore->arg); |
||||||
|
JumbleExpr(jstate, (Node *) fstore->newvals); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_RelabelType: |
||||||
|
{ |
||||||
|
RelabelType *rt = (RelabelType *) node; |
||||||
|
|
||||||
|
APP_JUMB(rt->resulttype); |
||||||
|
JumbleExpr(jstate, (Node *) rt->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CoerceViaIO: |
||||||
|
{ |
||||||
|
CoerceViaIO *cio = (CoerceViaIO *) node; |
||||||
|
|
||||||
|
APP_JUMB(cio->resulttype); |
||||||
|
JumbleExpr(jstate, (Node *) cio->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_ArrayCoerceExpr: |
||||||
|
{ |
||||||
|
ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(acexpr->resulttype); |
||||||
|
JumbleExpr(jstate, (Node *) acexpr->arg); |
||||||
|
JumbleExpr(jstate, (Node *) acexpr->elemexpr); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_ConvertRowtypeExpr: |
||||||
|
{ |
||||||
|
ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(crexpr->resulttype); |
||||||
|
JumbleExpr(jstate, (Node *) crexpr->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CollateExpr: |
||||||
|
{ |
||||||
|
CollateExpr *ce = (CollateExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(ce->collOid); |
||||||
|
JumbleExpr(jstate, (Node *) ce->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CaseExpr: |
||||||
|
{ |
||||||
|
CaseExpr *caseexpr = (CaseExpr *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, (Node *) caseexpr->arg); |
||||||
|
foreach(temp, caseexpr->args) |
||||||
|
{ |
||||||
|
CaseWhen *when = lfirst_node(CaseWhen, temp); |
||||||
|
|
||||||
|
JumbleExpr(jstate, (Node *) when->expr); |
||||||
|
JumbleExpr(jstate, (Node *) when->result); |
||||||
|
} |
||||||
|
JumbleExpr(jstate, (Node *) caseexpr->defresult); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CaseTestExpr: |
||||||
|
{ |
||||||
|
CaseTestExpr *ct = (CaseTestExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(ct->typeId); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_ArrayExpr: |
||||||
|
JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements); |
||||||
|
break; |
||||||
|
case T_RowExpr: |
||||||
|
JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args); |
||||||
|
break; |
||||||
|
case T_RowCompareExpr: |
||||||
|
{ |
||||||
|
RowCompareExpr *rcexpr = (RowCompareExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(rcexpr->rctype); |
||||||
|
JumbleExpr(jstate, (Node *) rcexpr->largs); |
||||||
|
JumbleExpr(jstate, (Node *) rcexpr->rargs); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CoalesceExpr: |
||||||
|
JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args); |
||||||
|
break; |
||||||
|
case T_MinMaxExpr: |
||||||
|
{ |
||||||
|
MinMaxExpr *mmexpr = (MinMaxExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(mmexpr->op); |
||||||
|
JumbleExpr(jstate, (Node *) mmexpr->args); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_SQLValueFunction: |
||||||
|
{ |
||||||
|
SQLValueFunction *svf = (SQLValueFunction *) node; |
||||||
|
|
||||||
|
APP_JUMB(svf->op); |
||||||
|
/* type is fully determined by op */ |
||||||
|
APP_JUMB(svf->typmod); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_XmlExpr: |
||||||
|
{ |
||||||
|
XmlExpr *xexpr = (XmlExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(xexpr->op); |
||||||
|
JumbleExpr(jstate, (Node *) xexpr->named_args); |
||||||
|
JumbleExpr(jstate, (Node *) xexpr->args); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_NullTest: |
||||||
|
{ |
||||||
|
NullTest *nt = (NullTest *) node; |
||||||
|
|
||||||
|
APP_JUMB(nt->nulltesttype); |
||||||
|
JumbleExpr(jstate, (Node *) nt->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_BooleanTest: |
||||||
|
{ |
||||||
|
BooleanTest *bt = (BooleanTest *) node; |
||||||
|
|
||||||
|
APP_JUMB(bt->booltesttype); |
||||||
|
JumbleExpr(jstate, (Node *) bt->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CoerceToDomain: |
||||||
|
{ |
||||||
|
CoerceToDomain *cd = (CoerceToDomain *) node; |
||||||
|
|
||||||
|
APP_JUMB(cd->resulttype); |
||||||
|
JumbleExpr(jstate, (Node *) cd->arg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CoerceToDomainValue: |
||||||
|
{ |
||||||
|
CoerceToDomainValue *cdv = (CoerceToDomainValue *) node; |
||||||
|
|
||||||
|
APP_JUMB(cdv->typeId); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_SetToDefault: |
||||||
|
{ |
||||||
|
SetToDefault *sd = (SetToDefault *) node; |
||||||
|
|
||||||
|
APP_JUMB(sd->typeId); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CurrentOfExpr: |
||||||
|
{ |
||||||
|
CurrentOfExpr *ce = (CurrentOfExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(ce->cvarno); |
||||||
|
if (ce->cursor_name) |
||||||
|
APP_JUMB_STRING(ce->cursor_name); |
||||||
|
APP_JUMB(ce->cursor_param); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_NextValueExpr: |
||||||
|
{ |
||||||
|
NextValueExpr *nve = (NextValueExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(nve->seqid); |
||||||
|
APP_JUMB(nve->typeId); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_InferenceElem: |
||||||
|
{ |
||||||
|
InferenceElem *ie = (InferenceElem *) node; |
||||||
|
|
||||||
|
APP_JUMB(ie->infercollid); |
||||||
|
APP_JUMB(ie->inferopclass); |
||||||
|
JumbleExpr(jstate, ie->expr); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_TargetEntry: |
||||||
|
{ |
||||||
|
TargetEntry *tle = (TargetEntry *) node; |
||||||
|
|
||||||
|
APP_JUMB(tle->resno); |
||||||
|
APP_JUMB(tle->ressortgroupref); |
||||||
|
JumbleExpr(jstate, (Node *) tle->expr); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_RangeTblRef: |
||||||
|
{ |
||||||
|
RangeTblRef *rtr = (RangeTblRef *) node; |
||||||
|
|
||||||
|
APP_JUMB(rtr->rtindex); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_JoinExpr: |
||||||
|
{ |
||||||
|
JoinExpr *join = (JoinExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(join->jointype); |
||||||
|
APP_JUMB(join->isNatural); |
||||||
|
APP_JUMB(join->rtindex); |
||||||
|
JumbleExpr(jstate, join->larg); |
||||||
|
JumbleExpr(jstate, join->rarg); |
||||||
|
JumbleExpr(jstate, join->quals); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_FromExpr: |
||||||
|
{ |
||||||
|
FromExpr *from = (FromExpr *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, (Node *) from->fromlist); |
||||||
|
JumbleExpr(jstate, from->quals); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_OnConflictExpr: |
||||||
|
{ |
||||||
|
OnConflictExpr *conf = (OnConflictExpr *) node; |
||||||
|
|
||||||
|
APP_JUMB(conf->action); |
||||||
|
JumbleExpr(jstate, (Node *) conf->arbiterElems); |
||||||
|
JumbleExpr(jstate, conf->arbiterWhere); |
||||||
|
JumbleExpr(jstate, (Node *) conf->onConflictSet); |
||||||
|
JumbleExpr(jstate, conf->onConflictWhere); |
||||||
|
APP_JUMB(conf->constraint); |
||||||
|
APP_JUMB(conf->exclRelIndex); |
||||||
|
JumbleExpr(jstate, (Node *) conf->exclRelTlist); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_List: |
||||||
|
foreach(temp, (List *) node) |
||||||
|
{ |
||||||
|
JumbleExpr(jstate, (Node *) lfirst(temp)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_IntList: |
||||||
|
foreach(temp, (List *) node) |
||||||
|
{ |
||||||
|
APP_JUMB(lfirst_int(temp)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_SortGroupClause: |
||||||
|
{ |
||||||
|
SortGroupClause *sgc = (SortGroupClause *) node; |
||||||
|
|
||||||
|
APP_JUMB(sgc->tleSortGroupRef); |
||||||
|
APP_JUMB(sgc->eqop); |
||||||
|
APP_JUMB(sgc->sortop); |
||||||
|
APP_JUMB(sgc->nulls_first); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_GroupingSet: |
||||||
|
{ |
||||||
|
GroupingSet *gsnode = (GroupingSet *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, (Node *) gsnode->content); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_WindowClause: |
||||||
|
{ |
||||||
|
WindowClause *wc = (WindowClause *) node; |
||||||
|
|
||||||
|
APP_JUMB(wc->winref); |
||||||
|
APP_JUMB(wc->frameOptions); |
||||||
|
JumbleExpr(jstate, (Node *) wc->partitionClause); |
||||||
|
JumbleExpr(jstate, (Node *) wc->orderClause); |
||||||
|
JumbleExpr(jstate, wc->startOffset); |
||||||
|
JumbleExpr(jstate, wc->endOffset); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_CommonTableExpr: |
||||||
|
{ |
||||||
|
CommonTableExpr *cte = (CommonTableExpr *) node; |
||||||
|
|
||||||
|
/* we store the string name because RTE_CTE RTEs need it */ |
||||||
|
APP_JUMB_STRING(cte->ctename); |
||||||
|
APP_JUMB(cte->ctematerialized); |
||||||
|
JumbleQueryInternal(jstate, castNode(Query, cte->ctequery)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_SetOperationStmt: |
||||||
|
{ |
||||||
|
SetOperationStmt *setop = (SetOperationStmt *) node; |
||||||
|
|
||||||
|
APP_JUMB(setop->op); |
||||||
|
APP_JUMB(setop->all); |
||||||
|
JumbleExpr(jstate, setop->larg); |
||||||
|
JumbleExpr(jstate, setop->rarg); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_RangeTblFunction: |
||||||
|
{ |
||||||
|
RangeTblFunction *rtfunc = (RangeTblFunction *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, rtfunc->funcexpr); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_TableFunc: |
||||||
|
{ |
||||||
|
TableFunc *tablefunc = (TableFunc *) node; |
||||||
|
|
||||||
|
JumbleExpr(jstate, tablefunc->docexpr); |
||||||
|
JumbleExpr(jstate, tablefunc->rowexpr); |
||||||
|
JumbleExpr(jstate, (Node *) tablefunc->colexprs); |
||||||
|
} |
||||||
|
break; |
||||||
|
case T_TableSampleClause: |
||||||
|
{ |
||||||
|
TableSampleClause *tsc = (TableSampleClause *) node; |
||||||
|
|
||||||
|
APP_JUMB(tsc->tsmhandler); |
||||||
|
JumbleExpr(jstate, (Node *) tsc->args); |
||||||
|
JumbleExpr(jstate, (Node *) tsc->repeatable); |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
/* Only a warning, since we can stumble along anyway */ |
||||||
|
elog(WARNING, "unrecognized node type: %d", |
||||||
|
(int) nodeTag(node)); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Record location of constant within query string of query tree |
||||||
|
* that is currently being walked. |
||||||
|
*/ |
||||||
|
static void |
||||||
|
RecordConstLocation(JumbleState *jstate, int location) |
||||||
|
{ |
||||||
|
/* -1 indicates unknown or undefined location */ |
||||||
|
if (location >= 0) |
||||||
|
{ |
||||||
|
/* enlarge array if needed */ |
||||||
|
if (jstate->clocations_count >= jstate->clocations_buf_size) |
||||||
|
{ |
||||||
|
jstate->clocations_buf_size *= 2; |
||||||
|
jstate->clocations = (LocationLen *) |
||||||
|
repalloc(jstate->clocations, |
||||||
|
jstate->clocations_buf_size * |
||||||
|
sizeof(LocationLen)); |
||||||
|
} |
||||||
|
jstate->clocations[jstate->clocations_count].location = location; |
||||||
|
/* initialize lengths to -1 to simplify third-party module usage */ |
||||||
|
jstate->clocations[jstate->clocations_count].length = -1; |
||||||
|
jstate->clocations_count++; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* queryjumble.h |
||||||
|
* Query normalization and fingerprinting. |
||||||
|
* |
||||||
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California |
||||||
|
* |
||||||
|
* IDENTIFICATION |
||||||
|
* src/include/utils/queryjumble.h |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
#ifndef QUERYJUBLE_H |
||||||
|
#define QUERYJUBLE_H |
||||||
|
|
||||||
|
#include "nodes/parsenodes.h" |
||||||
|
|
||||||
|
#define JUMBLE_SIZE 1024 /* query serialization buffer size */ |
||||||
|
|
||||||
|
/*
|
||||||
|
* Struct for tracking locations/lengths of constants during normalization |
||||||
|
*/ |
||||||
|
typedef struct LocationLen |
||||||
|
{ |
||||||
|
int location; /* start offset in query text */ |
||||||
|
int length; /* length in bytes, or -1 to ignore */ |
||||||
|
} LocationLen; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Working state for computing a query jumble and producing a normalized |
||||||
|
* query string |
||||||
|
*/ |
||||||
|
typedef struct JumbleState |
||||||
|
{ |
||||||
|
/* Jumble of current query tree */ |
||||||
|
unsigned char *jumble; |
||||||
|
|
||||||
|
/* Number of bytes used in jumble[] */ |
||||||
|
Size jumble_len; |
||||||
|
|
||||||
|
/* Array of locations of constants that should be removed */ |
||||||
|
LocationLen *clocations; |
||||||
|
|
||||||
|
/* Allocated length of clocations array */ |
||||||
|
int clocations_buf_size; |
||||||
|
|
||||||
|
/* Current number of valid entries in clocations array */ |
||||||
|
int clocations_count; |
||||||
|
|
||||||
|
/* highest Param id we've seen, in order to start normalization correctly */ |
||||||
|
int highest_extern_param_id; |
||||||
|
} JumbleState; |
||||||
|
|
||||||
|
const char *CleanQuerytext(const char *query, int *location, int *len); |
||||||
|
JumbleState *JumbleQuery(Query *query, const char *querytext); |
||||||
|
|
||||||
|
#endif /* QUERYJUMBLE_H */ |
||||||
Loading…
Reference in new issue