2022-03-28 16:45:58 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* parse_merge.c
|
|
|
|
* handle merge-statement in parser
|
|
|
|
*
|
2024-01-04 02:49:05 +01:00
|
|
|
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
|
2022-03-28 16:45:58 +02:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/backend/parser/parse_merge.c
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/sysattr.h"
|
|
|
|
#include "nodes/makefuncs.h"
|
|
|
|
#include "parser/analyze.h"
|
|
|
|
#include "parser/parse_clause.h"
|
2024-03-13 15:07:00 +01:00
|
|
|
#include "parser/parse_collate.h"
|
2022-03-28 16:45:58 +02:00
|
|
|
#include "parser/parse_cte.h"
|
|
|
|
#include "parser/parse_expr.h"
|
|
|
|
#include "parser/parse_merge.h"
|
|
|
|
#include "parser/parse_relation.h"
|
|
|
|
#include "parser/parse_target.h"
|
2024-03-13 15:07:00 +01:00
|
|
|
#include "parser/parsetree.h"
|
2022-03-28 16:45:58 +02:00
|
|
|
#include "utils/rel.h"
|
|
|
|
|
|
|
|
static void setNamespaceForMergeWhen(ParseState *pstate,
|
|
|
|
MergeWhenClause *mergeWhenClause,
|
|
|
|
Index targetRTI,
|
|
|
|
Index sourceRTI);
|
|
|
|
static void setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
|
|
|
|
bool rel_visible,
|
|
|
|
bool cols_visible);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make appropriate changes to the namespace visibility while transforming
|
|
|
|
* individual action's quals and targetlist expressions. In particular, for
|
|
|
|
* INSERT actions we must only see the source relation (since INSERT action is
|
2024-03-30 11:00:26 +01:00
|
|
|
* invoked for NOT MATCHED [BY TARGET] tuples and hence there is no target
|
|
|
|
* tuple to deal with). On the other hand, UPDATE and DELETE actions can see
|
|
|
|
* both source and target relations, unless invoked for NOT MATCHED BY SOURCE.
|
2022-03-28 16:45:58 +02:00
|
|
|
*
|
|
|
|
* Also, since the internal join node can hide the source and target
|
|
|
|
* relations, we must explicitly make the respective relation as visible so
|
|
|
|
* that columns can be referenced unqualified from these relations.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
setNamespaceForMergeWhen(ParseState *pstate, MergeWhenClause *mergeWhenClause,
|
|
|
|
Index targetRTI, Index sourceRTI)
|
|
|
|
{
|
|
|
|
RangeTblEntry *targetRelRTE,
|
|
|
|
*sourceRelRTE;
|
|
|
|
|
|
|
|
targetRelRTE = rt_fetch(targetRTI, pstate->p_rtable);
|
|
|
|
sourceRelRTE = rt_fetch(sourceRTI, pstate->p_rtable);
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
if (mergeWhenClause->matchKind == MERGE_WHEN_MATCHED)
|
2022-03-28 16:45:58 +02:00
|
|
|
{
|
|
|
|
Assert(mergeWhenClause->commandType == CMD_UPDATE ||
|
|
|
|
mergeWhenClause->commandType == CMD_DELETE ||
|
|
|
|
mergeWhenClause->commandType == CMD_NOTHING);
|
|
|
|
|
|
|
|
/* MATCHED actions can see both target and source relations. */
|
|
|
|
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
|
|
|
targetRelRTE, true, true);
|
|
|
|
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
|
|
|
sourceRelRTE, true, true);
|
|
|
|
}
|
2024-03-30 11:00:26 +01:00
|
|
|
else if (mergeWhenClause->matchKind == MERGE_WHEN_NOT_MATCHED_BY_SOURCE)
|
2022-03-28 16:45:58 +02:00
|
|
|
{
|
|
|
|
/*
|
2024-03-30 11:00:26 +01:00
|
|
|
* NOT MATCHED BY SOURCE actions can see the target relation, but they
|
|
|
|
* can't see the source relation.
|
|
|
|
*/
|
|
|
|
Assert(mergeWhenClause->commandType == CMD_UPDATE ||
|
|
|
|
mergeWhenClause->commandType == CMD_DELETE ||
|
|
|
|
mergeWhenClause->commandType == CMD_NOTHING);
|
|
|
|
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
|
|
|
targetRelRTE, true, true);
|
|
|
|
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
|
|
|
sourceRelRTE, false, false);
|
|
|
|
}
|
|
|
|
else /* MERGE_WHEN_NOT_MATCHED_BY_TARGET */
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* NOT MATCHED [BY TARGET] actions can't see target relation, but they
|
|
|
|
* can see source relation.
|
2022-03-28 16:45:58 +02:00
|
|
|
*/
|
|
|
|
Assert(mergeWhenClause->commandType == CMD_INSERT ||
|
|
|
|
mergeWhenClause->commandType == CMD_NOTHING);
|
|
|
|
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
|
|
|
targetRelRTE, false, false);
|
|
|
|
setNamespaceVisibilityForRTE(pstate->p_namespace,
|
|
|
|
sourceRelRTE, true, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* transformMergeStmt -
|
|
|
|
* transforms a MERGE statement
|
|
|
|
*/
|
|
|
|
Query *
|
|
|
|
transformMergeStmt(ParseState *pstate, MergeStmt *stmt)
|
|
|
|
{
|
|
|
|
Query *qry = makeNode(Query);
|
|
|
|
ListCell *l;
|
|
|
|
AclMode targetPerms = ACL_NO_RIGHTS;
|
2024-04-19 10:40:20 +02:00
|
|
|
bool is_terminal[NUM_MERGE_MATCH_KINDS];
|
2022-03-28 16:45:58 +02:00
|
|
|
Index sourceRTI;
|
|
|
|
List *mergeActionList;
|
|
|
|
ParseNamespaceItem *nsitem;
|
|
|
|
|
|
|
|
/* There can't be any outer WITH to worry about */
|
|
|
|
Assert(pstate->p_ctenamespace == NIL);
|
|
|
|
|
|
|
|
qry->commandType = CMD_MERGE;
|
|
|
|
qry->hasRecursive = false;
|
|
|
|
|
|
|
|
/* process the WITH clause independently of all else */
|
|
|
|
if (stmt->withClause)
|
|
|
|
{
|
|
|
|
if (stmt->withClause->recursive)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
|
|
errmsg("WITH RECURSIVE is not supported for MERGE statement")));
|
|
|
|
|
|
|
|
qry->cteList = transformWithClause(pstate, stmt->withClause);
|
|
|
|
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check WHEN clauses for permissions and sanity
|
|
|
|
*/
|
2024-03-30 11:00:26 +01:00
|
|
|
is_terminal[MERGE_WHEN_MATCHED] = false;
|
|
|
|
is_terminal[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = false;
|
|
|
|
is_terminal[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = false;
|
2022-03-28 16:45:58 +02:00
|
|
|
foreach(l, stmt->mergeWhenClauses)
|
|
|
|
{
|
|
|
|
MergeWhenClause *mergeWhenClause = (MergeWhenClause *) lfirst(l);
|
|
|
|
|
|
|
|
/*
|
2024-02-21 17:18:52 +01:00
|
|
|
* Collect permissions to check, according to action types. We require
|
|
|
|
* SELECT privileges for DO NOTHING because it'd be irregular to have
|
|
|
|
* a target relation with zero privileges checked, in case DO NOTHING
|
|
|
|
* is the only action. There's no damage from that: any meaningful
|
|
|
|
* MERGE command requires at least some access to the table anyway.
|
2022-03-28 16:45:58 +02:00
|
|
|
*/
|
|
|
|
switch (mergeWhenClause->commandType)
|
|
|
|
{
|
|
|
|
case CMD_INSERT:
|
|
|
|
targetPerms |= ACL_INSERT;
|
|
|
|
break;
|
|
|
|
case CMD_UPDATE:
|
|
|
|
targetPerms |= ACL_UPDATE;
|
|
|
|
break;
|
|
|
|
case CMD_DELETE:
|
|
|
|
targetPerms |= ACL_DELETE;
|
|
|
|
break;
|
|
|
|
case CMD_NOTHING:
|
2024-02-21 17:18:52 +01:00
|
|
|
targetPerms |= ACL_SELECT;
|
2022-03-28 16:45:58 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unknown action in MERGE WHEN clause");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for unreachable WHEN clauses
|
|
|
|
*/
|
2024-03-30 11:00:26 +01:00
|
|
|
if (is_terminal[mergeWhenClause->matchKind])
|
2022-03-28 16:45:58 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
|
|
errmsg("unreachable WHEN clause specified after unconditional WHEN clause")));
|
2023-01-10 15:17:47 +01:00
|
|
|
if (mergeWhenClause->condition == NULL)
|
2024-03-30 11:00:26 +01:00
|
|
|
is_terminal[mergeWhenClause->matchKind] = true;
|
2022-03-28 16:45:58 +02:00
|
|
|
}
|
|
|
|
|
2022-10-24 12:52:43 +02:00
|
|
|
/*
|
|
|
|
* Set up the MERGE target table. The target table is added to the
|
|
|
|
* namespace below and to joinlist in transform_MERGE_to_join, so don't do
|
|
|
|
* it here.
|
2024-02-29 16:56:59 +01:00
|
|
|
*
|
|
|
|
* Initially mergeTargetRelation is the same as resultRelation, so data is
|
|
|
|
* read from the table being updated. However, that might be changed by
|
|
|
|
* the rewriter, if the target is a trigger-updatable view, to allow
|
|
|
|
* target data to be read from the expanded view query while updating the
|
|
|
|
* original view relation.
|
2022-10-24 12:52:43 +02:00
|
|
|
*/
|
2022-03-28 16:45:58 +02:00
|
|
|
qry->resultRelation = setTargetTable(pstate, stmt->relation,
|
|
|
|
stmt->relation->inh,
|
|
|
|
false, targetPerms);
|
2024-02-29 16:56:59 +01:00
|
|
|
qry->mergeTargetRelation = qry->resultRelation;
|
2022-03-28 16:45:58 +02:00
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
/* The target relation must be a table or a view */
|
2022-03-28 16:45:58 +02:00
|
|
|
if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION &&
|
2024-02-29 16:56:59 +01:00
|
|
|
pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
|
|
|
|
pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW)
|
2022-03-28 16:45:58 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("cannot execute MERGE on relation \"%s\"",
|
|
|
|
RelationGetRelationName(pstate->p_target_relation)),
|
|
|
|
errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind)));
|
|
|
|
|
|
|
|
/* Now transform the source relation to produce the source RTE. */
|
|
|
|
transformFromClause(pstate,
|
|
|
|
list_make1(stmt->sourceRelation));
|
|
|
|
sourceRTI = list_length(pstate->p_rtable);
|
|
|
|
nsitem = GetNSItemByRangeTablePosn(pstate, sourceRTI, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that the target table doesn't conflict with the source table.
|
|
|
|
* This would typically be a checkNameSpaceConflicts call, but we want a
|
|
|
|
* more specific error message.
|
|
|
|
*/
|
|
|
|
if (strcmp(pstate->p_target_nsitem->p_names->aliasname,
|
|
|
|
nsitem->p_names->aliasname) == 0)
|
|
|
|
ereport(ERROR,
|
|
|
|
errcode(ERRCODE_DUPLICATE_ALIAS),
|
|
|
|
errmsg("name \"%s\" specified more than once",
|
|
|
|
pstate->p_target_nsitem->p_names->aliasname),
|
|
|
|
errdetail("The name is used both as MERGE target table and data source."));
|
|
|
|
|
2022-04-12 09:29:39 +02:00
|
|
|
/*
|
|
|
|
* There's no need for a targetlist here; it'll be set up by
|
|
|
|
* preprocess_targetlist later.
|
|
|
|
*/
|
|
|
|
qry->targetList = NIL;
|
2022-03-28 16:45:58 +02:00
|
|
|
qry->rtable = pstate->p_rtable;
|
2022-12-06 16:09:24 +01:00
|
|
|
qry->rteperminfos = pstate->p_rteperminfos;
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Transform the join condition. This includes references to the target
|
|
|
|
* side, so add that to the namespace.
|
|
|
|
*/
|
|
|
|
addNSItemToQuery(pstate, pstate->p_target_nsitem, false, true, true);
|
2024-03-30 11:00:26 +01:00
|
|
|
qry->mergeJoinCondition = transformExpr(pstate, stmt->joinCondition,
|
|
|
|
EXPR_KIND_JOIN_ON);
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Create the temporary query's jointree using the joinlist we built using
|
2024-03-30 11:00:26 +01:00
|
|
|
* just the source relation; the target relation is not included. The join
|
2022-03-28 16:45:58 +02:00
|
|
|
* will be constructed fully by transform_MERGE_to_join.
|
|
|
|
*/
|
2024-03-30 11:00:26 +01:00
|
|
|
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
|
2022-03-28 16:45:58 +02:00
|
|
|
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
/* Transform the RETURNING list, if any */
|
|
|
|
qry->returningList = transformReturningList(pstate, stmt->returningList,
|
|
|
|
EXPR_KIND_MERGE_RETURNING);
|
|
|
|
|
2022-03-28 16:45:58 +02:00
|
|
|
/*
|
|
|
|
* We now have a good query shape, so now look at the WHEN conditions and
|
|
|
|
* action targetlists.
|
|
|
|
*
|
|
|
|
* Overall, the MERGE Query's targetlist is NIL.
|
|
|
|
*
|
|
|
|
* Each individual action has its own targetlist that needs separate
|
|
|
|
* transformation. These transforms don't do anything to the overall
|
|
|
|
* targetlist, since that is only used for resjunk columns.
|
|
|
|
*
|
|
|
|
* We can reference any column in Target or Source, which is OK because
|
|
|
|
* both of those already have RTEs. There is nothing like the EXCLUDED
|
|
|
|
* pseudo-relation for INSERT ON CONFLICT.
|
|
|
|
*/
|
|
|
|
mergeActionList = NIL;
|
|
|
|
foreach(l, stmt->mergeWhenClauses)
|
|
|
|
{
|
|
|
|
MergeWhenClause *mergeWhenClause = lfirst_node(MergeWhenClause, l);
|
|
|
|
MergeAction *action;
|
|
|
|
|
|
|
|
action = makeNode(MergeAction);
|
|
|
|
action->commandType = mergeWhenClause->commandType;
|
2024-03-30 11:00:26 +01:00
|
|
|
action->matchKind = mergeWhenClause->matchKind;
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set namespace for the specific action. This must be done before
|
|
|
|
* analyzing the WHEN quals and the action targetlist.
|
|
|
|
*/
|
|
|
|
setNamespaceForMergeWhen(pstate, mergeWhenClause,
|
|
|
|
qry->resultRelation,
|
|
|
|
sourceRTI);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Transform the WHEN condition.
|
|
|
|
*
|
|
|
|
* Note that these quals are NOT added to the join quals; instead they
|
|
|
|
* are evaluated separately during execution to decide which of the
|
|
|
|
* WHEN MATCHED or WHEN NOT MATCHED actions to execute.
|
|
|
|
*/
|
|
|
|
action->qual = transformWhereClause(pstate, mergeWhenClause->condition,
|
|
|
|
EXPR_KIND_MERGE_WHEN, "WHEN");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Transform target lists for each INSERT and UPDATE action stmt
|
|
|
|
*/
|
|
|
|
switch (action->commandType)
|
|
|
|
{
|
|
|
|
case CMD_INSERT:
|
|
|
|
{
|
|
|
|
List *exprList = NIL;
|
|
|
|
ListCell *lc;
|
2022-12-06 16:09:24 +01:00
|
|
|
RTEPermissionInfo *perminfo;
|
2022-03-28 16:45:58 +02:00
|
|
|
ListCell *icols;
|
|
|
|
ListCell *attnos;
|
|
|
|
List *icolumns;
|
|
|
|
List *attrnos;
|
|
|
|
|
|
|
|
pstate->p_is_insert = true;
|
|
|
|
|
|
|
|
icolumns = checkInsertTargets(pstate,
|
|
|
|
mergeWhenClause->targetList,
|
|
|
|
&attrnos);
|
|
|
|
Assert(list_length(icolumns) == list_length(attrnos));
|
|
|
|
|
|
|
|
action->override = mergeWhenClause->override;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle INSERT much like in transformInsertStmt
|
|
|
|
*/
|
|
|
|
if (mergeWhenClause->values == NIL)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We have INSERT ... DEFAULT VALUES. We can handle
|
|
|
|
* this case by emitting an empty targetlist --- all
|
|
|
|
* columns will be defaulted when the planner expands
|
|
|
|
* the targetlist.
|
|
|
|
*/
|
|
|
|
exprList = NIL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Process INSERT ... VALUES with a single VALUES
|
|
|
|
* sublist. We treat this case separately for
|
|
|
|
* efficiency. The sublist is just computed directly
|
|
|
|
* as the Query's targetlist, with no VALUES RTE. So
|
|
|
|
* it works just like a SELECT without any FROM.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do basic expression transformation (same as a ROW()
|
|
|
|
* expr, but allow SetToDefault at top level)
|
|
|
|
*/
|
|
|
|
exprList = transformExpressionList(pstate,
|
|
|
|
mergeWhenClause->values,
|
|
|
|
EXPR_KIND_VALUES_SINGLE,
|
|
|
|
true);
|
|
|
|
|
|
|
|
/* Prepare row for assignment to target table */
|
|
|
|
exprList = transformInsertRow(pstate, exprList,
|
|
|
|
mergeWhenClause->targetList,
|
|
|
|
icolumns, attrnos,
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate action's target list using the computed list
|
|
|
|
* of expressions. Also, mark all the target columns as
|
|
|
|
* needing insert permissions.
|
|
|
|
*/
|
2022-12-06 16:09:24 +01:00
|
|
|
perminfo = pstate->p_target_nsitem->p_perminfo;
|
2022-03-28 16:45:58 +02:00
|
|
|
forthree(lc, exprList, icols, icolumns, attnos, attrnos)
|
|
|
|
{
|
|
|
|
Expr *expr = (Expr *) lfirst(lc);
|
|
|
|
ResTarget *col = lfirst_node(ResTarget, icols);
|
|
|
|
AttrNumber attr_num = (AttrNumber) lfirst_int(attnos);
|
|
|
|
TargetEntry *tle;
|
|
|
|
|
|
|
|
tle = makeTargetEntry(expr,
|
|
|
|
attr_num,
|
|
|
|
col->name,
|
|
|
|
false);
|
|
|
|
action->targetList = lappend(action->targetList, tle);
|
|
|
|
|
2022-12-06 16:09:24 +01:00
|
|
|
perminfo->insertedCols =
|
|
|
|
bms_add_member(perminfo->insertedCols,
|
2022-03-28 16:45:58 +02:00
|
|
|
attr_num - FirstLowInvalidHeapAttributeNumber);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CMD_UPDATE:
|
|
|
|
{
|
|
|
|
pstate->p_is_insert = false;
|
|
|
|
action->targetList =
|
|
|
|
transformUpdateTargetList(pstate,
|
|
|
|
mergeWhenClause->targetList);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CMD_DELETE:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMD_NOTHING:
|
|
|
|
action->targetList = NIL;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unknown action in MERGE WHEN clause");
|
|
|
|
}
|
|
|
|
|
|
|
|
mergeActionList = lappend(mergeActionList, action);
|
|
|
|
}
|
|
|
|
|
|
|
|
qry->mergeActionList = mergeActionList;
|
|
|
|
|
|
|
|
qry->hasTargetSRFs = false;
|
|
|
|
qry->hasSubLinks = pstate->p_hasSubLinks;
|
|
|
|
|
|
|
|
assign_query_collations(pstate, qry);
|
|
|
|
|
|
|
|
return qry;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
setNamespaceVisibilityForRTE(List *namespace, RangeTblEntry *rte,
|
|
|
|
bool rel_visible,
|
|
|
|
bool cols_visible)
|
|
|
|
{
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
foreach(lc, namespace)
|
|
|
|
{
|
|
|
|
ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
|
|
|
|
|
|
|
|
if (nsitem->p_rte == rte)
|
|
|
|
{
|
|
|
|
nsitem->p_rel_visible = rel_visible;
|
|
|
|
nsitem->p_cols_visible = cols_visible;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|