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