ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
safe_query.c
Go to the documentation of this file.
1/**
2 * @file safe_query.c
3 * @brief Hierarchical-CQ rewriter for the @c provsql.boolean_provenance GUC.
4 *
5 * Opt-in pre-pass invoked by @c process_query in @c provsql.c. Rewrites
6 * SELECT-FROM-WHERE conjunctive queries with a hierarchical structure
7 * (every shared variable's atom-set is either fully covered or fits
8 * into a multi-level inner-group decomposition) into a form whose
9 * provenance circuit is read-once. The result lets the linear-time
10 * @c BooleanCircuit::independentEvaluation method handle queries that
11 * would otherwise fall through to the dDNNF / tree-decomposition /
12 * external-knowledge-compiler pipeline.
13 *
14 * The hierarchical-CQ class and its read-once decomposability are the
15 * "safe queries" of Dalvi and Suciu, "The Dichotomy of Probabilistic
16 * Inference for Unions of Conjunctive Queries", J. ACM 59(6), 2012
17 * (doi:10.1145/2395116.2395119) ; the dichotomy theorem in that paper
18 * is the theoretical foundation for the rewrite this file implements.
19 *
20 * Entry point: @c try_safe_query_rewrite (see @c safe_query.h).
21 *
22 * The bulk of the file is detector + rewriter helpers. All non-API
23 * symbols are @c static.
24 */
25#include "postgres.h"
26#include "fmgr.h"
27#include "pg_config.h"
28#include "access/htup_details.h"
29#include "catalog/pg_class.h"
30#include "catalog/pg_inherits.h"
31#if PG_VERSION_NUM < 110000
32#include "catalog/pg_inherits_fn.h" /* has_superclass moved to pg_inherits.h in PG 11 */
33#endif
34#include "catalog/pg_type.h"
35#include "nodes/bitmapset.h"
36#include "nodes/makefuncs.h"
37#include "nodes/nodeFuncs.h"
38#include "nodes/parsenodes.h"
39#include "nodes/pg_list.h"
40#if PG_VERSION_NUM >= 120000
41#include "optimizer/optimizer.h"
42#else
43#include "optimizer/clauses.h" /* contain_volatile_functions */
44#endif
45#include "parser/parse_oper.h"
46#include "utils/builtins.h"
47#include "utils/datum.h"
48#include "utils/lsyscache.h"
49#include "utils/syscache.h"
50
51#include "compatibility.h"
52#include "provsql_mmap.h"
53#include "provsql_utils.h"
54#include "safe_query.h"
55
56extern int provsql_verbose; /* declared in provsql.c */
57
58/* -------------------------------------------------------------------------
59 * Safe-query optimisation (provsql.boolean_provenance)
60 *
61 * Slot for the hierarchical-CQ rewriter. When the GUC
62 * provsql.boolean_provenance is on, the planner-hook calls
63 * try_safe_query_rewrite() between the AGG-DISTINCT rewrite and
64 * get_provenance_attributes; if it returns a non-NULL Query, that
65 * Query is fed back into process_query() from the top, exactly the
66 * same recursion pattern as rewrite_agg_distinct().
67 *
68 * The first pass (is_safe_query_candidate) is a cheap shape /
69 * metadata gate; if it accepts, the second pass
70 * (find_hierarchical_root_atoms) builds the variable-equivalence
71 * relation and decides whether the query has a root variable. When
72 * both accept, rewrite_hierarchical_cq emits the wrapped Query.
73 * ------------------------------------------------------------------------- */
74
75/**
76 * @brief Walk a Query and reject anything outside the safe-query scope.
77 *
78 * Accepts only:
79 * - self-join-free conjunctive queries
80 * - no aggregation, window functions, DISTINCT ON, LIMIT/OFFSET,
81 * sublinks, or top-level set operations. Top-level UCQs
82 * (UNION / EXCEPT / INTERSECT) are processed branch-by-branch by
83 * the planner's recursive @c process_query, so each branch reaches
84 * this gate on its own and the outer set-operation node bails here.
85 * - an outer @c GROUP @c BY or top-level @c DISTINCT. Without one,
86 * the per-atom @c SELECT @c DISTINCT wraps would shrink the user-
87 * visible row count, so the rewrite would change the result set.
88 * - all base relations have a provenance metadata entry, none are
89 * OPAQUE. BID atom block-key validation is deferred to the
90 * rewriter (we cannot check it without knowing the root variable).
91 *
92 * @return @c true iff @p q is a candidate for the safe-query rewrite.
93 */
94static bool is_safe_query_candidate(const constants_t *constants, Query *q,
95 Bitmapset *approved_self_join_relids) {
96 ListCell *lc, *lc2;
97 List *seen_relids = NIL;
98
99 if (q->setOperations != NULL)
100 return false; /* UCQ branches handled by
101 * recursive process_query
102 * re-entry, not here */
103 if (q->hasAggs || q->hasWindowFuncs)
104 return false;
105 if (q->limitCount != NULL || q->limitOffset != NULL)
106 return false;
107 if (q->groupingSets != NIL)
108 return false;
109 if (q->hasDistinctOn)
110 return false;
111 if (q->hasSubLinks)
112 return false;
113 if (q->rtable == NIL)
114 return false; /* FROM-less; nothing to rewrite */
115 /* The per-atom @c SELECT @c DISTINCT wraps collapse duplicate
116 * source tuples on their projection slots; without an outer
117 * @c GROUP @c BY or top-level @c DISTINCT the user would observe a
118 * shrunken row count compared to the unrewritten query. Require
119 * one of them so the rewrite is row-count-preserving in the user's
120 * eye. Both are encoded as @c SortGroupClause lists; either is
121 * enough -- @c transform_distinct_into_group_by promotes the
122 * outer @c DISTINCT to a @c GROUP @c BY downstream of us. */
123 if (q->groupClause == NIL && q->distinctClause == NIL)
124 return false;
125
126 /* All FROM entries must be base relations referenced via plain
127 * RangeTblRef (no JoinExpr, no RTE_SUBQUERY / RTE_VALUES / ...).
128 * The fromlist check ensures we are looking at a flat join. */
129 foreach (lc, q->jointree->fromlist) {
130 Node *n = (Node *) lfirst(lc);
131 if (!IsA(n, RangeTblRef))
132 return false;
133 }
134
135 foreach (lc, q->rtable) {
136 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
138
139 if (rte->rtekind != RTE_RELATION)
140 return false;
141 /* Self-join-free: no two RTEs may share a relid, unless the
142 * disjoint-constant pre-pass has certified the relid's same-
143 * relid group as disjoint via mutually exclusive @c Var @c =
144 * @c Const conjuncts on the same column. The PK-unification
145 * pre-pass collapses any unifiable groups before reaching this
146 * point, so a duplicate here means either a non-unifiable group
147 * (which the disjoint-constant pre-pass may still rescue) or a
148 * group neither pre-pass can resolve (refuse). */
149 foreach (lc2, seen_relids) {
150 if (lfirst_oid(lc2) == rte->relid) {
151 if (approved_self_join_relids != NULL
152 && bms_is_member((int) rte->relid,
153 approved_self_join_relids))
154 continue;
155 return false;
156 }
157 }
158 seen_relids = lappend_oid(seen_relids, rte->relid);
159
160 /* Metadata gate.
161 *
162 * - No provsql column on the relation: accepted as deterministic,
163 * probability-1 tuples (every row behaves as if it carried a
164 * gate_one() leaf, so read-once factoring is unaffected).
165 *
166 * - provsql column present but no metadata entry: refuse. This
167 * covers CREATE TABLE AS SELECT, ALTER TABLE ADD COLUMN
168 * provsql, and ALTER TABLE RENAME ... TO provsql -- in all
169 * three the relation has a column ProvSQL would honour at
170 * evaluation time, but the column's content never passed
171 * through add_provenance / repair_key, so independence cannot
172 * be assumed.
173 *
174 * - provsql column present and metadata says OPAQUE: refuse
175 * (set_table_info, or a provenance_guard fire after a user-
176 * supplied INSERT / UPDATE).
177 *
178 * - provsql column present and metadata says TID or BID: accept.
179 * The BID block-key alignment check happens in the rewriter
180 * once the root variable is known. */
181 {
182 AttrNumber provsql_attno = get_attnum(rte->relid, PROVSQL_COLUMN_NAME);
183 bool has_provsql_col =
184 provsql_attno != InvalidAttrNumber
185 && get_atttype(rte->relid, provsql_attno) == constants->OID_TYPE_UUID;
186 bool has_meta = provsql_lookup_table_info(rte->relid, &info);
187
188 if (has_provsql_col && !has_meta)
189 return false;
190 if (has_meta && info.kind == PROVSQL_TABLE_OPAQUE)
191 return false;
192 }
193 }
194
195 list_free(seen_relids);
196
197 /* Ancestry-disjointness check. For every pair of RTEs with
198 * DIFFERENT relids, verify their registered base-ancestor sets
199 * don't overlap; reject the candidate when any pair does.
200 * Same-relid pairs are deliberately exempted: those are already
201 * handled by the syntactic shared-relid bail above and its PK-
202 * unification / disjoint-constant rescues, which prove disjointness
203 * at the gate level on a same-relid basis -- a coarser ancestry
204 * overlap check would undo those rescues.
205 *
206 * The fallback "no registry entry => self ancestor" branch covers
207 * the deterministic (no provsql column) case and any future RTE
208 * that slips through without ancestry: a base relid never appears
209 * in another RTE's ancestry register, so a singleton {self} set
210 * cannot cause a false positive against an unrelated derived
211 * table -- conservative on the safe side. */
212 {
213 int natoms = list_length(q->rtable);
214 uint16 *anc_n = palloc0(natoms * sizeof(uint16));
216 = palloc(natoms * sizeof(*anc));
217 int i = 0;
218 int j1, j2;
219 bool overlap = false;
220 ListCell *lc3;
221
222 foreach (lc3, q->rtable) {
223 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc3);
224 if (!provsql_lookup_ancestry(rte->relid, &anc_n[i], anc[i])) {
225 anc[i][0] = rte->relid;
226 anc_n[i] = 1;
227 }
228 i++;
229 }
230
231 for (j1 = 0; !overlap && j1 < natoms; j1++) {
232 RangeTblEntry *r1 = (RangeTblEntry *) list_nth(q->rtable, j1);
233 for (j2 = j1 + 1; !overlap && j2 < natoms; j2++) {
234 RangeTblEntry *r2 = (RangeTblEntry *) list_nth(q->rtable, j2);
235 uint16 a, b;
236 if (r1->relid == r2->relid)
237 continue; /* handled by syntactic bail + approvals above */
238 for (a = 0; !overlap && a < anc_n[j1]; a++)
239 for (b = 0; !overlap && b < anc_n[j2]; b++)
240 if (anc[j1][a] == anc[j2][b])
241 overlap = true;
242 }
243 }
244
245 pfree(anc);
246 pfree(anc_n);
247 if (overlap)
248 return false;
249 }
250
251 return true;
252}
253
254/**
255 * @brief One projected column of an atom's wrapping subquery.
256 *
257 * @c base_attno is the column of the base relation that supplies this
258 * slot. @c class_id is the variable-equivalence-class representative
259 * index from the union-find; only shared classes (those touching at
260 * least two atoms) ever appear as slots, of which the root class is
261 * one.
262 *
263 * The output column number of the slot inside the inner @c SELECT
264 * @c DISTINCT is the 1-based position of the slot in its atom's
265 * @c proj_slots list; the root-class slot is always first, so its
266 * output attno is 1.
267 */
268typedef struct safe_proj_slot {
269 AttrNumber base_attno;
271 AttrNumber outer_attno; ///< 1-based column in the inner sub-Query's targetList (or per-atom DISTINCT wrap for outer-wrap atoms). Matches the slot's position in the atom's proj_slots for outer-wrap atoms, for first-member grouped atoms, and for shared slots on non-first-member grouped atoms. Differs for singleton head Vars on non-first-members: those get the next position in the group's unified inner targetList after all earlier members' slots.
273
274/**
275 * @brief Per-atom rewrite metadata discovered by the hierarchy detector.
276 *
277 * @c rtindex is the 1-based index into @c q->rtable that matches @c Var.varno.
278 * @c proj_slots is the ordered list of @c safe_proj_slot * to project
279 * out of this atom's inner @c SELECT @c DISTINCT. The root-class slot
280 * is always first. Additional shared classes touching this atom
281 * (column pushdown) follow in ascending class-repr order.
282 *
283 * @c pushed_quals is the list of WHERE conjuncts that reference only
284 * this atom (single-atom Vars only) and were extracted from the outer
285 * query before the hierarchy analysis ran. They are AND-injected into
286 * the inner subquery's WHERE after a @c varno remap from
287 * @c rtindex to @c 1, so the atom-local predicates evaluate before the
288 * @c DISTINCT and the offending single-atom Vars never reach the outer
289 * scope.
290 */
291typedef struct safe_rewrite_atom {
292 Index rtindex;
295 int group_id; ///< -1 for atoms wrapped directly at the outer (one @c SELECT @c DISTINCT subquery per atom); >= 0 indexes into the rewrite's groups list and means the atom is a member of an inner sub-Query built around a partial-coverage shared class.
296 Index outer_rtindex; ///< Assigned by the rewriter: this atom's slot in the rebuilt outer rtable. Grouped atoms all share their group's outer_rtindex.
297 Index inner_rtindex; ///< Assigned by the rewriter for grouped atoms only: position inside the inner sub-Query's rtable (1-based). 0 for outer-wrap atoms.
298 AttrNumber root_anchor_attno; ///< For grouped atoms: base @c attno of the root-class binding column inside this atom. Used by the outer Var remap to recognise root-class references that should resolve to the inner sub-Query's single output column.
299 bool is_constant_pinned; ///< Reserved for future constant-selection follow-up work; currently never set (constant-pinned atoms are routed through the multi-component path before this struct is built, so each atom in @c rewrite_hierarchical_cq is unconditionally a regular hierarchical-component atom).
301
302/**
303 * @brief Descriptor for an inner sub-Query introduced when one or more
304 * shared classes have partial coverage.
305 *
306 * Every member atom shares the same partial-coverage set; the group is
307 * folded into a single @c RTE_SUBQUERY at the outer level whose
308 * @c targetList is the fully-covered-class bindings (root first, then
309 * other fully-covered classes in ascending repr order) plus the
310 * implicit @c provsql column, and whose @c groupClause aggregates the
311 * partial-coverage variables away. The hierarchical-CQ rewriter fires
312 * again when @c process_query re-enters on the inner sub-Query, so the
313 * per-atom @c SELECT @c DISTINCT wraps materialise inside.
314 */
315typedef struct safe_inner_group {
317 List *member_atoms; ///< List of safe_rewrite_atom *, in original-rtindex order
318 List *inner_quals; ///< List of Node *: cross-atom conjuncts whose vars all reference group members (original varnos; the rewriter remaps to inner varnos at build time)
319 Index outer_rtindex; ///< Assigned by the rewriter: position of the inner sub-Query RTE in the outer rtable
321
322/** @brief Walker context for @c safe_collect_vars_walker. */
323typedef struct safe_collect_vars_ctx {
324 List *vars; ///< Deduplicated list of distinct base-level Var nodes
326
327/**
328 * @brief Tree walker that collects every distinct base-level Var node.
329 *
330 * "Base level" means @c varlevelsup == 0; outer references are ignored.
331 * Vars are deduplicated by @c (varno, varattno).
332 */
333static bool safe_collect_vars_walker(Node *node, safe_collect_vars_ctx *ctx) {
334 ListCell *lc;
335 if (node == NULL)
336 return false;
337 if (IsA(node, Var)) {
338 Var *v = (Var *) node;
339 if (v->varlevelsup != 0)
340 return false;
341 foreach (lc, ctx->vars) {
342 Var *existing = (Var *) lfirst(lc);
343 if (existing->varno == v->varno && existing->varattno == v->varattno)
344 return false;
345 }
346 ctx->vars = lappend(ctx->vars, v);
347 return false;
348 }
349 return expression_tree_walker(node, safe_collect_vars_walker, ctx);
350}
351
352/**
353 * @brief Find the position of a Var inside the @c vars list (matched on
354 * @c (varno, varattno)). Returns -1 if not present.
355 */
356static int safe_var_index(List *vars, Index varno, AttrNumber varattno) {
357 ListCell *lc;
358 int i = 0;
359 foreach (lc, vars) {
360 Var *v = (Var *) lfirst(lc);
361 if (v->varno == varno && v->varattno == varattno)
362 return i;
363 i++;
364 }
365 return -1;
366}
367
368/**
369 * @brief Recognise a top-level WHERE conjunct that equates two base Vars.
370 *
371 * Matches @c OpExpr nodes whose operator is the canonical equality for
372 * the two operand types and whose operands strip down to base-level
373 * @c Var nodes (possibly through @c RelabelType wrappers, which carry
374 * binary-coercion casts). Returns @c true and fills @p *l, @p *r when
375 * matched.
376 */
377static bool safe_is_var_equality(Expr *qual, Var **l, Var **r) {
378 OpExpr *op;
379 Node *ln, *rn;
380 Var *lv, *rv;
381 Oid expected;
382
383 if (!IsA(qual, OpExpr))
384 return false;
385 op = (OpExpr *) qual;
386 if (list_length(op->args) != 2)
387 return false;
388 ln = (Node *) linitial(op->args);
389 rn = (Node *) lsecond(op->args);
390 while (IsA(ln, RelabelType))
391 ln = (Node *) ((RelabelType *) ln)->arg;
392 while (IsA(rn, RelabelType))
393 rn = (Node *) ((RelabelType *) rn)->arg;
394 if (!IsA(ln, Var) || !IsA(rn, Var))
395 return false;
396 lv = (Var *) ln;
397 rv = (Var *) rn;
398 if (lv->varlevelsup != 0 || rv->varlevelsup != 0)
399 return false;
400 expected = find_equality_operator(lv->vartype, rv->vartype);
401 if (expected == InvalidOid || op->opno != expected)
402 return false;
403 *l = lv;
404 *r = rv;
405 return true;
406}
407
408/**
409 * @brief Recognise @c OpExpr nodes of shape @c Var @c = @c Const.
410 *
411 * Mirrors @c safe_is_var_equality but matches an equality between a
412 * base-level @c Var and a planner-time @c Const literal (in either argument
413 * order), again stripping @c RelabelType wrappers to see through binary-
414 * coercion casts. This is the recogniser used by the constant-selection
415 * elimination pass (Dalvi & Suciu 2007 §5.1, induced FD @c ∅ @c → @c R.a)
416 * to flag union-find roots as pinned to a literal. Volatile predicates
417 * never reach this point: @c safe_split_quals routes them to the residual,
418 * and the constant-selection scan walks both @c per_atom and the residual
419 * but stops at any @c OpExpr that is not a plain @c Var/@c Const equality.
420 *
421 * On match, @p *var receives the base-level @c Var and @p *konst the
422 * literal; on no match, returns @c false without touching them. NULL
423 * constants (@c constisnull) do not yield an FD -- equality to NULL is
424 * never satisfied in standard SQL semantics, so the planner would have
425 * already short-circuited the row at executor time, but a @c Var @c = @c
426 * NULL conjunct is still SQL-legal and we conservatively reject it as
427 * non-FD-inducing.
428 */
429static bool safe_is_var_const_equality(Expr *qual, Var **var, Const **konst) {
430 OpExpr *op;
431 Node *ln, *rn;
432 Var *v;
433 Const *k;
434 Oid expected;
435
436 if (!IsA(qual, OpExpr))
437 return false;
438 op = (OpExpr *) qual;
439 if (list_length(op->args) != 2)
440 return false;
441 ln = (Node *) linitial(op->args);
442 rn = (Node *) lsecond(op->args);
443 while (IsA(ln, RelabelType))
444 ln = (Node *) ((RelabelType *) ln)->arg;
445 while (IsA(rn, RelabelType))
446 rn = (Node *) ((RelabelType *) rn)->arg;
447 if (IsA(ln, Var) && IsA(rn, Const)) {
448 v = (Var *) ln;
449 k = (Const *) rn;
450 } else if (IsA(ln, Const) && IsA(rn, Var)) {
451 k = (Const *) ln;
452 v = (Var *) rn;
453 } else {
454 return false;
455 }
456 if (v->varlevelsup != 0)
457 return false;
458 if (k->constisnull)
459 return false;
460 expected = find_equality_operator(v->vartype, k->consttype);
461 if (expected == InvalidOid || op->opno != expected)
462 return false;
463 *var = v;
464 *konst = k;
465 return true;
466}
467
468/**
469 * @brief Walk @p quals as a tree of @c AND, collecting equality conjuncts.
470 *
471 * Returns the matched @c (Var *, Var *) pairs as a flat list, with each
472 * pair contributed as two consecutive list entries (left, right).
473 * Non-equality conjuncts are silently ignored (they do not contribute
474 * to the variable equivalence relation but do not prevent the rewrite
475 * either; the detector will fall back to bail if no root variable is
476 * found). OR / NOT are not traversed -- they are not equijoins, and
477 * accepting them here would be unsound.
478 */
479static void safe_collect_equalities(Node *quals, List **out) {
480 if (quals == NULL)
481 return;
482 if (IsA(quals, List)) {
483 ListCell *lc;
484 foreach (lc, (List *) quals)
485 safe_collect_equalities((Node *) lfirst(lc), out);
486 return;
487 }
488 if (IsA(quals, BoolExpr)) {
489 BoolExpr *be = (BoolExpr *) quals;
490 if (be->boolop == AND_EXPR) {
491 ListCell *lc;
492 foreach (lc, be->args)
493 safe_collect_equalities((Node *) lfirst(lc), out);
494 }
495 return;
496 }
497 {
498 Var *l, *r;
499 if (safe_is_var_equality((Expr *) quals, &l, &r)) {
500 *out = lappend(*out, l);
501 *out = lappend(*out, r);
502 }
503 }
504}
505
506/** @brief Walker context for @c safe_collect_varnos_walker. */
508 Bitmapset *varnos; ///< Set of @c varno values seen in base-level Vars
510
511/**
512 * @brief Collect the distinct base-level @c varno values referenced by an
513 * expression sub-tree.
514 *
515 * Used by @c safe_split_quals to decide whether a WHERE conjunct is
516 * "atom-local" (single varno ⇒ pushable) or "cross-atom" (≥ 2 varnos
517 * ⇒ stays in the residual). Outer Vars (@c varlevelsup > 0) are
518 * ignored; the safe-query candidate gate has already rejected
519 * sublinks, so they cannot legitimately appear here.
520 */
521static bool safe_collect_varnos_walker(Node *node,
523 if (node == NULL)
524 return false;
525 if (IsA(node, Var)) {
526 Var *v = (Var *) node;
527 if (v->varlevelsup == 0 && (int) v->varno >= 1)
528 ctx->varnos = bms_add_member(ctx->varnos, (int) v->varno);
529 return false;
530 }
531 return expression_tree_walker(node, safe_collect_varnos_walker, ctx);
532}
533
534/**
535 * @brief Flatten the top-level @c AND tree of a WHERE clause into a flat
536 * list of conjunct @c Node *.
537 *
538 * Mirrors @c safe_collect_equalities' AND-tree recursion: a top-level
539 * @c List is treated as an implicit AND (as @c FromExpr->quals can be),
540 * a @c BoolExpr with @c AND_EXPR has its @c args recursed into, and any
541 * other node is treated as a single leaf conjunct (including OR /
542 * NOT / NULL-tests / equalities / arbitrary @c OpExpr). The resulting
543 * list shares pointers with the original tree; callers must
544 * @c copyObject before mutating.
545 */
546static void safe_flatten_and(Node *n, List **out) {
547 if (n == NULL)
548 return;
549 if (IsA(n, List)) {
550 ListCell *lc;
551 foreach (lc, (List *) n)
552 safe_flatten_and((Node *) lfirst(lc), out);
553 return;
554 }
555 if (IsA(n, BoolExpr) && ((BoolExpr *) n)->boolop == AND_EXPR) {
556 ListCell *lc;
557 foreach (lc, ((BoolExpr *) n)->args)
558 safe_flatten_and((Node *) lfirst(lc), out);
559 return;
560 }
561 *out = lappend(*out, n);
562}
563
564/**
565 * @brief Partition top-level WHERE conjuncts into atom-local quals
566 * (pushed into the matching atom's inner wrap) and the residual
567 * cross-atom quals (which stay in the outer query).
568 *
569 * @p per_atom_out is an array of length @p natoms (caller-allocated,
570 * zero-initialised) of @c List *. @p out_residual receives the
571 * residual conjunction, rebuilt as a single @c Node *: @c NULL when no
572 * residual conjuncts remain, the bare conjunct when exactly one, or a
573 * fresh @c BoolExpr(@c AND_EXPR) otherwise.
574 *
575 * A conjunct is pushed when its base-level Vars all reference the same
576 * @c varno and the conjunct contains no volatile function calls.
577 * Volatile predicates are kept in the outer scope because the inner
578 * @c SELECT @c DISTINCT can collapse the evaluation count -- we
579 * deliberately avoid changing the number of times such functions run.
580 *
581 * The pushed conjuncts are still shared with the original
582 * @c q->jointree->quals tree; @c safe_build_inner_wrap @c copyObject's
583 * them before performing the @c varno remap.
584 */
585static void safe_split_quals(Node *quals, int natoms,
586 List **per_atom_out, Node **out_residual) {
587 List *conjuncts = NIL;
588 List *residual = NIL;
589 ListCell *lc;
590 int i;
591
592 for (i = 0; i < natoms; i++)
593 per_atom_out[i] = NIL;
594
595 safe_flatten_and(quals, &conjuncts);
596
597 foreach (lc, conjuncts) {
598 Node *qual = (Node *) lfirst(lc);
599 safe_collect_varnos_ctx vctx = { NULL };
600 int nmembers;
601
602 if (contain_volatile_functions(qual)) {
603 residual = lappend(residual, qual);
604 continue;
605 }
606
607 safe_collect_varnos_walker(qual, &vctx);
608 nmembers = bms_num_members(vctx.varnos);
609 if (nmembers == 1) {
610 int v = bms_singleton_member(vctx.varnos);
611 if (v >= 1 && v <= natoms) {
612 per_atom_out[v - 1] = lappend(per_atom_out[v - 1], qual);
613 bms_free(vctx.varnos);
614 continue;
615 }
616 }
617 bms_free(vctx.varnos);
618 residual = lappend(residual, qual);
619 }
620
621 if (residual == NIL)
622 *out_residual = NULL;
623 else if (list_length(residual) == 1)
624 *out_residual = (Node *) linitial(residual);
625 else
626 *out_residual = (Node *) makeBoolExpr(AND_EXPR, residual, -1);
627}
628
629/**
630 * @brief Partition the cross-atom residual into per-group conjuncts and a
631 * new outer residual.
632 *
633 * For every top-level @c AND conjunct of @p residual:
634 *
635 * - if every base-level @c Var it references points at an atom in some
636 * inner group's member set, the conjunct moves into that group's
637 * @c inner_quals (in original-varno space; the rewriter remaps to
638 * inner varnos when it builds the sub-Query);
639 *
640 * - otherwise the conjunct stays in the rebuilt outer residual.
641 *
642 * Volatile conjuncts always stay in the outer residual: collapsing the
643 * row count inside a sub-Query with an aggregating @c GROUP @c BY would
644 * change how many times the volatile function runs.
645 */
646static void safe_partition_residual(Node *residual, List *atoms, List *groups,
647 Node **outer_residual_out) {
648 List *conjuncts = NIL;
649 List *outer_residual = NIL;
650 ListCell *lc;
651
652 if (residual == NULL) {
653 *outer_residual_out = NULL;
654 return;
655 }
656
657 safe_flatten_and(residual, &conjuncts);
658
659 foreach (lc, conjuncts) {
660 Node *qual = (Node *) lfirst(lc);
661 safe_collect_varnos_ctx vctx = { NULL };
662 int v;
663 int target_group = -1;
664 bool stays_outer = false;
665
666 if (contain_volatile_functions(qual)) {
667 outer_residual = lappend(outer_residual, qual);
668 continue;
669 }
670
671 safe_collect_varnos_walker(qual, &vctx);
672 v = -1;
673 while ((v = bms_next_member(vctx.varnos, v)) >= 0) {
674 int g;
675 if (v < 1 || v > list_length(atoms)) {
676 stays_outer = true;
677 break;
678 }
679 g = ((safe_rewrite_atom *) list_nth(atoms, v - 1))->group_id;
680 if (g < 0) {
681 stays_outer = true;
682 break;
683 }
684 if (target_group < 0)
685 target_group = g;
686 else if (target_group != g) {
687 stays_outer = true;
688 break;
689 }
690 }
691 bms_free(vctx.varnos);
692
693 if (stays_outer || target_group < 0)
694 outer_residual = lappend(outer_residual, qual);
695 else {
696 safe_inner_group *gr =
697 (safe_inner_group *) list_nth(groups, target_group);
698 gr->inner_quals = lappend(gr->inner_quals, qual);
699 }
700 }
701
702 if (outer_residual == NIL)
703 *outer_residual_out = NULL;
704 else if (list_length(outer_residual) == 1)
705 *outer_residual_out = (Node *) linitial(outer_residual);
706 else
707 *outer_residual_out = (Node *) makeBoolExpr(AND_EXPR, outer_residual, -1);
708}
709
710/** @brief Mutator context for @c safe_pushed_remap_mutator. */
711typedef struct safe_pushed_remap_ctx {
712 Index outer_rtindex; ///< varno in the outer scope to rewrite to 1
714
715/**
716 * @brief Rewrite @c Var.varno from the outer atom rtindex to @c 1, the
717 * sole RTE of the inner wrap subquery.
718 *
719 * Applied to each pushed conjunct before it is AND-injected into the
720 * inner @c Query's @c jointree->quals. @c varattno is preserved
721 * (the inner subquery's RTE is a fresh clone of the same base
722 * relation).
723 */
724static Node *safe_pushed_remap_mutator(Node *node,
726 if (node == NULL)
727 return NULL;
728 if (IsA(node, Var)) {
729 Var *v = (Var *) node;
730 if (v->varlevelsup == 0 && v->varno == ctx->outer_rtindex) {
731 Var *nv = (Var *) copyObject(v);
732 nv->varno = 1;
733#if PG_VERSION_NUM >= 130000
734 /* PG 13+ keeps a parallel @c varnosyn / @c varattnosyn for
735 * @c ruleutils.c-style query deparsing. Updating only
736 * @c varno here leaves @c varnosyn pointing at the outer atom's
737 * (now-stale) rtindex; @c pg_get_querydef then dereferences a
738 * Var whose syntactic-rtindex slot resolves through a different
739 * RTE than the semantic one, recurses into that RTE's
740 * subquery, and (because the syntactic dereference always finds
741 * its way back to the same Var) stack-overflows. Mirror the
742 * semantic remap on the syntactic side. */
743 nv->varnosyn = 1;
744#endif
745 return (Node *) nv;
746 }
747 return node;
748 }
749 return expression_tree_mutator(node, safe_pushed_remap_mutator, (void *) ctx);
750}
751
752/**
753 * @brief Run the hierarchy detector on @p q, returning per-atom rewrite info.
754 *
755 * Builds the variable equivalence relation induced by WHERE-clause
756 * @c Var = @c Var equalities, identifies a "root variable" (a class
757 * whose member Vars touch every base RTE), and decides how each
758 * remaining shared class is materialised. Fully-covered non-root
759 * classes become extra projection slots in the per-atom inner wrap.
760 * Partial-coverage shared classes -- those touching at least two but
761 * not all atoms -- trigger the multi-level path: the affected atoms
762 * are bundled into an inner sub-Query whose @c GROUP @c BY folds the
763 * partial-coverage variables before the outer join with the remaining
764 * atoms. Returns a list of @c safe_rewrite_atom * (one per
765 * @c q->rtable entry, in @c rtable order) plus, via @p groups_out,
766 * the list of @c safe_inner_group * the rewriter must build.
767 *
768 * Returns @c NIL when the query is not in the currently-supported
769 * shape:
770 *
771 * - fewer than two atoms (single-relation query needs no rewrite);
772 * - no root variable within the (single) connected component.
773 * Disconnected components are handled upstream in
774 * @c try_safe_query_rewrite via @c rewrite_multi_component, so
775 * this bail covers only the "connected but no variable touches
776 * every atom" case ;
777 * - an atom whose root binding spans more than one column. The
778 * rewrite would have to push an intra-atom equality (e.g.
779 * @c A(x,x) when @c x is the root) into the inner subquery ;
780 * the current rewriter does not synthesise such an equality and
781 * bails ;
782 * - a Var in @p quals or @c q->targetList that does not fit any of
783 * the slot kinds the rewriter knows how to expose : the
784 * fully-covered class (extra outer slot), a partial-coverage
785 * class whose atom landed in an inner group (inner-group slot),
786 * or a single-atom head Var on an atom whose wrap can carry an
787 * extra projection slot. Body-only Vars on a single-atom class
788 * and partial-coverage classes that the multi-group / bridge
789 * merger cannot route to any group fall outside this set and
790 * trigger the bail.
791 *
792 * The shape gate in @c is_safe_query_candidate has already enforced
793 * self-join-free, no aggs / windows / sublinks etc.; this function
794 * only adds the hierarchy-specific checks.
795 *
796 * @param constants Cached extension OIDs (unused here; reserved for
797 * future class/type lookups).
798 * @param q Input Query; the detector only @em reads it.
799 * @param quals Residual WHERE quals (post-split): the cross-atom
800 * conjunction that the union-find must reason
801 * about. Single-atom conjuncts have been
802 * extracted upstream and stored separately per
803 * atom.
804 * @param groups_out Out: list of @c safe_inner_group * produced when
805 * the partial-coverage path fires; @c NIL when the
806 * rewriter only needs single-level outer wraps.
807 */
808static List *find_hierarchical_root_atoms(const constants_t *constants,
809 Query *q, Node *quals,
810 List **groups_out) {
811 safe_collect_vars_ctx vctx = { NIL };
812 List *eq_pairs = NIL;
813 ListCell *lc;
814 Var **vars_arr;
815 int *cls;
816 int nvars;
817 int natoms = list_length(q->rtable);
818 int *class_atom_count;
819 int *class_atom_anchor_attno; /* per atom, per class: any one attno */
820 int root_class = -1;
821 List *atoms_out = NIL;
822 int *atom_group = NULL; /* per-atom group id: -1 = outer-wrap; 0 = inner */
823 bool *in_targetlist = NULL; /* per-var: appears somewhere in q->targetList */
824 int *first_member_of_group = NULL; /* per-group: smallest atom index in the group */
825 int *group_singleton_counter = NULL; /* per-group: running outer_attno counter for singleton head Vars on non-first-members */
826 bool have_partial_class = false;
827 int partial_first = -1; /* repr of the first partial-coverage class seen */
828 Bitmapset *bridging_classes = NULL; /* repr indices of partial-coverage classes whose touched atoms span more than one group; the bridge variable becomes an extra slot on the first_member of each touched group, and the outer's residual WHERE re-equates the groups' columns through the standard Var remap. */
829 /* PK / NOT-NULL UNIQUE FD support. @c determined_in[c*natoms+j]
830 * @c == @c true means class @c c is functionally determined inside
831 * RTE @c j (some key whose every column's class is anchored on @c j
832 * exists in @c j's relation, and @c c is anchored on @c j by a
833 * non-key column). @c class_atom_count_fd[c] is the FD-aware
834 * coverage of class @c c: the count of atoms where @c c is anchored
835 * @em and @em not FD-determined. @c fd_aware_mode triggers when no
836 * single class is fully covered by the raw (non-FD) atom count but
837 * the FD-aware atom-sets satisfy the textbook pairwise nested-or-
838 * disjoint hierarchicality condition; in that mode the rewriter
839 * uses a per-atom local anchor class (the lowest-repr class
840 * anchored on the atom) instead of a global root, and each atom's
841 * @c proj_slots holds one slot for every class anchored on it. */
842 bool *determined_in = NULL;
843 int *class_atom_count_fd = NULL;
844 bool fd_aware_mode = false;
845 int *atom_anchor_class = NULL; /* per atom (size natoms): repr of the class chosen as that atom's local "root" in fd_aware_mode */
846 int i, j;
847 Index varno;
848 bool ok;
849
850 *groups_out = NIL;
851 /* The constant-selection elimination is handled upstream by
852 * @c apply_constant_selection_fd_pass, so @p quals already has
853 * the redundant within-class equijoins dropped by the time this
854 * function is reached. */
855
856 if (natoms < 2)
857 return NIL;
858
859 /* Collect every distinct base-level Var occurring anywhere in the
860 * residual query (target list and the residual WHERE quals,
861 * i.e. cross-atom conjuncts that survive @c safe_split_quals).
862 * Each becomes a node in the union-find. */
863 expression_tree_walker((Node *) q->targetList,
865 if (quals)
866 expression_tree_walker(quals, safe_collect_vars_walker, &vctx);
867
868 nvars = list_length(vctx.vars);
869 if (nvars == 0)
870 return NIL;
871
872 vars_arr = palloc(nvars * sizeof(Var *));
873 cls = palloc(nvars * sizeof(int));
874 i = 0;
875 foreach (lc, vctx.vars) {
876 vars_arr[i] = (Var *) lfirst(lc);
877 cls[i] = i;
878 i++;
879 }
880
881 /* Union equality-related Vars. We walk only the residual quals
882 * (atom-local conjuncts were already split off); a top-level @c AND
883 * is decomposed conjunct-by-conjunct, but @c OR / @c NOT subtrees
884 * are never traversed because they would weaken, not strengthen,
885 * the equivalence relation. */
886 if (quals)
887 safe_collect_equalities(quals, &eq_pairs);
888
889 for (lc = list_head(eq_pairs); lc != NULL; lc = my_lnext(eq_pairs, lc)) {
890 Var *lv, *rv;
891 int li, ri, ci, cj, k;
892 lv = (Var *) lfirst(lc);
893 lc = my_lnext(eq_pairs, lc);
894 rv = (Var *) lfirst(lc);
895 li = safe_var_index(vctx.vars, lv->varno, lv->varattno);
896 ri = safe_var_index(vctx.vars, rv->varno, rv->varattno);
897 if (li < 0 || ri < 0)
898 continue;
899 ci = cls[li];
900 cj = cls[ri];
901 if (ci == cj)
902 continue;
903 for (k = 0; k < nvars; k++)
904 if (cls[k] == cj)
905 cls[k] = ci;
906 }
907
908 /* For each class, count how many distinct atoms (varno values) it
909 * touches. A class touching all `natoms` is a root variable. */
910 class_atom_count = palloc0(nvars * sizeof(int));
911 class_atom_anchor_attno =
912 palloc0((size_t) nvars * (size_t) natoms * sizeof(int));
913
914#define ANCHOR(c, atom_idx) class_atom_anchor_attno[(c) * natoms + (atom_idx)]
915
916 for (i = 0; i < nvars; i++) {
917 int c = cls[i];
918 int atom_idx;
919 varno = vars_arr[i]->varno;
920 if (varno < 1 || (int) varno > natoms)
921 continue; /* shouldn't happen */
922 atom_idx = (int) varno - 1;
923 if (ANCHOR(c, atom_idx) == 0) {
924 class_atom_count[c]++;
925 ANCHOR(c, atom_idx) = vars_arr[i]->varattno;
926 } else if (ANCHOR(c, atom_idx) != vars_arr[i]->varattno) {
927 /* Same class binds two columns of the same atom: the current
928 * rewriter does not push the implied intra-atom equality into
929 * the inner subquery, so mark this class unusable. Count >
930 * natoms is impossible otherwise, so we use this as a sentinel. */
931 class_atom_count[c] = natoms + 1;
932 }
933 }
934
935 /* PK-FD pass -- Dalvi & Suciu 2007 §5.1 induced FDs from PRIMARY
936 * KEYs and NOT-NULL UNIQUE constraints. For each base relation in
937 * the FROM list, look up its keys via the per-backend cache; for
938 * every key @c K every of whose columns is anchored on the
939 * relation (i.e. appears in the query as a Var of that RTE), mark
940 * every class anchored on the same RTE by a non-key column as
941 * @em FD-determined within that RTE. The intuition: under the
942 * key, each non-key column is a function of the key's columns, so
943 * the class containing the non-key column does not contribute an
944 * independent existential to the relation -- the FD-aware atom-set
945 * reduction the project-safety condition prescribes.
946 *
947 * Skipped relations:
948 *
949 * - @c RTE_RELATION entries whose @c relid does not yield any
950 * PRIMARY KEY / NOT-NULL UNIQUE through
951 * @c provsql_lookup_relation_keys (no FD to apply);
952 * - non-@c RTE_RELATION entries (subqueries, joins): the
953 * candidate gate has already rejected these via the shape
954 * check at the top of @c is_safe_query_candidate. */
955 determined_in =
956 palloc0((size_t) nvars * (size_t) natoms * sizeof(bool));
957#define DETERMINED(c, atom_idx) determined_in[(c) * natoms + (atom_idx)]
958
959 for (j = 0; j < natoms; j++) {
960 RangeTblEntry *rte = (RangeTblEntry *) list_nth(q->rtable, j);
962 uint16 ki;
963 if (rte->rtekind != RTE_RELATION)
964 continue;
965 if (!provsql_lookup_relation_keys(rte->relid, &keys))
966 continue;
967 for (ki = 0; ki < keys.key_n; ki++) {
968 const ProvenanceRelationKey *key = &keys.keys[ki];
969 bool all_anchored = true;
970 uint16 kc;
971 int vi;
972 /* Every column of the key must be present in the query @em and
973 * its class must be multi-atom -- i.e. an equijoin link binds
974 * the column to a Var on some other RTE. A key column in a
975 * singleton class is a "free body existential" that ranges over
976 * every value; under such a free column the FD @c K @c → @c A
977 * does not reduce @c A's atom-set (a different free-column
978 * value would give a different @c A, so @c A is not truly
979 * determined within the RTE). Composite-PK soundness trap:
980 * a partial match (some PK columns equated, others not) does
981 * not give the FD. */
982 for (kc = 0; kc < key->col_n; kc++) {
983 int idx = safe_var_index(vctx.vars,
984 (Index) (j + 1),
985 key->cols[kc]);
986 if (idx < 0) {
987 all_anchored = false;
988 break;
989 }
990 if (class_atom_count[cls[idx]] < 2
991 || class_atom_count[cls[idx]] > natoms) {
992 all_anchored = false;
993 break;
994 }
995 }
996 if (!all_anchored)
997 continue;
998 /* Apply: every Var on this RTE whose attno is NOT in the key
999 * has its class flagged as FD-determined within @c j. */
1000 for (vi = 0; vi < nvars; vi++) {
1001 Var *vp = vars_arr[vi];
1002 bool is_key_col = false;
1003 if (vp->varno != (Index) (j + 1))
1004 continue;
1005 for (kc = 0; kc < key->col_n; kc++) {
1006 if (vp->varattno == key->cols[kc]) {
1007 is_key_col = true;
1008 break;
1009 }
1010 }
1011 if (is_key_col)
1012 continue;
1013 DETERMINED(cls[vi], j) = true;
1014 }
1015 }
1016 }
1017
1018 /* Deterministic-relation transparency (Gatterbauer & Suciu 2015
1019 * dissociation framework). A relation that is not provenance-
1020 * tracked (no @c provsql column @em and no metadata entry in the
1021 * per-table cache) contributes probability-1 tuples: dissociating
1022 * tuples in a deterministic relation does not change the query's
1023 * probability, so the relation is structurally transparent -- it
1024 * filters the cross product but adds nothing to atom-set
1025 * membership. We model that by marking every union-find class
1026 * as FD-determined within the deterministic RTE, reusing the
1027 * @c DETERMINED matrix the PK-FD pass already populates; the
1028 * existing @c fd_aware_mode then drops the deterministic atom
1029 * from each class's @c atoms_fd and the pairwise hierarchicality
1030 * check accepts star-schema queries that the raw atom-count check
1031 * would refuse.
1032 *
1033 * Soundness guards (in coordination with the correlation-registry
1034 * follow-up):
1035 *
1036 * - @c rte->rtekind @c == @c RTE_RELATION : excluded by the
1037 * candidate gate already.
1038 * - @c has_provsql_col @c == @c false : the relation has no
1039 * @c provsql @c uuid column at all. A provsql column with no
1040 * metadata entry, or an OPAQUE-tagged provsql column, was
1041 * rejected by the candidate gate at @c is_safe_query_candidate;
1042 * this branch never sees those.
1043 * - @c pg_class.relkind @c == @c RELKIND_RELATION : exclude views
1044 * (@c 'v' / @c 'm'), foreign tables (@c 'f'), partitioned
1045 * parents (@c 'p'), composite types, etc. A view's body might
1046 * transitively reference the same probabilistic atoms as the
1047 * outer query, breaking the dissociation argument; the safe
1048 * rule is to refuse view descent here and let the ancestry-
1049 * disjointness gate downstream catch the cross-relation
1050 * correlation through the per-relation base-ancestor registry.
1051 * - No @c pg_inherits parent : an inheritance child shares its
1052 * parent's storage in PG; tagging it transparent could overlook
1053 * correlated rows in the parent. Refuse conservatively.
1054 *
1055 * The CTAS-correlation trap (manual @c CREATE @c TABLE @c foo
1056 * @c AS @c SELECT @c FROM @c <tracked>) is closed by the
1057 * lineage hook in @c provsql_ProcessUtility plus the
1058 * ancestry-based disjointness gate above ; users who manually
1059 * strip @c provsql from a CTAS bypass both and take on the
1060 * responsibility. */
1061 {
1063 for (j = 0; j < natoms; j++) {
1064 RangeTblEntry *rte = (RangeTblEntry *) list_nth(q->rtable, j);
1065 AttrNumber provsql_attno;
1066 bool has_provsql_col;
1067 bool has_meta;
1068 HeapTuple class_tup;
1069 Form_pg_class classform;
1070 bool ok_relkind;
1071 if (rte->rtekind != RTE_RELATION)
1072 continue;
1073 provsql_attno = get_attnum(rte->relid, PROVSQL_COLUMN_NAME);
1074 has_provsql_col =
1075 provsql_attno != InvalidAttrNumber
1076 && get_atttype(rte->relid, provsql_attno) == constants->OID_TYPE_UUID;
1077 has_meta = provsql_lookup_table_info(rte->relid, &info);
1078 if (has_provsql_col || has_meta)
1079 continue; /* probabilistic / OPAQUE atom */
1080
1081 class_tup =
1082 SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
1083 if (!HeapTupleIsValid(class_tup))
1084 continue;
1085 classform = (Form_pg_class) GETSTRUCT(class_tup);
1086 ok_relkind = (classform->relkind == RELKIND_RELATION);
1087 ReleaseSysCache(class_tup);
1088 if (!ok_relkind)
1089 continue;
1090
1091 if (has_superclass(rte->relid))
1092 continue; /* inheritance child */
1093
1094 /* All guards passed: mark every class FD-determined inside @c j.
1095 * The existing atom-set construction then excludes @c j from
1096 * each class's @c atoms_fd, and the pairwise hierarchicality
1097 * check sees the reduced sets. */
1098 for (i = 0; i < nvars; i++) {
1099 if (cls[i] != i)
1100 continue;
1101 DETERMINED(i, j) = true;
1102 }
1103 }
1104 }
1105
1106 /* FD-aware atom counts: how many atoms does each class touch that
1107 * are not FD-determining the class. Mirrors @c class_atom_count
1108 * but excludes the FD-pinned entries. */
1109 class_atom_count_fd = palloc0(nvars * sizeof(int));
1110 for (i = 0; i < nvars; i++) {
1111 int c;
1112 if (cls[i] != i)
1113 continue;
1114 if (class_atom_count[i] > natoms)
1115 continue; /* sentinel, leave at 0 */
1116 c = i;
1117 for (j = 0; j < natoms; j++) {
1118 if (ANCHOR(c, j) != 0 && !DETERMINED(c, j))
1119 class_atom_count_fd[c]++;
1120 }
1121 }
1122
1123 /* Single-atom head Vars: walk @c q->targetList once to mark every
1124 * @c vars_arr index that appears in the user's projection. Used
1125 * below to allow body-only Vars (singleton classes, @c count == 1)
1126 * to reach the outer scope as an extra @c proj_slot on their atom's
1127 * wrap. */
1128 in_targetlist = palloc0(nvars * sizeof(bool));
1129 {
1130 safe_collect_vars_ctx tlist_ctx = { NIL };
1131 ListCell *tlc;
1132 expression_tree_walker((Node *) q->targetList,
1133 safe_collect_vars_walker, &tlist_ctx);
1134 foreach (tlc, tlist_ctx.vars) {
1135 Var *v = (Var *) lfirst(tlc);
1136 int idx = safe_var_index(vctx.vars, v->varno, v->varattno);
1137 if (idx >= 0)
1138 in_targetlist[idx] = true;
1139 }
1140 }
1141
1142 /* Root class: a class touching every atom (count == natoms).
1143 * Pick the lowest repr index when multiple candidates exist, for
1144 * deterministic rewriter output. */
1145 for (i = 0; i < nvars; i++) {
1146 if (cls[i] != i)
1147 continue;
1148 if (class_atom_count[i] == natoms) {
1149 root_class = i;
1150 break;
1151 }
1152 }
1153
1154 /* FD-aware-mode fallback: no class touches every atom under the
1155 * raw count, but the FD-aware atom-sets might still satisfy
1156 * pairwise nested-or-disjoint hierarchicality. Concretely, we
1157 * accept the textbook H-query under a PK on the middle atom
1158 * (Dalvi & Suciu 2007 §5.1 @c R(x),S(x,y),T(y) with PK on @c S.x):
1159 * after the FD reduction @c atoms(B) drops to @c {T}, leaving
1160 * @c {R,S} and @c {T} as disjoint atom-sets covering every atom.
1161 * In that case there is no global root, but the rewrite still
1162 * works: each atom is wrapped in a flat @c SELECT @c DISTINCT
1163 * exposing every class anchored on it as a separate slot, and the
1164 * outer's residual equijoins resolve through
1165 * @c safe_remap_vars_mutator on the matching slot's
1166 * @c base_attno.
1167 *
1168 * Conditions for entering @c fd_aware_mode:
1169 *
1170 * 1. The standard root-class check failed.
1171 * 2. Every atom in @c q->rtable has at least one class anchored on
1172 * it (no orphan atoms -- otherwise the rewriter would have no
1173 * @c provsql column to multiply into the cross product for that
1174 * atom; the existing @c root_anchor_attno check enforces this
1175 * under a global root, and we re-enforce it here).
1176 * 3. Every pair of multi-atom classes (count >= 2 under the raw
1177 * count) has FD-aware atom-sets that are nested or disjoint.
1178 * Singleton classes (count @c == @c 1) are tolerated -- they
1179 * surface as single-atom-head Vars in the @em existing
1180 * in-targetlist path further down.
1181 * 4. No class has the @c natoms+1 sentinel (intra-atom equalities
1182 * across two columns of the same atom remain unsupported here,
1183 * same as in the non-FD path).
1184 * 5. The query has no @em raw partial-coverage classes whose
1185 * FD-aware count is still in @c [2, natoms-1]. Such classes
1186 * would normally route through the multi-level / inner-group
1187 * path, which is not adapted to per-atom anchors yet; the
1188 * FD-aware mode therefore demands every multi-atom class to
1189 * either cover all atoms (raw root, handled above) or to land
1190 * on a disjoint pair-block via the FD reduction. */
1191 if (root_class < 0) {
1192 bool eligible = true;
1193 /* Sentinel and orphan-atom checks. */
1194 for (i = 0; i < nvars && eligible; i++) {
1195 if (cls[i] != i)
1196 continue;
1197 if (class_atom_count[i] > natoms) {
1198 eligible = false;
1199 break;
1200 }
1201 }
1202 if (eligible) {
1203 bool *atom_covered = palloc0(natoms * sizeof(bool));
1204 for (i = 0; i < nvars; i++) {
1205 int c = cls[i];
1206 Index vn = vars_arr[i]->varno;
1207 if (vn < 1 || (int) vn > natoms)
1208 continue;
1209 if (class_atom_count[c] > natoms)
1210 continue; /* sentinel */
1211 atom_covered[vn - 1] = true;
1212 }
1213 for (j = 0; j < natoms; j++) {
1214 if (!atom_covered[j]) {
1215 eligible = false;
1216 break;
1217 }
1218 }
1219 pfree(atom_covered);
1220 }
1221 if (eligible) {
1222 /* Pairwise nested-or-disjoint on FD-aware atom-sets, considering
1223 * only multi-atom classes (singletons stay as in-targetlist head
1224 * Vars and don't constrain pairwise hierarchicality). */
1225 Bitmapset **atoms_fd = palloc0(nvars * sizeof(Bitmapset *));
1226 int *class_reprs = palloc(nvars * sizeof(int));
1227 int nreprs = 0;
1228 for (i = 0; i < nvars && eligible; i++) {
1229 if (cls[i] != i)
1230 continue;
1231 if (class_atom_count[i] > natoms)
1232 continue;
1233 if (class_atom_count[i] < 2)
1234 continue; /* singleton; ignored here */
1235 for (j = 0; j < natoms; j++) {
1236 if (ANCHOR(i, j) != 0 && !DETERMINED(i, j))
1237 atoms_fd[i] = bms_add_member(atoms_fd[i], j);
1238 }
1239 class_reprs[nreprs++] = i;
1240 }
1241 for (i = 0; i < nreprs && eligible; i++) {
1242 int k;
1243 for (k = i + 1; k < nreprs && eligible; k++) {
1244 Bitmapset *a = atoms_fd[class_reprs[i]];
1245 Bitmapset *b = atoms_fd[class_reprs[k]];
1246 bool nested = bms_is_subset(a, b) || bms_is_subset(b, a);
1247 bool disjoint = !bms_overlap(a, b);
1248 if (!nested && !disjoint) {
1249 eligible = false;
1250 break;
1251 }
1252 }
1253 }
1254 for (i = 0; i < nvars; i++)
1255 if (atoms_fd[i])
1256 bms_free(atoms_fd[i]);
1257 pfree(atoms_fd);
1258 pfree(class_reprs);
1259 }
1260 if (eligible) {
1261 /* Per-atom local anchor: the lowest-repr class anchored on the
1262 * atom that the outer residual most naturally joins on. Two
1263 * passes:
1264 *
1265 * 1. FD-aware preference -- pick a class that anchors on the
1266 * atom @em and is not FD-determined there. This is the
1267 * PK-FD case: under PK on @c S.x, class @c {S.y, T.y}
1268 * drops its @c S anchor for atom-set purposes, so @c S's
1269 * local root should be the @c {R.x, S.x} class instead.
1270 * 2. Fallback -- atoms with every anchored class FD-determined
1271 * (the deterministic-relation case: every class is tagged
1272 * determined inside the deterministic atom) still need a
1273 * slot column for the outer's residual equijoin to resolve
1274 * through. Use the first anchored class regardless of FD
1275 * status. The DISTINCT wrap on the slot column collapses
1276 * duplicate keys so each probabilistic token still appears
1277 * once across the cross product, preserving read-once. */
1278 atom_anchor_class = palloc(natoms * sizeof(int));
1279 for (j = 0; j < natoms; j++)
1280 atom_anchor_class[j] = -1;
1281 for (i = 0; i < nvars; i++) {
1282 int c;
1283 if (cls[i] != i)
1284 continue;
1285 if (class_atom_count[i] > natoms)
1286 continue;
1287 if (class_atom_count[i] < 2)
1288 continue;
1289 c = i;
1290 for (j = 0; j < natoms; j++) {
1291 if (ANCHOR(c, j) != 0 && !DETERMINED(c, j)
1292 && atom_anchor_class[j] < 0)
1293 atom_anchor_class[j] = c;
1294 }
1295 }
1296 for (j = 0; j < natoms; j++) {
1297 if (atom_anchor_class[j] >= 0)
1298 continue;
1299 for (i = 0; i < nvars; i++) {
1300 if (cls[i] != i)
1301 continue;
1302 if (class_atom_count[i] < 2 || class_atom_count[i] > natoms)
1303 continue;
1304 if (ANCHOR(i, j) != 0) {
1305 atom_anchor_class[j] = i;
1306 break;
1307 }
1308 }
1309 }
1310 /* An atom with no multi-atom anchor at all (e.g. only singleton-
1311 * class head Vars touch it) cannot be wrapped in
1312 * @c fd_aware_mode -- it would need a join key from the outer's
1313 * residual that no shared class provides. Bail. */
1314 for (j = 0; j < natoms; j++) {
1315 if (atom_anchor_class[j] < 0) {
1316 eligible = false;
1317 break;
1318 }
1319 }
1320 }
1321 if (eligible) {
1322 fd_aware_mode = true;
1323 } else {
1324 /* The @c bail block below pfrees @c determined_in,
1325 * @c class_atom_count_fd and @c atom_anchor_class itself; just
1326 * jump there. */
1327 goto bail;
1328 }
1329 }
1330
1331 /* Multi-level handling: any atom touched by at least one partial-
1332 * coverage shared class (count >= 2 but < natoms) goes into some
1333 * inner sub-Query. Two grouping strategies, decided per-query:
1334 *
1335 * - @em One @em big @em inner @em group: when at least one atom
1336 * has @em empty partial-coverage signature (no partial-coverage
1337 * class touches it), bundle every atom with non-empty signature
1338 * into one inner sub-Query and let the recursive call (via
1339 * @c process_query / Choice A) peel further partial-coverage
1340 * classes inside. The empty-signature atoms become outer
1341 * wraps; the recursion is guaranteed to make progress at each
1342 * level.
1343 *
1344 * - @em Disjoint @em multi-group: when every atom carries a non-
1345 * empty signature, the one-big-group approach would re-enter the
1346 * same shape, so we partition atoms by their @em exact
1347 * signature and build one inner sub-Query per distinct
1348 * signature. This only works when partial-coverage classes are
1349 * cleanly partitioned: every class @c c must touch atoms that
1350 * all share the same signature. Otherwise @c c "bridges"
1351 * multiple groups and the outer would need an extra join column;
1352 * we defer that case.
1353 */
1354 atom_group = palloc(natoms * sizeof(int));
1355 for (j = 0; j < natoms; j++)
1356 atom_group[j] = -1;
1357
1358 /* In @c fd_aware_mode every atom is an outer wrap (no inner groups);
1359 * the partial-coverage path below is bypassed, since the FD-reduced
1360 * atom-sets are by construction pairwise nested-or-disjoint and the
1361 * per-atom anchor in @c atom_anchor_class already encodes the
1362 * single-level wrap structure. */
1363 if (fd_aware_mode)
1364 goto skip_partial_coverage;
1365
1366 {
1367 Bitmapset **sig = palloc0(natoms * sizeof(Bitmapset *));
1368 bool has_outer_atom = true;
1369
1370 for (i = 0; i < nvars; i++) {
1371 int c = cls[i];
1372 if (c != i)
1373 continue;
1374 if (class_atom_count[c] < 2)
1375 continue; /* single-atom class checked below */
1376 if (class_atom_count[c] > natoms)
1377 continue; /* sentinel; handled below per-Var */
1378 if (class_atom_count[c] == natoms)
1379 continue; /* fully-covered: extra outer slot */
1380 have_partial_class = true;
1381 if (partial_first < 0)
1382 partial_first = c;
1383 for (j = 0; j < natoms; j++) {
1384 if (ANCHOR(c, j) != 0)
1385 sig[j] = bms_add_member(sig[j], c);
1386 }
1387 }
1388
1389 if (have_partial_class) {
1390 has_outer_atom = false;
1391 for (j = 0; j < natoms; j++) {
1392 if (bms_is_empty(sig[j])) {
1393 has_outer_atom = true;
1394 break;
1395 }
1396 }
1397 }
1398
1399 if (have_partial_class && has_outer_atom) {
1400 /* One-big-inner-group: atoms with any partial-coverage class go
1401 * into group 0; empty-signature atoms stay as outer wraps. */
1402 for (j = 0; j < natoms; j++)
1403 atom_group[j] = bms_is_empty(sig[j]) ? -1 : 0;
1404 } else if (have_partial_class) {
1405 /* Disjoint multi-group: partition atoms by exact signature,
1406 * then merge bridging-connected groups. A partial-coverage
1407 * class whose touched atoms span more than one group is a
1408 * "bridge"; rather than threading bridge-join columns through
1409 * the outer, we collapse every chain of bridging-connected
1410 * groups into one super-group. The recursive @c process_query
1411 * re-entry on the super-group's inner sub-Query then handles
1412 * the intra-super-group structure (the bridging class becomes
1413 * a fully-covered class inside the inner, and the residual
1414 * partial classes peel level by level). Each super-group
1415 * still becomes one outer @c RTE_SUBQUERY, joined with the
1416 * others only on the root variable -- so the resulting
1417 * circuit is read-once over independent components. */
1418 Bitmapset **group_sigs;
1419 int ngroups = 0;
1420 int g;
1421
1422 /* Assign group_id by signature equality, in order of first
1423 * appearance to keep the rewriter output deterministic. */
1424 group_sigs = palloc0(natoms * sizeof(Bitmapset *));
1425 for (j = 0; j < natoms; j++) {
1426 bool found = false;
1427 for (g = 0; g < ngroups; g++) {
1428 if (bms_equal(group_sigs[g], sig[j])) {
1429 atom_group[j] = g;
1430 found = true;
1431 break;
1432 }
1433 }
1434 if (!found) {
1435 atom_group[j] = ngroups;
1436 group_sigs[ngroups] = sig[j];
1437 ngroups++;
1438 }
1439 }
1440 pfree(group_sigs);
1441
1442 /* Identify bridging classes: partial-coverage classes whose
1443 * touched atoms span more than one group. */
1444 for (i = 0; i < nvars; i++) {
1445 int c = cls[i];
1446 Bitmapset *touched_groups = NULL;
1447 int jj;
1448 if (c != i)
1449 continue;
1450 if (class_atom_count[c] < 2 || class_atom_count[c] >= natoms)
1451 continue;
1452 for (jj = 0; jj < natoms; jj++) {
1453 if (ANCHOR(c, jj) != 0)
1454 touched_groups = bms_add_member(touched_groups,
1455 atom_group[jj]);
1456 }
1457 if (bms_num_members(touched_groups) > 1)
1458 bridging_classes = bms_add_member(bridging_classes, c);
1459 bms_free(touched_groups);
1460 }
1461
1462 /* Merge bridging-connected groups via union-find. After
1463 * merging, renumber super-groups densely starting from 0 and
1464 * rewrite @c atom_group accordingly. */
1465 if (!bms_is_empty(bridging_classes)) {
1466 int *parent = palloc(ngroups * sizeof(int));
1467 int *super = palloc(ngroups * sizeof(int));
1468 int next_super = 0;
1469 int c;
1470
1471 for (g = 0; g < ngroups; g++) {
1472 parent[g] = g;
1473 super[g] = -1;
1474 }
1475
1476 c = -1;
1477 while ((c = bms_next_member(bridging_classes, c)) >= 0) {
1478 int first_g = -1;
1479 int jj;
1480 for (jj = 0; jj < natoms; jj++) {
1481 int gj, ra, rb;
1482 if (ANCHOR(c, jj) == 0)
1483 continue;
1484 gj = atom_group[jj];
1485 if (first_g < 0) {
1486 first_g = gj;
1487 continue;
1488 }
1489 /* Path-compressed find. */
1490 ra = first_g;
1491 while (parent[ra] != ra) ra = parent[ra];
1492 rb = gj;
1493 while (parent[rb] != rb) rb = parent[rb];
1494 if (ra != rb)
1495 parent[rb] = ra;
1496 }
1497 }
1498
1499 for (g = 0; g < ngroups; g++) {
1500 int r = g;
1501 while (parent[r] != r) r = parent[r];
1502 if (super[r] < 0)
1503 super[r] = next_super++;
1504 super[g] = super[r];
1505 }
1506
1507 for (j = 0; j < natoms; j++) {
1508 if (atom_group[j] >= 0)
1509 atom_group[j] = super[atom_group[j]];
1510 }
1511
1512 pfree(parent);
1513 pfree(super);
1514 bms_free(bridging_classes);
1515 bridging_classes = NULL;
1516 }
1517 }
1518
1519 for (j = 0; j < natoms; j++) bms_free(sig[j]);
1520 pfree(sig);
1521 }
1522
1523skip_partial_coverage:
1524
1525 /* For each group, identify the @em first member (smallest
1526 * original-rtindex atom belonging to the group). Head Vars on
1527 * grouped atoms are only allowed on the first member: the inner
1528 * sub-Query's @c targetList is built from @c first_member->proj_slots,
1529 * so a head Var added to a non-first-member atom's @c proj_slots
1530 * would not actually surface in the inner output. Tracking this
1531 * here lets the per-Var check and proj_slots build below act
1532 * uniformly. */
1533 {
1534 int g, ngroups_local = 0;
1535 for (j = 0; j < natoms; j++)
1536 if (atom_group[j] >= 0 && atom_group[j] + 1 > ngroups_local)
1537 ngroups_local = atom_group[j] + 1;
1538 first_member_of_group = palloc(natoms * sizeof(int));
1539 group_singleton_counter = palloc(natoms * sizeof(int));
1540 for (g = 0; g < ngroups_local; g++) {
1541 first_member_of_group[g] = -1;
1542 group_singleton_counter[g] = 0;
1543 }
1544 for (j = 0; j < natoms; j++) {
1545 int g_loc = atom_group[j];
1546 if (g_loc >= 0 && first_member_of_group[g_loc] < 0)
1547 first_member_of_group[g_loc] = j;
1548 }
1549 }
1550
1551 ok = true;
1552
1553 /* Every Var anywhere in the query must belong to a class that
1554 * either touches every atom (slot at the outer level) or sits
1555 * inside the inner-group its atom belongs to. A Var whose class
1556 * touches an atom subset that doesn't match any outer or inner
1557 * slot would leak into the outer scope with no wrap to host it.
1558 *
1559 * In @c fd_aware_mode (multi-anchor), every multi-atom class is
1560 * exposed as a slot on every atom it anchors -- so any Var of a
1561 * multi-atom class is guaranteed a matching slot in its atom's
1562 * @c proj_slots regardless of FD-determined status. The check
1563 * simplifies to "either Var's class touches >= 2 atoms (slot built
1564 * below) or Var's class is a singleton with the Var in the
1565 * targetList (head-Var slot built below)". */
1566 for (i = 0; i < nvars; i++) {
1567 int c = cls[i];
1568 int atom_idx = (int) vars_arr[i]->varno - 1;
1569 if (fd_aware_mode) {
1570 if (class_atom_count[c] >= 2 && class_atom_count[c] <= natoms)
1571 continue;
1572 if (class_atom_count[c] == 1 && in_targetlist[i])
1573 continue;
1574 if (provsql_verbose >= 30)
1575 provsql_notice("safe-query rewriter (fd-aware): Var (varno=%u, varattno=%d) "
1576 "belongs to a class with no outer slot",
1577 (unsigned) vars_arr[i]->varno,
1578 (int) vars_arr[i]->varattno);
1579 ok = false;
1580 break;
1581 }
1582 if (class_atom_count[c] == natoms)
1583 continue;
1584 if (class_atom_count[c] >= 2 && class_atom_count[c] < natoms
1585 && atom_group[atom_idx] >= 0)
1586 continue;
1587 /* Single-atom head Var: only this atom's wrap binds the column,
1588 * so the wrap must expose it as an extra projection slot.
1589 * Outer-wrap atoms expose it in their own DISTINCT wrap; grouped
1590 * atoms add the slot to the inner sub-Query's targetList -- on
1591 * first_member at the natural next position, on non-first-members
1592 * at the per-group running counter after all earlier members'
1593 * slots (see the proj_slots build below). */
1594 if (class_atom_count[c] == 1 && in_targetlist[i])
1595 continue;
1596 if (provsql_verbose >= 30)
1597 provsql_notice("safe-query rewriter: Var (varno=%u, varattno=%d) "
1598 "belongs to a class that does not match any outer or "
1599 "inner-group slot -- rewrite scope does not yet cover "
1600 "this case",
1601 (unsigned) vars_arr[i]->varno,
1602 (int) vars_arr[i]->varattno);
1603 ok = false;
1604 break;
1605 }
1606 if (!ok)
1607 goto bail;
1608
1609 /* Build proj_slots per atom. Every atom -- outer-wrap @em or
1610 * grouped -- gets the same slot layout: the root class first
1611 * (output position 1), then every other fully-covered shared class
1612 * (count == natoms) touching this atom in ascending repr order.
1613 * Outer-wrap atoms use the slot list directly inside their per-atom
1614 * @c SELECT @c DISTINCT. Grouped atoms reuse the slot list for
1615 * two purposes: the first member's slot order determines the inner
1616 * sub-Query's @c targetList and @c groupClause (the inner exposes
1617 * one output column per fully-covered class), and every member's
1618 * slot list is consulted by the outer Var remap to map a base
1619 * @c attno to the matching output column of the group's
1620 * @c RTE_SUBQUERY. */
1621 for (j = 0; j < natoms; j++) {
1622 safe_rewrite_atom *sa = palloc(sizeof(safe_rewrite_atom));
1623 safe_proj_slot *root_slot;
1624 int local_root = fd_aware_mode ? atom_anchor_class[j] : root_class;
1625
1626 sa->rtindex = (Index) (j + 1);
1627 sa->proj_slots = NIL;
1628 sa->pushed_quals = NIL;
1629 sa->group_id = atom_group[j];
1630 sa->outer_rtindex = 0;
1631 sa->inner_rtindex = 0;
1632 sa->is_constant_pinned = false;
1633 sa->root_anchor_attno = (AttrNumber) ANCHOR(local_root, j);
1634 if (sa->root_anchor_attno == 0)
1635 goto bail; /* impossible if root truly covers all */
1636
1637 root_slot = palloc(sizeof(safe_proj_slot));
1638 root_slot->base_attno = sa->root_anchor_attno;
1639 root_slot->class_id = local_root;
1640 root_slot->outer_attno = 1;
1641 sa->proj_slots = lappend(sa->proj_slots, root_slot);
1642 for (i = 0; i < nvars; i++) {
1643 safe_proj_slot *slot;
1644 if (cls[i] != i || i == local_root)
1645 continue;
1646 if (fd_aware_mode) {
1647 /* FD-aware mode: expose every multi-atom class anchored on
1648 * this atom, irrespective of FD-determined status -- the
1649 * slot is needed for the outer's residual equijoin to
1650 * resolve via @c safe_remap_vars_mutator. Singleton classes
1651 * still go through the head-Var path below. */
1652 if (class_atom_count[i] < 2 || class_atom_count[i] > natoms)
1653 continue;
1654 } else if (class_atom_count[i] != natoms) {
1655 continue; /* partial-coverage handled via groups */
1656 }
1657 if (ANCHOR(i, j) == 0)
1658 continue;
1659 slot = palloc(sizeof(safe_proj_slot));
1660 slot->base_attno = (AttrNumber) ANCHOR(i, j);
1661 slot->class_id = i;
1662 slot->outer_attno = (AttrNumber) (list_length(sa->proj_slots) + 1);
1663 sa->proj_slots = lappend(sa->proj_slots, slot);
1664 }
1665 /* Single-atom head Vars: expose every body-only Var (singleton
1666 * class) that appears in the user's targetList as an extra slot.
1667 * For outer-wrap atoms the slot lives in the per-atom DISTINCT
1668 * wrap, and @c outer_attno is the natural position in the
1669 * atom's @c proj_slots. For grouped atoms the slot goes into
1670 * the inner sub-Query's @c targetList:
1671 * - on first_member, at the natural next position;
1672 * - on non-first-members, at the position handed out by the
1673 * group's running counter @c group_singleton_counter, which
1674 * picks up after first_member's last slot. */
1675 for (i = 0; i < nvars; i++) {
1676 safe_proj_slot *slot;
1677 ListCell *exlc;
1678 bool already_have = false;
1679 bool is_first_member;
1680 if (!in_targetlist[i])
1681 continue;
1682 if (class_atom_count[cls[i]] != 1)
1683 continue;
1684 if ((int) vars_arr[i]->varno - 1 != j)
1685 continue;
1686 foreach (exlc, sa->proj_slots) {
1687 safe_proj_slot *ex = (safe_proj_slot *) lfirst(exlc);
1688 if (ex->base_attno == vars_arr[i]->varattno) {
1689 already_have = true;
1690 break;
1691 }
1692 }
1693 if (already_have)
1694 continue;
1695 slot = palloc(sizeof(safe_proj_slot));
1696 slot->base_attno = vars_arr[i]->varattno;
1697 slot->class_id = cls[i];
1698 is_first_member = (sa->group_id >= 0
1699 && first_member_of_group[sa->group_id] == j);
1700 if (sa->group_id < 0 || is_first_member) {
1701 slot->outer_attno =
1702 (AttrNumber) (list_length(sa->proj_slots) + 1);
1703 } else {
1704 group_singleton_counter[sa->group_id]++;
1705 slot->outer_attno =
1706 (AttrNumber) group_singleton_counter[sa->group_id];
1707 }
1708 sa->proj_slots = lappend(sa->proj_slots, slot);
1709 }
1710 /* For first_member of a group: after its singletons are added,
1711 * initialise the group's running counter so non-first-members
1712 * pick up just past first_member's last slot. */
1713 if (sa->group_id >= 0
1714 && first_member_of_group[sa->group_id] == j) {
1715 group_singleton_counter[sa->group_id] =
1716 list_length(sa->proj_slots);
1717 }
1718
1719 /* BID alignment: when the atom is BID-tracked, every block_key
1720 * column must appear among the projection slots. Otherwise the
1721 * wrap's @c SELECT @c DISTINCT could collapse rows from the same
1722 * block under different projected values into multiple output
1723 * rows, replicating the block's @c gate_mulinput in the final
1724 * circuit and breaking the read-once property. An empty
1725 * @c block_key (whole table is one block) is even more
1726 * restrictive: rows that should stay together can be split by
1727 * any slot the wrap projects. We bail there too rather than
1728 * risk an unsound rewrite. */
1729 {
1730 RangeTblEntry *rte =
1731 (RangeTblEntry *) list_nth(q->rtable, j);
1733 if (provsql_lookup_table_info(rte->relid, &info)
1734 && info.kind == PROVSQL_TABLE_BID) {
1735 if (info.block_key_n == 0) {
1736 if (provsql_verbose >= 30)
1737 provsql_notice("safe-query rewriter: BID atom (varno=%d) "
1738 "has an empty block_key (whole table is one "
1739 "block); the wrap's DISTINCT could split the "
1740 "block across multiple output rows, deferred",
1741 j + 1);
1742 goto bail;
1743 } else {
1744 int k;
1745 for (k = 0; k < info.block_key_n; k++) {
1746 AttrNumber bk = info.block_key[k];
1747 ListCell *slc;
1748 bool found = false;
1749 foreach (slc, sa->proj_slots) {
1750 safe_proj_slot *slot = (safe_proj_slot *) lfirst(slc);
1751 if (slot->base_attno == bk) {
1752 found = true;
1753 break;
1754 }
1755 }
1756 if (!found) {
1757 if (provsql_verbose >= 30)
1758 provsql_notice("safe-query rewriter: BID atom (varno=%d) "
1759 "has block_key column attno=%d outside the "
1760 "projection slots; the wrap would split a "
1761 "block, deferred",
1762 j + 1, (int) bk);
1763 goto bail;
1764 }
1765 }
1766 }
1767 }
1768 }
1769
1770 atoms_out = lappend(atoms_out, sa);
1771 }
1772
1773 /* If we discovered an inner group, materialise it now. Member
1774 * atoms are listed in their original rtindex order; @c inner_quals
1775 * is filled later by @c try_safe_query_rewrite as it partitions the
1776 * residual conjuncts. All grouped atoms share the same
1777 * @c outer_rtindex, which the rewriter assigns when it walks the
1778 * outer rtable. */
1779 if (have_partial_class) {
1780 int max_gid = -1;
1781 int g;
1782 safe_inner_group **arr;
1783 ListCell *alc;
1784 foreach (alc, atoms_out) {
1785 safe_rewrite_atom *sa = (safe_rewrite_atom *) lfirst(alc);
1786 if (sa->group_id > max_gid)
1787 max_gid = sa->group_id;
1788 }
1789 arr = palloc0((max_gid + 1) * sizeof(safe_inner_group *));
1790 for (g = 0; g <= max_gid; g++) {
1791 arr[g] = palloc(sizeof(safe_inner_group));
1792 arr[g]->group_id = g;
1793 arr[g]->member_atoms = NIL;
1794 arr[g]->inner_quals = NIL;
1795 arr[g]->outer_rtindex = 0;
1796 }
1797 foreach (alc, atoms_out) {
1798 safe_rewrite_atom *sa = (safe_rewrite_atom *) lfirst(alc);
1799 if (sa->group_id >= 0)
1800 arr[sa->group_id]->member_atoms =
1801 lappend(arr[sa->group_id]->member_atoms, sa);
1802 }
1803
1804 /* Synthesize intra-group equalities for every fully-covered
1805 * class touching two or more atoms of the same group. The user
1806 * typically writes such equalities transitively
1807 * (e.g. @c a.x=b.x @c AND @c a.x=c.x), and the outer's residual
1808 * partitioning routes each transitive conjunct to the outer
1809 * because its varnos span groups. Without an explicit
1810 * @c b.x=c.x conjunct landing in the group's @c inner_quals,
1811 * the recursive @c process_query re-entry on the inner
1812 * sub-Query would see @c b.x and @c c.x as unrelated columns,
1813 * leaving @c c.x out of @c proj_slots -- the inner sub-Query
1814 * for @c c would then aggregate over @em every value of @c x
1815 * instead of the per-row @c x, and the resulting circuit would
1816 * over-count. We add the missing equalities here as @c OpExpr
1817 * nodes in original-varno space (the existing inner-build
1818 * machinery remaps them to inner varnos). */
1819 for (g = 0; g <= max_gid; g++) {
1820 for (i = 0; i < nvars; i++) {
1821 ListCell *mlc;
1822 safe_rewrite_atom *first_touching = NULL;
1823 AttrNumber first_attno = 0;
1824 Oid first_type = InvalidOid;
1825 int32 first_typmod = -1;
1826 Oid first_coll = InvalidOid;
1827 int c = cls[i];
1828 if (c != i)
1829 continue;
1830 if (class_atom_count[c] != natoms)
1831 continue; /* only fully-covered */
1832 foreach (mlc, arr[g]->member_atoms) {
1833 safe_rewrite_atom *m = (safe_rewrite_atom *) lfirst(mlc);
1834 int atom_idx = (int) m->rtindex - 1;
1835 AttrNumber attno = ANCHOR(c, atom_idx);
1836 RangeTblEntry *rte;
1837 HeapTuple atttup;
1838 Form_pg_attribute attform;
1839 Oid mtype, mcoll, eqop, eqfunc;
1840 int32 mtypmod;
1841 Var *lv, *rv;
1842 OpExpr *eq;
1843 if (attno == 0)
1844 continue;
1845 rte = (RangeTblEntry *) list_nth(q->rtable, atom_idx);
1846 atttup = SearchSysCache2(ATTNUM,
1847 ObjectIdGetDatum(rte->relid),
1848 Int16GetDatum(attno));
1849 if (!HeapTupleIsValid(atttup))
1850 continue;
1851 attform = (Form_pg_attribute) GETSTRUCT(atttup);
1852 mtype = attform->atttypid;
1853 mtypmod = attform->atttypmod;
1854 mcoll = attform->attcollation;
1855 ReleaseSysCache(atttup);
1856 if (first_touching == NULL) {
1857 first_touching = m;
1858 first_attno = attno;
1859 first_type = mtype;
1860 first_typmod = mtypmod;
1861 first_coll = mcoll;
1862 continue;
1863 }
1864 eqop = find_equality_operator(first_type, mtype);
1865 if (!OidIsValid(eqop))
1866 continue;
1867 eqfunc = get_opcode(eqop);
1868 if (!OidIsValid(eqfunc))
1869 continue;
1870 lv = makeVar(first_touching->rtindex, first_attno,
1871 first_type, first_typmod, first_coll, 0);
1872 rv = makeVar(m->rtindex, attno, mtype, mtypmod, mcoll, 0);
1873 eq = makeNode(OpExpr);
1874 eq->opno = eqop;
1875 eq->opfuncid = eqfunc;
1876 eq->opresulttype = BOOLOID;
1877 eq->opretset = false;
1878 eq->opcollid = InvalidOid;
1879 eq->inputcollid = first_coll;
1880 eq->args = list_make2(lv, rv);
1881 eq->location = -1;
1882 arr[g]->inner_quals =
1883 lappend(arr[g]->inner_quals, eq);
1884 }
1885 }
1886 }
1887
1888 for (g = 0; g <= max_gid; g++)
1889 *groups_out = lappend(*groups_out, arr[g]);
1890 pfree(arr);
1891 }
1892
1893#undef ANCHOR
1894#undef DETERMINED
1895
1896 pfree(class_atom_count);
1897 pfree(class_atom_anchor_attno);
1898 pfree(vars_arr);
1899 pfree(cls);
1900 pfree(atom_group);
1901 if (in_targetlist)
1902 pfree(in_targetlist);
1903 if (first_member_of_group)
1904 pfree(first_member_of_group);
1905 if (group_singleton_counter)
1906 pfree(group_singleton_counter);
1907 if (determined_in)
1908 pfree(determined_in);
1909 if (class_atom_count_fd)
1910 pfree(class_atom_count_fd);
1911 if (atom_anchor_class)
1912 pfree(atom_anchor_class);
1913 (void) constants;
1914 return atoms_out;
1915
1916bail:
1917 pfree(class_atom_count);
1918 pfree(class_atom_anchor_attno);
1919 pfree(vars_arr);
1920 pfree(cls);
1921 if (atom_group)
1922 pfree(atom_group);
1923 if (in_targetlist)
1924 pfree(in_targetlist);
1925 if (first_member_of_group)
1926 pfree(first_member_of_group);
1927 if (group_singleton_counter)
1928 pfree(group_singleton_counter);
1929 if (determined_in)
1930 pfree(determined_in);
1931 if (class_atom_count_fd)
1932 pfree(class_atom_count_fd);
1933 if (atom_anchor_class)
1934 pfree(atom_anchor_class);
1935 (void) constants;
1936 *groups_out = NIL;
1937 return NIL;
1938}
1939
1940/**
1941 * @brief Mutator context for @c safe_remap_vars_mutator.
1942 *
1943 * @c atoms gives one descriptor per original RTE. For both outer-wrap
1944 * atoms (@c group_id == -1) and grouped atoms (@c group_id >= 0), the
1945 * Var is rewritten by scanning the atom's @c proj_slots for the
1946 * matching @c base_attno: the new @c varno is the atom's (or its
1947 * group's) @c outer_rtindex, the new @c varattno is the slot's
1948 * 1-based position in @c proj_slots. A Var whose @c base_attno is
1949 * not in any slot (i.e. the column does not belong to any fully-
1950 * covered shared class) triggers an error -- the wrap / inner sub-
1951 * Query has no matching output column for it, and the detector should
1952 * have rejected such a query.
1953 */
1954typedef struct safe_remap_ctx {
1955 List *atoms; ///< List of safe_rewrite_atom *, one per RTE
1956 List *groups; ///< List of safe_inner_group *
1957 bool bail; ///< Set when a Var has no slot in its atom's projection;
1958 ///< the caller aborts the rewrite and falls back to the
1959 ///< default pipeline rather than emitting a broken plan.
1961
1962/**
1963 * @brief Rewrite Var nodes in the outer query after each base RTE has
1964 * been wrapped as a DISTINCT subquery projecting one or more
1965 * slot columns.
1966 *
1967 * For each base-level Var (varno, varattno), the matching atom is
1968 * @c atoms[varno-1]. We scan its @c proj_slots in order, looking
1969 * for a slot with @c base_attno == varattno, and remap the Var to
1970 * the 1-based output position of that slot. A Var with no matching
1971 * slot indicates the detector accepted a query it shouldn't have;
1972 * we @c provsql_error to surface the bug rather than silently emit
1973 * a broken plan.
1974 */
1975static Node *safe_remap_vars_mutator(Node *node, safe_remap_ctx *ctx) {
1976 if (node == NULL)
1977 return NULL;
1978 if (IsA(node, Var)) {
1979 Var *v = (Var *) node;
1980 if (v->varlevelsup == 0
1981 && v->varno >= 1 && (int) v->varno <= list_length(ctx->atoms)) {
1982 safe_rewrite_atom *sa =
1983 (safe_rewrite_atom *) list_nth(ctx->atoms, (int) v->varno - 1);
1984 if (sa->group_id >= 0) {
1985 safe_inner_group *gr =
1986 (safe_inner_group *) list_nth(ctx->groups, sa->group_id);
1987 ListCell *lc;
1988 foreach (lc, sa->proj_slots) {
1989 safe_proj_slot *slot = (safe_proj_slot *) lfirst(lc);
1990 if (slot->base_attno == v->varattno) {
1991 Var *vv = (Var *) copyObject(v);
1992 vv->varno = gr->outer_rtindex;
1993#if PG_VERSION_NUM >= 130000
1994 vv->varnosyn = gr->outer_rtindex;
1995#endif
1996 vv->varattno = slot->outer_attno;
1997#if PG_VERSION_NUM >= 130000
1998 vv->varattnosyn = slot->outer_attno;
1999#endif
2000 return (Node *) vv;
2001 }
2002 }
2003 /* Head/qual Var on a grouped atom that no shared-class slot
2004 * covers: the rewrite cannot produce a column the outer query
2005 * can reference, but the input SQL is still valid -- bail to
2006 * the default pipeline rather than raising. */
2007 ctx->bail = true;
2008 return (Node *) v;
2009 } else {
2010 ListCell *lc;
2011 foreach (lc, sa->proj_slots) {
2012 safe_proj_slot *slot = (safe_proj_slot *) lfirst(lc);
2013 if (slot->base_attno == v->varattno) {
2014 Var *vv = (Var *) copyObject(v);
2015 vv->varno = sa->outer_rtindex;
2016#if PG_VERSION_NUM >= 130000
2017 vv->varnosyn = sa->outer_rtindex;
2018#endif
2019 vv->varattno = slot->outer_attno;
2020#if PG_VERSION_NUM >= 130000
2021 vv->varattnosyn = slot->outer_attno;
2022#endif
2023 return (Node *) vv;
2024 }
2025 }
2026 /* Same situation, outer-wrap atom: bail instead of raising. */
2027 ctx->bail = true;
2028 return (Node *) v;
2029 }
2030 }
2031 return (Node *) v;
2032 }
2033 return expression_tree_mutator(node, safe_remap_vars_mutator, (void *) ctx);
2034}
2035
2036/**
2037 * @brief Build the inner @c Query that projects every slot in
2038 * @p proj_slots of @p base_rte under @c SELECT @c DISTINCT.
2039 *
2040 * One @c TargetEntry and one @c SortGroupClause are emitted per slot,
2041 * in @p proj_slots order; the root-class slot is conventionally first,
2042 * so it always ends up at output attno 1. The recursive
2043 * @c process_query call on this @c Query will discover the @c provsql
2044 * column on @p base_rte and append it to the inner target list, so the
2045 * wrapping outer query gets the slot columns at attno @c 1..N and the
2046 * @c provsql column at attno @c N+1.
2047 */
2048static Query *safe_build_inner_wrap(Query *outer_src,
2049 RangeTblEntry *base_rte,
2050 List *proj_slots,
2051 Index outer_rtindex,
2052 List *pushed_quals) {
2053 Query *inner = makeNode(Query);
2054 RangeTblRef *rtr = makeNode(RangeTblRef);
2055 FromExpr *jt = makeNode(FromExpr);
2056 RangeTblEntry *inner_rte;
2057 ListCell *lc;
2058 int slot_idx = 0;
2059
2060 inner_rte = copyObject(base_rte);
2061
2062 inner->commandType = CMD_SELECT;
2063 inner->canSetTag = false;
2064 inner->rtable = list_make1(inner_rte);
2065#if PG_VERSION_NUM >= 160000
2066 /* The cloned RTE's perminfoindex pointed into the OUTER query's
2067 * rteperminfos list; reattach the matching RTEPermissionInfo to
2068 * the inner query so the planner finds it under inner->rteperminfos
2069 * (otherwise list_nth_node on an empty list segfaults during
2070 * post-processing). */
2071 if (base_rte->perminfoindex != 0
2072 && outer_src && outer_src->rteperminfos != NIL
2073 && (int) base_rte->perminfoindex <= list_length(outer_src->rteperminfos)) {
2074 RTEPermissionInfo *rpi = list_nth_node(RTEPermissionInfo,
2075 outer_src->rteperminfos,
2076 base_rte->perminfoindex - 1);
2077 inner->rteperminfos = list_make1(copyObject(rpi));
2078 inner_rte->perminfoindex = 1;
2079 } else {
2080 inner->rteperminfos = NIL;
2081 inner_rte->perminfoindex = 0;
2082 }
2083#endif
2084 rtr->rtindex = 1;
2085 jt->fromlist = list_make1(rtr);
2086 jt->quals = NULL;
2087 inner->jointree = jt;
2088
2089 inner->targetList = NIL;
2090 inner->distinctClause = NIL;
2091 inner->hasDistinctOn = false;
2092
2093 foreach (lc, proj_slots) {
2094 safe_proj_slot *slot = (safe_proj_slot *) lfirst(lc);
2095 HeapTuple atttup;
2096 Form_pg_attribute attform;
2097 Oid atttypid;
2098 int32 atttypmod;
2099 Oid attcollation;
2100 Var *v;
2101 TargetEntry *te = makeNode(TargetEntry);
2102 SortGroupClause *sgc = makeNode(SortGroupClause);
2103
2104 atttup = SearchSysCache2(ATTNUM,
2105 ObjectIdGetDatum(base_rte->relid),
2106 Int16GetDatum(slot->base_attno));
2107 if (!HeapTupleIsValid(atttup))
2108 provsql_error("safe-query rewriter: cannot resolve attno %d of "
2109 "relation %u",
2110 (int) slot->base_attno, (unsigned) base_rte->relid);
2111 attform = (Form_pg_attribute) GETSTRUCT(atttup);
2112 atttypid = attform->atttypid;
2113 atttypmod = attform->atttypmod;
2114 attcollation = attform->attcollation;
2115 ReleaseSysCache(atttup);
2116
2117 slot_idx++;
2118 v = makeVar(1, slot->base_attno, atttypid, atttypmod, attcollation, 0);
2119 te->expr = (Expr *) v;
2120 te->resno = (AttrNumber) slot_idx;
2121 te->resname = psprintf("provsql_slot_%d", slot_idx);
2122 te->ressortgroupref = (Index) slot_idx;
2123 te->resorigtbl = base_rte->relid;
2124 te->resorigcol = slot->base_attno;
2125 te->resjunk = false;
2126 inner->targetList = lappend(inner->targetList, te);
2127
2128 sgc->tleSortGroupRef = (Index) slot_idx;
2129 get_sort_group_operators(atttypid, true, true, false,
2130 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
2131 sgc->nulls_first = false;
2132 inner->distinctClause = lappend(inner->distinctClause, sgc);
2133 }
2134
2135 /* Inject the pushed-down atom-local quals. Each is @c copyObject'd
2136 * (so the outer query's residual tree is untouched), then its base-
2137 * level @c Var.varno is rewritten from the outer atom's rtindex to
2138 * @c 1 -- the only RTE in the inner subquery. Single conjunct goes
2139 * in directly; multiple conjuncts are AND-bundled. */
2140 if (pushed_quals != NIL) {
2142 List *remapped = NIL;
2143 ListCell *qlc;
2144 rctx.outer_rtindex = outer_rtindex;
2145 foreach (qlc, pushed_quals) {
2146 Node *q = (Node *) copyObject(lfirst(qlc));
2147 q = safe_pushed_remap_mutator(q, &rctx);
2148 remapped = lappend(remapped, q);
2149 }
2150 if (list_length(remapped) == 1)
2151 inner->jointree->quals = (Node *) linitial(remapped);
2152 else
2153 inner->jointree->quals = (Node *) makeBoolExpr(AND_EXPR, remapped, -1);
2154 }
2155
2156 return inner;
2157}
2158
2159/** @brief Mutator context for @c safe_inner_varno_remap_mutator. */
2161 int *orig_to_inner; ///< 1-indexed array: orig rtindex -> inner rtindex (0 if not in group)
2164
2165/**
2166 * @brief Rewrite base-level @c Var.varno from the outer atom rtindex to
2167 * the corresponding inner-sub-Query rtindex.
2168 *
2169 * Applied to each conjunct that the partition pass routed into an inner
2170 * group (and to every pushed-down atom-local qual of every grouped
2171 * atom) before injection into the inner sub-Query's
2172 * @c jointree->quals. @c varattno is unchanged -- the inner
2173 * sub-Query's RTEs are fresh clones of the same base relations, so the
2174 * base attribute numbers carry over.
2175 */
2176static Node *safe_inner_varno_remap_mutator(Node *node,
2178 if (node == NULL)
2179 return NULL;
2180 if (IsA(node, Var)) {
2181 Var *v = (Var *) node;
2182 if (v->varlevelsup == 0
2183 && v->varno >= 1 && (int) v->varno <= ctx->natoms) {
2184 int newno = ctx->orig_to_inner[v->varno];
2185 if (newno > 0) {
2186 Var *nv = (Var *) copyObject(v);
2187 nv->varno = (Index) newno;
2188#if PG_VERSION_NUM >= 130000
2189 nv->varnosyn = (Index) newno;
2190#endif
2191 return (Node *) nv;
2192 }
2193 }
2194 return node;
2195 }
2196 return expression_tree_mutator(node,
2198 (void *) ctx);
2199}
2200
2201/**
2202 * @brief Build the inner sub-Query that aggregates a group of
2203 * partial-coverage atoms over their non-root shared variables.
2204 *
2205 * The sub-Query's @c rtable contains a clone of each member atom's
2206 * @c RangeTblEntry, in original-rtindex order. Its @c WHERE is the AND
2207 * of @c gr->inner_quals (cross-atom conjuncts within the group) and
2208 * every member atom's @c pushed_quals; each conjunct's @c Var.varno is
2209 * remapped from the outer atom rtindex to the inner rtindex via
2210 * @c safe_inner_varno_remap_mutator. The @c targetList exposes a single
2211 * column carrying the root-class binding of the first member; the
2212 * @c groupClause aggregates the per-group provenance over the inner
2213 * shared variables. When @c process_query re-enters on this sub-Query,
2214 * the hierarchical-CQ rewriter fires again and wraps each member atom
2215 * with its own @c SELECT @c DISTINCT.
2216 */
2217static Query *safe_build_group_subquery(Query *outer_src,
2218 safe_inner_group *gr,
2219 List *atoms) {
2220 Query *inner = makeNode(Query);
2221 FromExpr *jt = makeNode(FromExpr);
2222 safe_rewrite_atom *first_member;
2223 RangeTblEntry *first_rte;
2224 HeapTuple atttup;
2225 Form_pg_attribute attform;
2226 Oid atttypid;
2227 int32 atttypmod;
2228 Oid attcollation;
2229 ListCell *lc;
2230 int inner_idx = 0;
2232 int natoms = list_length(atoms);
2233
2234 inner->commandType = CMD_SELECT;
2235 inner->canSetTag = false;
2236 inner->rtable = NIL;
2237 inner->jointree = jt;
2238 jt->fromlist = NIL;
2239 jt->quals = NULL;
2240#if PG_VERSION_NUM >= 160000
2241 inner->rteperminfos = NIL;
2242#endif
2243
2244 rctx.orig_to_inner = palloc0((natoms + 1) * sizeof(int));
2245 rctx.natoms = natoms;
2246
2247 /* Clone each member atom's RTE into the inner rtable and record its
2248 * inner rtindex. Order follows the @c member_atoms list, which is
2249 * itself in original-rtindex order, so the inner rtindex matches the
2250 * member's natural reading order. */
2251 foreach (lc, gr->member_atoms) {
2252 safe_rewrite_atom *sa = (safe_rewrite_atom *) lfirst(lc);
2253 RangeTblEntry *src_rte =
2254 (RangeTblEntry *) list_nth(outer_src->rtable, (int) sa->rtindex - 1);
2255 RangeTblEntry *cloned = (RangeTblEntry *) copyObject(src_rte);
2256 RangeTblRef *rtr = makeNode(RangeTblRef);
2257
2258 inner_idx++;
2259 sa->inner_rtindex = (Index) inner_idx;
2260 rctx.orig_to_inner[sa->rtindex] = inner_idx;
2261
2262#if PG_VERSION_NUM >= 160000
2263 if (cloned->perminfoindex != 0
2264 && outer_src->rteperminfos != NIL
2265 && (int) cloned->perminfoindex
2266 <= list_length(outer_src->rteperminfos)) {
2267 RTEPermissionInfo *rpi = list_nth_node(RTEPermissionInfo,
2268 outer_src->rteperminfos,
2269 cloned->perminfoindex - 1);
2270 inner->rteperminfos =
2271 lappend(inner->rteperminfos, copyObject(rpi));
2272 cloned->perminfoindex = (Index) list_length(inner->rteperminfos);
2273 } else {
2274 cloned->perminfoindex = 0;
2275 }
2276#endif
2277
2278 inner->rtable = lappend(inner->rtable, cloned);
2279 rtr->rtindex = inner_idx;
2280 jt->fromlist = lappend(jt->fromlist, rtr);
2281 }
2282
2283 /* Assemble the inner WHERE: cross-atom conjuncts the partition pass
2284 * routed here, plus each member atom's pushed atom-local quals
2285 * (the atom-local pre-pass will re-extract them when the rewriter
2286 * re-enters on the inner sub-Query, but the conjuncts must travel
2287 * along with their atoms so the re-entry's @c safe_split_quals sees
2288 * them). */
2289 {
2290 List *all_quals = NIL;
2291 foreach (lc, gr->inner_quals)
2292 all_quals = lappend(all_quals,
2293 copyObject((Node *) lfirst(lc)));
2294 foreach (lc, gr->member_atoms) {
2295 safe_rewrite_atom *sa = (safe_rewrite_atom *) lfirst(lc);
2296 ListCell *qlc;
2297 foreach (qlc, sa->pushed_quals)
2298 all_quals = lappend(all_quals,
2299 copyObject((Node *) lfirst(qlc)));
2300 }
2301 {
2302 ListCell *qlc;
2303 List *remapped = NIL;
2304 foreach (qlc, all_quals) {
2305 Node *qq = (Node *) lfirst(qlc);
2306 qq = safe_inner_varno_remap_mutator(qq, &rctx);
2307 remapped = lappend(remapped, qq);
2308 }
2309 if (remapped == NIL)
2310 jt->quals = NULL;
2311 else if (list_length(remapped) == 1)
2312 jt->quals = (Node *) linitial(remapped);
2313 else
2314 jt->quals = (Node *) makeBoolExpr(AND_EXPR, remapped, -1);
2315 }
2316 }
2317
2318 /* targetList: one TargetEntry per fully-covered shared class, in the
2319 * order of the first member's @c proj_slots (root first, then
2320 * other fully-covered classes by ascending repr). All members of
2321 * the group agree on each fully-covered class's value inside the
2322 * group, so picking the first member's columns is correct. The
2323 * @c groupClause has a matching @c SortGroupClause per slot. */
2324 first_member = (safe_rewrite_atom *) linitial(gr->member_atoms);
2325 first_rte = (RangeTblEntry *)
2326 list_nth(outer_src->rtable, (int) first_member->rtindex - 1);
2327
2328 inner->targetList = NIL;
2329 inner->groupClause = NIL;
2330 /* Emit one TargetEntry per slot in @c outer_attno order, covering
2331 * first_member's slots first, then each non-first-member's
2332 * singleton-only slots in member-list order. Slots with the same
2333 * @c outer_attno across members (shared root + fully-covered
2334 * classes) are emitted once, attached to the first member that
2335 * owns them. We track which @c outer_attno values have already
2336 * been emitted via a Bitmapset. */
2337 {
2338 Bitmapset *emitted = NULL;
2339 ListCell *mlc;
2340 foreach (mlc, gr->member_atoms) {
2341 safe_rewrite_atom *m = (safe_rewrite_atom *) lfirst(mlc);
2342 RangeTblEntry *m_rte = (RangeTblEntry *)
2343 list_nth(outer_src->rtable, (int) m->rtindex - 1);
2344 ListCell *slot_lc;
2345 foreach (slot_lc, m->proj_slots) {
2346 safe_proj_slot *slot = (safe_proj_slot *) lfirst(slot_lc);
2347 TargetEntry *te;
2348 SortGroupClause *sgc;
2349 Var *cv;
2350 if (bms_is_member((int) slot->outer_attno, emitted))
2351 continue;
2352 emitted = bms_add_member(emitted, (int) slot->outer_attno);
2353
2354 atttup = SearchSysCache2(ATTNUM,
2355 ObjectIdGetDatum(m_rte->relid),
2356 Int16GetDatum(slot->base_attno));
2357 if (!HeapTupleIsValid(atttup))
2358 provsql_error("safe-query rewriter: cannot resolve attno %d of "
2359 "relation %u in inner sub-Query",
2360 (int) slot->base_attno, (unsigned) m_rte->relid);
2361 attform = (Form_pg_attribute) GETSTRUCT(atttup);
2362 atttypid = attform->atttypid;
2363 atttypmod = attform->atttypmod;
2364 attcollation = attform->attcollation;
2365 ReleaseSysCache(atttup);
2366
2367 te = makeNode(TargetEntry);
2368 sgc = makeNode(SortGroupClause);
2369 cv = makeVar((Index) m->inner_rtindex,
2370 slot->base_attno,
2371 atttypid, atttypmod, attcollation, 0);
2372 te->expr = (Expr *) cv;
2373 te->resno = slot->outer_attno;
2374 te->resname = psprintf("provsql_slot_%d",
2375 (int) slot->outer_attno);
2376 te->ressortgroupref = (Index) slot->outer_attno;
2377 te->resorigtbl = m_rte->relid;
2378 te->resorigcol = slot->base_attno;
2379 te->resjunk = false;
2380 inner->targetList = lappend(inner->targetList, te);
2381
2382 sgc->tleSortGroupRef = (Index) slot->outer_attno;
2383 get_sort_group_operators(atttypid, true, true, false,
2384 &sgc->sortop, &sgc->eqop, NULL,
2385 &sgc->hashable);
2386 sgc->nulls_first = false;
2387 inner->groupClause = lappend(inner->groupClause, sgc);
2388 }
2389 }
2390 bms_free(emitted);
2391 }
2392
2393 (void) first_member; (void) first_rte;
2394 pfree(rctx.orig_to_inner);
2395 return inner;
2396}
2397
2398/**
2399 * @brief Apply the (multi-level when needed) hierarchical-CQ rewrite.
2400 *
2401 * Walks the outer rtable in original order. Each atom is replaced by
2402 * an @c RTE_SUBQUERY. Atoms with @c group_id @c == @c -1 get a direct
2403 * outer wrap (@c SELECT @c DISTINCT on their projection slots). The
2404 * first atom of each inner group emits the group's sub-Query
2405 * (@c safe_build_group_subquery), and subsequent group members are
2406 * skipped from the outer rtable -- they live inside the inner
2407 * sub-Query. The outer rtable therefore has one entry per outer-wrap
2408 * atom plus one entry per inner group, generally fewer than the
2409 * original.
2410 *
2411 * The remap pass then rewrites every base Var in the outer
2412 * @c targetList and residual WHERE. Both outer-wrap and grouped
2413 * Vars resolve by scanning the atom's @c proj_slots for the matching
2414 * @c base_attno: the new @c varno is the atom's (or its group's)
2415 * @c outer_rtindex, and the new @c varattno is the slot's 1-based
2416 * position in @c proj_slots (which matches the output column of the
2417 * outer wrap or of the inner sub-Query's @c targetList).
2418 */
2419static Query *rewrite_hierarchical_cq(const constants_t *constants,
2420 Query *q, List *atoms, List *groups,
2421 Node *residual) {
2422 Query *outer = copyObject(q);
2423 safe_remap_ctx mctx;
2424 List *new_rtable = NIL;
2425#if PG_VERSION_NUM >= 160000
2426 List *new_rteperminfos = NIL;
2427#endif
2428 List *new_fromlist = NIL;
2429 ListCell *lc;
2430 int j;
2431 int outer_pos = 0;
2432 int ngroups = list_length(groups);
2433 bool *group_emitted = NULL;
2434 int total_atoms_in_groups = 0;
2435 int ninner = 0;
2436
2437 (void) constants;
2438
2439 if (ngroups > 0) {
2440 group_emitted = palloc0(ngroups * sizeof(bool));
2441 foreach (lc, groups) {
2442 safe_inner_group *gr = (safe_inner_group *) lfirst(lc);
2443 total_atoms_in_groups += list_length(gr->member_atoms);
2444 }
2445 }
2446
2447 /* Replace the outer WHERE with the residual (atom-local conjuncts
2448 * were extracted upstream; conjuncts wholly inside a group were
2449 * routed into that group's inner_quals before this function runs).
2450 * A fresh @c copyObject keeps the outer tree independent. */
2451 if (outer->jointree)
2452 outer->jointree->quals =
2453 residual ? (Node *) copyObject(residual) : NULL;
2454
2455 /* Walk original rtable in order; emit either a direct per-atom
2456 * outer wrap or, the first time we hit a group member, the group's
2457 * inner sub-Query RTE. */
2458 j = 0;
2459 foreach (lc, outer->rtable) {
2460 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
2461 safe_rewrite_atom *sa = (safe_rewrite_atom *) list_nth(atoms, j);
2462 RangeTblRef *rtr;
2463
2464 if (sa->group_id < 0) {
2465 Query *inner = safe_build_inner_wrap(outer, rte, sa->proj_slots,
2466 sa->rtindex, sa->pushed_quals);
2467 RangeTblEntry *new_rte = makeNode(RangeTblEntry);
2468 Alias *eref = makeNode(Alias);
2469 ListCell *slot_lc;
2470 int slot_idx = 0;
2471
2472 eref->aliasname = rte->eref && rte->eref->aliasname
2473 ? pstrdup(rte->eref->aliasname)
2474 : pstrdup("provsql_wrap");
2475 eref->colnames = NIL;
2476 foreach (slot_lc, sa->proj_slots) {
2477 (void) lfirst(slot_lc);
2478 slot_idx++;
2479 eref->colnames = lappend(eref->colnames,
2480 makeString(psprintf("provsql_slot_%d",
2481 slot_idx)));
2482 }
2483
2484 new_rte->rtekind = RTE_SUBQUERY;
2485 new_rte->subquery = inner;
2486 new_rte->alias = NULL;
2487 new_rte->eref = eref;
2488 new_rte->inFromCl = rte->inFromCl;
2489 new_rte->lateral = false;
2490#if PG_VERSION_NUM < 160000
2491 new_rte->requiredPerms = 0;
2492#endif
2493
2494 outer_pos++;
2495 sa->outer_rtindex = (Index) outer_pos;
2496 new_rtable = lappend(new_rtable, new_rte);
2497 rtr = makeNode(RangeTblRef);
2498 rtr->rtindex = outer_pos;
2499 new_fromlist = lappend(new_fromlist, rtr);
2500 } else {
2501 int g = sa->group_id;
2502 safe_inner_group *gr = (safe_inner_group *) list_nth(groups, g);
2503
2504 if (!group_emitted[g]) {
2505 Query *inner = safe_build_group_subquery(outer, gr, atoms);
2506 RangeTblEntry *new_rte = makeNode(RangeTblEntry);
2507 Alias *eref = makeNode(Alias);
2508 int slot_idx = 0;
2509 int total_inner_cols = inner->targetList != NIL
2510 ? list_length(inner->targetList) : 0;
2511
2512 eref->aliasname = pstrdup("provsql_group");
2513 eref->colnames = NIL;
2514 for (slot_idx = 1; slot_idx <= total_inner_cols; slot_idx++) {
2515 eref->colnames = lappend(eref->colnames,
2516 makeString(psprintf("provsql_slot_%d",
2517 slot_idx)));
2518 }
2519
2520 new_rte->rtekind = RTE_SUBQUERY;
2521 new_rte->subquery = inner;
2522 new_rte->alias = NULL;
2523 new_rte->eref = eref;
2524 new_rte->inFromCl = rte->inFromCl;
2525 new_rte->lateral = false;
2526#if PG_VERSION_NUM < 160000
2527 new_rte->requiredPerms = 0;
2528#endif
2529
2530 outer_pos++;
2531 gr->outer_rtindex = (Index) outer_pos;
2532 new_rtable = lappend(new_rtable, new_rte);
2533 rtr = makeNode(RangeTblRef);
2534 rtr->rtindex = outer_pos;
2535 new_fromlist = lappend(new_fromlist, rtr);
2536 group_emitted[g] = true;
2537 ninner++;
2538 }
2539 sa->outer_rtindex = gr->outer_rtindex;
2540 }
2541 j++;
2542 }
2543
2544 outer->rtable = new_rtable;
2545 if (outer->jointree)
2546 outer->jointree->fromlist = new_fromlist;
2547#if PG_VERSION_NUM >= 160000
2548 /* The rebuilt rtable is a fresh list of RTE_SUBQUERY entries; none
2549 * of them reference @c outer->rteperminfos, so clear it. The inner
2550 * sub-Queries carry their own @c rteperminfos. */
2551 outer->rteperminfos = new_rteperminfos;
2552#endif
2553
2554 /* Remap outer Vars: outer-wrap atoms resolve to their slot's column
2555 * of their atom's wrapping subquery; grouped atoms resolve to the
2556 * group's inner sub-Query at output column 1; constant-pinned atoms
2557 * expose only the synthesised anchor (the per-atom @c pushed_quals
2558 * are already AND-injected into the inner subquery by
2559 * @c safe_build_inner_wrap), so a Var referencing a pinned atom
2560 * here would have no slot to resolve to -- the residual-cleanup
2561 * pass should have dropped any such Var. If one slips through,
2562 * @c safe_remap_vars_mutator's pinned-atom branch raises @c bail
2563 * and the rewriter falls back to the regular pipeline. */
2564 mctx.atoms = atoms;
2565 mctx.groups = groups;
2566 mctx.bail = false;
2567 outer->targetList = (List *)
2568 safe_remap_vars_mutator((Node *) outer->targetList, &mctx);
2569 if (outer->jointree && outer->jointree->quals)
2570 outer->jointree->quals =
2571 safe_remap_vars_mutator(outer->jointree->quals, &mctx);
2572
2573 if (group_emitted)
2574 pfree(group_emitted);
2575
2576 /* A Var with no projection slot means the rewrite cannot honour the
2577 * outer query without inventing an output column for it (e.g. a
2578 * GROUP BY column on a grouped atom whose value is not shared
2579 * across the group's other members). Bail to the regular pipeline:
2580 * the input SQL is still valid, the rewrite just does not apply. */
2581 if (mctx.bail) {
2582 if (provsql_verbose >= 30)
2583 provsql_notice("safe-query rewrite bailed: a Var has no projection "
2584 "slot in its (grouped or outer-wrap) atom");
2585 return NULL;
2586 }
2587
2588 if (provsql_verbose >= 30) {
2589 StringInfoData buf;
2590 int total_slots = 0;
2591 int total_pushed = 0;
2592 int has_col_push = 0;
2593 foreach (lc, atoms) {
2594 safe_rewrite_atom *sa = (safe_rewrite_atom *) lfirst(lc);
2595 total_slots += list_length(sa->proj_slots);
2596 total_pushed += list_length(sa->pushed_quals);
2597 if (list_length(sa->proj_slots) > 1)
2598 has_col_push = 1;
2599 }
2600 initStringInfo(&buf);
2601 appendStringInfo(&buf,
2602 "safe-query rewrite fired: wrapped %d atoms with "
2603 "SELECT DISTINCT on %d total slot(s)",
2604 list_length(atoms) - total_atoms_in_groups,
2605 total_slots);
2606 if (ninner > 0) {
2607 appendStringInfo(&buf,
2608 ", folded %d atom%s into %d inner sub-Quer%s",
2609 total_atoms_in_groups,
2610 total_atoms_in_groups == 1 ? "" : "s",
2611 ninner, ninner == 1 ? "y" : "ies");
2612 }
2613 if (has_col_push || total_pushed > 0) {
2614 const char *sep = " (";
2615 if (has_col_push) {
2616 appendStringInfoString(&buf, sep);
2617 appendStringInfoString(&buf, "column pushdown");
2618 sep = "; ";
2619 }
2620 if (total_pushed > 0) {
2621 appendStringInfoString(&buf, sep);
2622 appendStringInfo(&buf, "%d atom-local qual%s pushed",
2623 total_pushed, total_pushed == 1 ? "" : "s");
2624 }
2625 appendStringInfoChar(&buf, ')');
2626 }
2627 provsql_notice("%s", buf.data);
2628 pfree(buf.data);
2629 }
2630
2631 return outer;
2632}
2633
2634/**
2635 * @brief Compute atom-level connected components.
2636 *
2637 * Two atoms belong to the same component iff there is a chain of
2638 * equality conjuncts in @p quals that connects one of their Vars to
2639 * one of the other's Vars. Uses a quick atom-level union-find driven
2640 * by the equality pairs already extracted by
2641 * @c safe_collect_equalities, then compacts representatives into
2642 * 0-based component ids written into @p atom_to_comp.
2643 *
2644 * @return Number of distinct components.
2645 */
2646static int compute_atom_components(Query *q, Node *quals, int *atom_to_comp) {
2647 int natoms = list_length(q->rtable);
2648 int *dsu = palloc(natoms * sizeof(int));
2649 List *eq_pairs = NIL;
2650 int *root_to_comp;
2651 int ncomp = 0;
2652 int j;
2653 ListCell *lc;
2654
2655 for (j = 0; j < natoms; j++)
2656 dsu[j] = j;
2657
2658 safe_collect_equalities(quals, &eq_pairs);
2659 for (lc = list_head(eq_pairs); lc != NULL; lc = my_lnext(eq_pairs, lc)) {
2660 Var *lv, *rv;
2661 int la, ra;
2662 lv = (Var *) lfirst(lc);
2663 lc = my_lnext(eq_pairs, lc);
2664 rv = (Var *) lfirst(lc);
2665 la = (int) lv->varno - 1;
2666 ra = (int) rv->varno - 1;
2667 if (la < 0 || la >= natoms || ra < 0 || ra >= natoms)
2668 continue;
2669 while (dsu[la] != la) la = dsu[la];
2670 while (dsu[ra] != ra) ra = dsu[ra];
2671 if (la != ra)
2672 dsu[la] = ra;
2673 }
2674
2675 root_to_comp = palloc(natoms * sizeof(int));
2676 for (j = 0; j < natoms; j++) {
2677 int r = j;
2678 int k;
2679 bool found = false;
2680 while (dsu[r] != r) r = dsu[r];
2681 dsu[j] = r;
2682 for (k = 0; k < ncomp; k++) {
2683 if (root_to_comp[k] == r) {
2684 atom_to_comp[j] = k;
2685 found = true;
2686 break;
2687 }
2688 }
2689 if (!found) {
2690 root_to_comp[ncomp] = r;
2691 atom_to_comp[j] = ncomp++;
2692 }
2693 }
2694 pfree(root_to_comp);
2695 pfree(dsu);
2696 return ncomp;
2697}
2698
2699/** @brief Mutator context for @c safe_outer_te_remap_mutator. */
2701 int *atom_to_comp; ///< per-atom component id
2702 int *atom_to_inner_attno; ///< per-atom column position in its component's inner targetList (1-based; 0 = not exposed)
2703 Index *comp_to_outer_rtindex; ///< per-component outer-rtable position (1-based)
2704 bool bail; ///< set when a Var has no exposed inner column; caller falls back to the regular pipeline
2706
2707/**
2708 * @brief Rewrite Vars in the outer targetList for the multi-component
2709 * rewrite.
2710 *
2711 * Each base-level Var(varno=v, varattno=a) in the user's targetList is
2712 * looked up in @c atom_to_inner_attno[v-1] to find which output column
2713 * of the matching component's inner sub-Query carries the Var, then
2714 * rewritten to point at that component's @c RTE_SUBQUERY in the outer
2715 * rtable. A Var whose @c atom_to_inner_attno entry is 0 (i.e. the
2716 * detector did not pick this column for its inner sub-Query)
2717 * indicates a bug or a query the caller should have refused; we
2718 * @c provsql_error to surface it.
2719 */
2720static Node *safe_outer_te_remap_mutator(Node *node,
2722 if (node == NULL)
2723 return NULL;
2724 if (IsA(node, Var)) {
2725 Var *v = (Var *) node;
2726 if (v->varlevelsup == 0 && v->varno >= 1) {
2727 int atom_idx = (int) v->varno - 1;
2728 int comp = ctx->atom_to_comp[atom_idx];
2729 AttrNumber inner_attno =
2730 (AttrNumber) ctx->atom_to_inner_attno[atom_idx];
2731 Index outer_rtindex = ctx->comp_to_outer_rtindex[comp];
2732 Var *vv;
2733 if (inner_attno == 0) {
2734 /* Same bailout pattern as safe_remap_vars_mutator: signal the
2735 * caller to abandon the multi-component rewrite rather than
2736 * raise on a valid input the rewriter just cannot handle. */
2737 ctx->bail = true;
2738 return (Node *) v;
2739 }
2740 vv = (Var *) copyObject(v);
2741 vv->varno = outer_rtindex;
2742 vv->varattno = inner_attno;
2743#if PG_VERSION_NUM >= 130000
2744 vv->varnosyn = outer_rtindex;
2745 vv->varattnosyn = inner_attno;
2746#endif
2747 return (Node *) vv;
2748 }
2749 return node;
2750 }
2751 return expression_tree_mutator(node, safe_outer_te_remap_mutator,
2752 (void *) ctx);
2753}
2754
2755/**
2756 * @brief Apply the multi-component rewrite.
2757 *
2758 * Assumes @p atom_to_comp partitions the @c q->rtable atoms into
2759 * @p ncomp connected components (@p ncomp >= 2) and that every
2760 * @c TargetEntry in @c q->targetList has all its Vars in a single
2761 * component. Builds one inner @c Query per component, each carrying:
2762 * - the component's atoms as @c RTE_RELATION clones,
2763 * - the cross-atom WHERE conjuncts and atom-local pushed quals
2764 * confined to those atoms,
2765 * - the slice of @c q->targetList referencing this component's
2766 * atoms (fresh @c ressortgroupref) plus matching @c groupClause,
2767 * and assembles an outer @c Query whose @c rtable is the list of
2768 * inner sub-Queries. Each output row's provenance is the
2769 * @c gate_times of the per-component provsqls; Choice A re-entry
2770 * lets the single-component rewriter handle each component
2771 * independently.
2772 *
2773 * Returns @c NULL to fall through when any component has no Var-
2774 * carrying @c TargetEntry to anchor its inner sub-Query (the all-
2775 * constant case, e.g. @c SELECT @c DISTINCT @c 1 @c FROM @c A,B,
2776 * is deferred).
2777 */
2778static Query *rewrite_multi_component(const constants_t *constants,
2779 Query *q,
2780 Node *residual,
2781 List **per_atom_quals,
2782 int *atom_to_comp,
2783 int ncomp) {
2784 Query *outer;
2785 int natoms = list_length(q->rtable);
2786 Query **inner_queries;
2787 List **inner_quals; /* per-component list of Node* */
2788 List **inner_tlists; /* per-component list of TargetEntry* (orig) */
2789 int *comp_inner_idx; /* per-component running rtindex counter */
2790 int *atom_to_inner_idx; /* per-atom 1-based rtindex inside its component */
2791 int *atom_to_inner_attno; /* per-atom 1-based attno of its first targetList exposure */
2792 Index *comp_outer_rtindex;
2793 int k, j;
2794 ListCell *lc;
2795 List *conjuncts = NIL;
2796 List *outer_resid = NIL;
2797
2798 (void) constants;
2799
2800 /* Allocate per-component scratch. */
2801 inner_quals = palloc0(ncomp * sizeof(List *));
2802 inner_tlists = palloc0(ncomp * sizeof(List *));
2803 comp_inner_idx = palloc0(ncomp * sizeof(int));
2804 atom_to_inner_idx = palloc0(natoms * sizeof(int));
2805 atom_to_inner_attno = palloc0(natoms * sizeof(int));
2806 comp_outer_rtindex = palloc0(ncomp * sizeof(Index));
2807
2808 /* Assign per-component inner rtindexes in original-rtindex order. */
2809 for (j = 0; j < natoms; j++) {
2810 int c = atom_to_comp[j];
2811 comp_inner_idx[c]++;
2812 atom_to_inner_idx[j] = comp_inner_idx[c];
2813 }
2814
2815 /* Partition the user's targetList by component. Reject any TE
2816 * whose Vars span more than one component (impossible for a truly
2817 * disconnected CQ -- belt-and-braces). Reject the all-constant
2818 * case (a TE with no Vars at all) by returning NULL; we defer
2819 * that. */
2820 foreach (lc, q->targetList) {
2821 TargetEntry *te = (TargetEntry *) lfirst(lc);
2822 safe_collect_varnos_ctx vctx = { NULL };
2823 int v;
2824 int chosen = -1;
2825 safe_collect_varnos_walker((Node *) te->expr, &vctx);
2826 if (bms_is_empty(vctx.varnos)) {
2827 /* No atom Vars: a constant-only or @c provenance()-only TE
2828 * (the latter is rewritten downstream). It stays at the outer
2829 * level; nothing to push into any inner sub-Query. */
2830 bms_free(vctx.varnos);
2831 continue;
2832 }
2833 v = -1;
2834 while ((v = bms_next_member(vctx.varnos, v)) >= 0) {
2835 int c;
2836 if (v < 1 || v > natoms) {
2837 bms_free(vctx.varnos);
2838 return NULL;
2839 }
2840 c = atom_to_comp[v - 1];
2841 if (chosen < 0)
2842 chosen = c;
2843 else if (chosen != c) {
2844 bms_free(vctx.varnos);
2845 return NULL; /* cross-component TE; not disconnected */
2846 }
2847 }
2848 bms_free(vctx.varnos);
2849 inner_tlists[chosen] = lappend(inner_tlists[chosen], te);
2850 }
2851
2852 /* A component with no user-Var TargetEntry still needs an anchor
2853 * inside its inner sub-Query: without something in the targetList,
2854 * the inner has no column to group on and PostgreSQL won't accept
2855 * the subquery. Synthesise a @c Const(1) for those components.
2856 * The outer doesn't reference these anchors (no user TE points at
2857 * them); they only exist to fold the inner to one row per per-
2858 * component grouping (here, one row total since there are no
2859 * Vars to group by). */
2860 for (k = 0; k < ncomp; k++) {
2861 if (inner_tlists[k] == NIL) {
2862 TargetEntry *anchor = makeNode(TargetEntry);
2863 anchor->expr = (Expr *) makeConst(INT4OID, -1, InvalidOid,
2864 sizeof(int32),
2865 Int32GetDatum(1), false, true);
2866 anchor->resno = 1;
2867 anchor->resname = pstrdup("provsql_anchor");
2868 anchor->ressortgroupref = 1;
2869 anchor->resjunk = false;
2870 inner_tlists[k] = list_make1(anchor);
2871 }
2872 }
2873
2874 /* Partition the residual cross-atom conjuncts by component. A
2875 * conjunct whose Vars span more than one component stays at the
2876 * outer level (shouldn't happen for a truly disconnected CQ but be
2877 * defensive). */
2878 safe_flatten_and(residual, &conjuncts);
2879 foreach (lc, conjuncts) {
2880 Node *qual = (Node *) lfirst(lc);
2881 safe_collect_varnos_ctx vctx = { NULL };
2882 int v;
2883 int chosen = -1;
2884 bool keep_outer = false;
2885 safe_collect_varnos_walker(qual, &vctx);
2886 v = -1;
2887 while ((v = bms_next_member(vctx.varnos, v)) >= 0) {
2888 int c;
2889 if (v < 1 || v > natoms) {
2890 keep_outer = true;
2891 break;
2892 }
2893 c = atom_to_comp[v - 1];
2894 if (chosen < 0)
2895 chosen = c;
2896 else if (chosen != c) {
2897 keep_outer = true;
2898 break;
2899 }
2900 }
2901 bms_free(vctx.varnos);
2902 if (keep_outer || chosen < 0)
2903 outer_resid = lappend(outer_resid, qual);
2904 else
2905 inner_quals[chosen] = lappend(inner_quals[chosen], qual);
2906 }
2907
2908 /* Build one inner Query per component. */
2909 inner_queries = palloc0(ncomp * sizeof(Query *));
2910 for (k = 0; k < ncomp; k++) {
2911 Query *inner = makeNode(Query);
2912 FromExpr *jt = makeNode(FromExpr);
2913 int inner_attno = 0;
2914 int inner_sgr = 0;
2915 int *orig_to_inner;
2916
2917 inner->commandType = CMD_SELECT;
2918 inner->canSetTag = false;
2919 inner->rtable = NIL;
2920 inner->jointree = jt;
2921 jt->fromlist = NIL;
2922 jt->quals = NULL;
2923#if PG_VERSION_NUM >= 160000
2924 inner->rteperminfos = NIL;
2925#endif
2926
2927 orig_to_inner = palloc0((natoms + 1) * sizeof(int));
2928
2929 /* Clone the component's atoms into the inner rtable. */
2930 for (j = 0; j < natoms; j++) {
2931 RangeTblEntry *src_rte, *cloned;
2932 RangeTblRef *rtr;
2933 int inner_rtindex;
2934 if (atom_to_comp[j] != k)
2935 continue;
2936 src_rte = (RangeTblEntry *) list_nth(q->rtable, j);
2937 cloned = (RangeTblEntry *) copyObject(src_rte);
2938#if PG_VERSION_NUM >= 160000
2939 if (cloned->perminfoindex != 0
2940 && q->rteperminfos != NIL
2941 && (int) cloned->perminfoindex <= list_length(q->rteperminfos)) {
2942 RTEPermissionInfo *rpi = list_nth_node(RTEPermissionInfo,
2943 q->rteperminfos,
2944 cloned->perminfoindex - 1);
2945 inner->rteperminfos =
2946 lappend(inner->rteperminfos, copyObject(rpi));
2947 cloned->perminfoindex = (Index) list_length(inner->rteperminfos);
2948 } else {
2949 cloned->perminfoindex = 0;
2950 }
2951#endif
2952 inner->rtable = lappend(inner->rtable, cloned);
2953 inner_rtindex = list_length(inner->rtable);
2954 orig_to_inner[j + 1] = inner_rtindex;
2955 rtr = makeNode(RangeTblRef);
2956 rtr->rtindex = inner_rtindex;
2957 jt->fromlist = lappend(jt->fromlist, rtr);
2958 }
2959
2960 /* Inner WHERE: cross-atom conjuncts within this component + atom-
2961 * local pushed quals for this component's atoms. Var.varno is
2962 * rewritten from the original rtindex to the inner rtindex via a
2963 * tiny inline remap. */
2964 {
2965 List *all = NIL;
2966 ListCell *qlc;
2967 foreach (qlc, inner_quals[k])
2968 all = lappend(all, copyObject((Node *) lfirst(qlc)));
2969 for (j = 0; j < natoms; j++) {
2970 if (atom_to_comp[j] != k)
2971 continue;
2972 foreach (qlc, per_atom_quals[j])
2973 all = lappend(all, copyObject((Node *) lfirst(qlc)));
2974 }
2975 if (all != NIL) {
2977 List *remapped = NIL;
2978 rctx.orig_to_inner = orig_to_inner;
2979 rctx.natoms = natoms;
2980 foreach (qlc, all) {
2981 Node *qq = (Node *) lfirst(qlc);
2982 qq = safe_inner_varno_remap_mutator(qq, &rctx);
2983 remapped = lappend(remapped, qq);
2984 }
2985 if (list_length(remapped) == 1)
2986 jt->quals = (Node *) linitial(remapped);
2987 else
2988 jt->quals = (Node *) makeBoolExpr(AND_EXPR, remapped, -1);
2989 }
2990 }
2991
2992 /* Inner targetList: clone the user's TEs that landed in this
2993 * component, remap their Vars to the inner rtindexes, assign
2994 * fresh resno + ressortgroupref, and synthesise a matching
2995 * groupClause that GROUPs BY every slot. */
2996 inner->targetList = NIL;
2997 inner->groupClause = NIL;
2998 {
2999 ListCell *tlc;
3000 foreach (tlc, inner_tlists[k]) {
3001 TargetEntry *src_te = (TargetEntry *) lfirst(tlc);
3002 TargetEntry *new_te = (TargetEntry *) copyObject(src_te);
3004 SortGroupClause *sgc = makeNode(SortGroupClause);
3005 Oid expr_type;
3006 rctx.orig_to_inner = orig_to_inner;
3007 rctx.natoms = natoms;
3008 new_te->expr = (Expr *) safe_inner_varno_remap_mutator(
3009 (Node *) new_te->expr, &rctx);
3010 inner_attno++;
3011 inner_sgr++;
3012 new_te->resno = (AttrNumber) inner_attno;
3013 new_te->ressortgroupref = (Index) inner_sgr;
3014 new_te->resjunk = false;
3015 /* Track exposure for outer Var remap. Each user TE keeps
3016 * the first atom-Var encountered; for our restricted scope
3017 * (every TE has Vars in a single component, and each Var of
3018 * a given (varno, varattno) ends up at one inner column) this
3019 * gives the right mapping. */
3020 {
3021 safe_collect_varnos_ctx vctx = { NULL };
3022 int v;
3023 safe_collect_varnos_walker((Node *) src_te->expr, &vctx);
3024 v = -1;
3025 while ((v = bms_next_member(vctx.varnos, v)) >= 0) {
3026 if (v >= 1 && v <= natoms && atom_to_comp[v - 1] == k)
3027 atom_to_inner_attno[v - 1] = inner_attno;
3028 }
3029 bms_free(vctx.varnos);
3030 }
3031 inner->targetList = lappend(inner->targetList, new_te);
3032
3033 expr_type = exprType((Node *) new_te->expr);
3034 sgc->tleSortGroupRef = (Index) inner_sgr;
3035 get_sort_group_operators(expr_type, true, true, false,
3036 &sgc->sortop, &sgc->eqop, NULL,
3037 &sgc->hashable);
3038 sgc->nulls_first = false;
3039 inner->groupClause = lappend(inner->groupClause, sgc);
3040 }
3041 }
3042
3043 inner_queries[k] = inner;
3044 pfree(orig_to_inner);
3045 }
3046
3047 /* Build the outer Query: rtable is one RTE_SUBQUERY per
3048 * component; jointree.fromlist holds N RangeTblRefs; targetList /
3049 * groupClause / distinctClause / etc. are copied from the user's
3050 * Query with Vars remapped to the matching component's inner
3051 * output column. */
3052 outer = copyObject(q);
3053 outer->rtable = NIL;
3054 outer->jointree->fromlist = NIL;
3055 outer->jointree->quals = (outer_resid == NIL) ? NULL
3056 : (list_length(outer_resid) == 1
3057 ? (Node *) linitial(outer_resid)
3058 : (Node *) makeBoolExpr(AND_EXPR,
3059 outer_resid, -1));
3060#if PG_VERSION_NUM >= 160000
3061 outer->rteperminfos = NIL;
3062#endif
3063 for (k = 0; k < ncomp; k++) {
3064 RangeTblEntry *new_rte = makeNode(RangeTblEntry);
3065 Alias *eref = makeNode(Alias);
3066 ListCell *tlc;
3067 int slot_idx = 0;
3068
3069 eref->aliasname = psprintf("provsql_component_%d", k + 1);
3070 eref->colnames = NIL;
3071 foreach (tlc, inner_queries[k]->targetList) {
3072 TargetEntry *ite = (TargetEntry *) lfirst(tlc);
3073 slot_idx++;
3074 eref->colnames = lappend(eref->colnames,
3075 makeString(ite->resname
3076 ? pstrdup(ite->resname)
3077 : psprintf("col_%d", slot_idx)));
3078 }
3079
3080 new_rte->rtekind = RTE_SUBQUERY;
3081 new_rte->subquery = inner_queries[k];
3082 new_rte->alias = NULL;
3083 new_rte->eref = eref;
3084 new_rte->inFromCl = true;
3085 new_rte->lateral = false;
3086#if PG_VERSION_NUM < 160000
3087 new_rte->requiredPerms = 0;
3088#endif
3089
3090 outer->rtable = lappend(outer->rtable, new_rte);
3091 comp_outer_rtindex[k] = (Index) list_length(outer->rtable);
3092 {
3093 RangeTblRef *rtr = makeNode(RangeTblRef);
3094 rtr->rtindex = comp_outer_rtindex[k];
3095 outer->jointree->fromlist = lappend(outer->jointree->fromlist, rtr);
3096 }
3097 }
3098
3099 /* Remap Vars in the outer targetList and jointree.quals. */
3100 {
3102 tctx.atom_to_comp = atom_to_comp;
3103 tctx.atom_to_inner_attno = atom_to_inner_attno;
3104 tctx.comp_to_outer_rtindex = comp_outer_rtindex;
3105 tctx.bail = false;
3106 outer->targetList = (List *) safe_outer_te_remap_mutator(
3107 (Node *) outer->targetList, &tctx);
3108 if (outer->jointree->quals)
3109 outer->jointree->quals =
3110 safe_outer_te_remap_mutator(outer->jointree->quals, &tctx);
3111 if (tctx.bail) {
3112 if (provsql_verbose >= 30)
3113 provsql_notice("safe-query multi-component rewrite bailed: a Var "
3114 "has no exposed column in its component's inner "
3115 "sub-Query");
3116 pfree(inner_queries);
3117 pfree(inner_quals);
3118 return NULL;
3119 }
3120 }
3121
3122 if (provsql_verbose >= 30)
3123 provsql_notice("safe-query multi-component rewrite fired: split %d "
3124 "atoms into %d disconnected component%s",
3125 natoms, ncomp, ncomp == 1 ? "" : "s");
3126
3127 pfree(inner_queries);
3128 pfree(inner_quals);
3129 pfree(inner_tlists);
3130 pfree(comp_inner_idx);
3131 pfree(atom_to_inner_idx);
3132 pfree(atom_to_inner_attno);
3133 pfree(comp_outer_rtindex);
3134 return outer;
3135}
3136
3137/**
3138 * @brief Constant-selection elimination pre-pass.
3139 *
3140 * Implements Dalvi & Suciu 2007 §5.1's induced-FD construction
3141 * (@c ∅ @c → @c R.a from a @c R.a @c = @c c conjunct), specialised
3142 * to the safe-query rewriter's representation:
3143 *
3144 * - Build a Var-level union-find from the equijoin conjuncts in
3145 * @p *residual_in_out. Every pair of Vars that share an
3146 * equijoin (transitively, through the closure) lands in the same
3147 * equivalence class.
3148 * - Scan @p per_atom_quals[i] (atom-local conjuncts) and
3149 * @p *residual_in_out (cross-atom conjuncts) for @c Var @c = @c
3150 * Const matches. Mark the matched Var's class repr as constant-
3151 * pinned, recording one of the literals for propagation.
3152 * - For every Var in a constant-pinned class, synthesise the
3153 * corresponding @c Var @c = @c const conjunct on the Var's atom's
3154 * @p per_atom_quals list (when not already present, dedup'd by
3155 * @c (varno,varattno)). After this step every atom touching the
3156 * class carries the local filter, so the standard atom-local
3157 * pushdown path materialises it in the wrap.
3158 * - Drop top-level @c AND conjuncts of @p *residual_in_out whose
3159 * every base-level Var is in a constant-pinned class. These are
3160 * the equijoin conjuncts that brought constant atoms together
3161 * (e.g. @c R.x @c = @c S.x under @c S.x @c = @c 42); after
3162 * propagation each side carries its own @c Var @c = @c const
3163 * filter, so the original equijoin is redundant and would only
3164 * prevent the rewriter from resolving columns the constant-pinned
3165 * atoms' wraps no longer project.
3166 *
3167 * Effect on the rest of @c try_safe_query_rewrite: with cross-atom
3168 * equijoin links to constant-pinned atoms removed, those atoms
3169 * become their own connected components, and the existing
3170 * multi-component path in @c try_safe_query_rewrite handles them by
3171 * emitting a separate inner sub-Query per component. The recursive
3172 * @c process_query re-entry then collapses each constant-pinned
3173 * atom to a single aggregated @c gate_plus token, while the
3174 * remaining atoms keep the standard single-component hierarchical
3175 * shape. This is the read-once factoring constant-pinning
3176 * prescribes -- the pinned atom's contribution factors out as an
3177 * independent @c gate_times child of the result.
3178 */
3179static void apply_constant_selection_fd_pass(Query *q, List **per_atom_quals,
3180 Node **residual_in_out) {
3181 int natoms = list_length(q->rtable);
3182 safe_collect_vars_ctx vctx = { NIL };
3183 List *eq_pairs = NIL;
3184 Var **vars_arr;
3185 int *cls;
3186 int nvars;
3187 int i;
3188 ListCell *lc;
3189 bool *is_constant_class;
3190 Const **class_const_value;
3191 List *all_const_conjuncts = NIL;
3192
3193 if (natoms < 2)
3194 return;
3195
3196 /* Collect distinct base-level Vars from targetList, residual,
3197 * and every per-atom-quals list. All of these may carry the
3198 * Vars whose classes the equijoin closure will merge. */
3199 expression_tree_walker((Node *) q->targetList,
3201 if (*residual_in_out)
3202 expression_tree_walker(*residual_in_out,
3204 if (per_atom_quals != NULL) {
3205 int j;
3206 for (j = 0; j < natoms; j++) {
3207 ListCell *qlc;
3208 foreach (qlc, per_atom_quals[j])
3209 expression_tree_walker((Node *) lfirst(qlc),
3211 }
3212 }
3213 nvars = list_length(vctx.vars);
3214 if (nvars == 0)
3215 return;
3216
3217 vars_arr = palloc(nvars * sizeof(Var *));
3218 cls = palloc(nvars * sizeof(int));
3219 i = 0;
3220 foreach (lc, vctx.vars) {
3221 vars_arr[i] = (Var *) lfirst(lc);
3222 cls[i] = i;
3223 i++;
3224 }
3225
3226 /* Union-find on residual equijoin conjuncts. */
3227 if (*residual_in_out)
3228 safe_collect_equalities(*residual_in_out, &eq_pairs);
3229 for (lc = list_head(eq_pairs); lc != NULL; lc = my_lnext(eq_pairs, lc)) {
3230 Var *lv, *rv;
3231 int li, ri, ci, cj, k;
3232 lv = (Var *) lfirst(lc);
3233 lc = my_lnext(eq_pairs, lc);
3234 rv = (Var *) lfirst(lc);
3235 li = safe_var_index(vctx.vars, lv->varno, lv->varattno);
3236 ri = safe_var_index(vctx.vars, rv->varno, rv->varattno);
3237 if (li < 0 || ri < 0)
3238 continue;
3239 ci = cls[li];
3240 cj = cls[ri];
3241 if (ci == cj)
3242 continue;
3243 for (k = 0; k < nvars; k++)
3244 if (cls[k] == cj)
3245 cls[k] = ci;
3246 }
3247
3248 /* Scan per_atom + residual for @c Var @c = @c Const conjuncts;
3249 * mark the matched Var's class as constant-pinned. */
3250 is_constant_class = palloc0(nvars * sizeof(bool));
3251 class_const_value = palloc0(nvars * sizeof(Const *));
3252 if (per_atom_quals != NULL) {
3253 int j;
3254 for (j = 0; j < natoms; j++) {
3255 ListCell *qlc;
3256 foreach (qlc, per_atom_quals[j])
3257 all_const_conjuncts = lappend(all_const_conjuncts, lfirst(qlc));
3258 }
3259 }
3260 if (*residual_in_out)
3261 safe_flatten_and(*residual_in_out, &all_const_conjuncts);
3262
3263 {
3264 ListCell *qlc;
3265 foreach (qlc, all_const_conjuncts) {
3266 Expr *e = (Expr *) lfirst(qlc);
3267 Var *v;
3268 Const *k;
3269 int idx, root;
3270 if (!safe_is_var_const_equality(e, &v, &k))
3271 continue;
3272 idx = safe_var_index(vctx.vars, v->varno, v->varattno);
3273 if (idx < 0)
3274 continue;
3275 root = cls[idx];
3276 if (!is_constant_class[root]) {
3277 is_constant_class[root] = true;
3278 class_const_value[root] = k;
3279 }
3280 }
3281 }
3282 list_free(all_const_conjuncts);
3283
3284 /* Propagate: for every Var in a constant-pinned class, ensure
3285 * @c Var @c = @c const sits in the Var's atom's pushdown list. */
3286 if (per_atom_quals != NULL) {
3287 for (i = 0; i < nvars; i++) {
3288 int root = cls[i];
3289 Var *vp = vars_arr[i];
3290 Const *k = class_const_value[root];
3291 int atom_idx;
3292 bool already = false;
3293 ListCell *qlc;
3294 OpExpr *new_op;
3295 Oid eqop;
3296 Var *v_existing;
3297 Const *k_existing;
3298 if (!is_constant_class[root] || k == NULL)
3299 continue;
3300 if (vp->varno < 1 || (int) vp->varno > natoms)
3301 continue;
3302 atom_idx = (int) vp->varno - 1;
3303 foreach (qlc, per_atom_quals[atom_idx]) {
3304 if (safe_is_var_const_equality((Expr *) lfirst(qlc),
3305 &v_existing, &k_existing)
3306 && v_existing->varno == vp->varno
3307 && v_existing->varattno == vp->varattno) {
3308 already = true;
3309 break;
3310 }
3311 }
3312 if (already)
3313 continue;
3314 eqop = find_equality_operator(vp->vartype, k->consttype);
3315 if (eqop == InvalidOid)
3316 continue;
3317 new_op = (OpExpr *) makeNode(OpExpr);
3318 new_op->opno = eqop;
3319 new_op->opfuncid = InvalidOid;
3320 new_op->opresulttype = BOOLOID;
3321 new_op->opretset = false;
3322 new_op->opcollid = InvalidOid;
3323 new_op->inputcollid = vp->varcollid;
3324 new_op->args = list_make2(copyObject(vp), copyObject(k));
3325 new_op->location = -1;
3326 per_atom_quals[atom_idx] =
3327 lappend(per_atom_quals[atom_idx], new_op);
3328 }
3329 }
3330
3331 /* Drop residual conjuncts whose every Var is in a constant-pinned
3332 * class: those equijoins are now redundant (each side carries its
3333 * own propagated @c Var @c = @c const filter). Crucially, this
3334 * also disconnects the constant-pinned atoms from the rest of the
3335 * residual, so the multi-component dispatcher splits them off
3336 * into their own inner sub-Queries -- which @c process_query then
3337 * collapses to a single aggregated @c gate_plus token per atom,
3338 * factoring the pinned atom out as an independent @c gate_times
3339 * child of the top-level circuit. That factoring is what
3340 * preserves read-once across multiple-match rows on the rest of
3341 * the query; leaving the equijoin in place would make the
3342 * pinned atom appear as a regular atom in the outer cross
3343 * product, duplicating its provsql across the per-row
3344 * @c gate_times and breaking the read-once invariant. */
3345 if (*residual_in_out != NULL) {
3346 List *conjuncts = NIL;
3347 List *kept = NIL;
3348 ListCell *qlc;
3349 safe_flatten_and(*residual_in_out, &conjuncts);
3350 foreach (qlc, conjuncts) {
3351 Node *cj = (Node *) lfirst(qlc);
3352 safe_collect_vars_ctx cv = { NIL };
3353 ListCell *vlc;
3354 bool all_constant = true;
3355 bool any_var = false;
3356 expression_tree_walker(cj, safe_collect_vars_walker, &cv);
3357 foreach (vlc, cv.vars) {
3358 Var *v = (Var *) lfirst(vlc);
3359 int idx = safe_var_index(vctx.vars, v->varno, v->varattno);
3360 any_var = true;
3361 if (idx < 0 || !is_constant_class[cls[idx]]) {
3362 all_constant = false;
3363 break;
3364 }
3365 }
3366 list_free(cv.vars);
3367 if (any_var && all_constant)
3368 continue;
3369 kept = lappend(kept, cj);
3370 }
3371 if (kept == NIL)
3372 *residual_in_out = NULL;
3373 else if (list_length(kept) == 1)
3374 *residual_in_out = (Node *) linitial(kept);
3375 else
3376 *residual_in_out = (Node *) makeBoolExpr(AND_EXPR, kept, -1);
3377 list_free(conjuncts);
3378 }
3379
3380 pfree(is_constant_class);
3381 pfree(class_const_value);
3382 pfree(vars_arr);
3383 pfree(cls);
3384}
3385
3386/** @brief Mutator context for @c safe_unify_remap_mutator. */
3387typedef struct safe_unify_remap_ctx {
3388 int *old_to_new; ///< 1-indexed array: original rtindex -> compacted rtindex (after dropping non-keeper RTEs). Non-keepers map to their keeper's new index; keepers map to their own compacted index.
3389 int natoms; ///< Length of the original rtable (1-based domain of @c old_to_new).
3391
3392/**
3393 * @brief Tree mutator that renumbers @c Var.varno and
3394 * @c RangeTblRef.rtindex through the PK-unifiable self-join
3395 * map.
3396 *
3397 * Applied to every node of the unified @c Query : the @c targetList,
3398 * the @c jointree (which itself contains @c RangeTblRef leaves
3399 * referring to surviving RTEs as well as expression subtrees in the
3400 * @c quals), the @c havingQual when present, etc. Vars with
3401 * @c varlevelsup @c > @c 0 are outer references and left alone --
3402 * the candidate gate has already rejected sublinks, so they cannot
3403 * legitimately appear, but the guard keeps the mutator local.
3404 *
3405 * @c varnosyn / @c varattnosyn (PG 13+ "syntactic" parallel of the
3406 * semantic rtindex used by @c ruleutils.c's deparser) are kept in
3407 * sync; without that, @c pg_get_querydef on the unified query
3408 * stack-overflows because the syntactic dereference and the
3409 * semantic one disagree.
3410 */
3411static Node *safe_unify_remap_mutator(Node *node,
3412 safe_unify_remap_ctx *ctx) {
3413 if (node == NULL)
3414 return NULL;
3415 if (IsA(node, Var)) {
3416 Var *v = (Var *) node;
3417 if (v->varlevelsup == 0
3418 && v->varno >= 1 && (int) v->varno <= ctx->natoms) {
3419 int newno = ctx->old_to_new[v->varno];
3420 if (newno > 0 && newno != (int) v->varno) {
3421 Var *nv = (Var *) copyObject(v);
3422 nv->varno = (Index) newno;
3423#if PG_VERSION_NUM >= 130000
3424 nv->varnosyn = (Index) newno;
3425#endif
3426 return (Node *) nv;
3427 }
3428 }
3429 return node;
3430 }
3431 if (IsA(node, RangeTblRef)) {
3432 RangeTblRef *rtr = (RangeTblRef *) node;
3433 if (rtr->rtindex >= 1 && rtr->rtindex <= ctx->natoms) {
3434 int newno = ctx->old_to_new[rtr->rtindex];
3435 if (newno > 0 && newno != rtr->rtindex) {
3436 RangeTblRef *nr = (RangeTblRef *) copyObject(rtr);
3437 nr->rtindex = newno;
3438 return (Node *) nr;
3439 }
3440 }
3441 return node;
3442 }
3443 return expression_tree_mutator(node, safe_unify_remap_mutator, (void *) ctx);
3444}
3445
3446/**
3447 * @brief PK-unifiable self-join detection and unification.
3448 *
3449 * A query of shape @c R @c r1, @c R @c r2 @c WHERE @c r1.x @c =
3450 * @c r2.x with @c PRIMARY @c KEY @c (x) on @c R forces @c r1 and
3451 * @c r2 to refer to the same tuple. The two RTEs collapse to one
3452 * single-atom CQ; the safe-query candidate gate's "no two RTEs may
3453 * share a relid" bail becomes a missed-opportunity bail when the PK
3454 * proves the shared-relid pair is non-self-joining at the tuple
3455 * level.
3456 *
3457 * This pre-pass runs before @c is_safe_query_candidate. It returns
3458 * @c NULL when no unification fires; otherwise it returns a fresh
3459 * @c Query in which:
3460 *
3461 * - For every group of same-relid RTEs whose pairwise PK columns
3462 * are equated through the union-find closure of the residual
3463 * equijoins, all but one member (the lowest-rtindex survivor)
3464 * are dropped from @c rtable.
3465 * - @c jointree->fromlist's @c RangeTblRef entries are renumbered
3466 * or dropped to match. Multiple original entries pointing at
3467 * the same survivor are deduplicated.
3468 * - Every @c Var.varno (and parallel @c varnosyn) in @c targetList
3469 * and @c jointree->quals is rewritten to point at the survivor's
3470 * new (compacted) rtindex.
3471 *
3472 * Soundness traps:
3473 *
3474 * - Composite PK requires every column to be equated; the pairwise
3475 * check uses the union-find closure so transitive equijoins
3476 * (e.g. @c r1.x @c = @c r3.x @c AND @c r2.x @c = @c r3.x) suffice.
3477 * - Partial unification (3 RTEs of the same relid where only two
3478 * have their PK columns equated) bails the entire group: the
3479 * candidate gate would otherwise still refuse the surviving
3480 * duplicates. Full unification or full bail.
3481 * - NOT-NULL UNIQUE is FD-equivalent to PRIMARY KEY (the PK-FD pass
3482 * above treats them identically); the same key cache feeds this
3483 * pass, and the same NOT-NULL guard excludes nullable UNIQUEs.
3484 */
3485static Query *try_pk_self_join_unification(Query *q) {
3486 int natoms = list_length(q->rtable);
3487 bool found_dup;
3488 List *seen_relids;
3489 ListCell *lc;
3490 safe_collect_vars_ctx vctx = { NIL };
3491 List *eq_pairs = NIL;
3492 Var **vars_arr;
3493 int *cls;
3494 int nvars;
3495 int i, j;
3496 int *keeper;
3497 bool any_unified;
3498 Query *new_q;
3499 int *old_to_new;
3500 List *new_rtable;
3501 List *new_fromlist;
3502 int new_idx;
3503 bool *seen_new;
3505
3506 if (natoms < 2)
3507 return NULL;
3508
3509 /* Fast exit when there is no duplicate-relid pair to unify. */
3510 found_dup = false;
3511 seen_relids = NIL;
3512 foreach (lc, q->rtable) {
3513 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
3514 ListCell *lc2;
3515 if (rte->rtekind != RTE_RELATION)
3516 continue;
3517 foreach (lc2, seen_relids) {
3518 if (lfirst_oid(lc2) == rte->relid) {
3519 found_dup = true;
3520 break;
3521 }
3522 }
3523 if (found_dup)
3524 break;
3525 seen_relids = lappend_oid(seen_relids, rte->relid);
3526 }
3527 list_free(seen_relids);
3528 if (!found_dup)
3529 return NULL;
3530
3531 /* Build the union-find over base-level Vars referenced in
3532 * targetList and jointree->quals. We deliberately do not consult
3533 * @c per_atom_quals here because PK unification cares about
3534 * cross-RTE equijoins only -- a @c Var @c = @c Const conjunct is
3535 * atom-local and pins a single Var, but unification requires Vars
3536 * on two distinct RTEs to share a class. */
3537 expression_tree_walker((Node *) q->targetList,
3539 if (q->jointree && q->jointree->quals)
3540 expression_tree_walker(q->jointree->quals,
3542 nvars = list_length(vctx.vars);
3543 if (nvars == 0)
3544 return NULL;
3545
3546 vars_arr = palloc(nvars * sizeof(Var *));
3547 cls = palloc(nvars * sizeof(int));
3548 i = 0;
3549 foreach (lc, vctx.vars) {
3550 vars_arr[i] = (Var *) lfirst(lc);
3551 cls[i] = i;
3552 i++;
3553 }
3554 if (q->jointree && q->jointree->quals)
3555 safe_collect_equalities(q->jointree->quals, &eq_pairs);
3556 for (lc = list_head(eq_pairs); lc != NULL; lc = my_lnext(eq_pairs, lc)) {
3557 Var *lv, *rv;
3558 int li, ri, ci, cj, k;
3559 lv = (Var *) lfirst(lc);
3560 lc = my_lnext(eq_pairs, lc);
3561 rv = (Var *) lfirst(lc);
3562 li = safe_var_index(vctx.vars, lv->varno, lv->varattno);
3563 ri = safe_var_index(vctx.vars, rv->varno, rv->varattno);
3564 if (li < 0 || ri < 0)
3565 continue;
3566 ci = cls[li];
3567 cj = cls[ri];
3568 if (ci == cj)
3569 continue;
3570 for (k = 0; k < nvars; k++)
3571 if (cls[k] == cj)
3572 cls[k] = ci;
3573 }
3574
3575 /* Group same-relid RTEs and check pairwise PK-unifiability inside
3576 * every group. @c keeper[j] starts as @c j (self-keeper) and gets
3577 * pointed at the group's lowest-rtindex survivor when the group
3578 * fully unifies. */
3579 keeper = palloc(natoms * sizeof(int));
3580 for (j = 0; j < natoms; j++)
3581 keeper[j] = j;
3582
3583 any_unified = false;
3584 for (j = 0; j < natoms; j++) {
3585 RangeTblEntry *rte_j;
3587 List *group;
3588 bool all_pairs_unify;
3589 int k;
3590 ListCell *lc_a, *lc_b;
3591 if (keeper[j] != j)
3592 continue;
3593 rte_j = (RangeTblEntry *) list_nth(q->rtable, j);
3594 if (rte_j->rtekind != RTE_RELATION)
3595 continue;
3596 if (!provsql_lookup_relation_keys(rte_j->relid, &keys))
3597 continue;
3598
3599 group = list_make1_int(j);
3600 for (k = j + 1; k < natoms; k++) {
3601 RangeTblEntry *rte_k = (RangeTblEntry *) list_nth(q->rtable, k);
3602 if (rte_k->rtekind != RTE_RELATION)
3603 continue;
3604 if (rte_k->relid != rte_j->relid)
3605 continue;
3606 group = lappend_int(group, k);
3607 }
3608 if (list_length(group) < 2) {
3609 list_free(group);
3610 continue;
3611 }
3612
3613 /* Pairwise check: every pair in the group must share at least
3614 * one key whose every column lies in the same union-find class
3615 * across the two RTEs. Any pair that misses bails the entire
3616 * group (partial unification is a deliberate non-goal: full
3617 * unification or full bail keeps the soundness argument
3618 * single-pair). */
3619 all_pairs_unify = true;
3620 foreach (lc_a, group) {
3621 int aa = lfirst_int(lc_a);
3622 foreach (lc_b, group) {
3623 int bb = lfirst_int(lc_b);
3624 bool this_pair_unifies = false;
3625 uint16 ki;
3626 if (bb <= aa)
3627 continue;
3628 for (ki = 0; ki < keys.key_n; ki++) {
3629 const ProvenanceRelationKey *key = &keys.keys[ki];
3630 bool all_pk_equated = true;
3631 uint16 kc;
3632 for (kc = 0; kc < key->col_n; kc++) {
3633 AttrNumber attno = key->cols[kc];
3634 int idx_a =
3635 safe_var_index(vctx.vars, (Index) (aa + 1), attno);
3636 int idx_b =
3637 safe_var_index(vctx.vars, (Index) (bb + 1), attno);
3638 if (idx_a < 0 || idx_b < 0 || cls[idx_a] != cls[idx_b]) {
3639 all_pk_equated = false;
3640 break;
3641 }
3642 }
3643 if (all_pk_equated) {
3644 this_pair_unifies = true;
3645 break;
3646 }
3647 }
3648 if (!this_pair_unifies) {
3649 all_pairs_unify = false;
3650 break;
3651 }
3652 }
3653 if (!all_pairs_unify)
3654 break;
3655 }
3656
3657 if (all_pairs_unify) {
3658 foreach (lc_a, group) {
3659 int aa = lfirst_int(lc_a);
3660 if (aa != j) {
3661 keeper[aa] = j;
3662 any_unified = true;
3663 }
3664 }
3665 }
3666 list_free(group);
3667 }
3668
3669 if (!any_unified) {
3670 pfree(keeper);
3671 pfree(vars_arr);
3672 pfree(cls);
3673 return NULL;
3674 }
3675
3676 /* Build the @c old_to_new map. Keepers get consecutive new
3677 * (1-based) indexes; non-keepers reuse their keeper's new index.
3678 * Resolution walks @c keeper transitively in case a chain emerged
3679 * during the merge loop above. */
3680 old_to_new = palloc0((natoms + 1) * sizeof(int));
3681 new_idx = 1;
3682 for (j = 0; j < natoms; j++) {
3683 int root = j;
3684 while (keeper[root] != root)
3685 root = keeper[root];
3686 if (root == j)
3687 old_to_new[j + 1] = new_idx++;
3688 }
3689 for (j = 0; j < natoms; j++) {
3690 int root = j;
3691 while (keeper[root] != root)
3692 root = keeper[root];
3693 if (root != j)
3694 old_to_new[j + 1] = old_to_new[root + 1];
3695 }
3696
3697 /* Build the compacted rtable: surviving entries in original order. */
3698 new_rtable = NIL;
3699 for (j = 0; j < natoms; j++) {
3700 int root = j;
3701 while (keeper[root] != root)
3702 root = keeper[root];
3703 if (root == j) {
3704 RangeTblEntry *rte = (RangeTblEntry *) list_nth(q->rtable, j);
3705 new_rtable = lappend(new_rtable, copyObject(rte));
3706 }
3707 }
3708
3709 /* Build the compacted fromlist: walk the original fromlist, drop
3710 * @c RangeTblRef entries pointing at non-keepers, renumber the
3711 * rest, and skip duplicates that arose from co-keepers (every
3712 * non-RangeTblRef entry passes through unchanged -- the candidate
3713 * gate has already rejected those shapes, but the mutator keeps
3714 * its precondition local). */
3715 new_fromlist = NIL;
3716 seen_new = palloc0((new_idx + 1) * sizeof(bool));
3717 if (q->jointree) {
3718 foreach (lc, q->jointree->fromlist) {
3719 Node *n = (Node *) lfirst(lc);
3720 if (IsA(n, RangeTblRef)) {
3721 RangeTblRef *rtr = (RangeTblRef *) n;
3722 int new_no = old_to_new[rtr->rtindex];
3723 RangeTblRef *clone;
3724 if (new_no <= 0)
3725 continue;
3726 if (seen_new[new_no])
3727 continue;
3728 seen_new[new_no] = true;
3729 clone = (RangeTblRef *) copyObject(rtr);
3730 clone->rtindex = new_no;
3731 new_fromlist = lappend(new_fromlist, clone);
3732 } else {
3733 new_fromlist = lappend(new_fromlist, copyObject(n));
3734 }
3735 }
3736 }
3737 pfree(seen_new);
3738
3739 /* Assemble the new Query. @c copyObject the input first so the
3740 * planner's original @c Query is left untouched (a downstream
3741 * bail must leave the input pristine); the mutator then rewrites
3742 * Vars / @c RangeTblRefs in place on the copy. */
3743 new_q = (Query *) copyObject(q);
3744 new_q->rtable = new_rtable;
3745 if (new_q->jointree)
3746 new_q->jointree->fromlist = new_fromlist;
3747#if PG_VERSION_NUM >= 160000
3748 /* @c rteperminfos is left intact; surviving RTEs' @c perminfoindex
3749 * still points at the matching record in the original list, and
3750 * orphan records are harmless (PG enforces no 1-to-1 invariant). */
3751#endif
3752
3753 rctx.old_to_new = old_to_new;
3754 rctx.natoms = natoms;
3755 new_q->targetList = (List *)
3756 safe_unify_remap_mutator((Node *) new_q->targetList, &rctx);
3757 if (new_q->jointree && new_q->jointree->quals)
3758 new_q->jointree->quals =
3759 safe_unify_remap_mutator(new_q->jointree->quals, &rctx);
3760
3761 pfree(old_to_new);
3762 pfree(keeper);
3763 pfree(vars_arr);
3764 pfree(cls);
3765
3766 return new_q;
3767}
3768
3769/**
3770 * @brief Disjoint-constant self-join certification.
3771 *
3772 * When two (or more) RTEs over the same relation each carry a
3773 * @c Var @c = @c Const conjunct on the same column with
3774 * provably-different literals, their tuple-sets are disjoint: a
3775 * single base-relation row can satisfy at most one of the constant
3776 * predicates, so the @c provsql tokens never overlap across the
3777 * RTEs. The shared-relid bail in @c is_safe_query_candidate then
3778 * becomes too conservative -- the standard per-atom @c SELECT
3779 * @c DISTINCT wrap on each RTE (with its constant predicate
3780 * pushed in) factors the relation into disjoint virtual partitions,
3781 * each acting as an independent atom.
3782 *
3783 * This pre-pass runs after @c try_pk_self_join_unification and
3784 * before @c is_safe_query_candidate. For each same-relid
3785 * group of >1 RTE remaining in @c q->rtable, it checks pairwise
3786 * whether every pair has @c Var @c = @c Const conjuncts on the
3787 * same @c varattno with @em provably distinct literal values. When
3788 * the entire group satisfies the check, the relid is added to the
3789 * returned @c Bitmapset; the candidate gate consults that set and
3790 * skips the shared-relid bail for those relids.
3791 *
3792 * "Provably distinct" uses @c datumIsEqual on the @c Const values
3793 * after matching @c consttype: two literals of the same type with
3794 * different @c constvalue are guaranteed different at executor
3795 * time. Conservative: when types disagree or when @c datumIsEqual
3796 * cannot decide (TOAST'ed varlena where the stored representation
3797 * differs from the logical value), the pair is treated as NOT
3798 * provably-disjoint -- the certification simply doesn't fire on
3799 * that group, and the candidate gate's existing shared-relid bail
3800 * refuses the query as before.
3801 *
3802 * Soundness traps:
3803 *
3804 * - Disjointness on the @em same column (@c varattno match). A
3805 * pair like @c r1.kind @c = @c 'A' @c AND @c r2.color @c = @c
3806 * 'B' is NOT disjoint -- an R-tuple with @c kind @c = @c 'A'
3807 * @em and @c color @c = @c 'B' satisfies both.
3808 * - Pairwise across every pair: a 3-RTE group with two disjoint
3809 * pairs but one non-disjoint pair stays @em not certified;
3810 * partial certification would mean the candidate gate still
3811 * finds two RTEs of the same relid that are NOT provably
3812 * disjoint, and the rewrite would be unsound on the rows where
3813 * both predicates can match.
3814 * - Equality-to-literal only: inequalities (@c r.kind @c <> @c
3815 * 'A') do not pin a column to a single value and do not
3816 * contribute to provable disjointness. @c safe_is_var_const_equality
3817 * enforces this through the operator-OID check.
3818 * - Transitive disjointness via FDs (e.g. @c kind @c → @c
3819 * category, with @c r1.category @c = @c 'X' / @c r2.category
3820 * @c = @c 'Y') is deferred to the general FD closure follow-up.
3821 */
3822static Bitmapset *
3824 int natoms = list_length(q->rtable);
3825 Bitmapset *approved = NULL;
3826 bool *processed;
3827 List **rte_const_quals;
3828 int j;
3829
3830 if (natoms < 2)
3831 return NULL;
3832
3833 /* Fast exit when no duplicate-relid pair appears. Same
3834 * structural check as @c try_pk_self_join_unification's gate;
3835 * keeps the certification path off the hot path entirely for the
3836 * common self-join-free case. */
3837 {
3838 bool found_dup = false;
3839 List *seen = NIL;
3840 ListCell *lc;
3841 foreach (lc, q->rtable) {
3842 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
3843 ListCell *lc2;
3844 if (rte->rtekind != RTE_RELATION)
3845 continue;
3846 foreach (lc2, seen) {
3847 if (lfirst_oid(lc2) == rte->relid) {
3848 found_dup = true;
3849 break;
3850 }
3851 }
3852 if (found_dup)
3853 break;
3854 seen = lappend_oid(seen, rte->relid);
3855 }
3856 list_free(seen);
3857 if (!found_dup)
3858 return NULL;
3859 }
3860
3861 /* Per-RTE list of @c Var @c = @c Const conjuncts pulled out of
3862 * @c q->jointree->quals. Single-atom conjuncts on RTE @c j land
3863 * in @c rte_const_quals[j]. Cross-atom conjuncts (equijoins,
3864 * mixed-varno predicates) are ignored -- they don't contribute
3865 * disjoint-constant evidence. */
3866 processed = palloc0(natoms * sizeof(bool));
3867 rte_const_quals = palloc0(natoms * sizeof(List *));
3868 if (q->jointree && q->jointree->quals) {
3869 List *conjuncts = NIL;
3870 ListCell *lc;
3871 safe_flatten_and(q->jointree->quals, &conjuncts);
3872 foreach (lc, conjuncts) {
3873 Expr *e = (Expr *) lfirst(lc);
3874 Var *v;
3875 Const *k;
3876 if (!safe_is_var_const_equality(e, &v, &k))
3877 continue;
3878 if (v->varno < 1 || (int) v->varno > natoms)
3879 continue;
3880 rte_const_quals[v->varno - 1] =
3881 lappend(rte_const_quals[v->varno - 1], (void *) e);
3882 }
3883 list_free(conjuncts);
3884 }
3885
3886 /* Walk RTEs; for each unprocessed RTE @c j, gather every
3887 * unprocessed same-relid sibling @c k @c > @c j, then verify
3888 * pairwise disjointness across the group. */
3889 for (j = 0; j < natoms; j++) {
3890 RangeTblEntry *rte_j;
3891 List *group;
3892 int k;
3893 bool all_pairs_disjoint;
3894 ListCell *lc_a, *lc_b;
3895
3896 if (processed[j])
3897 continue;
3898 rte_j = (RangeTblEntry *) list_nth(q->rtable, j);
3899 if (rte_j->rtekind != RTE_RELATION)
3900 continue;
3901
3902 group = list_make1_int(j);
3903 for (k = j + 1; k < natoms; k++) {
3904 RangeTblEntry *rte_k = (RangeTblEntry *) list_nth(q->rtable, k);
3905 if (processed[k])
3906 continue;
3907 if (rte_k->rtekind != RTE_RELATION)
3908 continue;
3909 if (rte_k->relid != rte_j->relid)
3910 continue;
3911 group = lappend_int(group, k);
3912 }
3913 if (list_length(group) < 2) {
3914 list_free(group);
3915 continue;
3916 }
3917
3918 /* Pairwise check. A pair (@c aa, @c bb) is disjoint when there
3919 * exists @em some column @c c such that @c aa carries
3920 * @c r.c @c = @c k_a and @c bb carries @c r.c @c = @c k_b with
3921 * @c k_a @c ≠ @c k_b (same @c consttype, distinct
3922 * @c constvalue). */
3923 all_pairs_disjoint = true;
3924 foreach (lc_a, group) {
3925 int aa = lfirst_int(lc_a);
3926 foreach (lc_b, group) {
3927 int bb = lfirst_int(lc_b);
3928 bool this_pair_disjoint = false;
3929 ListCell *lc_qa;
3930 if (bb <= aa)
3931 continue;
3932 foreach (lc_qa, rte_const_quals[aa]) {
3933 Expr *e_a = (Expr *) lfirst(lc_qa);
3934 Var *v_a;
3935 Const *k_a;
3936 ListCell *lc_qb;
3937 if (!safe_is_var_const_equality(e_a, &v_a, &k_a))
3938 continue;
3939 foreach (lc_qb, rte_const_quals[bb]) {
3940 Expr *e_b = (Expr *) lfirst(lc_qb);
3941 Var *v_b;
3942 Const *k_b;
3943 if (!safe_is_var_const_equality(e_b, &v_b, &k_b))
3944 continue;
3945 if (v_a->varattno != v_b->varattno)
3946 continue;
3947 if (k_a->consttype != k_b->consttype)
3948 continue;
3949 if (k_a->constisnull || k_b->constisnull)
3950 continue;
3951 if (!datumIsEqual(k_a->constvalue, k_b->constvalue,
3952 k_a->constbyval, k_a->constlen)) {
3953 this_pair_disjoint = true;
3954 break;
3955 }
3956 }
3957 if (this_pair_disjoint)
3958 break;
3959 }
3960 if (!this_pair_disjoint) {
3961 all_pairs_disjoint = false;
3962 break;
3963 }
3964 }
3965 if (!all_pairs_disjoint)
3966 break;
3967 }
3968
3969 if (all_pairs_disjoint) {
3970 approved = bms_add_member(approved, (int) rte_j->relid);
3971 foreach (lc_a, group)
3972 processed[lfirst_int(lc_a)] = true;
3973 }
3974 list_free(group);
3975 }
3976
3977 pfree(processed);
3978 for (j = 0; j < natoms; j++)
3979 if (rte_const_quals[j])
3980 list_free(rte_const_quals[j]);
3981 pfree(rte_const_quals);
3982
3983 return approved;
3984}
3985
3986/* -------------------------------------------------------------------------
3987 * Subquery inlining pre-pass.
3988 *
3989 * Pull simple @c RTE_SUBQUERY fromlist entries (typically view bodies
3990 * after PG's parser-time rewriting, but also inline @c FROM @c (SELECT
3991 * ...) subqueries) up into the outer query so the detector and
3992 * rewriter see a single rtable of base @c RTE_RELATION entries. Runs
3993 * before @c try_pk_self_join_unification and the candidate gate.
3994 *
3995 * A subquery is "simple" -- safe to inline without changing observable
3996 * semantics -- when it is a flat conjunctive @c SELECT (no @c DISTINCT,
3997 * @c GROUP @c BY, @c HAVING, aggregates, window functions, set
3998 * operations, sublinks, CTEs, @c ORDER @c BY, @c LIMIT / @c OFFSET,
3999 * SRFs in the target list), its fromlist is plain @c RangeTblRef
4000 * entries, every member RTE is either @c RTE_RELATION or another
4001 * inlineable @c RTE_SUBQUERY, no member RTE is @c LATERAL or a security
4002 * barrier, and every non-@c resjunk target-list entry reduces to a
4003 * base-level @c Var (possibly through @c RelabelType wrappers carrying
4004 * binary-coercion casts).
4005 *
4006 * The fixed-point loop iterates one fromlist entry at a time: each
4007 * inlining step strictly removes one @c RTE_SUBQUERY reference from
4008 * the fromlist, and the body's freshly-promoted entries become
4009 * candidates for the next iteration -- so termination is bounded by
4010 * the input @c Query's syntactic nesting depth.
4011 *
4012 * The candidate gate's "no two RTEs may share a relid" check, run
4013 * after this pre-pass, enforces the disjoint-base-ancestor property
4014 * the propagation design needs: two fromlist entries that ultimately
4015 * read the same base relation (a view + base-table mix, or two views
4016 * sharing an underlying table) inline to duplicate relids and trip
4017 * the shared-relid bail (modulo the PK / disjoint-constant self-join
4018 * rescues already in place).
4019 * ------------------------------------------------------------------------- */
4020
4021/** @brief Walker context for @c safe_inline_shift_mutator. */
4023 int offset; ///< Added to every base-level @c Var.varno and @c RangeTblRef.rtindex
4025
4026/**
4027 * @brief Add @c offset to the @c varno of every base-level (@c
4028 * varlevelsup @c == @c 0) @c Var and the @c rtindex of every
4029 * @c RangeTblRef in @p node. Used when relocating a
4030 * subquery's rtable entries into the tail of the outer
4031 * query's rtable.
4032 *
4033 * Outer @c Vars (@c varlevelsup @c > @c 0) and outer
4034 * @c RangeTblRefs cannot legitimately appear in an inlineable
4035 * subquery -- the inlineable predicate refuses LATERAL RTEs and
4036 * sublinks -- but we leave them alone defensively.
4037 */
4038static Node *safe_inline_shift_mutator(Node *node,
4039 safe_inline_shift_ctx *ctx) {
4040 if (node == NULL)
4041 return NULL;
4042 if (IsA(node, Var)) {
4043 Var *v = (Var *) node;
4044 if (v->varlevelsup == 0) {
4045 Var *nv = (Var *) copyObject(v);
4046 nv->varno = (Index) ((int) v->varno + ctx->offset);
4047#if PG_VERSION_NUM >= 130000
4048 if (nv->varnosyn == v->varno)
4049 nv->varnosyn = nv->varno;
4050#endif
4051 return (Node *) nv;
4052 }
4053 return node;
4054 }
4055 if (IsA(node, RangeTblRef)) {
4056 RangeTblRef *rtr = (RangeTblRef *) node;
4057 RangeTblRef *nr = (RangeTblRef *) copyObject(rtr);
4058 nr->rtindex = rtr->rtindex + ctx->offset;
4059 return (Node *) nr;
4060 }
4061 return expression_tree_mutator(node, safe_inline_shift_mutator,
4062 (void *) ctx);
4063}
4064
4065/** @brief Walker context for @c safe_inline_subst_mutator. */
4067 Index target_rtindex; ///< rtindex of the inlined subquery in the outer rtable
4068 List *target_list; ///< Inlined subquery's @c targetList
4069 int outer_offset; ///< Shift applied to Vars inside substituted TLE expressions
4071
4072/**
4073 * @brief Replace every outer-scope @c Var pointing at the inlined
4074 * subquery RTE with a shifted copy of the matching target-list
4075 * entry's expression.
4076 *
4077 * The substituted expression is @c copyObject'd before its base-level
4078 * @c Vars / @c RangeTblRefs are renumbered by @c outer_offset, so the
4079 * inlined subquery's @c targetList is left intact for any other
4080 * outer-scope @c Var still referencing it.
4081 */
4082static Node *safe_inline_subst_mutator(Node *node,
4083 safe_inline_subst_ctx *ctx) {
4084 if (node == NULL)
4085 return NULL;
4086 if (IsA(node, Var)) {
4087 Var *v = (Var *) node;
4088 if (v->varlevelsup == 0 && v->varno == ctx->target_rtindex) {
4089 TargetEntry *te;
4090 Node *subst;
4092 if (v->varattno < 1
4093 || v->varattno > list_length(ctx->target_list))
4094 return node; /* defensive: TLE missing for this attno */
4095 te = (TargetEntry *) list_nth(ctx->target_list,
4096 v->varattno - 1);
4097 subst = (Node *) copyObject(te->expr);
4098 sctx.offset = ctx->outer_offset;
4099 return safe_inline_shift_mutator(subst, &sctx);
4100 }
4101 return node;
4102 }
4103 return expression_tree_mutator(node, safe_inline_subst_mutator,
4104 (void *) ctx);
4105}
4106
4107/**
4108 * @brief Decide whether @p sub may be inlined. See the chapter
4109 * comment above for the predicate; the recursion through
4110 * nested @c RTE_SUBQUERY entries is bounded by the input
4111 * query's syntactic nesting depth.
4112 */
4113static bool is_inlineable_subquery(Query *sub) {
4114 ListCell *lc;
4115 if (sub == NULL || sub->commandType != CMD_SELECT)
4116 return false;
4117 if (sub->setOperations != NULL
4118 || sub->hasSubLinks
4119 || sub->hasAggs
4120 || sub->hasWindowFuncs
4121 || sub->hasTargetSRFs
4122 || sub->hasModifyingCTE
4123 || sub->hasDistinctOn
4124 || sub->cteList != NIL
4125 || sub->distinctClause != NIL
4126 || sub->groupClause != NIL
4127 || sub->groupingSets != NIL
4128 || sub->havingQual != NULL
4129 || sub->sortClause != NIL
4130 || sub->limitCount != NULL
4131 || sub->limitOffset != NULL)
4132 return false;
4133 if (sub->jointree == NULL || sub->jointree->fromlist == NIL)
4134 return false;
4135 foreach (lc, sub->jointree->fromlist) {
4136 Node *n = (Node *) lfirst(lc);
4137 if (!IsA(n, RangeTblRef))
4138 return false;
4139 }
4140 foreach (lc, sub->rtable) {
4141 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4142 if (rte->lateral)
4143 return false;
4144 if (rte->securityQuals != NIL)
4145 return false;
4146 if (rte->rtekind == RTE_RELATION)
4147 continue;
4148 if (rte->rtekind == RTE_SUBQUERY) {
4149 if (rte->security_barrier)
4150 return false;
4151 if (!is_inlineable_subquery(rte->subquery))
4152 return false;
4153 continue;
4154 }
4155 return false;
4156 }
4157 /* Target list entries must reduce to a base-level @c Var
4158 * (possibly through @c RelabelType wrappers for binary-coercion
4159 * casts). This keeps the substitution semantics a simple
4160 * Var-for-Var swap, avoids expanding the outer query with
4161 * function-call or set-returning expressions, and rules out the
4162 * outer-scope reference cases (RowExpr, sublink-bearing
4163 * expressions, correlated TLEs). resjunk entries are skipped --
4164 * they are never referenced from the outer query. */
4165 foreach (lc, sub->targetList) {
4166 TargetEntry *te = (TargetEntry *) lfirst(lc);
4167 Node *e = (Node *) te->expr;
4168 if (te->resjunk)
4169 continue;
4170 while (e != NULL && IsA(e, RelabelType))
4171 e = (Node *) ((RelabelType *) e)->arg;
4172 if (e == NULL || !IsA(e, Var))
4173 return false;
4174 if (((Var *) e)->varlevelsup != 0)
4175 return false;
4176 }
4177 return true;
4178}
4179
4180/**
4181 * @brief Inline the subquery RTE at @p target_rti into @p q in place.
4182 * Caller is responsible for compaction (the orphan RTE is left
4183 * in @c q->rtable so other still-pending rtindex references
4184 * don't shift mid-pass).
4185 */
4186static void inline_one_subquery(Query *q, int target_rti) {
4187 RangeTblEntry *target_rte = (RangeTblEntry *)
4188 list_nth(q->rtable, target_rti - 1);
4189 Query *sub = target_rte->subquery;
4190 int outer_offset = list_length(q->rtable);
4191 ListCell *lc;
4192 List *sub_rtable_copies = NIL;
4193 Node *sub_quals_shifted = NULL;
4194 List *sub_fromlist_shifted = NIL;
4195 List *new_fromlist = NIL;
4198#if PG_VERSION_NUM >= 160000
4199 int outer_perminfo_count = list_length(q->rteperminfos);
4200#endif
4201
4202 foreach (lc, sub->rtable)
4203 sub_rtable_copies =
4204 lappend(sub_rtable_copies, copyObject(lfirst(lc)));
4205
4206#if PG_VERSION_NUM >= 160000
4207 /* Migrate the subquery's RTEPermissionInfo records into the outer
4208 * query so the planner finds them under @c q->rteperminfos once
4209 * the cloned RTEs land in @c q->rtable. perminfoindex on each
4210 * cloned RTE is shifted by the outer's old perminfos count. */
4211 foreach (lc, sub->rteperminfos)
4212 q->rteperminfos =
4213 lappend(q->rteperminfos, copyObject(lfirst(lc)));
4214 foreach (lc, sub_rtable_copies) {
4215 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4216 if (rte->perminfoindex != 0)
4217 rte->perminfoindex = (Index)
4218 ((int) rte->perminfoindex + outer_perminfo_count);
4219 }
4220#endif
4221
4222 q->rtable = list_concat(q->rtable, sub_rtable_copies);
4223
4224 sctx.offset = outer_offset;
4225 if (sub->jointree && sub->jointree->quals != NULL)
4226 sub_quals_shifted = safe_inline_shift_mutator(
4227 (Node *) copyObject(sub->jointree->quals), &sctx);
4228 if (sub->jointree)
4229 sub_fromlist_shifted = (List *)
4231 (Node *) copyObject(sub->jointree->fromlist), &sctx);
4232
4233 /* Splice the (shifted) subquery fromlist into the outer fromlist
4234 * in place of the @c RangeTblRef pointing at @p target_rti.
4235 * Other entries pass through. */
4236 foreach (lc, q->jointree->fromlist) {
4237 Node *n = (Node *) lfirst(lc);
4238 if (IsA(n, RangeTblRef)
4239 && ((RangeTblRef *) n)->rtindex == target_rti)
4240 new_fromlist = list_concat(new_fromlist, sub_fromlist_shifted);
4241 else
4242 new_fromlist = lappend(new_fromlist, n);
4243 }
4244 q->jointree->fromlist = new_fromlist;
4245
4246 if (sub_quals_shifted != NULL) {
4247 if (q->jointree->quals == NULL)
4248 q->jointree->quals = sub_quals_shifted;
4249 else
4250 q->jointree->quals = (Node *) makeBoolExpr(
4251 AND_EXPR,
4252 list_make2(q->jointree->quals, sub_quals_shifted),
4253 -1);
4254 }
4255
4256 ictx.target_rtindex = (Index) target_rti;
4257 ictx.target_list = sub->targetList;
4258 ictx.outer_offset = outer_offset;
4259 q->targetList = (List *)
4260 safe_inline_subst_mutator((Node *) q->targetList, &ictx);
4261 q->returningList = (List *)
4262 safe_inline_subst_mutator((Node *) q->returningList, &ictx);
4263 if (q->jointree)
4264 q->jointree->quals =
4265 safe_inline_subst_mutator(q->jointree->quals, &ictx);
4266 q->limitOffset = safe_inline_subst_mutator(q->limitOffset, &ictx);
4267 q->limitCount = safe_inline_subst_mutator(q->limitCount, &ictx);
4268}
4269
4270/** @brief Walker context for @c safe_inline_compact_mutator. */
4273 int *old_to_new; ///< 1-based map; 0 marks dropped (orphan) slots
4275
4276/**
4277 * @brief Renumber Vars / RangeTblRefs via @c old_to_new. Slots
4278 * mapped to 0 (the inlined-orphan rtindexes) would signal a
4279 * live reference into a dropped RTE -- defensively, the
4280 * node passes through unchanged so the downstream candidate
4281 * gate notices and refuses the query.
4282 */
4283static Node *safe_inline_compact_mutator(Node *node,
4285 if (node == NULL)
4286 return NULL;
4287 if (IsA(node, Var)) {
4288 Var *v = (Var *) node;
4289 if (v->varlevelsup == 0
4290 && (int) v->varno >= 1
4291 && (int) v->varno <= ctx->old_size) {
4292 int newno = ctx->old_to_new[v->varno];
4293 if (newno > 0 && newno != (int) v->varno) {
4294 Var *nv = (Var *) copyObject(v);
4295 nv->varno = (Index) newno;
4296#if PG_VERSION_NUM >= 130000
4297 if (nv->varnosyn == v->varno)
4298 nv->varnosyn = (Index) newno;
4299#endif
4300 return (Node *) nv;
4301 }
4302 }
4303 return node;
4304 }
4305 if (IsA(node, RangeTblRef)) {
4306 RangeTblRef *rtr = (RangeTblRef *) node;
4307 if (rtr->rtindex >= 1 && rtr->rtindex <= ctx->old_size) {
4308 int newno = ctx->old_to_new[rtr->rtindex];
4309 if (newno > 0 && newno != rtr->rtindex) {
4310 RangeTblRef *nr = (RangeTblRef *) copyObject(rtr);
4311 nr->rtindex = newno;
4312 return (Node *) nr;
4313 }
4314 }
4315 return node;
4316 }
4317 return expression_tree_mutator(node, safe_inline_compact_mutator,
4318 (void *) ctx);
4319}
4320
4321/**
4322 * @brief Drop orphan RTEs (the inlined subquery slots) from
4323 * @c q->rtable and renumber every surviving Var / RangeTblRef.
4324 */
4325static void compact_orphan_rtes(Query *q, Bitmapset *orphans) {
4326 int old_size = list_length(q->rtable);
4327 int *old_to_new;
4328 int next = 1;
4329 int i;
4330 List *new_rtable = NIL;
4331 ListCell *lc;
4333
4334 old_to_new = palloc0((old_size + 1) * sizeof(int));
4335 for (i = 1; i <= old_size; i++) {
4336 if (bms_is_member(i, orphans))
4337 old_to_new[i] = 0;
4338 else
4339 old_to_new[i] = next++;
4340 }
4341 i = 1;
4342 foreach (lc, q->rtable) {
4343 if (old_to_new[i] != 0)
4344 new_rtable = lappend(new_rtable, lfirst(lc));
4345 i++;
4346 }
4347 q->rtable = new_rtable;
4348
4349 ctx.old_size = old_size;
4350 ctx.old_to_new = old_to_new;
4351 q->targetList = (List *)
4352 safe_inline_compact_mutator((Node *) q->targetList, &ctx);
4353 q->returningList = (List *)
4354 safe_inline_compact_mutator((Node *) q->returningList, &ctx);
4355 if (q->jointree) {
4356 q->jointree->fromlist = (List *)
4358 (Node *) q->jointree->fromlist, &ctx);
4359 q->jointree->quals =
4360 safe_inline_compact_mutator(q->jointree->quals, &ctx);
4361 }
4362 q->limitOffset = safe_inline_compact_mutator(q->limitOffset, &ctx);
4363 q->limitCount = safe_inline_compact_mutator(q->limitCount, &ctx);
4364
4365 pfree(old_to_new);
4366}
4367
4368/**
4369 * @brief Subquery-inlining pre-pass. See the chapter comment.
4370 * Returns @c NULL when nothing inlined (caller keeps the
4371 * original @p q); else a fresh @c Query with the inlining
4372 * and compaction baked in.
4373 */
4374static Query *try_inline_simple_subqueries(Query *q) {
4375 Query *new_q;
4376 Bitmapset *orphans = NULL;
4377 bool any_inlined = false;
4378 ListCell *lc;
4379 bool found_subq = false;
4380
4381 if (q->jointree == NULL || q->jointree->fromlist == NIL)
4382 return NULL;
4383
4384 /* Fast exit: no fromlist subquery means nothing to do. Non-
4385 * @c RangeTblRef fromlist entries (raw @c JoinExpr / @c FromExpr)
4386 * are passed through so the candidate gate's existing rejector
4387 * sees them as before. */
4388 foreach (lc, q->jointree->fromlist) {
4389 Node *n = (Node *) lfirst(lc);
4390 RangeTblRef *rtr;
4391 RangeTblEntry *rte;
4392 if (!IsA(n, RangeTblRef))
4393 continue;
4394 rtr = (RangeTblRef *) n;
4395 if (rtr->rtindex < 1 || rtr->rtindex > list_length(q->rtable))
4396 continue;
4397 rte = (RangeTblEntry *) list_nth(q->rtable, rtr->rtindex - 1);
4398 if (rte->rtekind == RTE_SUBQUERY) {
4399 found_subq = true;
4400 break;
4401 }
4402 }
4403 if (!found_subq)
4404 return NULL;
4405
4406 new_q = (Query *) copyObject(q);
4407
4408 /* Fixed-point loop: each iteration inlines one fromlist subquery
4409 * (if any remain inlineable); the inlined body's promoted entries
4410 * become candidates for the next iteration. Bounded by the input
4411 * query's syntactic nesting depth. */
4412 for (;;) {
4413 int target_rti = 0;
4414 foreach (lc, new_q->jointree->fromlist) {
4415 Node *n = (Node *) lfirst(lc);
4416 RangeTblRef *rtr;
4417 RangeTblEntry *rte;
4418 if (!IsA(n, RangeTblRef))
4419 continue;
4420 rtr = (RangeTblRef *) n;
4421 if (rtr->rtindex < 1
4422 || rtr->rtindex > list_length(new_q->rtable))
4423 continue;
4424 rte = (RangeTblEntry *)
4425 list_nth(new_q->rtable, rtr->rtindex - 1);
4426 if (rte->rtekind != RTE_SUBQUERY)
4427 continue;
4428 if (rte->lateral || rte->security_barrier)
4429 continue;
4430 if (!is_inlineable_subquery(rte->subquery))
4431 continue;
4432 target_rti = rtr->rtindex;
4433 break;
4434 }
4435 if (target_rti == 0)
4436 break;
4437 inline_one_subquery(new_q, target_rti);
4438 orphans = bms_add_member(orphans, target_rti);
4439 any_inlined = true;
4440 }
4441
4442 if (!any_inlined) {
4443 bms_free(orphans);
4444 return NULL;
4445 }
4446
4447 /* PG 14 and 15 leave OLD / NEW rule-placeholder RTEs (relkind =
4448 * RELKIND_VIEW, inFromCl = false) in any view body's rtable;
4449 * @c inline_one_subquery copies the whole sub-rtable up so those
4450 * placeholders land in @c new_q->rtable. They share the view's
4451 * relid (so the candidate gate's self-join check would mistakenly
4452 * fire on them) and are never referenced from the jointree, so
4453 * mark them as orphans for @c compact_orphan_rtes to drop. */
4454 {
4455 int idx = 1;
4456 foreach (lc, new_q->rtable) {
4457 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4458 if (rte->rtekind == RTE_RELATION
4459 && rte->relkind == RELKIND_VIEW
4460 && !rte->inFromCl)
4461 orphans = bms_add_member(orphans, idx);
4462 idx++;
4463 }
4464 }
4465
4466 compact_orphan_rtes(new_q, orphans);
4467 bms_free(orphans);
4468 return new_q;
4469}
4470
4471/* -------------------------------------------------------------------------
4472 * INNER JoinExpr flattening pre-pass.
4473 *
4474 * PG's ANSI-syntax @c INNER @c JOIN ... @c ON / @c CROSS @c JOIN
4475 * leaves a @c JoinExpr in @c q->jointree->fromlist instead of a flat
4476 * list of @c RangeTblRefs, so the candidate gate's "fromlist must be
4477 * all @c RangeTblRef" check refuses the query even though gate-level
4478 * semantics match the comma-style @c FROM @c A, @c B counterpart.
4479 * This pre-pass walks the fromlist, replaces every @c INNER
4480 * @c JoinExpr with its (recursively-flattened) leaf @c RangeTblRefs,
4481 * and AND-merges each join's @c ON clause into @c q->jointree->quals.
4482 * The synthetic @c RTE_JOIN entries PG creates alongside each
4483 * @c JoinExpr become orphan after flattening; @c compact_orphan_rtes
4484 * (reused from the subquery-inlining pass) drops them and renumbers
4485 * the remaining Vars / @c RangeTblRefs.
4486 *
4487 * Refuses to flatten on anything that would change observable
4488 * semantics or require resolving aliased columns: outer joins
4489 * (@c LEFT / @c RIGHT / @c FULL preserve NULL-padding rows whose
4490 * provenance is the OR-NOT of the inner-match disjunction,
4491 * incompatible with TID), join aliases (@c JOIN ... @c AS @c j -- the
4492 * outer query may reference @c j.col, which after flattening would
4493 * need resolving through @c joinaliasvars; same complexity as PG's
4494 * own subquery pull-up), and @c USING clauses (also drive
4495 * @c joinaliasvars magic). On refusal, returns @c NULL and the
4496 * candidate gate's existing rejector sees the @c JoinExpr as before.
4497 * ------------------------------------------------------------------------- */
4498
4499/** @brief Walker context for @c safe_flatten_join_arm. */
4501 Bitmapset *orphans; ///< rtindexes of dissolved RTE_JOIN entries
4502 bool failed; ///< unsupported JoinExpr encountered
4503 Node *merged_quals; ///< AND-merged ON clauses from every flattened JoinExpr
4505
4506/**
4507 * @brief Recursively flatten a fromlist arm into a list of @c
4508 * RangeTblRef nodes, appending each @c JoinExpr's @c ON clause
4509 * to @c ctx->merged_quals along the way. On failure (an
4510 * unsupported JoinExpr shape), sets @c ctx->failed and returns
4511 * @c NIL ; the caller bails out via the failed flag.
4512 */
4513static List *safe_flatten_join_arm(Node *arm, safe_flatten_join_ctx *ctx) {
4514 if (arm == NULL || ctx->failed)
4515 return NIL;
4516 if (IsA(arm, RangeTblRef))
4517 return list_make1(arm);
4518 if (IsA(arm, JoinExpr)) {
4519 JoinExpr *je = (JoinExpr *) arm;
4520 List *larms, *rarms;
4521 if (je->jointype != JOIN_INNER
4522 || je->alias != NULL
4523 || je->usingClause != NIL) {
4524 ctx->failed = true;
4525 return NIL;
4526 }
4527 larms = safe_flatten_join_arm(je->larg, ctx);
4528 if (ctx->failed) return NIL;
4529 rarms = safe_flatten_join_arm(je->rarg, ctx);
4530 if (ctx->failed) return NIL;
4531 if (je->quals != NULL) {
4532 Node *q_copy = (Node *) copyObject(je->quals);
4533 if (ctx->merged_quals == NULL)
4534 ctx->merged_quals = q_copy;
4535 else
4536 ctx->merged_quals = (Node *) makeBoolExpr(
4537 AND_EXPR,
4538 list_make2(ctx->merged_quals, q_copy),
4539 -1);
4540 }
4541 if (je->rtindex > 0)
4542 ctx->orphans = bms_add_member(ctx->orphans, je->rtindex);
4543 return list_concat(larms, rarms);
4544 }
4545 /* Unknown shape (e.g. raw FromExpr nested in fromlist): refuse. */
4546 ctx->failed = true;
4547 return NIL;
4548}
4549
4550/**
4551 * @brief Recursively scan @p q (and any subquery body in its rtable)
4552 * for a @c JoinExpr in any fromlist. Used as a quick-exit
4553 * gate before the (more expensive) copy-and-flatten path.
4554 */
4555static bool safe_has_join_expr_anywhere(Query *q) {
4556 ListCell *lc;
4557 if (q == NULL || q->jointree == NULL)
4558 return false;
4559 foreach (lc, q->jointree->fromlist) {
4560 if (IsA(lfirst(lc), JoinExpr))
4561 return true;
4562 }
4563 foreach (lc, q->rtable) {
4564 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4565 if (rte->rtekind == RTE_SUBQUERY
4566 && safe_has_join_expr_anywhere(rte->subquery))
4567 return true;
4568 }
4569 return false;
4570}
4571
4572/**
4573 * @brief In-place flattener for the @c Query at any nesting depth.
4574 * Walks @p q's fromlist (replacing JoinExprs with their flat
4575 * arms and AND-merging their ON clauses), then recurses into
4576 * every @c RTE_SUBQUERY body. Returns @c false when any
4577 * unsupported JoinExpr shape is hit (outer join, alias,
4578 * @c USING), which causes the whole pre-pass to bail.
4579 *
4580 * The caller is responsible for having @c copyObject'd @p q
4581 * first. Recursion runs the subquery-body in-place mutation
4582 * on the same copy.
4583 */
4585 ListCell *lc;
4586 List *new_fromlist = NIL;
4587 safe_flatten_join_ctx ctx = {0};
4588
4589 if (q == NULL || q->jointree == NULL)
4590 return true;
4591
4592 ctx.merged_quals = NULL;
4593 ctx.orphans = NULL;
4594 ctx.failed = false;
4595
4596 foreach (lc, q->jointree->fromlist) {
4597 Node *n = (Node *) lfirst(lc);
4598 if (IsA(n, RangeTblRef)) {
4599 new_fromlist = lappend(new_fromlist, n);
4600 } else if (IsA(n, JoinExpr)) {
4601 List *arms = safe_flatten_join_arm(n, &ctx);
4602 if (ctx.failed) {
4603 bms_free(ctx.orphans);
4604 return false;
4605 }
4606 new_fromlist = list_concat(new_fromlist, arms);
4607 } else {
4608 bms_free(ctx.orphans);
4609 return false;
4610 }
4611 }
4612 q->jointree->fromlist = new_fromlist;
4613
4614 if (ctx.merged_quals != NULL) {
4615 if (q->jointree->quals == NULL)
4616 q->jointree->quals = ctx.merged_quals;
4617 else
4618 q->jointree->quals = (Node *) makeBoolExpr(
4619 AND_EXPR,
4620 list_make2(q->jointree->quals, ctx.merged_quals),
4621 -1);
4622 }
4623
4624 if (ctx.orphans != NULL) {
4626 bms_free(ctx.orphans);
4627 }
4628
4629 /* Recurse into RTE_SUBQUERY bodies so a JoinExpr nested inside a
4630 * subquery (the common @c FROM @c (SELECT ... @c JOIN ...) shape)
4631 * gets flattened too -- the subquery-inlining pre-pass that runs
4632 * downstream only accepts subqueries whose fromlist is already
4633 * flat. */
4634 foreach (lc, q->rtable) {
4635 RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
4636 if (rte->rtekind == RTE_SUBQUERY && rte->subquery != NULL) {
4637 if (!safe_flatten_inner_joins_inplace(rte->subquery))
4638 return false;
4639 }
4640 }
4641
4642 return true;
4643}
4644
4645/**
4646 * @brief Pre-pass driver. Returns @c NULL when nothing flattened;
4647 * else a fresh @c Query with INNER JoinExprs at every nesting
4648 * depth dissolved into flat @c RangeTblRefs, their @c ON
4649 * clauses AND-merged into the corresponding quals, and the
4650 * rtable compacted to drop orphan @c RTE_JOIN entries.
4651 */
4652static Query *try_flatten_inner_joins(Query *q) {
4653 Query *new_q;
4654
4656 return NULL;
4657
4658 new_q = (Query *) copyObject(q);
4660 return NULL;
4661 return new_q;
4662}
4663
4664/**
4665 * @brief Top-level entry point for the safe-query rewrite.
4666 *
4667 * Runs the shape gate then the hierarchy detector. If both accept,
4668 * applies the single-level rewrite and returns the rewritten Query;
4669 * the caller (@c process_query) feeds it back from the top so that
4670 * inner subqueries are themselves re-considered (multi-level
4671 * recursion via Choice A). Returns @c NULL to fall through to the
4672 * existing pipeline.
4673 */
4674Query *try_safe_query_rewrite(const constants_t *constants, Query *q) {
4675 List *atoms;
4676 List *groups = NIL;
4677 Node *residual = NULL;
4678 Node *outer_residual = NULL;
4679 List **per_atom = NULL;
4680 int natoms;
4681 int i;
4682 ListCell *lc;
4683
4684#if PG_VERSION_NUM >= 180000
4685 /* Same trick as rewrite_agg_distinct: PG 18's RTE_GROUP virtual
4686 * entry derails the shape gate ("all rtable entries are
4687 * RTE_RELATION") and the union-find ("varno must index q->rtable")
4688 * before they can see the underlying base relations. Strip it
4689 * here so the rest of try_safe_query_rewrite (and, on a bail, the
4690 * existing pipeline) see a flat range table with the grouped Vars
4691 * resolved back to their base-table expressions. */
4693#endif
4694
4695 /* INNER JoinExpr flattening pre-pass. Replaces ANSI-syntax
4696 * @c INNER @c JOIN ... @c ON / @c CROSS @c JOIN entries with
4697 * flat @c RangeTblRefs + AND-merged @c ON-clauses so the
4698 * candidate gate's flat-fromlist requirement matches the
4699 * comma-style @c FROM @c A, @c B counterpart. Skipped on outer
4700 * joins, aliased joins, and @c USING clauses (the candidate
4701 * gate then refuses the @c JoinExpr as before). Recurses into
4702 * @c RTE_SUBQUERY bodies so a subquery whose own fromlist
4703 * contains an inner join lands at the subsequent inlining pass
4704 * with a flat fromlist -- otherwise the conservative
4705 * inlineable predicate would refuse it. */
4706 {
4707 Query *flattened = try_flatten_inner_joins(q);
4708 if (flattened != NULL)
4709 q = flattened;
4710 }
4711
4712 /* Subquery-inlining pre-pass. Pulls simple @c RTE_SUBQUERY
4713 * fromlist entries (most commonly view bodies inlined by PG's
4714 * parser) up into the outer query so the detector and rewriter
4715 * see a single flat rtable of @c RTE_RELATION entries. Returns
4716 * @c NULL when no inlining applied; else a fresh @c Query with
4717 * the inlined RTEs, merged WHERE conjuncts, and a compacted
4718 * rtable. Two views (or a view + base table) that ultimately
4719 * read the same relation produce duplicate relids after inlining
4720 * and trip the candidate gate's shared-relid bail downstream
4721 * (modulo the PK / disjoint-constant self-join rescues). */
4722 {
4723 Query *inlined = try_inline_simple_subqueries(q);
4724 if (inlined != NULL)
4725 q = inlined;
4726 }
4727
4728 /* PK-unifiable self-join pre-pass. When two RTEs over the same
4729 * relation have all PRIMARY KEY (or NOT-NULL UNIQUE) columns
4730 * equated through the union-find closure, the key proves they
4731 * refer to the same tuple; merge the duplicate RTEs into a single
4732 * survivor before the shared-relid bail in @c is_safe_query_candidate
4733 * rejects the query. Returns @c NULL when no unification applies,
4734 * else a fresh @c Query with the merge baked in. */
4735 {
4736 Query *unified = try_pk_self_join_unification(q);
4737 if (unified != NULL)
4738 q = unified;
4739 }
4740
4741 /* Disjoint-constant self-join pre-pass. Same-relid groups that
4742 * survive the PK-unification step (no PK to collapse them) can
4743 * still be rescued when their constant predicates prove their
4744 * tuple-sets disjoint. This call certifies eligible relids; the
4745 * candidate gate skips its shared-relid bail for those. */
4746 {
4747 Bitmapset *approved = try_disjoint_constant_self_join_split(q);
4748 if (!is_safe_query_candidate(constants, q, approved)) {
4749 if (approved)
4750 bms_free(approved);
4751 return NULL;
4752 }
4753 if (approved)
4754 bms_free(approved);
4755 }
4756
4757 /* Atom-local pre-pass: pull out atom-local WHERE conjuncts so the
4758 * detector only sees Vars that participate in cross-atom structure.
4759 * Single-atom existential Vars hidden inside pushable predicates
4760 * (e.g. @c c.z @c > @c 5 in @c A(x,y),B(x,y),C(x,y,z)) thus
4761 * disappear from the union-find input and stop tripping the
4762 * "every Var in a class touching every atom" check. */
4763 natoms = list_length(q->rtable);
4764 per_atom = palloc0(natoms * sizeof(List *));
4765 safe_split_quals(q->jointree ? q->jointree->quals : NULL,
4766 natoms, per_atom, &residual);
4767
4768 /* Constant-selection elimination pre-pass. Identifies union-find
4769 * classes pinned to a literal by some @c Var @c = @c Const
4770 * conjunct, propagates the literal to every Var in the class
4771 * (atom-local synthesised conjuncts), and drops the redundant
4772 * cross-atom equijoins. The multi-component dispatch immediately
4773 * below then sees constant-pinned atoms as separate components
4774 * and routes them through the existing per-component subquery
4775 * shape, which produces the read-once @c gate_times factoring
4776 * constant-pinning needs (each pinned atom becomes its own
4777 * @c gate_plus child of the top @c gate_times). */
4778 apply_constant_selection_fd_pass(q, per_atom, &residual);
4779
4780 /* Multi-component dispatch: when the atoms split into more than
4781 * one connected component (q :- A(x), B(y) with no join), the
4782 * single-component detector below can't find a root variable.
4783 * Build a Cartesian outer over one inner sub-Query per component
4784 * and let Choice A re-entry handle each component on its own. */
4785 if (natoms >= 2) {
4786 int *atom_to_comp = palloc(natoms * sizeof(int));
4787 int ncomp = compute_atom_components(q, residual, atom_to_comp);
4788 if (ncomp > 1) {
4789 Query *rewritten = rewrite_multi_component(
4790 constants, q, residual, per_atom, atom_to_comp, ncomp);
4791 pfree(atom_to_comp);
4792 if (rewritten != NULL) {
4793 pfree(per_atom);
4794 return rewritten;
4795 }
4796 } else {
4797 pfree(atom_to_comp);
4798 }
4799 }
4800
4801 atoms = find_hierarchical_root_atoms(constants, q, residual, &groups);
4802 if (atoms == NIL) {
4803 if (provsql_verbose >= 30)
4804 provsql_notice("safe-query candidate accepted by shape gate but no "
4805 "root variable found -- falling through");
4806 pfree(per_atom);
4807 return NULL;
4808 }
4809
4810 /* Attach per-atom pushed conjuncts to the rewrite descriptors.
4811 * The constant-selection pre-pass above may have appended
4812 * synthesised @c Var @c = @c const conjuncts to some atoms' lists
4813 * (the propagated literals from constant-pinned classes); they
4814 * follow the same atom-local pushdown path as user-written
4815 * single-atom conjuncts and end up in the inner DISTINCT wrap's
4816 * @c WHERE. */
4817 i = 0;
4818 foreach (lc, atoms) {
4819 safe_rewrite_atom *sa = (safe_rewrite_atom *) lfirst(lc);
4820 sa->pushed_quals = per_atom[i];
4821 i++;
4822 }
4823 pfree(per_atom);
4824
4825 /* With at least one inner group, partition the residual cross-atom
4826 * conjuncts -- those wholly inside a group move into the group's
4827 * inner_quals; the rest stay in the outer residual. With no inner
4828 * groups, partition is a no-op (every conjunct stays outer) and the
4829 * rewriter does single-level outer-only wrapping. */
4830 if (groups != NIL)
4831 safe_partition_residual(residual, atoms, groups, &outer_residual);
4832 else
4833 outer_residual = residual;
4834
4835 return rewrite_hierarchical_cq(constants, q, atoms, groups, outer_residual);
4836}
@ PROVSQL_TABLE_BID
@ PROVSQL_TABLE_OPAQUE
#define PROVSQL_TABLE_INFO_MAX_ANCESTORS
Cap on the number of base ancestors recorded per relation.
PostgreSQL cross-version compatibility shims for ProvSQL.
static ListCell * my_lnext(const List *l, const ListCell *c)
Version-agnostic wrapper around lnext().
int provsql_verbose
Verbosity level; controlled by the provsql.verbose_level GUC.
Definition provsql.c:78
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
#define provsql_notice(fmt,...)
Emit a ProvSQL informational notice (execution continues).
Background worker and IPC primitives for mmap-backed circuit storage.
Oid find_equality_operator(Oid ltypeId, Oid rtypeId)
Find the equality operator OID for two given types.
bool provsql_lookup_ancestry(Oid relid, uint16 *ancestor_n_out, Oid *ancestors_out)
Look up the base-ancestor set of a tracked relation.
bool provsql_lookup_table_info(Oid relid, ProvenanceTableInfo *out)
Look up per-table provenance metadata with a backend-local cache.
bool provsql_lookup_relation_keys(Oid relid, ProvenanceRelationKeys *out)
Look up the PRIMARY-KEY and NOT-NULL-UNIQUE keys of a relation with a backend-local cache.
Core types, constants, and utilities shared across ProvSQL.
#define PROVSQL_COLUMN_NAME
Canonical name of the per-row provenance column installed by add_provenance / repair_key.
#define DETERMINED(c, atom_idx)
static void safe_split_quals(Node *quals, int natoms, List **per_atom_out, Node **out_residual)
Partition top-level WHERE conjuncts into atom-local quals (pushed into the matching atom's inner wrap...
Definition safe_query.c:585
static bool safe_collect_vars_walker(Node *node, safe_collect_vars_ctx *ctx)
Tree walker that collects every distinct base-level Var node.
Definition safe_query.c:333
static Node * safe_unify_remap_mutator(Node *node, safe_unify_remap_ctx *ctx)
Tree mutator that renumbers Var.varno and RangeTblRef.rtindex through the PK-unifiable self-join map.
static Query * try_flatten_inner_joins(Query *q)
Pre-pass driver.
#define ANCHOR(c, atom_idx)
static bool safe_has_join_expr_anywhere(Query *q)
Recursively scan q (and any subquery body in its rtable) for a JoinExpr in any fromlist.
static void safe_partition_residual(Node *residual, List *atoms, List *groups, Node **outer_residual_out)
Partition the cross-atom residual into per-group conjuncts and a new outer residual.
Definition safe_query.c:646
Query * try_safe_query_rewrite(const constants_t *constants, Query *q)
Top-level entry point for the safe-query rewrite.
static bool safe_collect_varnos_walker(Node *node, safe_collect_varnos_ctx *ctx)
Collect the distinct base-level varno values referenced by an expression sub-tree.
Definition safe_query.c:521
static Bitmapset * try_disjoint_constant_self_join_split(Query *q)
Disjoint-constant self-join certification.
static void safe_flatten_and(Node *n, List **out)
Flatten the top-level AND tree of a WHERE clause into a flat list of conjunct Node *.
Definition safe_query.c:546
static List * safe_flatten_join_arm(Node *arm, safe_flatten_join_ctx *ctx)
Recursively flatten a fromlist arm into a list of RangeTblRef nodes, appending each JoinExpr's ON cla...
static Node * safe_inline_compact_mutator(Node *node, safe_inline_compact_ctx *ctx)
Renumber Vars / RangeTblRefs via old_to_new.
static Query * rewrite_hierarchical_cq(const constants_t *constants, Query *q, List *atoms, List *groups, Node *residual)
Apply the (multi-level when needed) hierarchical-CQ rewrite.
static List * find_hierarchical_root_atoms(const constants_t *constants, Query *q, Node *quals, List **groups_out)
Run the hierarchy detector on q, returning per-atom rewrite info.
Definition safe_query.c:808
static Query * try_pk_self_join_unification(Query *q)
PK-unifiable self-join detection and unification.
static int safe_var_index(List *vars, Index varno, AttrNumber varattno)
Find the position of a Var inside the vars list (matched on (varno, varattno)).
Definition safe_query.c:356
static bool safe_flatten_inner_joins_inplace(Query *q)
In-place flattener for the Query at any nesting depth.
static Node * safe_inner_varno_remap_mutator(Node *node, safe_inner_varno_remap_ctx *ctx)
Rewrite base-level Var.varno from the outer atom rtindex to the corresponding inner-sub-Query rtindex...
static Node * safe_inline_subst_mutator(Node *node, safe_inline_subst_ctx *ctx)
Replace every outer-scope Var pointing at the inlined subquery RTE with a shifted copy of the matchin...
static void apply_constant_selection_fd_pass(Query *q, List **per_atom_quals, Node **residual_in_out)
Constant-selection elimination pre-pass.
static Query * try_inline_simple_subqueries(Query *q)
Subquery-inlining pre-pass.
static void inline_one_subquery(Query *q, int target_rti)
Inline the subquery RTE at target_rti into q in place.
static Query * safe_build_inner_wrap(Query *outer_src, RangeTblEntry *base_rte, List *proj_slots, Index outer_rtindex, List *pushed_quals)
Build the inner Query that projects every slot in proj_slots of base_rte under SELECT DISTINCT.
static void safe_collect_equalities(Node *quals, List **out)
Walk quals as a tree of AND, collecting equality conjuncts.
Definition safe_query.c:479
static bool safe_is_var_const_equality(Expr *qual, Var **var, Const **konst)
Recognise OpExpr nodes of shape Var = Const.
Definition safe_query.c:429
static Node * safe_pushed_remap_mutator(Node *node, safe_pushed_remap_ctx *ctx)
Rewrite Var.varno from the outer atom rtindex to 1, the sole RTE of the inner wrap subquery.
Definition safe_query.c:724
static bool is_safe_query_candidate(const constants_t *constants, Query *q, Bitmapset *approved_self_join_relids)
Walk a Query and reject anything outside the safe-query scope.
Definition safe_query.c:94
static Node * safe_remap_vars_mutator(Node *node, safe_remap_ctx *ctx)
Rewrite Var nodes in the outer query after each base RTE has been wrapped as a DISTINCT subquery proj...
static void compact_orphan_rtes(Query *q, Bitmapset *orphans)
Drop orphan RTEs (the inlined subquery slots) from q->rtable and renumber every surviving Var / Range...
static bool safe_is_var_equality(Expr *qual, Var **l, Var **r)
Recognise a top-level WHERE conjunct that equates two base Vars.
Definition safe_query.c:377
static Node * safe_outer_te_remap_mutator(Node *node, safe_outer_te_remap_ctx *ctx)
Rewrite Vars in the outer targetList for the multi-component rewrite.
static Query * rewrite_multi_component(const constants_t *constants, Query *q, Node *residual, List **per_atom_quals, int *atom_to_comp, int ncomp)
Apply the multi-component rewrite.
static int compute_atom_components(Query *q, Node *quals, int *atom_to_comp)
Compute atom-level connected components.
static bool is_inlineable_subquery(Query *sub)
Decide whether sub may be inlined.
static Query * safe_build_group_subquery(Query *outer_src, safe_inner_group *gr, List *atoms)
Build the inner sub-Query that aggregates a group of partial-coverage atoms over their non-root share...
static Node * safe_inline_shift_mutator(Node *node, safe_inline_shift_ctx *ctx)
Add offset to the varno of every base-level (varlevelsup == 0) Var and the rtindex of every RangeTblR...
Public surface of the safe-query (hierarchical-CQ) rewriter.
void strip_group_rte_pg18(Query *q)
PG 18 helper: strip the synthetic RTE_GROUP entry from q in place, resolving every grouped Var back t...
One PRIMARY-KEY or NOT-NULL-UNIQUE key on a relation.
AttrNumber cols[PROVSQL_KEY_CACHE_MAX_KEY_COLS]
Per-relation set of PRIMARY-KEY and NOT-NULL-UNIQUE keys.
ProvenanceRelationKey keys[PROVSQL_KEY_CACHE_MAX_KEYS]
Per-relation metadata for the safe-query optimisation.
AttrNumber block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]
Block-key column numbers.
uint16_t block_key_n
Number of valid entries in block_key.
uint8_t kind
One of provsql_table_kind.
Structure to store the value of various constants.
Oid OID_TYPE_UUID
OID of the uuid TYPE.
Walker context for safe_collect_varnos_walker.
Definition safe_query.c:507
Bitmapset * varnos
Set of varno values seen in base-level Vars.
Definition safe_query.c:508
Walker context for safe_collect_vars_walker.
Definition safe_query.c:323
List * vars
Deduplicated list of distinct base-level Var nodes.
Definition safe_query.c:324
Walker context for safe_flatten_join_arm.
bool failed
unsupported JoinExpr encountered
Bitmapset * orphans
rtindexes of dissolved RTE_JOIN entries
Node * merged_quals
AND-merged ON clauses from every flattened JoinExpr.
Walker context for safe_inline_compact_mutator.
int * old_to_new
1-based map; 0 marks dropped (orphan) slots
Walker context for safe_inline_shift_mutator.
int offset
Added to every base-level Var.varno and RangeTblRef.rtindex.
Walker context for safe_inline_subst_mutator.
Index target_rtindex
rtindex of the inlined subquery in the outer rtable
List * target_list
Inlined subquery's targetList.
int outer_offset
Shift applied to Vars inside substituted TLE expressions.
Descriptor for an inner sub-Query introduced when one or more shared classes have partial coverage.
Definition safe_query.c:315
List * inner_quals
List of Node *: cross-atom conjuncts whose vars all reference group members (original varnos; the rew...
Definition safe_query.c:318
List * member_atoms
List of safe_rewrite_atom *, in original-rtindex order.
Definition safe_query.c:317
Index outer_rtindex
Assigned by the rewriter: position of the inner sub-Query RTE in the outer rtable.
Definition safe_query.c:319
Mutator context for safe_inner_varno_remap_mutator.
int * orig_to_inner
1-indexed array: orig rtindex -> inner rtindex (0 if not in group)
Mutator context for safe_outer_te_remap_mutator.
Index * comp_to_outer_rtindex
per-component outer-rtable position (1-based)
int * atom_to_inner_attno
per-atom column position in its component's inner targetList (1-based; 0 = not exposed)
int * atom_to_comp
per-atom component id
bool bail
set when a Var has no exposed inner column; caller falls back to the regular pipeline
One projected column of an atom's wrapping subquery.
Definition safe_query.c:268
AttrNumber outer_attno
1-based column in the inner sub-Query's targetList (or per-atom DISTINCT wrap for outer-wrap atoms)....
Definition safe_query.c:271
AttrNumber base_attno
Definition safe_query.c:269
Mutator context for safe_pushed_remap_mutator.
Definition safe_query.c:711
Index outer_rtindex
varno in the outer scope to rewrite to 1
Definition safe_query.c:712
Mutator context for safe_remap_vars_mutator.
List * atoms
List of safe_rewrite_atom *, one per RTE.
List * groups
List of safe_inner_group *.
bool bail
Set when a Var has no slot in its atom's projection; the caller aborts the rewrite and falls back to ...
Per-atom rewrite metadata discovered by the hierarchy detector.
Definition safe_query.c:291
Index outer_rtindex
Assigned by the rewriter: this atom's slot in the rebuilt outer rtable. Grouped atoms all share their...
Definition safe_query.c:296
AttrNumber root_anchor_attno
For grouped atoms: base attno of the root-class binding column inside this atom. Used by the outer Va...
Definition safe_query.c:298
Index inner_rtindex
Assigned by the rewriter for grouped atoms only: position inside the inner sub-Query's rtable (1-base...
Definition safe_query.c:297
int group_id
-1 for atoms wrapped directly at the outer (one SELECT DISTINCT subquery per atom); >= 0 indexes into...
Definition safe_query.c:295
bool is_constant_pinned
Reserved for future constant-selection follow-up work; currently never set (constant-pinned atoms are...
Definition safe_query.c:299
Mutator context for safe_unify_remap_mutator.
int * old_to_new
1-indexed array: original rtindex -> compacted rtindex (after dropping non-keeper RTEs)....
int natoms
Length of the original rtable (1-based domain of old_to_new).