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