| /*------------------------------------------------------------------------- |
| * |
| * 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 */ |
| |
| /* GUC parameters */ |
| int compute_query_id = COMPUTE_QUERY_ID_AUTO; |
| |
| /* True when compute_query_id is ON, or AUTO and a module requests them */ |
| bool query_id_enabled = false; |
| |
| static uint64 compute_utility_query_id(const char *str, int query_location, 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 * |
| JumbleQueryDirect(Query *query, const char *querytext) |
| { |
| JumbleState *jstate = NULL; |
| |
| Assert(IsA(query, Query)); |
| Assert(query->utilityStmt == NULL); |
| |
| 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; |
| } |
| |
| JumbleState * |
| JumbleQuery(Query *query, const char *querytext) |
| { |
| Assert(IsQueryIdEnabled()); |
| |
| if (query->utilityStmt) |
| { |
| query->queryId = compute_utility_query_id(querytext, |
| query->stmt_location, |
| query->stmt_len); |
| return NULL; |
| } |
| else |
| { |
| return JumbleQueryDirect(query, querytext); |
| } |
| } |
| |
| /* |
| * Enables query identifier computation. |
| * |
| * Third-party plugins can use this function to inform core that they require |
| * a query identifier to be computed. |
| */ |
| void |
| EnableQueryId(void) |
| { |
| if (compute_query_id != COMPUTE_QUERY_ID_OFF) |
| query_id_enabled = true; |
| } |
| |
| /* |
| * Compute a query identifier for the given utility query string. |
| */ |
| static uint64 |
| compute_utility_query_id(const char *query_text, int query_location, int query_len) |
| { |
| uint64 queryId; |
| const char *sql; |
| |
| /* |
| * Confine our attention to the relevant part of the string, if the query |
| * is a portion of a multi-statement source string. |
| */ |
| sql = CleanQuerytext(query_text, &query_location, &query_len); |
| |
| queryId = DatumGetUInt64(hash_any_extended((const unsigned char *) sql, |
| 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) |
| { |
| 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); |
| APP_JUMB(query->groupDistinct); |
| 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); |
| APP_JUMB(query->limitOption); |
| 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); |
| APP_JUMB(rte->inh); |
| 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); |
| APP_JUMB(grpnode->agglevelsup); |
| } |
| 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++; |
| } |
| } |