/src/postgres/src/backend/optimizer/util/appendinfo.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * appendinfo.c |
4 | | * Routines for mapping between append parent(s) and children |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/optimizer/util/appendinfo.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #include "postgres.h" |
16 | | |
17 | | #include "access/htup_details.h" |
18 | | #include "access/table.h" |
19 | | #include "foreign/fdwapi.h" |
20 | | #include "nodes/makefuncs.h" |
21 | | #include "nodes/nodeFuncs.h" |
22 | | #include "optimizer/appendinfo.h" |
23 | | #include "optimizer/pathnode.h" |
24 | | #include "optimizer/planmain.h" |
25 | | #include "parser/parsetree.h" |
26 | | #include "utils/lsyscache.h" |
27 | | #include "utils/rel.h" |
28 | | #include "utils/syscache.h" |
29 | | |
30 | | |
31 | | typedef struct |
32 | | { |
33 | | PlannerInfo *root; |
34 | | int nappinfos; |
35 | | AppendRelInfo **appinfos; |
36 | | } adjust_appendrel_attrs_context; |
37 | | |
38 | | static void make_inh_translation_list(Relation oldrelation, |
39 | | Relation newrelation, |
40 | | Index newvarno, |
41 | | AppendRelInfo *appinfo); |
42 | | static Node *adjust_appendrel_attrs_mutator(Node *node, |
43 | | adjust_appendrel_attrs_context *context); |
44 | | |
45 | | |
46 | | /* |
47 | | * make_append_rel_info |
48 | | * Build an AppendRelInfo for the parent-child pair |
49 | | */ |
50 | | AppendRelInfo * |
51 | | make_append_rel_info(Relation parentrel, Relation childrel, |
52 | | Index parentRTindex, Index childRTindex) |
53 | 0 | { |
54 | 0 | AppendRelInfo *appinfo = makeNode(AppendRelInfo); |
55 | |
|
56 | 0 | appinfo->parent_relid = parentRTindex; |
57 | 0 | appinfo->child_relid = childRTindex; |
58 | 0 | appinfo->parent_reltype = parentrel->rd_rel->reltype; |
59 | 0 | appinfo->child_reltype = childrel->rd_rel->reltype; |
60 | 0 | make_inh_translation_list(parentrel, childrel, childRTindex, appinfo); |
61 | 0 | appinfo->parent_reloid = RelationGetRelid(parentrel); |
62 | |
|
63 | 0 | return appinfo; |
64 | 0 | } |
65 | | |
66 | | /* |
67 | | * make_inh_translation_list |
68 | | * Build the list of translations from parent Vars to child Vars for |
69 | | * an inheritance child, as well as a reverse-translation array. |
70 | | * |
71 | | * The reverse-translation array has an entry for each child relation |
72 | | * column, which is either the 1-based index of the corresponding parent |
73 | | * column, or 0 if there's no match (that happens for dropped child columns, |
74 | | * as well as child columns beyond those of the parent, which are allowed in |
75 | | * traditional inheritance though not partitioning). |
76 | | * |
77 | | * For paranoia's sake, we match type/collation as well as attribute name. |
78 | | */ |
79 | | static void |
80 | | make_inh_translation_list(Relation oldrelation, Relation newrelation, |
81 | | Index newvarno, |
82 | | AppendRelInfo *appinfo) |
83 | 0 | { |
84 | 0 | List *vars = NIL; |
85 | 0 | AttrNumber *pcolnos; |
86 | 0 | TupleDesc old_tupdesc = RelationGetDescr(oldrelation); |
87 | 0 | TupleDesc new_tupdesc = RelationGetDescr(newrelation); |
88 | 0 | Oid new_relid = RelationGetRelid(newrelation); |
89 | 0 | int oldnatts = old_tupdesc->natts; |
90 | 0 | int newnatts = new_tupdesc->natts; |
91 | 0 | int old_attno; |
92 | 0 | int new_attno = 0; |
93 | | |
94 | | /* Initialize reverse-translation array with all entries zero */ |
95 | 0 | appinfo->num_child_cols = newnatts; |
96 | 0 | appinfo->parent_colnos = pcolnos = |
97 | 0 | (AttrNumber *) palloc0(newnatts * sizeof(AttrNumber)); |
98 | |
|
99 | 0 | for (old_attno = 0; old_attno < oldnatts; old_attno++) |
100 | 0 | { |
101 | 0 | Form_pg_attribute att; |
102 | 0 | char *attname; |
103 | 0 | Oid atttypid; |
104 | 0 | int32 atttypmod; |
105 | 0 | Oid attcollation; |
106 | |
|
107 | 0 | att = TupleDescAttr(old_tupdesc, old_attno); |
108 | 0 | if (att->attisdropped) |
109 | 0 | { |
110 | | /* Just put NULL into this list entry */ |
111 | 0 | vars = lappend(vars, NULL); |
112 | 0 | continue; |
113 | 0 | } |
114 | 0 | attname = NameStr(att->attname); |
115 | 0 | atttypid = att->atttypid; |
116 | 0 | atttypmod = att->atttypmod; |
117 | 0 | attcollation = att->attcollation; |
118 | | |
119 | | /* |
120 | | * When we are generating the "translation list" for the parent table |
121 | | * of an inheritance set, no need to search for matches. |
122 | | */ |
123 | 0 | if (oldrelation == newrelation) |
124 | 0 | { |
125 | 0 | vars = lappend(vars, makeVar(newvarno, |
126 | 0 | (AttrNumber) (old_attno + 1), |
127 | 0 | atttypid, |
128 | 0 | atttypmod, |
129 | 0 | attcollation, |
130 | 0 | 0)); |
131 | 0 | pcolnos[old_attno] = old_attno + 1; |
132 | 0 | continue; |
133 | 0 | } |
134 | | |
135 | | /* |
136 | | * Otherwise we have to search for the matching column by name. |
137 | | * There's no guarantee it'll have the same column position, because |
138 | | * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. |
139 | | * However, in simple cases, the relative order of columns is mostly |
140 | | * the same in both relations, so try the column of newrelation that |
141 | | * follows immediately after the one that we just found, and if that |
142 | | * fails, let syscache handle it. |
143 | | */ |
144 | 0 | if (new_attno >= newnatts || |
145 | 0 | (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || |
146 | 0 | strcmp(attname, NameStr(att->attname)) != 0) |
147 | 0 | { |
148 | 0 | HeapTuple newtup; |
149 | |
|
150 | 0 | newtup = SearchSysCacheAttName(new_relid, attname); |
151 | 0 | if (!HeapTupleIsValid(newtup)) |
152 | 0 | elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", |
153 | 0 | attname, RelationGetRelationName(newrelation)); |
154 | 0 | new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; |
155 | 0 | Assert(new_attno >= 0 && new_attno < newnatts); |
156 | 0 | ReleaseSysCache(newtup); |
157 | |
|
158 | 0 | att = TupleDescAttr(new_tupdesc, new_attno); |
159 | 0 | } |
160 | | |
161 | | /* Found it, check type and collation match */ |
162 | 0 | if (atttypid != att->atttypid || atttypmod != att->atttypmod) |
163 | 0 | ereport(ERROR, |
164 | 0 | (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), |
165 | 0 | errmsg("attribute \"%s\" of relation \"%s\" does not match parent's type", |
166 | 0 | attname, RelationGetRelationName(newrelation)))); |
167 | 0 | if (attcollation != att->attcollation) |
168 | 0 | ereport(ERROR, |
169 | 0 | (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), |
170 | 0 | errmsg("attribute \"%s\" of relation \"%s\" does not match parent's collation", |
171 | 0 | attname, RelationGetRelationName(newrelation)))); |
172 | | |
173 | 0 | vars = lappend(vars, makeVar(newvarno, |
174 | 0 | (AttrNumber) (new_attno + 1), |
175 | 0 | atttypid, |
176 | 0 | atttypmod, |
177 | 0 | attcollation, |
178 | 0 | 0)); |
179 | 0 | pcolnos[new_attno] = old_attno + 1; |
180 | 0 | new_attno++; |
181 | 0 | } |
182 | | |
183 | 0 | appinfo->translated_vars = vars; |
184 | 0 | } |
185 | | |
186 | | /* |
187 | | * adjust_appendrel_attrs |
188 | | * Copy the specified query or expression and translate Vars referring to a |
189 | | * parent rel to refer to the corresponding child rel instead. We also |
190 | | * update rtindexes appearing outside Vars, such as resultRelation and |
191 | | * jointree relids. |
192 | | * |
193 | | * Note: this is only applied after conversion of sublinks to subplans, |
194 | | * so we don't need to cope with recursion into sub-queries. |
195 | | * |
196 | | * Note: this is not hugely different from what pullup_replace_vars() does; |
197 | | * maybe we should try to fold the two routines together. |
198 | | */ |
199 | | Node * |
200 | | adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, |
201 | | AppendRelInfo **appinfos) |
202 | 0 | { |
203 | 0 | adjust_appendrel_attrs_context context; |
204 | |
|
205 | 0 | context.root = root; |
206 | 0 | context.nappinfos = nappinfos; |
207 | 0 | context.appinfos = appinfos; |
208 | | |
209 | | /* If there's nothing to adjust, don't call this function. */ |
210 | 0 | Assert(nappinfos >= 1 && appinfos != NULL); |
211 | | |
212 | | /* Should never be translating a Query tree. */ |
213 | 0 | Assert(node == NULL || !IsA(node, Query)); |
214 | |
|
215 | 0 | return adjust_appendrel_attrs_mutator(node, &context); |
216 | 0 | } |
217 | | |
218 | | static Node * |
219 | | adjust_appendrel_attrs_mutator(Node *node, |
220 | | adjust_appendrel_attrs_context *context) |
221 | 0 | { |
222 | 0 | AppendRelInfo **appinfos = context->appinfos; |
223 | 0 | int nappinfos = context->nappinfos; |
224 | 0 | int cnt; |
225 | |
|
226 | 0 | if (node == NULL) |
227 | 0 | return NULL; |
228 | 0 | if (IsA(node, Var)) |
229 | 0 | { |
230 | 0 | Var *var = (Var *) copyObject(node); |
231 | 0 | AppendRelInfo *appinfo = NULL; |
232 | |
|
233 | 0 | if (var->varlevelsup != 0) |
234 | 0 | return (Node *) var; /* no changes needed */ |
235 | | |
236 | | /* |
237 | | * You might think we need to adjust var->varnullingrels, but that |
238 | | * shouldn't need any changes. It will contain outer-join relids, |
239 | | * while the transformation we are making affects only baserels. |
240 | | * Below, we just propagate var->varnullingrels into the translated |
241 | | * Var. |
242 | | * |
243 | | * If var->varnullingrels isn't empty, and the translation wouldn't be |
244 | | * a Var, we have to fail. One could imagine wrapping the translated |
245 | | * expression in a PlaceHolderVar, but that won't work because this is |
246 | | * typically used after freezing placeholders. Fortunately, the case |
247 | | * appears unreachable at the moment. We can see nonempty |
248 | | * var->varnullingrels here, but only in cases involving partitionwise |
249 | | * joining, and in such cases the translations will always be Vars. |
250 | | * (Non-Var translations occur only for appendrels made by flattening |
251 | | * UNION ALL subqueries.) Should we need to make this work in future, |
252 | | * a possible fix is to mandate that prepjointree.c create PHVs for |
253 | | * all non-Var outputs of such subqueries, and then we could look up |
254 | | * the pre-existing PHV here. Or perhaps just wrap the translations |
255 | | * that way to begin with? |
256 | | * |
257 | | * If var->varreturningtype is not VAR_RETURNING_DEFAULT, then that |
258 | | * also needs to be copied to the translated Var. That too would fail |
259 | | * if the translation wasn't a Var, but that should never happen since |
260 | | * a non-default var->varreturningtype is only used for Vars referring |
261 | | * to the result relation, which should never be a flattened UNION ALL |
262 | | * subquery. |
263 | | */ |
264 | | |
265 | 0 | for (cnt = 0; cnt < nappinfos; cnt++) |
266 | 0 | { |
267 | 0 | if (var->varno == appinfos[cnt]->parent_relid) |
268 | 0 | { |
269 | 0 | appinfo = appinfos[cnt]; |
270 | 0 | break; |
271 | 0 | } |
272 | 0 | } |
273 | |
|
274 | 0 | if (appinfo) |
275 | 0 | { |
276 | 0 | var->varno = appinfo->child_relid; |
277 | | /* it's now a generated Var, so drop any syntactic labeling */ |
278 | 0 | var->varnosyn = 0; |
279 | 0 | var->varattnosyn = 0; |
280 | 0 | if (var->varattno > 0) |
281 | 0 | { |
282 | 0 | Node *newnode; |
283 | |
|
284 | 0 | if (var->varattno > list_length(appinfo->translated_vars)) |
285 | 0 | elog(ERROR, "attribute %d of relation \"%s\" does not exist", |
286 | 0 | var->varattno, get_rel_name(appinfo->parent_reloid)); |
287 | 0 | newnode = copyObject(list_nth(appinfo->translated_vars, |
288 | 0 | var->varattno - 1)); |
289 | 0 | if (newnode == NULL) |
290 | 0 | elog(ERROR, "attribute %d of relation \"%s\" does not exist", |
291 | 0 | var->varattno, get_rel_name(appinfo->parent_reloid)); |
292 | 0 | if (IsA(newnode, Var)) |
293 | 0 | { |
294 | 0 | ((Var *) newnode)->varreturningtype = var->varreturningtype; |
295 | 0 | ((Var *) newnode)->varnullingrels = var->varnullingrels; |
296 | 0 | } |
297 | 0 | else |
298 | 0 | { |
299 | 0 | if (var->varreturningtype != VAR_RETURNING_DEFAULT) |
300 | 0 | elog(ERROR, "failed to apply returningtype to a non-Var"); |
301 | 0 | if (var->varnullingrels != NULL) |
302 | 0 | elog(ERROR, "failed to apply nullingrels to a non-Var"); |
303 | 0 | } |
304 | 0 | return newnode; |
305 | 0 | } |
306 | 0 | else if (var->varattno == 0) |
307 | 0 | { |
308 | | /* |
309 | | * Whole-row Var: if we are dealing with named rowtypes, we |
310 | | * can use a whole-row Var for the child table plus a coercion |
311 | | * step to convert the tuple layout to the parent's rowtype. |
312 | | * Otherwise we have to generate a RowExpr. |
313 | | */ |
314 | 0 | if (OidIsValid(appinfo->child_reltype)) |
315 | 0 | { |
316 | 0 | Assert(var->vartype == appinfo->parent_reltype); |
317 | 0 | if (appinfo->parent_reltype != appinfo->child_reltype) |
318 | 0 | { |
319 | 0 | ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); |
320 | |
|
321 | 0 | r->arg = (Expr *) var; |
322 | 0 | r->resulttype = appinfo->parent_reltype; |
323 | 0 | r->convertformat = COERCE_IMPLICIT_CAST; |
324 | 0 | r->location = -1; |
325 | | /* Make sure the Var node has the right type ID, too */ |
326 | 0 | var->vartype = appinfo->child_reltype; |
327 | 0 | return (Node *) r; |
328 | 0 | } |
329 | 0 | } |
330 | 0 | else |
331 | 0 | { |
332 | | /* |
333 | | * Build a RowExpr containing the translated variables. |
334 | | * |
335 | | * In practice var->vartype will always be RECORDOID here, |
336 | | * so we need to come up with some suitable column names. |
337 | | * We use the parent RTE's column names. |
338 | | * |
339 | | * Note: we can't get here for inheritance cases, so there |
340 | | * is no need to worry that translated_vars might contain |
341 | | * some dummy NULLs. |
342 | | */ |
343 | 0 | RowExpr *rowexpr; |
344 | 0 | List *fields; |
345 | 0 | RangeTblEntry *rte; |
346 | |
|
347 | 0 | rte = rt_fetch(appinfo->parent_relid, |
348 | 0 | context->root->parse->rtable); |
349 | 0 | fields = copyObject(appinfo->translated_vars); |
350 | 0 | rowexpr = makeNode(RowExpr); |
351 | 0 | rowexpr->args = fields; |
352 | 0 | rowexpr->row_typeid = var->vartype; |
353 | 0 | rowexpr->row_format = COERCE_IMPLICIT_CAST; |
354 | 0 | rowexpr->colnames = copyObject(rte->eref->colnames); |
355 | 0 | rowexpr->location = -1; |
356 | |
|
357 | 0 | if (var->varreturningtype != VAR_RETURNING_DEFAULT) |
358 | 0 | elog(ERROR, "failed to apply returningtype to a non-Var"); |
359 | 0 | if (var->varnullingrels != NULL) |
360 | 0 | elog(ERROR, "failed to apply nullingrels to a non-Var"); |
361 | | |
362 | 0 | return (Node *) rowexpr; |
363 | 0 | } |
364 | 0 | } |
365 | | /* system attributes don't need any other translation */ |
366 | 0 | } |
367 | 0 | else if (var->varno == ROWID_VAR) |
368 | 0 | { |
369 | | /* |
370 | | * If it's a ROWID_VAR placeholder, see if we've reached a leaf |
371 | | * target rel, for which we can translate the Var to a specific |
372 | | * instantiation. We should never be asked to translate to a set |
373 | | * of relids containing more than one leaf target rel, so the |
374 | | * answer will be unique. If we're still considering non-leaf |
375 | | * inheritance levels, return the ROWID_VAR Var as-is. |
376 | | */ |
377 | 0 | Relids leaf_result_relids = context->root->leaf_result_relids; |
378 | 0 | Index leaf_relid = 0; |
379 | |
|
380 | 0 | for (cnt = 0; cnt < nappinfos; cnt++) |
381 | 0 | { |
382 | 0 | if (bms_is_member(appinfos[cnt]->child_relid, |
383 | 0 | leaf_result_relids)) |
384 | 0 | { |
385 | 0 | if (leaf_relid) |
386 | 0 | elog(ERROR, "cannot translate to multiple leaf relids"); |
387 | 0 | leaf_relid = appinfos[cnt]->child_relid; |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | 0 | if (leaf_relid) |
392 | 0 | { |
393 | 0 | RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *) |
394 | 0 | list_nth(context->root->row_identity_vars, var->varattno - 1); |
395 | |
|
396 | 0 | if (bms_is_member(leaf_relid, ridinfo->rowidrels)) |
397 | 0 | { |
398 | | /* Substitute the Var given in the RowIdentityVarInfo */ |
399 | 0 | var = copyObject(ridinfo->rowidvar); |
400 | | /* ... but use the correct relid */ |
401 | 0 | var->varno = leaf_relid; |
402 | | /* identity vars shouldn't have nulling rels */ |
403 | 0 | Assert(var->varnullingrels == NULL); |
404 | | /* varnosyn in the RowIdentityVarInfo is probably wrong */ |
405 | 0 | var->varnosyn = 0; |
406 | 0 | var->varattnosyn = 0; |
407 | 0 | } |
408 | 0 | else |
409 | 0 | { |
410 | | /* |
411 | | * This leaf rel can't return the desired value, so |
412 | | * substitute a NULL of the correct type. |
413 | | */ |
414 | 0 | return (Node *) makeNullConst(var->vartype, |
415 | 0 | var->vartypmod, |
416 | 0 | var->varcollid); |
417 | 0 | } |
418 | 0 | } |
419 | 0 | } |
420 | 0 | return (Node *) var; |
421 | 0 | } |
422 | 0 | if (IsA(node, CurrentOfExpr)) |
423 | 0 | { |
424 | 0 | CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); |
425 | |
|
426 | 0 | for (cnt = 0; cnt < nappinfos; cnt++) |
427 | 0 | { |
428 | 0 | AppendRelInfo *appinfo = appinfos[cnt]; |
429 | |
|
430 | 0 | if (cexpr->cvarno == appinfo->parent_relid) |
431 | 0 | { |
432 | 0 | cexpr->cvarno = appinfo->child_relid; |
433 | 0 | break; |
434 | 0 | } |
435 | 0 | } |
436 | 0 | return (Node *) cexpr; |
437 | 0 | } |
438 | 0 | if (IsA(node, PlaceHolderVar)) |
439 | 0 | { |
440 | | /* Copy the PlaceHolderVar node with correct mutation of subnodes */ |
441 | 0 | PlaceHolderVar *phv; |
442 | |
|
443 | 0 | phv = (PlaceHolderVar *) expression_tree_mutator(node, |
444 | 0 | adjust_appendrel_attrs_mutator, |
445 | 0 | context); |
446 | | /* now fix PlaceHolderVar's relid sets */ |
447 | 0 | if (phv->phlevelsup == 0) |
448 | 0 | { |
449 | 0 | phv->phrels = adjust_child_relids(phv->phrels, |
450 | 0 | nappinfos, appinfos); |
451 | | /* as above, we needn't touch phnullingrels */ |
452 | 0 | } |
453 | 0 | return (Node *) phv; |
454 | 0 | } |
455 | | /* Shouldn't need to handle planner auxiliary nodes here */ |
456 | 0 | Assert(!IsA(node, SpecialJoinInfo)); |
457 | 0 | Assert(!IsA(node, AppendRelInfo)); |
458 | 0 | Assert(!IsA(node, PlaceHolderInfo)); |
459 | 0 | Assert(!IsA(node, MinMaxAggInfo)); |
460 | | |
461 | | /* |
462 | | * We have to process RestrictInfo nodes specially. (Note: although |
463 | | * set_append_rel_pathlist will hide RestrictInfos in the parent's |
464 | | * baserestrictinfo list from us, it doesn't hide those in joininfo.) |
465 | | */ |
466 | 0 | if (IsA(node, RestrictInfo)) |
467 | 0 | { |
468 | 0 | RestrictInfo *oldinfo = (RestrictInfo *) node; |
469 | 0 | RestrictInfo *newinfo = makeNode(RestrictInfo); |
470 | | |
471 | | /* Copy all flat-copiable fields, notably including rinfo_serial */ |
472 | 0 | memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); |
473 | | |
474 | | /* Recursively fix the clause itself */ |
475 | 0 | newinfo->clause = (Expr *) |
476 | 0 | adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); |
477 | | |
478 | | /* and the modified version, if an OR clause */ |
479 | 0 | newinfo->orclause = (Expr *) |
480 | 0 | adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); |
481 | | |
482 | | /* adjust relid sets too */ |
483 | 0 | newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, |
484 | 0 | context->nappinfos, |
485 | 0 | context->appinfos); |
486 | 0 | newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, |
487 | 0 | context->nappinfos, |
488 | 0 | context->appinfos); |
489 | 0 | newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, |
490 | 0 | context->nappinfos, |
491 | 0 | context->appinfos); |
492 | 0 | newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, |
493 | 0 | context->nappinfos, |
494 | 0 | context->appinfos); |
495 | 0 | newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, |
496 | 0 | context->nappinfos, |
497 | 0 | context->appinfos); |
498 | | |
499 | | /* |
500 | | * Reset cached derivative fields, since these might need to have |
501 | | * different values when considering the child relation. Note we |
502 | | * don't reset left_ec/right_ec: each child variable is implicitly |
503 | | * equivalent to its parent, so still a member of the same EC if any. |
504 | | */ |
505 | 0 | newinfo->eval_cost.startup = -1; |
506 | 0 | newinfo->norm_selec = -1; |
507 | 0 | newinfo->outer_selec = -1; |
508 | 0 | newinfo->left_em = NULL; |
509 | 0 | newinfo->right_em = NULL; |
510 | 0 | newinfo->scansel_cache = NIL; |
511 | 0 | newinfo->left_bucketsize = -1; |
512 | 0 | newinfo->right_bucketsize = -1; |
513 | 0 | newinfo->left_mcvfreq = -1; |
514 | 0 | newinfo->right_mcvfreq = -1; |
515 | |
|
516 | 0 | return (Node *) newinfo; |
517 | 0 | } |
518 | | |
519 | | /* |
520 | | * NOTE: we do not need to recurse into sublinks, because they should |
521 | | * already have been converted to subplans before we see them. |
522 | | */ |
523 | 0 | Assert(!IsA(node, SubLink)); |
524 | 0 | Assert(!IsA(node, Query)); |
525 | | /* We should never see these Query substructures, either. */ |
526 | 0 | Assert(!IsA(node, RangeTblRef)); |
527 | 0 | Assert(!IsA(node, JoinExpr)); |
528 | |
|
529 | 0 | return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, context); |
530 | 0 | } |
531 | | |
532 | | /* |
533 | | * adjust_appendrel_attrs_multilevel |
534 | | * Apply Var translations from an appendrel parent down to a child. |
535 | | * |
536 | | * Replace Vars in the "node" expression that reference "parentrel" with |
537 | | * the appropriate Vars for "childrel". childrel can be more than one |
538 | | * inheritance level removed from parentrel. |
539 | | */ |
540 | | Node * |
541 | | adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, |
542 | | RelOptInfo *childrel, |
543 | | RelOptInfo *parentrel) |
544 | 0 | { |
545 | 0 | AppendRelInfo **appinfos; |
546 | 0 | int nappinfos; |
547 | | |
548 | | /* Recurse if immediate parent is not the top parent. */ |
549 | 0 | if (childrel->parent != parentrel) |
550 | 0 | { |
551 | 0 | if (childrel->parent) |
552 | 0 | node = adjust_appendrel_attrs_multilevel(root, node, |
553 | 0 | childrel->parent, |
554 | 0 | parentrel); |
555 | 0 | else |
556 | 0 | elog(ERROR, "childrel is not a child of parentrel"); |
557 | 0 | } |
558 | | |
559 | | /* Now translate for this child. */ |
560 | 0 | appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos); |
561 | |
|
562 | 0 | node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); |
563 | |
|
564 | 0 | pfree(appinfos); |
565 | |
|
566 | 0 | return node; |
567 | 0 | } |
568 | | |
569 | | /* |
570 | | * Substitute child relids for parent relids in a Relid set. The array of |
571 | | * appinfos specifies the substitutions to be performed. |
572 | | */ |
573 | | Relids |
574 | | adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) |
575 | 0 | { |
576 | 0 | Bitmapset *result = NULL; |
577 | 0 | int cnt; |
578 | |
|
579 | 0 | for (cnt = 0; cnt < nappinfos; cnt++) |
580 | 0 | { |
581 | 0 | AppendRelInfo *appinfo = appinfos[cnt]; |
582 | | |
583 | | /* Remove parent, add child */ |
584 | 0 | if (bms_is_member(appinfo->parent_relid, relids)) |
585 | 0 | { |
586 | | /* Make a copy if we are changing the set. */ |
587 | 0 | if (!result) |
588 | 0 | result = bms_copy(relids); |
589 | |
|
590 | 0 | result = bms_del_member(result, appinfo->parent_relid); |
591 | 0 | result = bms_add_member(result, appinfo->child_relid); |
592 | 0 | } |
593 | 0 | } |
594 | | |
595 | | /* If we made any changes, return the modified copy. */ |
596 | 0 | if (result) |
597 | 0 | return result; |
598 | | |
599 | | /* Otherwise, return the original set without modification. */ |
600 | 0 | return relids; |
601 | 0 | } |
602 | | |
603 | | /* |
604 | | * Substitute child's relids for parent's relids in a Relid set. |
605 | | * The childrel can be multiple inheritance levels below the parent. |
606 | | */ |
607 | | Relids |
608 | | adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, |
609 | | RelOptInfo *childrel, |
610 | | RelOptInfo *parentrel) |
611 | 0 | { |
612 | 0 | AppendRelInfo **appinfos; |
613 | 0 | int nappinfos; |
614 | | |
615 | | /* |
616 | | * If the given relids set doesn't contain any of the parent relids, it |
617 | | * will remain unchanged. |
618 | | */ |
619 | 0 | if (!bms_overlap(relids, parentrel->relids)) |
620 | 0 | return relids; |
621 | | |
622 | | /* Recurse if immediate parent is not the top parent. */ |
623 | 0 | if (childrel->parent != parentrel) |
624 | 0 | { |
625 | 0 | if (childrel->parent) |
626 | 0 | relids = adjust_child_relids_multilevel(root, relids, |
627 | 0 | childrel->parent, |
628 | 0 | parentrel); |
629 | 0 | else |
630 | 0 | elog(ERROR, "childrel is not a child of parentrel"); |
631 | 0 | } |
632 | | |
633 | | /* Now translate for this child. */ |
634 | 0 | appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos); |
635 | |
|
636 | 0 | relids = adjust_child_relids(relids, nappinfos, appinfos); |
637 | |
|
638 | 0 | pfree(appinfos); |
639 | |
|
640 | 0 | return relids; |
641 | 0 | } |
642 | | |
643 | | /* |
644 | | * adjust_inherited_attnums |
645 | | * Translate an integer list of attribute numbers from parent to child. |
646 | | */ |
647 | | List * |
648 | | adjust_inherited_attnums(List *attnums, AppendRelInfo *context) |
649 | 0 | { |
650 | 0 | List *result = NIL; |
651 | 0 | ListCell *lc; |
652 | | |
653 | | /* This should only happen for an inheritance case, not UNION ALL */ |
654 | 0 | Assert(OidIsValid(context->parent_reloid)); |
655 | | |
656 | | /* Look up each attribute in the AppendRelInfo's translated_vars list */ |
657 | 0 | foreach(lc, attnums) |
658 | 0 | { |
659 | 0 | AttrNumber parentattno = lfirst_int(lc); |
660 | 0 | Var *childvar; |
661 | | |
662 | | /* Look up the translation of this column: it must be a Var */ |
663 | 0 | if (parentattno <= 0 || |
664 | 0 | parentattno > list_length(context->translated_vars)) |
665 | 0 | elog(ERROR, "attribute %d of relation \"%s\" does not exist", |
666 | 0 | parentattno, get_rel_name(context->parent_reloid)); |
667 | 0 | childvar = (Var *) list_nth(context->translated_vars, parentattno - 1); |
668 | 0 | if (childvar == NULL || !IsA(childvar, Var)) |
669 | 0 | elog(ERROR, "attribute %d of relation \"%s\" does not exist", |
670 | 0 | parentattno, get_rel_name(context->parent_reloid)); |
671 | | |
672 | 0 | result = lappend_int(result, childvar->varattno); |
673 | 0 | } |
674 | 0 | return result; |
675 | 0 | } |
676 | | |
677 | | /* |
678 | | * adjust_inherited_attnums_multilevel |
679 | | * As above, but traverse multiple inheritance levels as needed. |
680 | | */ |
681 | | List * |
682 | | adjust_inherited_attnums_multilevel(PlannerInfo *root, List *attnums, |
683 | | Index child_relid, Index top_parent_relid) |
684 | 0 | { |
685 | 0 | AppendRelInfo *appinfo = root->append_rel_array[child_relid]; |
686 | |
|
687 | 0 | if (!appinfo) |
688 | 0 | elog(ERROR, "child rel %d not found in append_rel_array", child_relid); |
689 | | |
690 | | /* Recurse if immediate parent is not the top parent. */ |
691 | 0 | if (appinfo->parent_relid != top_parent_relid) |
692 | 0 | attnums = adjust_inherited_attnums_multilevel(root, attnums, |
693 | 0 | appinfo->parent_relid, |
694 | 0 | top_parent_relid); |
695 | | |
696 | | /* Now translate for this child */ |
697 | 0 | return adjust_inherited_attnums(attnums, appinfo); |
698 | 0 | } |
699 | | |
700 | | /* |
701 | | * get_translated_update_targetlist |
702 | | * Get the processed_tlist of an UPDATE query, translated as needed to |
703 | | * match a child target relation. |
704 | | * |
705 | | * Optionally also return the list of target column numbers translated |
706 | | * to this target relation. (The resnos in processed_tlist MUST NOT be |
707 | | * relied on for this purpose.) |
708 | | */ |
709 | | void |
710 | | get_translated_update_targetlist(PlannerInfo *root, Index relid, |
711 | | List **processed_tlist, List **update_colnos) |
712 | 0 | { |
713 | | /* This is pretty meaningless for commands other than UPDATE. */ |
714 | 0 | Assert(root->parse->commandType == CMD_UPDATE); |
715 | 0 | if (relid == root->parse->resultRelation) |
716 | 0 | { |
717 | | /* |
718 | | * Non-inheritance case, so it's easy. The caller might be expecting |
719 | | * a tree it can scribble on, though, so copy. |
720 | | */ |
721 | 0 | *processed_tlist = copyObject(root->processed_tlist); |
722 | 0 | if (update_colnos) |
723 | 0 | *update_colnos = copyObject(root->update_colnos); |
724 | 0 | } |
725 | 0 | else |
726 | 0 | { |
727 | 0 | Assert(bms_is_member(relid, root->all_result_relids)); |
728 | 0 | *processed_tlist = (List *) |
729 | 0 | adjust_appendrel_attrs_multilevel(root, |
730 | 0 | (Node *) root->processed_tlist, |
731 | 0 | find_base_rel(root, relid), |
732 | 0 | find_base_rel(root, root->parse->resultRelation)); |
733 | 0 | if (update_colnos) |
734 | 0 | *update_colnos = |
735 | 0 | adjust_inherited_attnums_multilevel(root, root->update_colnos, |
736 | 0 | relid, |
737 | 0 | root->parse->resultRelation); |
738 | 0 | } |
739 | 0 | } |
740 | | |
741 | | /* |
742 | | * find_appinfos_by_relids |
743 | | * Find AppendRelInfo structures for base relations listed in relids. |
744 | | * |
745 | | * The relids argument is typically a join relation's relids, which can |
746 | | * include outer-join RT indexes in addition to baserels. We silently |
747 | | * ignore the outer joins. |
748 | | * |
749 | | * The AppendRelInfos are returned in an array, which can be pfree'd by the |
750 | | * caller. *nappinfos is set to the number of entries in the array. |
751 | | */ |
752 | | AppendRelInfo ** |
753 | | find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) |
754 | 0 | { |
755 | 0 | AppendRelInfo **appinfos; |
756 | 0 | int cnt = 0; |
757 | 0 | int i; |
758 | | |
759 | | /* Allocate an array that's certainly big enough */ |
760 | 0 | appinfos = (AppendRelInfo **) |
761 | 0 | palloc(sizeof(AppendRelInfo *) * bms_num_members(relids)); |
762 | |
|
763 | 0 | i = -1; |
764 | 0 | while ((i = bms_next_member(relids, i)) >= 0) |
765 | 0 | { |
766 | 0 | AppendRelInfo *appinfo = root->append_rel_array[i]; |
767 | |
|
768 | 0 | if (!appinfo) |
769 | 0 | { |
770 | | /* Probably i is an OJ index, but let's check */ |
771 | 0 | if (find_base_rel_ignore_join(root, i) == NULL) |
772 | 0 | continue; |
773 | | /* It's a base rel, but we lack an append_rel_array entry */ |
774 | 0 | elog(ERROR, "child rel %d not found in append_rel_array", i); |
775 | 0 | } |
776 | | |
777 | 0 | appinfos[cnt++] = appinfo; |
778 | 0 | } |
779 | 0 | *nappinfos = cnt; |
780 | 0 | return appinfos; |
781 | 0 | } |
782 | | |
783 | | |
784 | | /***************************************************************************** |
785 | | * |
786 | | * ROW-IDENTITY VARIABLE MANAGEMENT |
787 | | * |
788 | | * This code lacks a good home, perhaps. We choose to keep it here because |
789 | | * adjust_appendrel_attrs_mutator() is its principal co-conspirator. That |
790 | | * function does most of what is needed to expand ROWID_VAR Vars into the |
791 | | * right things. |
792 | | * |
793 | | *****************************************************************************/ |
794 | | |
795 | | /* |
796 | | * add_row_identity_var |
797 | | * Register a row-identity column to be used in UPDATE/DELETE/MERGE. |
798 | | * |
799 | | * The Var must be equal(), aside from varno, to any other row-identity |
800 | | * column with the same rowid_name. Thus, for example, "wholerow" |
801 | | * row identities had better use vartype == RECORDOID. |
802 | | * |
803 | | * rtindex is currently redundant with rowid_var->varno, but we specify |
804 | | * it as a separate parameter in case this is ever generalized to support |
805 | | * non-Var expressions. (We could reasonably handle expressions over |
806 | | * Vars of the specified rtindex, but for now that seems unnecessary.) |
807 | | */ |
808 | | void |
809 | | add_row_identity_var(PlannerInfo *root, Var *orig_var, |
810 | | Index rtindex, const char *rowid_name) |
811 | 0 | { |
812 | 0 | TargetEntry *tle; |
813 | 0 | Var *rowid_var; |
814 | 0 | RowIdentityVarInfo *ridinfo; |
815 | 0 | ListCell *lc; |
816 | | |
817 | | /* For now, the argument must be just a Var of the given rtindex */ |
818 | 0 | Assert(IsA(orig_var, Var)); |
819 | 0 | Assert(orig_var->varno == rtindex); |
820 | 0 | Assert(orig_var->varlevelsup == 0); |
821 | 0 | Assert(orig_var->varnullingrels == NULL); |
822 | | |
823 | | /* |
824 | | * If we're doing non-inherited UPDATE/DELETE/MERGE, there's little need |
825 | | * for ROWID_VAR shenanigans. Just shove the presented Var into the |
826 | | * processed_tlist, and we're done. |
827 | | */ |
828 | 0 | if (rtindex == root->parse->resultRelation) |
829 | 0 | { |
830 | 0 | tle = makeTargetEntry((Expr *) orig_var, |
831 | 0 | list_length(root->processed_tlist) + 1, |
832 | 0 | pstrdup(rowid_name), |
833 | 0 | true); |
834 | 0 | root->processed_tlist = lappend(root->processed_tlist, tle); |
835 | 0 | return; |
836 | 0 | } |
837 | | |
838 | | /* |
839 | | * Otherwise, rtindex should reference a leaf target relation that's being |
840 | | * added to the query during expand_inherited_rtentry(). |
841 | | */ |
842 | 0 | Assert(bms_is_member(rtindex, root->leaf_result_relids)); |
843 | 0 | Assert(root->append_rel_array[rtindex] != NULL); |
844 | | |
845 | | /* |
846 | | * We have to find a matching RowIdentityVarInfo, or make one if there is |
847 | | * none. To allow using equal() to match the vars, change the varno to |
848 | | * ROWID_VAR, leaving all else alone. |
849 | | */ |
850 | 0 | rowid_var = copyObject(orig_var); |
851 | | /* This could eventually become ChangeVarNodes() */ |
852 | 0 | rowid_var->varno = ROWID_VAR; |
853 | | |
854 | | /* Look for an existing row-id column of the same name */ |
855 | 0 | foreach(lc, root->row_identity_vars) |
856 | 0 | { |
857 | 0 | ridinfo = (RowIdentityVarInfo *) lfirst(lc); |
858 | 0 | if (strcmp(rowid_name, ridinfo->rowidname) != 0) |
859 | 0 | continue; |
860 | 0 | if (equal(rowid_var, ridinfo->rowidvar)) |
861 | 0 | { |
862 | | /* Found a match; we need only record that rtindex needs it too */ |
863 | 0 | ridinfo->rowidrels = bms_add_member(ridinfo->rowidrels, rtindex); |
864 | 0 | return; |
865 | 0 | } |
866 | 0 | else |
867 | 0 | { |
868 | | /* Ooops, can't handle this */ |
869 | 0 | elog(ERROR, "conflicting uses of row-identity name \"%s\"", |
870 | 0 | rowid_name); |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | | /* No request yet, so add a new RowIdentityVarInfo */ |
875 | 0 | ridinfo = makeNode(RowIdentityVarInfo); |
876 | 0 | ridinfo->rowidvar = copyObject(rowid_var); |
877 | | /* for the moment, estimate width using just the datatype info */ |
878 | 0 | ridinfo->rowidwidth = get_typavgwidth(exprType((Node *) rowid_var), |
879 | 0 | exprTypmod((Node *) rowid_var)); |
880 | 0 | ridinfo->rowidname = pstrdup(rowid_name); |
881 | 0 | ridinfo->rowidrels = bms_make_singleton(rtindex); |
882 | |
|
883 | 0 | root->row_identity_vars = lappend(root->row_identity_vars, ridinfo); |
884 | | |
885 | | /* Change rowid_var into a reference to this row_identity_vars entry */ |
886 | 0 | rowid_var->varattno = list_length(root->row_identity_vars); |
887 | | |
888 | | /* Push the ROWID_VAR reference variable into processed_tlist */ |
889 | 0 | tle = makeTargetEntry((Expr *) rowid_var, |
890 | 0 | list_length(root->processed_tlist) + 1, |
891 | 0 | pstrdup(rowid_name), |
892 | 0 | true); |
893 | 0 | root->processed_tlist = lappend(root->processed_tlist, tle); |
894 | 0 | } |
895 | | |
896 | | /* |
897 | | * add_row_identity_columns |
898 | | * |
899 | | * This function adds the row identity columns needed by the core code. |
900 | | * FDWs might call add_row_identity_var() for themselves to add nonstandard |
901 | | * columns. (Duplicate requests are fine.) |
902 | | */ |
903 | | void |
904 | | add_row_identity_columns(PlannerInfo *root, Index rtindex, |
905 | | RangeTblEntry *target_rte, |
906 | | Relation target_relation) |
907 | 0 | { |
908 | 0 | CmdType commandType = root->parse->commandType; |
909 | 0 | char relkind = target_relation->rd_rel->relkind; |
910 | 0 | Var *var; |
911 | |
|
912 | 0 | Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE || commandType == CMD_MERGE); |
913 | |
|
914 | 0 | if (relkind == RELKIND_RELATION || |
915 | 0 | relkind == RELKIND_MATVIEW || |
916 | 0 | relkind == RELKIND_PARTITIONED_TABLE) |
917 | 0 | { |
918 | | /* |
919 | | * Emit CTID so that executor can find the row to merge, update or |
920 | | * delete. |
921 | | */ |
922 | 0 | var = makeVar(rtindex, |
923 | 0 | SelfItemPointerAttributeNumber, |
924 | 0 | TIDOID, |
925 | 0 | -1, |
926 | 0 | InvalidOid, |
927 | 0 | 0); |
928 | 0 | add_row_identity_var(root, var, rtindex, "ctid"); |
929 | 0 | } |
930 | 0 | else if (relkind == RELKIND_FOREIGN_TABLE) |
931 | 0 | { |
932 | | /* |
933 | | * Let the foreign table's FDW add whatever junk TLEs it wants. |
934 | | */ |
935 | 0 | FdwRoutine *fdwroutine; |
936 | |
|
937 | 0 | fdwroutine = GetFdwRoutineForRelation(target_relation, false); |
938 | |
|
939 | 0 | if (fdwroutine->AddForeignUpdateTargets != NULL) |
940 | 0 | fdwroutine->AddForeignUpdateTargets(root, rtindex, |
941 | 0 | target_rte, target_relation); |
942 | | |
943 | | /* |
944 | | * For UPDATE, we need to make the FDW fetch unchanged columns by |
945 | | * asking it to fetch a whole-row Var. That's because the top-level |
946 | | * targetlist only contains entries for changed columns, but |
947 | | * ExecUpdate will need to build the complete new tuple. (Actually, |
948 | | * we only really need this in UPDATEs that are not pushed to the |
949 | | * remote side, but it's hard to tell if that will be the case at the |
950 | | * point when this function is called.) |
951 | | * |
952 | | * We will also need the whole row if there are any row triggers, so |
953 | | * that the executor will have the "old" row to pass to the trigger. |
954 | | * Alas, this misses system columns. |
955 | | */ |
956 | 0 | if (commandType == CMD_UPDATE || |
957 | 0 | (target_relation->trigdesc && |
958 | 0 | (target_relation->trigdesc->trig_delete_after_row || |
959 | 0 | target_relation->trigdesc->trig_delete_before_row))) |
960 | 0 | { |
961 | 0 | var = makeVar(rtindex, |
962 | 0 | InvalidAttrNumber, |
963 | 0 | RECORDOID, |
964 | 0 | -1, |
965 | 0 | InvalidOid, |
966 | 0 | 0); |
967 | 0 | add_row_identity_var(root, var, rtindex, "wholerow"); |
968 | 0 | } |
969 | 0 | } |
970 | 0 | } |
971 | | |
972 | | /* |
973 | | * distribute_row_identity_vars |
974 | | * |
975 | | * After we have finished identifying all the row identity columns |
976 | | * needed by an inherited UPDATE/DELETE/MERGE query, make sure that |
977 | | * these columns will be generated by all the target relations. |
978 | | * |
979 | | * This is more or less like what build_base_rel_tlists() does, |
980 | | * except that it would not understand what to do with ROWID_VAR Vars. |
981 | | * Since that function runs before inheritance relations are expanded, |
982 | | * it will never see any such Vars anyway. |
983 | | */ |
984 | | void |
985 | | distribute_row_identity_vars(PlannerInfo *root) |
986 | 0 | { |
987 | 0 | Query *parse = root->parse; |
988 | 0 | int result_relation = parse->resultRelation; |
989 | 0 | RangeTblEntry *target_rte; |
990 | 0 | RelOptInfo *target_rel; |
991 | 0 | ListCell *lc; |
992 | | |
993 | | /* |
994 | | * There's nothing to do if this isn't an inherited UPDATE/DELETE/MERGE. |
995 | | */ |
996 | 0 | if (parse->commandType != CMD_UPDATE && parse->commandType != CMD_DELETE && |
997 | 0 | parse->commandType != CMD_MERGE) |
998 | 0 | { |
999 | 0 | Assert(root->row_identity_vars == NIL); |
1000 | 0 | return; |
1001 | 0 | } |
1002 | 0 | target_rte = rt_fetch(result_relation, parse->rtable); |
1003 | 0 | if (!target_rte->inh) |
1004 | 0 | { |
1005 | 0 | Assert(root->row_identity_vars == NIL); |
1006 | 0 | return; |
1007 | 0 | } |
1008 | | |
1009 | | /* |
1010 | | * Ordinarily, we expect that leaf result relation(s) will have added some |
1011 | | * ROWID_VAR Vars to the query. However, it's possible that constraint |
1012 | | * exclusion suppressed every leaf relation. The executor will get upset |
1013 | | * if the plan has no row identity columns at all, even though it will |
1014 | | * certainly process no rows. Handle this edge case by re-opening the top |
1015 | | * result relation and adding the row identity columns it would have used, |
1016 | | * as preprocess_targetlist() would have done if it weren't marked "inh". |
1017 | | * Then re-run build_base_rel_tlists() to ensure that the added columns |
1018 | | * get propagated to the relation's reltarget. (This is a bit ugly, but |
1019 | | * it seems better to confine the ugliness and extra cycles to this |
1020 | | * unusual corner case.) |
1021 | | */ |
1022 | 0 | if (root->row_identity_vars == NIL) |
1023 | 0 | { |
1024 | 0 | Relation target_relation; |
1025 | |
|
1026 | 0 | target_relation = table_open(target_rte->relid, NoLock); |
1027 | 0 | add_row_identity_columns(root, result_relation, |
1028 | 0 | target_rte, target_relation); |
1029 | 0 | table_close(target_relation, NoLock); |
1030 | 0 | build_base_rel_tlists(root, root->processed_tlist); |
1031 | | /* There are no ROWID_VAR Vars in this case, so we're done. */ |
1032 | 0 | return; |
1033 | 0 | } |
1034 | | |
1035 | | /* |
1036 | | * Dig through the processed_tlist to find the ROWID_VAR reference Vars, |
1037 | | * and forcibly copy them into the reltarget list of the topmost target |
1038 | | * relation. That's sufficient because they'll be copied to the |
1039 | | * individual leaf target rels (with appropriate translation) later, |
1040 | | * during appendrel expansion --- see set_append_rel_size(). |
1041 | | */ |
1042 | 0 | target_rel = find_base_rel(root, result_relation); |
1043 | |
|
1044 | 0 | foreach(lc, root->processed_tlist) |
1045 | 0 | { |
1046 | 0 | TargetEntry *tle = lfirst(lc); |
1047 | 0 | Var *var = (Var *) tle->expr; |
1048 | |
|
1049 | 0 | if (var && IsA(var, Var) && var->varno == ROWID_VAR) |
1050 | 0 | { |
1051 | 0 | target_rel->reltarget->exprs = |
1052 | 0 | lappend(target_rel->reltarget->exprs, copyObject(var)); |
1053 | | /* reltarget cost and width will be computed later */ |
1054 | 0 | } |
1055 | 0 | } |
1056 | 0 | } |