* Copyright (c) 2020-2025 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 2021, openGauss Contributors
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* ---------------------------------------------------------------------------------------
*
* auto_parameterization.cpp
* A module that turns a "simple query" into a parameterized query
*
* IDENTIFICATION
* src/gausskernel/optimizer/commands/auto_parameterization.cpp
*
* ---------------------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "securec.h"
#include "nodes/nodes.h"
#include "nodes/nodeFuncs.h"
#include "nodes/execnodes.h"
#include "nodes/parsenodes_common.h"
#include "nodes/params.h"
#include "nodes/makefuncs.h"
#include "nodes/primnodes.h"
#include "parser/scanner.h"
#include "utils/int8.h"
#include "utils/plancache.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/portal.h"
#include "utils/snapmgr.h"
#include "utils/varbit.h"
#include "tcop/utility.h"
#include "tcop/dest.h"
#include "parser/analyze.h"
#include "parser/parse_expr.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_node.h"
#include "rewrite/rewriteHandler.h"
#include "replication/libpqsw.h"
#include "executor/executor.h"
#include "opfusion/opfusion.h"
#include "opfusion/opfusion_util.h"
#include "optimizer/bucketpruning.h"
#include "access/printtup.h"
#include "commands/auto_parameterization.h"
static void insertIntoParameterizedHashTable(ParamCachedKey* key, CachedPlanSource* psrc, bool* found);
static void storeParamCachedPlan(ParamCachedKey* key, CachedPlanSource* psrc);
static ParamCachedPlan* fetchCachedPlan(ParamCachedKey* key);
static char* execParameterization(Node* parsetree, ParameterizationInfo* paramContext);
static bool parameterizeParsetree(Node* node, ParameterizationInfo* paramContext);
static void fillInConstantLengths(ParamState* pstate, const char* query);
static bool isNodeSkipParam(Node* node);
static inline int compLocation(const void* a, const void* b);
static bool canTurnParam(Node* node, Node* a_const, ParameterizationInfo* param);
static char* generateNormalizedQuery(ParamState* pstate, const char* query, int* queryLenP, int encoding);
static ParamListInfo syncParams(Oid* paramTypes, int numParams, List* params, CachedPlanSource* psrc,
const char* queryString, EState* estate);
static void dropFromQueryHashTable(const ParamCachedKey* key);
static void dropParamCachedPlan(CachedPlanSource* plansource);
extern uint32 cachedPlanKeyHashFunc(const void* key, Size keysize);
extern int cachedPlanKeyHashMatch(const void* key1, const void* key2, Size keysize);
static bool composeParamInfo(ParameterizationInfo* paramInfo);
static void tryTurnConstToParam(Node** node, Node* a_const, ParameterizationInfo* paramInfo, bool* res,
ListCell* lc = NULL);
static void makeParamKey(ParamCachedKey* paramCachedKey, ParameterizationInfo* paramInfo, char* parameterizedQuery,
Oid relOid);
static bool executeParamQuery(CachedPlanSource* psrc, ParamListInfo paramListInfo, DestReceiver* dest,
char* completionTag, CommandDest cmdDest);
static bool tryBypass(CachedPlanSource* psrc, ParameterizationInfo* paramInfo, DestReceiver* dest,
ParamListInfo* paramListInfo, char* completionTag);
static CachedPlanSource* buildParamCachedPlan(Node* parsetree, const char* queryString, ParamCachedKey* paramCachedKey,
ParameterizationInfo* paramInfo, ParamListInfo* paramListInfo,
MemoryContext oldContext);
void saveParamCachedPlan(CachedPlanSource* plansource);
static void parsetreeRollBack(ParameterizationInfo* paramInfo);
static bool isTypeValid(Oid argType);
static bool validateType(ParamLocationLen clocations[], Oid* argTypes, int nargs);
static bool IsStringLengthValid(const char* queryString);
static bool IsQualifiedParsetree(Node* parsetree, RangeVar** rel);
static bool IsQualifiedTbl(RangeVar* rel, Oid* relOid);
static bool isDataValid(Oid paramType, Node* node);
char* query_type_text[FIXED_QUERY_TYPE_LEN] = {"OTHERS", "INSERT", "UPDATE", "DELETE"};
bool isQualifiedIuds(Node* parsetree, const char* queryString, Oid* relOid)
{
*relOid = InvalidOid;
if (!IsStringLengthValid(queryString)) {
return false;
}
RangeVar* rel = NULL;
bool res = IsQualifiedParsetree(parsetree, &rel);
if (res == false || rel == NULL || rel->relname == NULL) {
return false;
}
if (!IsQualifiedTbl(rel, relOid)) {
return false;
}
return true;
}
static bool IsStringLengthValid(const char* queryString)
{
return (strlen(queryString) <= MAX_PARAM_QUERY_LEN);
}
static bool IsQualifiedParsetree(Node* parsetree, RangeVar** rel)
{
bool res = true;
switch (nodeTag(parsetree)) {
case T_InsertStmt: {
InsertStmt* stmt = (InsertStmt*)parsetree;
if (stmt->relation == NULL || stmt->returningList != NIL || stmt->withClause != NULL ||
stmt->upsertClause != NULL || stmt->hintState != NULL || stmt->selectStmt == NULL || stmt->hasIgnore ||
((SelectStmt*)stmt->selectStmt)->valuesLists == NULL) {
res = false;
}
if (stmt->relation != NULL) {
*rel = (RangeVar*)stmt->relation;
}
break;
}
case T_DeleteStmt: {
DeleteStmt* stmt = (DeleteStmt*)parsetree;
if (stmt->whereClause == NULL || stmt->usingClause != NULL || stmt->returningList != NIL ||
stmt->withClause != NULL || stmt->hintState != NULL || stmt->sortClause != NULL ||
stmt->limitClause != NULL || stmt->relations == NIL || list_length(stmt->relations) != 1) {
res = false;
}
if (list_length(stmt->relations) == 1) {
*rel = (RangeVar*)linitial(stmt->relations);
}
break;
}
case T_UpdateStmt: {
UpdateStmt* stmt = (UpdateStmt*)parsetree;
if (stmt->returningList != NULL || stmt->withClause != NULL || stmt->hintState != NULL ||
stmt->sortClause != NULL || stmt->limitClause != NULL || stmt->hasIgnore ||
stmt->relationClause == NIL || list_length(stmt->relationClause) != 1) {
res = false;
}
if (list_length(stmt->relationClause) == 1) {
*rel = (RangeVar*)linitial(stmt->relationClause);
if ((*rel)->partitionname != NULL) {
res = false;
}
}
break;
}
default:
res = false;
}
return res;
}
static bool IsQualifiedTbl(RangeVar* rel, Oid* relOid)
{
*relOid = RelnameGetRelid(rel->relname);
if (!OidIsValid(*relOid)) {
return false;
}
Relation relation = heap_open(*relOid, NoLock);
if (RelationIsPartitioned(relation)) {
heap_close(relation, NoLock);
return false;
}
heap_close(relation, NoLock);
return true;
}
static bool isNodeSkipParam(Node* node)
{
bool res = false;
switch (nodeTag(node)) {
case T_FuncCall:
res = true;
break;
case T_SortGroupClause:
res = true;
break;
case T_CollateClause:
res = true;
break;
case T_CollateExpr:
res = true;
break;
case T_TypeName:
res = true;
break;
case T_TypeCast:
res = true;
break;
case T_SortBy:
res = true;
break;
case T_HintState:
case T_OuterInnerRels:
case T_JoinMethodHint:
case T_LeadingHint:
case T_RowsHint:
case T_StreamHint:
case T_BlockNameHint:
case T_ScanMethodHint:
case T_MultiNodeHint:
case T_PredpushHint:
case T_PredpushSameLevelHint:
case T_SkewHint:
case T_RewriteHint:
case T_GatherHint:
case T_SetHint:
case T_PlanCacheHint:
case T_NoExpandHint:
case T_SqlIgnoreHint:
case T_NoGPCHint:
case T_NullTest:
case T_SubLink:
case T_CaseExpr:
case T_PredictByFunction:
res = true;
break;
default:
break;
}
return res;
}
static ParamListInfo syncParams(Oid* paramTypes, int numParams, List* params, CachedPlanSource* psrc,
const char* queryString, EState* estate)
{
ParamListInfo paramListInfo;
ParseState* pstate = NULL;
Oid paramCollation;
int paramCharset;
int i;
ListCell* lc = NULL;
List* exprstates = NIL;
if (numParams == 0) {
return NULL;
}
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
paramCollation = GetCollationConnection();
paramCharset = GetCharsetConnection();
i = 0;
foreach (lc, params) {
Node* expr = (Node*)lfirst(lc);
Oid expectedTypeId = paramTypes[i];
Oid givenTypeId;
expr = transformExpr(pstate, expr, EXPR_KIND_EXECUTE_PARAMETER);
if (pstate->p_hasSubLinks)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can't use sublinks in parameterization")));
if (pstate->p_hasAggs)
ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("can't use agg function in parameterization")));
if (pstate->p_hasWindowFuncs)
ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), errmsg("can't use window function in parameterization")));
givenTypeId = exprType(expr);
expr = coerce_to_target_type(pstate, expr, givenTypeId, expectedTypeId, -1, COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST, NULL, NULL, -1);
if (expr == NULL)
ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("parameter $%d of type %s cannot be coerced to the expected type %s", i + 1,
format_type_be(givenTypeId), format_type_be(expectedTypeId)),
errhint("You will need to rewrite or cast the expression.")));
assign_expr_collations(pstate, expr);
if (OidIsValid(paramCollation) && IsSupportCharsetType(expectedTypeId)) {
expr = coerce_to_target_charset(expr, paramCharset, expectedTypeId, -1, paramCollation, false);
}
lfirst(lc) = expr;
i++;
}
pfree_ext(pstate);
paramListInfo = (ParamListInfo)palloc(offsetof(ParamListInfoData, params) + numParams * sizeof(ParamExternData));
paramListInfo->paramFetch = NULL;
paramListInfo->paramFetchArg = NULL;
paramListInfo->parserSetup = NULL;
paramListInfo->parserSetupArg = NULL;
paramListInfo->params_need_process = false;
paramListInfo->numParams = numParams;
paramListInfo->uParamInfo = DEFUALT_INFO;
paramListInfo->params_lazy_bind = false;
bool isInsertConst = IsA(psrc->raw_parse_tree, InsertStmt);
foreach (lc, params) {
if (!IsA(lfirst(lc), Const)) {
isInsertConst = false;
break;
}
}
i = 0;
if (isInsertConst) {
foreach (lc, params) {
Const* e = (Const*)lfirst(lc);
ParamExternData* prm = ¶mListInfo->params[i];
prm->ptype = paramTypes[i];
prm->pflags = PARAM_FLAG_CONST;
prm->value = e->constvalue;
prm->isnull = e->constisnull;
prm->tabInfo = NULL;
i++;
}
} else {
exprstates = ExecPrepareExprList(params, estate);
foreach (lc, exprstates) {
ExprState* n = (ExprState*)lfirst(lc);
ParamExternData* prm = ¶mListInfo->params[i];
prm->ptype = paramTypes[i];
prm->pflags = PARAM_FLAG_CONST;
prm->value = ExecEvalExprSwitchContext(n, GetPerTupleExprContext(estate), &prm->isnull);
prm->tabInfo = NULL;
i++;
}
}
return paramListInfo;
}
bool execQueryParameterization(Node* parsetree, const char* queryString, CommandDest cmdDest, char* completionTag,
Oid relOid)
{
ParameterizationInfo* paramInfo = NULL;
ParamCachedKey paramCachedKey;
MemoryContext oldContext = NULL;
ParamListInfo paramListInfo = NULL;
DestReceiver* dest = NULL;
CachedPlanSource* psrc = NULL;
EState* estate = NULL;
ParamListInfo params = NULL;
paramInfo = (ParameterizationInfo*)palloc0(sizeof(ParameterizationInfo));
paramInfo->parent_node = parsetree;
paramInfo->query_string = queryString;
paramInfo->is_skip = false;
char* parameterizedQuery = execParameterization(parsetree, paramInfo);
if (parameterizedQuery == NULL || paramInfo->param_count == 0) {
return false;
}
if (!composeParamInfo(paramInfo)) {
return false;
}
makeParamKey(¶mCachedKey, paramInfo, parameterizedQuery, relOid);
dest = CreateDestReceiver(cmdDest);
ParamCachedPlan* paramCachedPlan = fetchCachedPlan(¶mCachedKey);
if (paramCachedPlan != NULL) {
psrc = paramCachedPlan->psrc;
t_thrd.postgres_cxt.cur_command_tag = transform_node_tag(psrc->raw_parse_tree);
if (!validateType(paramInfo->param_state.clocations, psrc->param_types, psrc->num_params)) {
parsetreeRollBack(paramInfo);
return false;
}
estate = CreateExecutorState();
estate->es_param_list_info = params;
paramListInfo = syncParams(psrc->param_types, psrc->num_params, paramInfo->params, psrc, queryString, estate);
if (tryBypass(psrc, paramInfo, dest, ¶mListInfo, completionTag)) {
return true;
}
if (executeParamQuery(psrc, paramListInfo, dest, completionTag, cmdDest)) {
return true;
}
} else {
oldContext = MemoryContextSwitchTo(u_sess->param_cxt.query_param_cxt);
psrc = buildParamCachedPlan(parsetree, queryString, ¶mCachedKey, paramInfo, ¶mListInfo, oldContext);
(void)MemoryContextSwitchTo(oldContext);
if (psrc == NULL) {
parsetreeRollBack(paramInfo);
return false;
}
if (executeParamQuery(psrc, paramListInfo, dest, completionTag, cmdDest)) {
return true;
}
}
parsetreeRollBack(paramInfo);
pfree_ext(paramListInfo);
pfree_ext(paramInfo);
return false;
}
static void storeParamCachedPlan(ParamCachedKey* key, CachedPlanSource* psrc)
{
bool found = false;
if (unlikely(!u_sess->param_cxt.parameterized_queries)) {
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("hash table for parameterized query does not exist")));
}
insertIntoParameterizedHashTable(key, psrc, &found);
if (found) {
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_PSTATEMENT),
errmsg("parameterized query \"%s\" already exists", key->parameterized_query)));
}
saveParamCachedPlan(psrc);
return;
}
static void insertIntoParameterizedHashTable(ParamCachedKey* key, CachedPlanSource* psrc, bool* found)
{
ParamCachedPlan* entry = NULL;
entry = (ParamCachedPlan*)hash_search(u_sess->param_cxt.parameterized_queries, key, HASH_ENTER, found);
if (!(*found)) {
entry->psrc = psrc;
}
return;
}
static char* execParameterization(Node* parsetree, ParameterizationInfo* paramInfo)
{
paramInfo->parent_node = parsetree;
paramInfo->param_count = 0;
parameterizeParsetree(parsetree, paramInfo);
if (paramInfo->is_skip || paramInfo->param_count == 0) {
return NULL;
}
paramInfo->param_state.clocations_count = paramInfo->param_count;
const char* queryString = paramInfo->query_string;
int encoding = GetDatabaseEncoding();
int queryLen = strlen(queryString);
char* parameterizedQuery = generateNormalizedQuery(&(paramInfo->param_state), queryString, &queryLen, encoding);
parameterizedQuery[queryLen] = '\0';
return parameterizedQuery;
}
static ParamCachedPlan* fetchCachedPlan(ParamCachedKey* key)
{
if (u_sess->param_cxt.parameterized_queries == NULL) {
return NULL;
}
ParamCachedPlan* entry = NULL;
entry = (ParamCachedPlan*)hash_search(u_sess->param_cxt.parameterized_queries, key, HASH_FIND, NULL);
if (entry == NULL) {
ereport(LOG, (errcode(ERRCODE_UNDEFINED_PSTATEMENT),
errmsg("ParamCachedPlan %s not found\n", key->parameterized_query)));
}
return entry;
}
static bool parameterizeParsetree(Node* node, ParameterizationInfo* paramInfo)
{
if (node == NULL) {
return false;
}
if (paramInfo->is_skip) {
return true;
}
if (IsA(node, ParamRef)) {
paramInfo->is_skip = true;
return true;
}
Node* saveParentNode = paramInfo->parent_node;
if (isNodeSkipParam(node)) {
paramInfo->is_skip = true;
return true;
} else if (IsA(node, A_Const)) {
canTurnParam(paramInfo->parent_node, node, paramInfo);
} else {
paramInfo->parent_node = node;
}
bool res = raw_expression_tree_walker(node, (bool (*)())parameterizeParsetree, paramInfo);
paramInfo->parent_node = saveParentNode;
return res;
}
static void tryTurnConstToParam(Node** node, Node* a_const, ParameterizationInfo* paramInfo, bool* res,
ListCell* lc)
{
if (*node == a_const) {
if (paramInfo->param_count == MAX_PARAM_NODES) {
paramInfo->is_skip = true;
*res = false;
return;
}
A_Const* tmp = (A_Const*)a_const;
ParamLocationLen* curParam = &(paramInfo->param_state.clocations[paramInfo->param_count]);
curParam->location = tmp->location;
curParam->node = *node;
curParam->node_addr = node;
curParam->lc = lc;
paramInfo->param_count++;
*res = true;
}
return;
}
static bool canTurnParam(Node* node, Node* a_const, ParameterizationInfo* param)
{
if (node == NULL) {
return false;
}
bool res = false;
switch (nodeTag(node)) {
case T_Alias:
case T_RangeVar:
case T_Expr:
case T_Var:
case T_Const:
case T_Param:
case T_Aggref:
case T_GroupingFunc:
case T_WindowFunc:
case T_ArrayRef:
case T_FuncExpr:
case T_NamedArgExpr:
case T_OpExpr:
case T_DistinctExpr:
case T_NullIfExpr:
case T_ScalarArrayOpExpr:
case T_BoolExpr:
case T_AlternativeSubPlan:
case T_FieldSelect:
case T_FieldStore:
case T_RelabelType:
case T_CoerceViaIO:
case T_CaseTestExpr:
case T_ArrayExpr:
case T_RowExpr:
case T_RowCompareExpr:
case T_CoalesceExpr:
case T_MinMaxExpr:
case T_XmlExpr:
case T_CoerceToDomainValue:
case T_SetToDefault:
case T_CurrentOfExpr:
case T_ParamRef:
case T_A_Star:
case T_TypeName:
case T_GroupingSet:
case T_FuncWithArgs:
case T_LockingClause:
case T_WithClause:
case T_StartWithInfo:
case T_SqlLoadColPosInfo:
case T_SqlLoadSequInfo:
case T_SqlLoadFillerInfo:
case T_SqlLoadConsInfo:
case T_RenameCell:
case T_FunctionPartitionInfo:
case T_SortBy:
case T_CaseExpr:
break;
case T_A_Indices: {
A_Indices *aIndices = (A_Indices *) node;
tryTurnConstToParam(&aIndices->lidx, a_const, param, &res);
tryTurnConstToParam(&aIndices->uidx, a_const, param, &res);
break;
}
case T_ResTarget: {
ResTarget *resTarget = (ResTarget *) node;
tryTurnConstToParam(&resTarget->val, a_const, param, &res);
break;
}
case T_A_Indirection: {
A_Indirection *aIndirection = (A_Indirection *) node;
tryTurnConstToParam(&aIndirection->arg, a_const, param, &res);
break;
}
case T_WindowDef: {
WindowDef *windowDef = (WindowDef *) node;
tryTurnConstToParam(&windowDef->startOffset, a_const, param, &res);
tryTurnConstToParam(&windowDef->endOffset, a_const, param, &res);
break;
}
case T_RangeTableSample: {
RangeTableSample *rangeTableSample = (RangeTableSample *) node;
tryTurnConstToParam(&rangeTableSample->relation, a_const, param, &res);
tryTurnConstToParam(&rangeTableSample->repeatable, a_const, param, &res);
break;
}
case T_ColumnDef: {
ColumnDef *columnDef = (ColumnDef *) node;
tryTurnConstToParam(&columnDef->raw_default, a_const, param, &res);
tryTurnConstToParam(&columnDef->cooked_default, a_const, param, &res);
tryTurnConstToParam(&columnDef->update_default, a_const, param, &res);
break;
}
case T_Constraint: {
Constraint *constraint = (Constraint *) node;
tryTurnConstToParam(&constraint->raw_expr, a_const, param, &res);
tryTurnConstToParam(&constraint->where_clause, a_const, param, &res);
tryTurnConstToParam(&constraint->update_expr, a_const, param, &res);
break;
}
case T_DefElem: {
DefElem *defElem = (DefElem *) node;
tryTurnConstToParam(&defElem->arg, a_const, param, &res);
break;
}
case T_TimeCapsuleClause: {
TimeCapsuleClause *timeCapsuleClause = (TimeCapsuleClause *) node;
tryTurnConstToParam(&timeCapsuleClause->tvver, a_const, param, &res);
break;
}
case T_XmlSerialize: {
XmlSerialize *xmlSerialize = (XmlSerialize *) node;
tryTurnConstToParam(&xmlSerialize->expr, a_const, param, &res);
break;
}
case T_SqlLoadScalarSpec: {
SqlLoadScalarSpec *sqlLoadScalarSpec = (SqlLoadScalarSpec *) node;
tryTurnConstToParam(&sqlLoadScalarSpec->position_info, a_const, param, &res);
tryTurnConstToParam(&sqlLoadScalarSpec->sqlstr, a_const, param, &res);
break;
}
case T_SqlLoadColExpr: {
SqlLoadColExpr *sqlLoadColExpr = (SqlLoadColExpr *) node;
tryTurnConstToParam(&sqlLoadColExpr->const_info, a_const, param, &res);
tryTurnConstToParam(&sqlLoadColExpr->scalar_spec, a_const, param, &res);
tryTurnConstToParam(&sqlLoadColExpr->sequence_info, a_const, param, &res);
break;
}
case T_FunctionParameter: {
FunctionParameter *functionParameter = (FunctionParameter *) node;
tryTurnConstToParam(&functionParameter->defexpr, a_const, param, &res);
break;
}
case T_SubLink: {
SubLink *subLink = (SubLink *) node;
tryTurnConstToParam(&subLink->subselect, a_const, param, &res);
break;
}
case T_EstSPNode: {
EstSPNode *estSpNode = (EstSPNode *) node;
tryTurnConstToParam(&estSpNode->expr, a_const, param, &res);
break;
}
case T_InsertStmt: {
InsertStmt *insertStmt = (InsertStmt *) node;
tryTurnConstToParam(&insertStmt->selectStmt, a_const, param, &res);
break;
}
case T_A_Expr: {
A_Expr *aExpr = (A_Expr *) node;
tryTurnConstToParam(&aExpr->lexpr, a_const, param, &res);
tryTurnConstToParam(&aExpr->rexpr, a_const, param, &res);
break;
}
case T_List: {
ListCell *lc = NULL;
foreach(lc, (List*)node) {
Node* tmp = (Node*)lfirst(lc);
tryTurnConstToParam(&tmp, a_const, param, &res, lc);
}
break;
}
case T_SelectStmt: {
SelectStmt *selectStmt = (SelectStmt *) node;
tryTurnConstToParam(&selectStmt->limitOffset, a_const, param, &res);
tryTurnConstToParam(&selectStmt->limitCount, a_const, param, &res);
tryTurnConstToParam(&selectStmt->startWithClause, a_const, param, &res);
tryTurnConstToParam(&selectStmt->whereClause, a_const, param, &res);
tryTurnConstToParam(&selectStmt->havingClause, a_const, param, &res);
break;
}
case T_DeleteStmt: {
DeleteStmt *deleteStmt = (DeleteStmt *) node;
tryTurnConstToParam(&deleteStmt->whereClause, a_const, param, &res);
tryTurnConstToParam(&deleteStmt->limitClause, a_const, param, &res);
break;
}
case T_UpdateStmt: {
UpdateStmt *updateStmt = (UpdateStmt *) node;
tryTurnConstToParam(&updateStmt->whereClause, a_const, param, &res);
tryTurnConstToParam(&updateStmt->limitClause, a_const, param, &res);
break;
}
default:
ereport(ERROR,
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("Node type unrecognized: %d\n", nodeTag(node))));
}
return res;
}
static char* generateNormalizedQuery(ParamState* pstate, const char* query, int* queryLenP, int encoding)
{
char* normQuery = NULL;
int queryLen = *queryLenP;
int maxOutputLen, i, rc;
int lenToWrt;
int querLoc = 0;
int nQuerLoc = 0;
int lastOff = 0;
int lastTokLen = 0;
* Get constants' lengths (core system only gives us locations). Note
* this also ensures the items are sorted by location.
*/
fillInConstantLengths(pstate, query);
maxOutputLen = Min(queryLen, g_instance.attr.attr_common.pgstat_track_activity_query_size - 1);
normQuery = static_cast<char*>(palloc0(maxOutputLen + 1));
for (i = 0; i < pstate->clocations_count; i++) {
* off: Offset from start for cur tok
* tokLen: Length (in bytes) of that tok
*/
int off, tokLen;
off = pstate->clocations[i].location;
tokLen = pstate->clocations[i].length;
if (tokLen < 0) {
continue;
}
lenToWrt = off - lastOff;
lenToWrt -= lastTokLen;
lenToWrt = Min(lenToWrt, maxOutputLen - nQuerLoc);
* query string can't be matched(location in Query is bigger
* than query string)
* - delete from plan_table where statement_id='test statement_id',
* for sql 'delete plan_table', transformDeleteStmt method will
* modify Query member.
*/
if (lenToWrt <= 0) {
break;
}
rc = memcpy_s(normQuery + nQuerLoc, maxOutputLen - nQuerLoc, query + querLoc, lenToWrt);
securec_check(rc, "\0", "\0");
nQuerLoc += lenToWrt;
if (nQuerLoc < maxOutputLen) {
normQuery[nQuerLoc++] = '?';
}
querLoc = off + tokLen;
lastOff = off;
lastTokLen = tokLen;
if (nQuerLoc >= maxOutputLen) {
break;
}
}
* We've copied up until the last ignorable constant. Copy over the
* remaining bytes of the original query string, or at least as much as
* will fit.
*/
lenToWrt = queryLen - querLoc;
lenToWrt = Min(lenToWrt, maxOutputLen - nQuerLoc);
if (lenToWrt > 0) {
rc = memcpy_s(normQuery + nQuerLoc, maxOutputLen - nQuerLoc, query + querLoc, lenToWrt);
securec_check(rc, "\0", "\0");
nQuerLoc += lenToWrt;
}
* If we ran out of space, we need to do an encoding-aware truncation,
* just to make sure we don't have an incomplete character at the end.
*/
if (nQuerLoc >= maxOutputLen) {
queryLen = pg_encoding_mbcliplen(encoding, normQuery, nQuerLoc,
g_instance.attr.attr_common.pgstat_track_activity_query_size - 1);
} else {
queryLen = nQuerLoc;
}
*queryLenP = queryLen;
return normQuery;
}
static void fillInConstantLengths(ParamState* pstate, const char* query)
{
ParamLocationLen* locs = NULL;
core_yyscan_t yyscanner;
core_yy_extra_type yyextra;
core_YYSTYPE yylval;
YYLTYPE yylloc;
int lastLoc = -1;
int i;
* Sort the records by location so that we can process them in order while
* scanning the query text.
*/
if (pstate->clocations_count > 1) {
qsort(pstate->clocations, pstate->clocations_count, sizeof(ParamLocationLen), compLocation);
}
locs = pstate->clocations;
yyscanner = scanner_init(query, &yyextra, &ScanKeywords, ScanKeywordTokens);
void* coreYYlex = u_sess->hook_cxt.coreYYlexHook ? u_sess->hook_cxt.coreYYlexHook : (void*)core_yylex;
for (i = 0; i < pstate->clocations_count; i++) {
int loc = locs[i].location;
int tok;
Assert(loc >= 0);
if (loc <= lastLoc) {
continue;
}
for (;;) {
tok = ((coreYYlexFunc)coreYYlex)(&yylval, &yylloc, yyscanner);
if (tok == 0) {
break;
}
* We should find the token position exactly, but if we somehow
* run past it, work with that.
*/
if (yylloc >= loc) {
if (query[loc] == '-') {
tok = ((coreYYlexFunc)coreYYlex)(&yylval, &yylloc, yyscanner);
if (tok == 0) {
break;
}
}
* We now rely on the assumption that flex has placed a zero
* byte after the text of the current token in scanbuf.
*/
locs[i].length = strlen(yyextra.scanbuf + loc);
break;
}
}
if (tok == 0) {
break;
}
lastLoc = loc;
}
scanner_finish(yyscanner);
}
static inline int compLocation(const void* a, const void* b)
{
int l = ((const ParamLocationLen*)a)->location;
int r = ((const ParamLocationLen*)b)->location;
if (l < r) {
return -1;
} else if (l > r) {
return +1;
} else {
return 0;
}
}
void dropAllParameterizedQueries(void)
{
HASH_SEQ_STATUS seq;
ParamCachedPlan* entry = NULL;
ResourceOwner originalOwner = t_thrd.utils_cxt.CurrentResourceOwner;
if (!u_sess->param_cxt.parameterized_queries) {
return;
}
#define ReleaseTempResourceOwner() \
do { \
ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true); \
ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_LOCKS, false, true); \
ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, true); \
if (NULL == originalOwner && t_thrd.utils_cxt.CurrentResourceOwner) { \
ResourceOwner tempOwner = t_thrd.utils_cxt.CurrentResourceOwner; \
t_thrd.utils_cxt.CurrentResourceOwner = originalOwner; \
ResourceOwnerDelete(tempOwner); \
} \
} while (0);
if (NULL == originalOwner) {
* make sure ResourceOwner is not null, since it may acess catalog
* when the pooler tries to create new connections
*/
t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "dropAllParameterizedQueries",
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER));
}
bool failFlagDropCachedPlan = false;
ErrorData* edata = NULL;
MemoryContext oldContext = CurrentMemoryContext;
bool isSharedPlan = false;
hash_seq_init(&seq, u_sess->param_cxt.parameterized_queries);
while ((entry = (ParamCachedPlan*)hash_seq_search(&seq)) != NULL) {
PG_TRY();
{
Assert(entry->psrc->magic == CACHEDPLANSOURCE_MAGIC);
isSharedPlan = entry->psrc->gpc.status.InShareTable();
if (!isSharedPlan) {
CN_GPC_LOG("prepare remove private", entry->psrc, entry->paramCachedKey.parameterized_query);
dropParamCachedPlan(entry->psrc);
CN_GPC_LOG("prepare remove private succ", 0, entry->paramCachedKey.parameterized_query);
}
}
PG_CATCH();
{
failFlagDropCachedPlan = true;
MemoryContextSwitchTo(oldContext);
edata = CopyErrorData();
FlushErrorState();
ereport(LOG, (errmodule(MOD_EXECUTOR), errcode(ERRCODE_INTERNAL_ERROR),
errmsg("failed to drop cached plan when drop all prepared statements: %s", edata->message)));
FreeErrorData(edata);
}
PG_END_TRY();
if (isSharedPlan) {
CN_GPC_LOG("prepare remove ", entry->psrc, entry->paramCachedKey.parameterized_query);
entry->psrc->gpc.status.SubRefCount();
}
dropFromQueryHashTable(&entry->paramCachedKey);
}
ReleaseTempResourceOwner();
CN_GPC_LOG("remove prepare statment all", 0, 0);
ReleaseTempResourceOwner();
if (failFlagDropCachedPlan) {
destroy_handles();
ereport(ERROR,
(errmodule(MOD_EXECUTOR), errcode(ERRCODE_INTERNAL_ERROR), errmsg("failed to drop cached plan")));
}
}
static void dropFromQueryHashTable(const ParamCachedKey* key)
{
hash_search(u_sess->param_cxt.parameterized_queries, key, HASH_REMOVE, NULL);
}
* dropParamCachedPlan: destroy a cached plan.
*
* Actually this only destroys the CachedPlanSource: any referenced CachedPlan
* is released, but not destroyed until its refcount goes to zero. That
* handles the situation where DropCachedPlan is called while the plan is
* still in use.
*/
static void dropParamCachedPlan(CachedPlanSource* plansource)
{
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
if (plansource->is_saved) {
if (u_sess->param_cxt.first_saved_plan == plansource) {
u_sess->param_cxt.first_saved_plan = plansource->next_saved;
} else {
CachedPlanSource* psrc = NULL;
for (psrc = u_sess->param_cxt.first_saved_plan; psrc; psrc = psrc->next_saved) {
if (psrc->next_saved == plansource) {
psrc->next_saved = plansource->next_saved;
break;
}
}
}
plansource->is_saved = false;
}
plansource->next_saved = NULL;
DropCachedPlanInternal(plansource);
plansource->magic = 0;
* Remove the CachedPlanSource and all subsidiary data (including the
* query_context if any). But if it's a one-shot we can't free anything.
*/
if (!plansource->is_oneshot)
MemoryContextDelete(plansource->context);
}
extern uint32 cachedPlanKeyHashFunc(const void* key, Size keysize)
{
const ParamCachedKey* paramCachedKey = (const ParamCachedKey*)key;
uint32 hashValue1 =
DatumGetUInt32(hash_any((const unsigned char*)paramCachedKey->parameterized_query, paramCachedKey->query_len));
uint32 hashValue2 = DatumGetUInt32(
hash_any((const unsigned char*)paramCachedKey->param_types, paramCachedKey->num_param * sizeof(Oid)));
hashValue1 ^= hashValue2;
return hashValue1;
}
extern int cachedPlanKeyHashMatch(const void* key1, const void* key2, Size keysize)
{
ParamCachedKey* leftKey = (ParamCachedKey*)key1;
ParamCachedKey* rightKey = (ParamCachedKey*)key2;
Assert(leftKey != NULL);
Assert(rightKey != NULL);
if (memcmp(key1, key2, keysize)) {
return 1;
}
return 0;
}
static bool composeParamInfo(ParameterizationInfo* paramInfo)
{
if (paramInfo->param_state.clocations == NULL) {
return false;
}
A_Const* aconst;
paramInfo->param_types = (Oid*)palloc(sizeof(Oid) * paramInfo->param_count);
for (int i = 0; i < paramInfo->param_count; i++) {
Node* tmpNode = paramInfo->param_state.clocations[i].node;
aconst = (A_Const*)tmpNode;
int location = aconst->location;
Const* con = NULL;
Oid typeOid = 0;
con = make_const(NULL, &aconst->val, location);
typeOid = con->consttype;
paramInfo->param_types[i] = typeOid;
paramInfo->params = lappend(paramInfo->params, paramInfo->param_state.clocations[i].node);
ParamRef* paramRef = makeNode(ParamRef);
paramRef->location = location;
paramRef->number = i + 1;
if (paramInfo->param_state.clocations[i].lc == NULL) {
*(paramInfo->param_state.clocations[i].node_addr) = (Node*)paramRef;
} else {
lfirst(paramInfo->param_state.clocations[i].lc) = paramRef;
}
}
return true;
}
static void makeParamKey(ParamCachedKey* paramCachedKey, ParameterizationInfo* paramInfo, char* parameterizedQuery,
Oid relOid)
{
error_t rc = 0;
memset_s(paramCachedKey, sizeof(ParamCachedKey), 0, sizeof(ParamCachedKey));
int paramQueryLen = strlen(parameterizedQuery);
rc = memcpy_s(paramCachedKey->parameterized_query, MAX_PARAM_QUERY_LEN, parameterizedQuery, paramQueryLen);
securec_check(rc, "\0", "\0");
paramCachedKey->parameterized_query[paramQueryLen] = '\0';
rc = memcpy_s(paramCachedKey->param_types, sizeof(paramCachedKey->param_types), paramInfo->param_types,
paramInfo->param_count * sizeof(Oid));
securec_check(rc, "\0", "\0");
paramCachedKey->relOid = relOid;
paramCachedKey->query_len = paramQueryLen;
paramCachedKey->num_param = paramInfo->param_count;
return;
}
static CachedPlanSource* buildParamCachedPlan(Node* parsetree, const char* queryString, ParamCachedKey* paramCachedKey,
ParameterizationInfo* paramInfo, ParamListInfo* paramListInfo,
MemoryContext oldContext)
{
Query* query = NULL;
CachedPlanSource* psrc = NULL;
List* queryList = NIL;
EState* estate = NULL;
bool fixedResult = FORCE_VALIDATE_PLANCACHE_RESULT;
int nargs = 0;
ParamListInfo params = NULL;
if (u_sess->param_cxt.param_cached_plan_count >= u_sess->attr.attr_sql.max_parameterized_query_stored) {
dropAllParameterizedQueries();
u_sess->param_cxt.param_cached_plan_count = 0;
}
psrc = CreateCachedPlan(parsetree,
paramCachedKey->parameterized_query,
NULL,
CreateCommandTag(parsetree)
);
(void)MemoryContextSwitchTo(oldContext);
query = parse_analyze_varparams(parsetree, queryString, ¶mInfo->param_types, &nargs);
if (paramInfo->param_count != nargs) {
return NULL;
}
if (!validateType(paramInfo->param_state.clocations, paramInfo->param_types, paramInfo->param_count)) {
pfree_ext(psrc);
return NULL;
}
queryList = QueryRewrite(query);
estate = CreateExecutorState();
estate->es_param_list_info = params;
*paramListInfo =
syncParams(paramInfo->param_types, paramInfo->param_count, paramInfo->params, psrc, queryString, estate);
(void)MemoryContextSwitchTo(u_sess->param_cxt.query_param_cxt);
CompleteCachedPlan(psrc,
queryList,
NULL,
paramInfo->param_types,
NULL,
paramInfo->param_count,
NULL,
NULL,
0,
fixedResult,
"");
storeParamCachedPlan(paramCachedKey, psrc);
u_sess->param_cxt.param_cached_plan_count++;
return psrc;
}
static bool tryBypass(CachedPlanSource* psrc, ParameterizationInfo* paramInfo, DestReceiver* dest,
ParamListInfo* paramListInfo, char* completionTag)
{
if (!psrc->fixed_result && FORCE_VALIDATE_PLANCACHE_RESULT)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE does not support variable-result cached plans")));
OpFusion::clearForCplan((OpFusion*)psrc->opFusionObj, psrc);
if (psrc->opFusionObj != NULL) {
Assert(psrc->cplan == NULL);
(void)RevalidateCachedQuery(psrc);
}
if (psrc->opFusionObj != NULL) {
u_sess->param_cxt.use_parame = true;
OpFusion* opFusionObj = (OpFusion*)(psrc->opFusionObj);
opFusionObj->setPreparedDestReceiver(dest);
opFusionObj->useOuterParameter(*paramListInfo);
opFusionObj->setCurrentOpFusionObj(opFusionObj);
opFusionObj->m_local.m_isFirst = true;
CachedPlanSource* cps = opFusionObj->m_global->m_psrc;
bool needBucketId = cps != NULL && cps->gplan;
if (needBucketId) {
setCachedPlanBucketId(cps->gplan, *paramListInfo);
}
if (OpFusion::process(FUSION_EXECUTE, NULL, completionTag, false, NULL)) {
return true;
}
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Bypass process Failed")));
}
return false;
}
static bool executeParamQuery(CachedPlanSource* psrc, ParamListInfo paramListInfo, DestReceiver* dest,
char* completionTag, CommandDest cmdDest)
{
CachedPlan* cplan = NULL;
List* plan_list = NIL;
Portal portal = NULL;
long count = FETCH_ALL;
int eflags = 0;
u_sess->param_cxt.use_parame = true;
if (ENABLE_CACHEDPLAN_MGR) {
cplan = GetWiseCachedPlan(psrc, paramListInfo, false);
} else {
cplan = GetCachedPlan(psrc, paramListInfo, false);
}
plan_list = cplan->stmt_list;
if (OpFusion::IsSqlBypass(psrc, plan_list)) {
psrc->opFusionObj = OpFusion::FusionFactory(OpFusion::getFusionType(cplan, paramListInfo, NULL),
u_sess->param_cxt.query_param_cxt, psrc, NULL, paramListInfo);
psrc->is_checked_opfusion = true;
if (psrc->opFusionObj != NULL) {
((OpFusion*)psrc->opFusionObj)->setPreparedDestReceiver(dest);
((OpFusion*)psrc->opFusionObj)->useOuterParameter(paramListInfo);
((OpFusion*)psrc->opFusionObj)->setCurrentOpFusionObj((OpFusion*)psrc->opFusionObj);
if (OpFusion::process(FUSION_EXECUTE, NULL, completionTag, false, NULL)) {
return true;
}
Assert(0);
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Bypass process Failed")));
}
}
portal = CreateNewPortal();
portal->visible = false;
if (cmdDest == DestRemote) {
SetRemoteDestReceiverParams(dest, portal);
}
PortalDefineQuery(portal, NULL, psrc->query_string, psrc->commandTag, plan_list, cplan);
portal->nextval_default_expr_type = psrc->nextval_default_expr_type;
* Run the portal as appropriate.
*/
PortalStart(portal, paramListInfo, eflags, GetActiveSnapshot());
(void)MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt);
(void)PortalRun(portal, count, false, dest, dest, completionTag);
PortalDrop(portal, false);
pfree_ext(paramListInfo);
return true;
}
void saveParamCachedPlan(CachedPlanSource* plansource)
{
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
Assert(!plansource->is_saved);
Assert(plansource->gpc.status.InShareTable() == false);
if (plansource->is_oneshot)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot save one-shot cached plan")));
* In typical use, this function would be called before generating any
* plans from the CachedPlanSource. If there is a generic plan, moving it
* into u_sess->cache_mem_cxt would be pretty risky since it's unclear
* whether the caller has taken suitable care with making references
* long-lived. Best thing to do seems to be to discard the plan.
*/
ReleaseGenericPlan(plansource);
START_CRIT_SECTION();
* Add the entry to the session's global list of cached plans.
*/
plansource->next_saved = u_sess->param_cxt.first_saved_plan;
u_sess->param_cxt.first_saved_plan = plansource;
plansource->is_saved = true;
END_CRIT_SECTION();
}
static void parsetreeRollBack(ParameterizationInfo* paramInfo)
{
int paramCount = paramInfo->param_count;
for (int i = 0; i < paramCount; i++) {
Node* originNode = paramInfo->param_state.clocations[i].node;
if (paramInfo->param_state.clocations[i].lc == NULL) {
*(paramInfo->param_state.clocations[i].node_addr) = originNode;
} else {
lfirst(paramInfo->param_state.clocations[i].lc) = originNode;
}
}
return;
}
static bool isDataValid(Oid paramType, Node* node)
{
A_Const* aconst = (A_Const*)node;
Value value = aconst->val;
switch (paramType) {
case INT1OID: {
long num = value.val.ival;
if (num < SCHAR_MIN || num > SCHAR_MAX) {
return false;
}
break;
}
case INT2OID: {
long num = value.val.ival;
if (num < SHRT_MIN || num > SHRT_MAX) {
return false;
}
break;
}
case INT4OID: {
long num = value.val.ival;
if (num < INT_MIN || num > INT_MAX) {
return false;
}
break;
}
case INT8OID: {
int64 num = 0L;
if (nodeTag(&value) == T_Float) {
if (!scanint8(strVal(&value), true, &num)) {
return false;
}
if (num < LONG_MIN || num > LONG_MAX) {
return false;
}
}
break;
}
default:
break;
}
return true;
}
static bool isTypeValid(Oid argType)
{
so we do not want handle it at least now */
if (argType == InvalidOid || argType == UNKNOWNOID || argType == INT16OID) {
return false;
}
return true;
}
static bool validateType(ParamLocationLen clocations[], Oid* argTypes, int nargs)
{
Node* node = NULL;
Oid argType = InvalidOid;
for (int i = 0; i < nargs; i++) {
node = clocations[i].node;
argType = argTypes[i];
if (!isTypeValid(argType) || !isDataValid(argType, node)) {
return false;
}
}
return true;
}