forked from huawei/openGauss-server
565 lines
22 KiB
C++
565 lines
22 KiB
C++
/* -------------------------------------------------------------------------
|
|
*
|
|
* preptlist.cpp
|
|
* Routines to preprocess the parse tree target list
|
|
*
|
|
* For INSERT and UPDATE queries, the targetlist must contain an entry for
|
|
* each attribute of the target relation in the correct order. For all query
|
|
* types, we may need to add junk tlist entries for Vars used in the RETURNING
|
|
* list and row ID information needed for EvalPlanQual checking.
|
|
*
|
|
* NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD
|
|
* routines also do preprocessing of the targetlist. The division of labor
|
|
* between here and there is a bit arbitrary and historical.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/optimizer/prep/preptlist.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/sysattr.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "foreign/fdwapi.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/prep.h"
|
|
#include "optimizer/streamplan.h"
|
|
#include "optimizer/tlist.h"
|
|
#include "parser/parsetree.h"
|
|
#include "parser/parse_coerce.h"
|
|
#include "parser/parse_merge.h"
|
|
#include "pgxc/pgxc.h"
|
|
#include "rewrite/rewriteHandler.h"
|
|
#include "tcop/utility.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/rel_gs.h"
|
|
|
|
static List* expand_targetlist(List* tlist, int command_type, Index result_relation, List* range_table);
|
|
#ifdef STREAMPLAN
|
|
static List* add_distribute_column(List* tlist, Index result_relation, List* range_table);
|
|
#endif
|
|
|
|
/*
|
|
* preprocess_targetlist
|
|
* Driver for preprocessing the parse tree targetlist.
|
|
*
|
|
* Returns the new targetlist.
|
|
*/
|
|
List* preprocess_targetlist(PlannerInfo* root, List* tlist)
|
|
{
|
|
Query* parse = root->parse;
|
|
int result_relation = parse->resultRelation;
|
|
List* range_table = parse->rtable;
|
|
CmdType command_type = parse->commandType;
|
|
ListCell* lc = NULL;
|
|
|
|
/*
|
|
* Sanity check: if there is a result relation, it'd better be a real
|
|
* relation not a subquery. Else parser or rewriter messed up.
|
|
*/
|
|
if (result_relation) {
|
|
RangeTblEntry* rte = rt_fetch(result_relation, range_table);
|
|
|
|
if (rte->subquery != NULL || rte->relid == InvalidOid)
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
|
(errmsg("subquery cannot be result relation"))));
|
|
}
|
|
|
|
/*
|
|
* for heap_form_tuple to work, the targetlist must match the exact order
|
|
* of the attributes. We also need to fill in any missing attributes. -ay
|
|
* 10/94
|
|
*/
|
|
if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
|
|
tlist = expand_targetlist(tlist, command_type, result_relation, range_table);
|
|
#ifdef STREAMPLAN
|
|
else if (IS_STREAM_PLAN && command_type == CMD_DELETE)
|
|
tlist = add_distribute_column(tlist, result_relation, range_table);
|
|
#endif
|
|
|
|
if (command_type == CMD_MERGE) {
|
|
ListCell* l = NULL;
|
|
bool is_qual_expanded = false;
|
|
|
|
/*
|
|
* For MERGE, add any junk column(s) needed to allow the executor to
|
|
* identify the rows to be updated or deleted, with different
|
|
* handling for partitioned tables.
|
|
*/
|
|
rewriteTargetListMerge(parse, result_relation, range_table);
|
|
|
|
/*
|
|
* For MERGE command, handle targetlist of each MergeAction separately.
|
|
* Give the same treatment to MergeAction->targetList as we would have
|
|
* given to a regular INSERT/UPDATE/DELETE.
|
|
*/
|
|
foreach (l, parse->mergeActionList) {
|
|
MergeAction* action = (MergeAction*)lfirst(l);
|
|
|
|
switch (action->commandType) {
|
|
case CMD_INSERT:
|
|
case CMD_UPDATE:
|
|
action->targetList =
|
|
expand_targetlist(action->targetList, action->commandType, result_relation, range_table);
|
|
if (action->pulluped_targetList != NIL)
|
|
action->pulluped_targetList = expand_targetlist(
|
|
action->pulluped_targetList, action->commandType, result_relation, range_table);
|
|
|
|
break;
|
|
case CMD_DELETE:
|
|
break;
|
|
case CMD_NOTHING:
|
|
break;
|
|
default:
|
|
ereport(ERROR, ((errcode(ERRCODE_CASE_NOT_FOUND), errmsg("unknown action in MERGE WHEN clause"))));
|
|
}
|
|
/* are there any quals to handle the quals for pgxc */
|
|
if (!IS_STREAM_PLAN && action->qual != NULL && !IS_SINGLE_NODE) {
|
|
if (!is_qual_expanded) {
|
|
/* expand vars in action's qual to make sure we can later reference it */
|
|
parse->targetList = expandQualTL(parse->targetList, parse);
|
|
/* we only expand once */
|
|
is_qual_expanded = true;
|
|
}
|
|
/* make action's qual and targetlist refer to plan's targetlist */
|
|
action->qual = substitute_var(action->qual, parse->targetList);
|
|
}
|
|
}
|
|
|
|
if (!IS_STREAM_PLAN && !IS_SINGLE_NODE) {
|
|
/* add action's targetlist in the query targetlist */
|
|
parse->targetList = expandActionTL(parse->targetList, parse);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add necessary junk columns for rowmarked rels. These values are needed
|
|
* for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
|
|
* rechecking. See comments for PlanRowMark in plannodes.h.
|
|
*/
|
|
foreach (lc, root->rowMarks) {
|
|
PlanRowMark* rc = (PlanRowMark*)lfirst(lc);
|
|
Var* var = NULL;
|
|
char resname[32]; /* 32 is the max length unique identifier for resjunk columns */
|
|
TargetEntry* tle = NULL;
|
|
RangeTblEntry* rte = NULL;
|
|
int ret;
|
|
|
|
/* child rels use the same junk attrs as their parents */
|
|
if (rc->rti != rc->prti)
|
|
continue;
|
|
rte = rt_fetch(rc->rti, range_table);
|
|
|
|
if (rc->markType != ROW_MARK_COPY) {
|
|
/* It's a regular table, so fetch its TID */
|
|
var = makeVar(rc->rti, SelfItemPointerAttributeNumber, TIDOID, -1, InvalidOid, 0);
|
|
ret = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "ctid%u", rc->rowmarkId);
|
|
securec_check_ss(ret, "", "");
|
|
|
|
tle = makeTargetEntry((Expr*)var, list_length(tlist) + 1, pstrdup(resname), true);
|
|
tlist = lappend(tlist, tle);
|
|
|
|
/* if parent of inheritance tree, need the tableoid too */
|
|
bool need_oid = rc->isParent || rte->ispartrel || rte->orientation == REL_COL_ORIENTED;
|
|
if (need_oid) {
|
|
var = makeVar(rc->rti, TableOidAttributeNumber, OIDOID, -1, InvalidOid, 0);
|
|
ret = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "tableoid%u", rc->rowmarkId);
|
|
securec_check_ss(ret, "", "");
|
|
tle = makeTargetEntry((Expr*)var, list_length(tlist) + 1, pstrdup(resname), true);
|
|
tlist = lappend(tlist, tle);
|
|
}
|
|
if (rte->relhasbucket) {
|
|
var = makeVar(rc->rti, BucketIdAttributeNumber, INT2OID, -1, InvalidBktId, 0);
|
|
ret = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "tablebucketid%u", rc->rowmarkId);
|
|
securec_check_ss(ret, "", "");
|
|
tle = makeTargetEntry((Expr*)var, list_length(tlist) + 1, pstrdup(resname), true);
|
|
tlist = lappend(tlist, tle);
|
|
}
|
|
|
|
/*
|
|
* Add table's distribute columns to targetlist for FOR UPDATE/SHARE
|
|
* in stream plan.
|
|
*/
|
|
if (IS_STREAM_PLAN && (rc->markType == ROW_MARK_EXCLUSIVE || rc->markType == ROW_MARK_SHARE)) {
|
|
tlist = add_distribute_column(tlist, rc->rti, range_table);
|
|
}
|
|
} else {
|
|
bool is_hdfs_ftbl = false;
|
|
List* sub_tlist = NIL;
|
|
if (rte->relkind == RELKIND_FOREIGN_TABLE) {
|
|
if (isObsOrHdfsTableFormTblOid(rte->relid) || IS_OBS_CSV_TXT_FOREIGN_TABLE(rte->relid)) {
|
|
is_hdfs_ftbl = true;
|
|
}
|
|
} else if (rte->rtekind == RTE_SUBQUERY && (IS_STREAM_PLAN || IS_SINGLE_NODE)) {
|
|
sub_tlist = rte->subquery->targetList;
|
|
}
|
|
|
|
if (!is_hdfs_ftbl && sub_tlist == NIL) {
|
|
/* Not a table, so we need the whole row as a junk var */
|
|
var = makeWholeRowVar(rt_fetch(rc->rti, range_table), rc->rti, 0, false);
|
|
|
|
if (var == NULL) {
|
|
ereport(ERROR,(errcode(ERRCODE_UNDEFINED_FILE),
|
|
errmsg("could not get the whole row to build a junk var.")));
|
|
return NULL;
|
|
}
|
|
|
|
int rcode = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "wholerow%u", rc->rowmarkId);
|
|
securec_check_ss(rcode, "\0", "\0");
|
|
tle = makeTargetEntry((Expr*)var, list_length(tlist) + 1, pstrdup(resname), true);
|
|
tlist = lappend(tlist, tle);
|
|
} else {
|
|
/* Convert wholerow(table.*) to table.col1, table.col2, ...., table.coln. */
|
|
int varattno;
|
|
Relation rel = NULL;
|
|
int maxattrs;
|
|
TupleDesc tupdesc = NULL;
|
|
ListCell* lc2 = NULL;
|
|
|
|
if (is_hdfs_ftbl) {
|
|
rel = relation_open(rte->relid, AccessShareLock);
|
|
tupdesc = rel->rd_att;
|
|
maxattrs = tupdesc->natts;
|
|
} else {
|
|
maxattrs = list_length(sub_tlist);
|
|
lc2 = list_head(sub_tlist);
|
|
}
|
|
|
|
for (varattno = 0; varattno < maxattrs; varattno++) {
|
|
Oid vartype;
|
|
int32 vartypmod;
|
|
Oid varCollid;
|
|
if (is_hdfs_ftbl) {
|
|
Form_pg_attribute attr = tupdesc->attrs[varattno];
|
|
vartype = attr->atttypid;
|
|
varCollid = attr->attcollation;
|
|
vartypmod = attr->atttypmod;
|
|
} else {
|
|
if (varattno != 0)
|
|
lc2 = lnext(lc2);
|
|
TargetEntry* tleLocal = (TargetEntry*)lfirst(lc2);
|
|
Expr* expr = tleLocal->expr;
|
|
if (tleLocal->resjunk)
|
|
continue;
|
|
vartype = exprType((Node*)expr);
|
|
vartypmod = exprTypmod((Node*)expr);
|
|
varCollid = exprCollation((Node*)expr);
|
|
}
|
|
/* set var collation to ensure var equation when finding sort pathkey. */
|
|
var = makeVar(rc->rti, varattno + 1, vartype, vartypmod, varCollid, 0);
|
|
|
|
ret = snprintf_s(resname, sizeof(resname), sizeof(resname) - 1, "wholerow%u", rc->rowmarkId);
|
|
securec_check_ss(ret, "\0", "\0");
|
|
tle = makeTargetEntry((Expr*)var, list_length(tlist) + 1, pstrdup(resname), true);
|
|
|
|
tlist = lappend(tlist, tle);
|
|
}
|
|
|
|
rc->markType = ROW_MARK_COPY_DATUM;
|
|
rc->numAttrs = maxattrs;
|
|
if (is_hdfs_ftbl)
|
|
relation_close(rel, AccessShareLock);
|
|
}
|
|
#ifdef STREAMPLAN
|
|
if (IS_STREAM_PLAN && var->vartype == RECORDOID) {
|
|
if (rte->relname != NULL) {
|
|
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
|
NOTPLANSHIPPING_LENGTH,
|
|
"Type of Record in %s that is not a real table can not be shipped",
|
|
rte->relname);
|
|
securec_check_ss_c(sprintf_rc, "\0", "\0");
|
|
} else {
|
|
errno_t sprintf_rc = sprintf_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason,
|
|
NOTPLANSHIPPING_LENGTH,
|
|
"Type of Record in non-real table can not be shipped");
|
|
securec_check_ss_c(sprintf_rc, "\0", "\0");
|
|
}
|
|
mark_stream_unsupport();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the query has a RETURNING list, add resjunk entries for any Vars
|
|
* used in RETURNING that belong to other relations. We need to do this
|
|
* to make these Vars available for the RETURNING calculation. Vars that
|
|
* belong to the result rel don't need to be added, because they will be
|
|
* made to refer to the actual heap tuple.
|
|
*/
|
|
if (parse->returningList && list_length(parse->rtable) > 1) {
|
|
List* vars = NIL;
|
|
ListCell* l = NULL;
|
|
|
|
vars = pull_var_clause((Node*)parse->returningList, PVC_RECURSE_AGGREGATES, PVC_INCLUDE_PLACEHOLDERS);
|
|
foreach (l, vars) {
|
|
Var* var = (Var*)lfirst(l);
|
|
TargetEntry* tle = NULL;
|
|
|
|
if (IsA(var, Var) && var->varno == (unsigned int)result_relation)
|
|
continue; /* don't need it */
|
|
|
|
if (tlist_member((Node*)var, tlist))
|
|
continue; /* already got it */
|
|
|
|
tle = makeTargetEntry((Expr*)var, list_length(tlist) + 1, NULL, true);
|
|
|
|
tlist = lappend(tlist, tle);
|
|
}
|
|
list_free_ext(vars);
|
|
}
|
|
|
|
return tlist;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* TARGETLIST EXPANSION
|
|
*
|
|
*****************************************************************************/
|
|
/*
|
|
* expand_targetlist
|
|
* Given a target list as generated by the parser and a result relation,
|
|
* add targetlist entries for any missing attributes, and ensure the
|
|
* non-junk attributes appear in proper field order.
|
|
*/
|
|
static List* expand_targetlist(List* tlist, int command_type, Index result_relation, List* range_table)
|
|
{
|
|
List* new_tlist = NIL;
|
|
ListCell* tlist_item = NULL;
|
|
Relation rel;
|
|
int attrno, numattrs;
|
|
|
|
tlist_item = list_head(tlist);
|
|
|
|
/*
|
|
* The rewriter should have already ensured that the TLEs are in correct
|
|
* order; but we have to insert TLEs for any missing attributes.
|
|
*
|
|
* Scan the tuple description in the relation's relcache entry to make
|
|
* sure we have all the user attributes in the right order. We assume
|
|
* that the rewriter already acquired at least AccessShareLock on the
|
|
* relation, so we need no lock here.
|
|
*/
|
|
rel = heap_open(getrelid(result_relation, range_table), NoLock);
|
|
|
|
numattrs = RelationGetNumberOfAttributes(rel);
|
|
|
|
for (attrno = 1; attrno <= numattrs; attrno++) {
|
|
Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
|
|
TargetEntry* new_tle = NULL;
|
|
|
|
if (tlist_item != NULL) {
|
|
TargetEntry* old_tle = (TargetEntry*)lfirst(tlist_item);
|
|
|
|
if (!old_tle->resjunk && old_tle->resno == attrno) {
|
|
new_tle = old_tle;
|
|
tlist_item = lnext(tlist_item);
|
|
}
|
|
}
|
|
|
|
if (new_tle == NULL) {
|
|
/*
|
|
* Didn't find a matching tlist entry, so make one.
|
|
*
|
|
* For INSERT, generate a NULL constant. (We assume the rewriter
|
|
* would have inserted any available default value.) Also, if the
|
|
* column isn't dropped, apply any domain constraints that might
|
|
* exist --- this is to catch domain NOT NULL.
|
|
*
|
|
* For UPDATE, generate a Var reference to the existing value of
|
|
* the attribute, so that it gets copied to the new tuple. But
|
|
* generate a NULL for dropped columns (we want to drop any old
|
|
* values).
|
|
*
|
|
* When generating a NULL constant for a dropped column, we label
|
|
* it INT4 (any other guaranteed-to-exist datatype would do as
|
|
* well). We can't label it with the dropped column's datatype
|
|
* since that might not exist anymore. It does not really matter
|
|
* what we claim the type is, since NULL is NULL --- its
|
|
* representation is datatype-independent. This could perhaps
|
|
* confuse code comparing the finished plan to the target
|
|
* relation, however.
|
|
*/
|
|
Oid atttype = att_tup->atttypid;
|
|
int32 atttypmod = att_tup->atttypmod;
|
|
Oid attcollation = att_tup->attcollation;
|
|
Node* new_expr = NULL;
|
|
|
|
switch (command_type) {
|
|
case CMD_INSERT:
|
|
if (!att_tup->attisdropped) {
|
|
new_expr = (Node*)makeConst(atttype,
|
|
-1,
|
|
attcollation,
|
|
att_tup->attlen,
|
|
(Datum)0,
|
|
true, /* isnull */
|
|
att_tup->attbyval);
|
|
new_expr =
|
|
coerce_to_domain(new_expr, InvalidOid, -1, atttype, COERCE_IMPLICIT_CAST, -1, false, false);
|
|
} else {
|
|
/* Insert NULL for dropped column */
|
|
new_expr = (Node*)makeConst(INT4OID,
|
|
-1,
|
|
InvalidOid,
|
|
sizeof(int32),
|
|
(Datum)0,
|
|
true, /* isnull */
|
|
true /* byval */);
|
|
}
|
|
break;
|
|
case CMD_MERGE:
|
|
case CMD_UPDATE:
|
|
if (!att_tup->attisdropped) {
|
|
new_expr = (Node*)makeVar(result_relation, attrno, atttype, atttypmod, attcollation, 0);
|
|
} else {
|
|
/* Insert NULL for dropped column */
|
|
new_expr = (Node*)makeConst(INT4OID,
|
|
-1,
|
|
InvalidOid,
|
|
sizeof(int32),
|
|
(Datum)0,
|
|
true, /* isnull */
|
|
true /* byval */);
|
|
}
|
|
break;
|
|
default:
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_DATA_EXCEPTION),
|
|
(errmsg("unrecognized command_type: %d", (int)command_type))));
|
|
new_expr = NULL; /* keep compiler quiet */
|
|
break;
|
|
}
|
|
|
|
new_tle = makeTargetEntry((Expr*)new_expr, attrno, pstrdup(NameStr(att_tup->attname)), false);
|
|
}
|
|
|
|
new_tlist = lappend(new_tlist, new_tle);
|
|
}
|
|
|
|
/*
|
|
* The remaining tlist entries should be resjunk; append them all to the
|
|
* end of the new tlist, making sure they have resnos higher than the last
|
|
* real attribute. (Note: although the rewriter already did such
|
|
* renumbering, we have to do it again here in case we are doing an UPDATE
|
|
* in a table with dropped columns, or an inheritance child table with
|
|
* extra columns.)
|
|
*/
|
|
while (tlist_item != NULL) {
|
|
TargetEntry* old_tle = (TargetEntry*)lfirst(tlist_item);
|
|
|
|
if (!old_tle->resjunk)
|
|
ereport(ERROR,
|
|
(errmodule(MOD_OPT),
|
|
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
|
|
(errmsg("targetlist is not sorted correctly"))));
|
|
/* Get the resno right, but don't copy unnecessarily */
|
|
if (old_tle->resno != attrno) {
|
|
old_tle = flatCopyTargetEntry(old_tle);
|
|
old_tle->resno = attrno;
|
|
}
|
|
new_tlist = lappend(new_tlist, old_tle);
|
|
attrno++;
|
|
tlist_item = lnext(tlist_item);
|
|
}
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
return new_tlist;
|
|
}
|
|
|
|
/*
|
|
* Locate PlanRowMark for given RT index, or return NULL if none
|
|
*
|
|
* This probably ought to be elsewhere, but there's no very good place
|
|
*/
|
|
PlanRowMark* get_plan_rowmark(List* rowmarks, Index rtindex)
|
|
{
|
|
ListCell* l = NULL;
|
|
|
|
foreach (l, rowmarks) {
|
|
PlanRowMark* rc = (PlanRowMark*)lfirst(l);
|
|
|
|
if (rc->rti == rtindex)
|
|
return rc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef STREAMPLAN
|
|
/*
|
|
* add_distribute_column
|
|
* Given a target list as generated by the parser and a result relation,
|
|
* add distribute entries to the target list if the attribute is missing, and
|
|
* ensure it appears in the final target list for stream distribution.
|
|
*/
|
|
static List* add_distribute_column(List* tlist, Index result_relation, List* range_table)
|
|
{
|
|
Relation rel = heap_open(getrelid(result_relation, range_table), NoLock);
|
|
int2vector* disattrno = NULL;
|
|
ListCell* lc = NULL;
|
|
|
|
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
|
disattrno = get_baserel_distributekey_no(getrelid(result_relation, range_table));
|
|
|
|
if (disattrno == NULL) {
|
|
heap_close(rel, NoLock);
|
|
return tlist;
|
|
}
|
|
|
|
int no;
|
|
int len = disattrno->dim1;
|
|
bool* isExist = (bool*)palloc0(len * sizeof(bool));
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
no = disattrno->values[i];
|
|
foreach (lc, tlist) {
|
|
TargetEntry* tle = (TargetEntry*)lfirst(lc);
|
|
Expr* node = tle->expr;
|
|
|
|
if (IsA(node, Var) && ((Var*)node)->varno == result_relation && ((Var*)node)->varattno == no) {
|
|
isExist[i] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if distribute column is not in targetlist, then add it */
|
|
for (int i = 0; i < len; i++) {
|
|
if (isExist[i] == false) {
|
|
no = disattrno->values[i];
|
|
Form_pg_attribute att_tup = rel->rd_att->attrs[no - 1];
|
|
Node* new_expr =
|
|
(Node*)makeVar(result_relation, no, att_tup->atttypid, att_tup->atttypmod, att_tup->attcollation, 0);
|
|
TargetEntry* tle =
|
|
makeTargetEntry((Expr*)new_expr, list_length(tlist) + 1, pstrdup(NameStr(att_tup->attname)), true);
|
|
tlist = lappend(tlist, tle);
|
|
}
|
|
}
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
return tlist;
|
|
}
|
|
|
|
List* preprocess_upsert_targetlist(List* tlist, int result_relation, List* range_table)
|
|
{
|
|
return expand_targetlist(tlist, CMD_UPDATE, result_relation, range_table);
|
|
}
|
|
#endif
|
|
|