ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
provsql.c
Go to the documentation of this file.
1/**
2 * @file provsql.c
3 * @brief PostgreSQL planner hook for transparent provenance tracking.
4 *
5 * This file installs a @c planner_hook that intercepts every SELECT query
6 * and rewrites it to propagate a provenance circuit token (UUID) alongside
7 * normal result tuples. The rewriting proceeds in three conceptual phases:
8 *
9 * -# **Discovery** – scan the range table for relations/subqueries that
10 * already carry a @c provsql UUID column (@c get_provenance_attributes).
11 * -# **Expression building** – combine the discovered tokens according
12 * to the semiring operation that corresponds to the SQL operator in use
13 * (⊗ for joins, ⊕ for duplicate elimination, ⊖ for EXCEPT) and wrap
14 * aggregations (@c make_provenance_expression,
15 * @c make_aggregation_expression).
16 * -# **Splice** – append the resulting provenance expression to the target
17 * list and replace any explicit @c provenance() call in the query with
18 * the computed expression (@c add_to_select,
19 * @c replace_provenance_function_by_expression).
20 */
21#include "postgres.h"
22#include "fmgr.h"
23#include "miscadmin.h"
24#include "pg_config.h"
25#include "access/htup_details.h"
26#include "access/sysattr.h"
27#include "catalog/pg_aggregate.h"
28#include "catalog/pg_class.h" /* RELKIND_VIEW */
29#include "catalog/pg_collation.h"
30#include "catalog/pg_operator.h"
31#include "catalog/pg_proc.h"
32#include "catalog/pg_type.h"
33#include "nodes/makefuncs.h"
34#include "utils/jsonb.h"
35#include "nodes/nodeFuncs.h"
36#include "nodes/print.h"
37#include "executor/executor.h"
38#if PG_VERSION_NUM >= 120000
39#include "optimizer/optimizer.h"
40#else
41#include "optimizer/var.h" /* contain_vars_of_level */
42#endif
43#include "optimizer/planner.h"
44#include "parser/parse_coerce.h"
45#include "parser/parse_node.h"
46#include "parser/parse_oper.h"
47#include "rewrite/rewriteManip.h"
48#include "parser/parse_relation.h"
49#include "utils/builtins.h"
50#include "parser/parsetree.h"
51#include "storage/lwlock.h"
52#include "storage/shmem.h"
53#include "utils/fmgroids.h"
54#include "utils/guc.h"
55#include "utils/lsyscache.h"
56#include "utils/ruleutils.h"
57#include "utils/syscache.h"
58#include "catalog/namespace.h"
59#include "catalog/pg_cast.h"
60#include "commands/createas.h"
61#include "executor/spi.h"
62#include "tcop/utility.h"
63#include "tcop/tcopprot.h" /* pg_parse_query, pg_analyze_and_rewrite_fixedparams */
64#include <time.h>
65
66#include "classify_query.h"
67#include "joint_width_query.h"
68#include "provsql_mmap.h"
69#include "provsql_shmem.h"
70#include "provsql_utils.h"
71#include "safe_query.h"
72
73#if PG_VERSION_NUM < 100000
74#error "ProvSQL requires PostgreSQL version 10 or later"
75#endif
76
77#include "compatibility.h"
78
79PG_MODULE_MAGIC; ///< Required PostgreSQL extension magic block
80
81/* -------------------------------------------------------------------------
82 * Global state & forward declarations
83 * ------------------------------------------------------------------------- */
84
86static bool provsql_active = true; ///< @c true while ProvSQL query rewriting is enabled
88static bool provsql_update_provenance = false; ///< @c true when provenance tracking for DML is enabled
89int provsql_verbose = 100; ///< Verbosity level; controlled by the @c provsql.verbose_level GUC
90char *provsql_last_eval_method = NULL; ///< Last probability evaluation method(s) used; exposed via @c provsql.last_eval_method
91bool provsql_aggtoken_text_as_uuid = false; ///< When @c true, @c agg_token::text emits the underlying provenance UUID instead of @c "value (*)"
92char *provsql_tool_search_path = NULL; ///< Colon-separated directory list prepended to @c PATH when invoking external tools (d4, c2d, minic2d, dsharp, weightmc, graph-easy); controlled by the @c provsql.tool_search_path GUC. Superuser-only (@c PGC_SUSET): it dictates which directories the postgres OS user searches for executables, so a non-privileged role must not be able to point it at an attacker-controlled binary.
93char *provsql_fallback_compiler = NULL; ///< Compiler used by @c BooleanCircuit::makeDD as the final fallback after @c interpretAsDD and tree-decomposition both fail; controlled by the @c provsql.fallback_compiler GUC (default @c "d4")
94char *provsql_kcmcp_server = NULL; ///< Launch command for the managed KCMCP server (with a @c {endpoint} placeholder); controlled by the @c provsql.kcmcp_server GUC. Empty means no managed server is launched.
95int provsql_monte_carlo_seed = -1; ///< Seed for the Monte Carlo sampler; -1 means non-deterministic (std::random_device); controlled by the @c provsql.monte_carlo_seed GUC
96int provsql_rv_mc_samples = 10000; ///< Default sample count for analytical-evaluator MC fallbacks; 0 disables fallback (callers raise instead); controlled by the @c provsql.rv_mc_samples GUC
97int provsql_dtree_max_subproblems = 0; ///< Debug/safety hard cap on d-tree subproblems before it bails (0 = off; the chooser auto-budgets at the next-best method's cost regardless); @c provsql.dtree_max_subproblems GUC
98int provsql_joint_max_treewidth = 10; ///< Maximum joint treewidth the joint-width UCQ compiler attempts before declining (caller falls back to the ladder); @c provsql.joint_max_treewidth GUC
99int provsql_joint_max_states = 65536; ///< Per-bag DP state-count cap of the joint-width UCQ compiler (the true safety net); @c provsql.joint_max_states GUC
100bool provsql_joint_width = true; ///< Recognise unsafe UCQs at planner time and route their existence provenance through the joint-width compiler (on by default); the @c provsql.joint_width GUC is a debug-only switch to disable it
101bool provsql_mobius = true; ///< Try the safe-UCQ Möbius-inversion route (a guaranteed-PTIME exact route for its class) BEFORE the joint-width compiler, which it short-circuits on success (on by default); the @c provsql.mobius GUC is a debug-only switch to disable it
102int provsql_mobius_max_gates = 4000000; ///< Data-cost cap of the Möbius route: it declines (falling through to joint-width / the ladder) once its compile has built more than this many gates, bounding the \f$O(|D|^k)\f$ blow-up of a high-level safe query on large data; @c provsql.mobius_max_gates GUC
103bool provsql_simplify_on_load = true; ///< Run universal cmp-resolution passes when @c getGenericCircuit returns; controlled by the @c provsql.simplify_on_load GUC
104bool provsql_hybrid_evaluation = true; ///< Run the hybrid-evaluator simplifier inside @c probability_evaluate; controlled by the @c provsql.hybrid_evaluation GUC
105bool provsql_cmp_probability_evaluation = true; ///< Run closed-form / analytic probability evaluators for @c gate_cmps inside @c probability_evaluate (currently the Poisson-binomial pre-pass for HAVING-COUNT; future MIN / MAX / SUM evaluators will gate on the same GUC); controlled by the @c provsql.cmp_probability_evaluation GUC
106bool provsql_inversion_free = true; ///< Insert the inversion-free structured-d-DNNF path into the default probability chain (after independent, when a certificate is present); controlled by the @c provsql.inversion_free GUC
107bool provsql_boolean_provenance = false; ///< Derived flag: the session's provenance class is 'boolean' -- enables the Boolean-only machinery (safe-query read-once rewrite, Boolean circuit simplifications), whose outputs are tagged so that semiring evaluations admitting no homomorphism from Boolean functions refuse to run on them. Set from the @c provsql.provenance GUC.
108bool provsql_absorptive_provenance = false; ///< Derived flag: the session's provenance class is 'absorptive' or 'boolean' -- licenses constructions sound for absorptive semirings only (cyclic recursive queries stopped at the absorptive value fixpoint, the bounded-treewidth reachability route's certified circuits, absorptive circuit simplifications; tokens tagged accordingly). Set from the @c provsql.provenance GUC.
109
110/** @brief Values of the @c provsql.provenance enum GUC, from most general to most specialised. */
112 PROVSQL_PROVENANCE_WHERE, ///< Universal semiring provenance plus where-provenance gates.
113 PROVSQL_PROVENANCE_SEMIRING, ///< Universal semiring provenance (default).
114 PROVSQL_PROVENANCE_ABSORPTIVE, ///< Absorptive-semiring constructions licensed (tagged).
115 PROVSQL_PROVENANCE_BOOLEAN ///< Boolean-only machinery licensed (tagged); implies absorptive.
117
118static int provsql_provenance_class = PROVSQL_PROVENANCE_SEMIRING; ///< Backing variable of the @c provsql.provenance GUC.
119
120/** @brief Option table of the @c provsql.provenance GUC. */
121static const struct config_enum_entry provsql_provenance_options[] = {
122 {"where", PROVSQL_PROVENANCE_WHERE, false},
123 {"semiring", PROVSQL_PROVENANCE_SEMIRING, false},
124 {"absorptive", PROVSQL_PROVENANCE_ABSORPTIVE, false},
125 {"boolean", PROVSQL_PROVENANCE_BOOLEAN, false},
126 {NULL, 0, false}
127};
128
129/** @brief Assign hook of @c provsql.provenance: refresh the derived per-class flags. */
137
138extern void _PG_init(void);
139extern void _PG_fini(void);
140
141static planner_hook_type prev_planner = NULL; ///< Previous planner hook (chained)
142
143static Query *process_query(const constants_t *constants, Query *q,
144 bool **removed, bool wrap_root, bool top_level,
145 bool in_boolean_rewrite,
146 const InvFreeMarkerCtx *inv_ctx);
147static Expr *wrap_in_assume_boolean(const constants_t *constants, Expr *expr);
148static Expr *wrap_in_annotate(const constants_t *constants, Expr *expr,
149 const char *cert);
150
151/* -------------------------------------------------------------------------
152 * Provenance attribute construction
153 * ------------------------------------------------------------------------- */
154
155/**
156 * @brief Build a Var node that references the provenance column of a relation.
157 *
158 * Creates a @c Var pointing to attribute @p attid of range-table entry
159 * @p relid, typed as UUID, and marks the column as selected in the
160 * permission bitmap so PostgreSQL grants access correctly.
161 *
162 * @param constants Extension OID cache.
163 * @param q Owning query (needed to update permission info on PG 16+).
164 * @param r Range-table entry that owns the provenance column.
165 * @param relid 1-based index of @p r in @p q->rtable.
166 * @param attid 1-based attribute number of the provenance column in @p r.
167 * @return A freshly allocated @c Var node.
168 */
169static Var *make_provenance_attribute(const constants_t *constants, Query *q,
170 RangeTblEntry *r, Index relid,
171 AttrNumber attid) {
172 Var *v = makeNode(Var);
173
174 v->varno = relid;
175 v->varattno = attid;
176
177#if PG_VERSION_NUM >= 130000
178 v->varnosyn = relid;
179 v->varattnosyn = attid;
180#else
181 v->varnoold = relid;
182 v->varoattno = attid;
183#endif
184
185 v->vartype = constants->OID_TYPE_UUID;
186 v->varcollid = InvalidOid;
187 v->vartypmod = -1;
188 v->location = -1;
189
190#if PG_VERSION_NUM >= 160000
191 if (r->perminfoindex != 0) {
192 RTEPermissionInfo *rpi =
193 list_nth_node(RTEPermissionInfo, q->rteperminfos, r->perminfoindex - 1);
194 rpi->selectedCols = bms_add_member(
195 rpi->selectedCols, attid - FirstLowInvalidHeapAttributeNumber);
196 }
197#else
198 r->selectedCols = bms_add_member(r->selectedCols,
199 attid - FirstLowInvalidHeapAttributeNumber);
200#endif
201
202 return v;
203}
204
205/* -------------------------------------------------------------------------
206 * Helper mutators: attribute-number fixup and type patching
207 * ------------------------------------------------------------------------- */
208
209/** @brief Context for the @c reduce_varattno_mutator tree walker. */
211 Index varno; ///< Range-table entry whose attribute numbers are being adjusted
212 int *offset; ///< Per-attribute cumulative shift to apply
214
215/**
216 * @brief Tree-mutator callback that adjusts Var attribute numbers.
217 * @param node Current expression tree node.
218 * @param ctx Pointer to a @c reduce_varattno_mutator_context.
219 * @return Possibly modified node.
220 */
221static Node *reduce_varattno_mutator(Node *node, void *ctx) {
223 if (node == NULL)
224 return NULL;
225
226 if (IsA(node, Var)) {
227 Var *v = (Var *)node;
228
229 if (v->varno == context->varno) {
230 v->varattno += context->offset[v->varattno - 1];
231 }
232 }
233
234 return expression_tree_mutator(node, reduce_varattno_mutator, ctx);
235}
236
237/**
238 * @brief Adjust Var attribute numbers in @p targetList after columns are removed.
239 *
240 * When provenance columns are stripped from a subquery's target list, the
241 * remaining columns shift left. This function applies a pre-computed
242 * @p offset array (one entry per original column) to correct all @c Var
243 * nodes that reference range-table entry @p varno.
244 *
245 * @param targetList Target list of the outer query to patch.
246 * @param varno Range-table entry whose attribute numbers need fixing.
247 * @param offset Cumulative shift per original attribute (negative or zero).
248 */
249static void reduce_varattno_by_offset(List *targetList, Index varno,
250 int *offset) {
251 ListCell *lc;
252 reduce_varattno_mutator_context context = {varno, offset};
253
254 foreach (lc, targetList) {
255 Node *te = lfirst(lc);
256 expression_tree_mutator(te, reduce_varattno_mutator, &context);
257 }
258}
259
260/** @brief Context for the @c aggregation_type_mutator tree walker. */
262 Index varno; ///< Range-table entry index of the aggregate var
263 Index varattno; ///< Attribute number of the aggregate column
264 const constants_t *constants; ///< Extension OID cache
266
267/**
268 * @brief Check if a Var matches the target aggregate column.
269 */
270static bool is_target_agg_var(Node *node,
272 if (IsA(node, Var)) {
273 Var *v = (Var *)node;
274 return v->varno == context->varno && v->varattno == context->varattno;
275 }
276 return false;
277}
278
279/**
280 * @brief Tree-mutator that retypes a specific Var to @c agg_token.
281 *
282 * When the target Var is inside a cast FuncExpr, replaces the cast
283 * function with the equivalent agg_token→target cast from pg_cast.
284 * When the Var appears bare (e.g. in a TargetEntry for display), it is
285 * retyped to agg_token directly. In all other contexts (arithmetic,
286 * window functions, etc.), wraps the Var in an explicit agg_token→original
287 * cast so that parent nodes receive the expected type.
288 *
289 * @param node Current expression tree node.
290 * @param ctx Pointer to an @c aggregation_type_mutator_context (varno,
291 * varattno, and constants).
292 * @return Possibly modified node.
293 */
294static Node *
295aggregation_type_mutator(Node *node, void *ctx) {
297 if (node == NULL)
298 return NULL;
299
300 if (IsA(node, FuncExpr)) {
301 FuncExpr *f = (FuncExpr *)node;
302
303 /* Check if this is a cast wrapping our target Var */
304 if (list_length(f->args) == 1 &&
305 is_target_agg_var(linitial(f->args), context)) {
306 /* Look up the cast from agg_token to the target type */
307 HeapTuple castTuple = SearchSysCache2(CASTSOURCETARGET,
308 ObjectIdGetDatum(context->constants->OID_TYPE_AGG_TOKEN),
309 ObjectIdGetDatum(f->funcresulttype));
310
311 if (HeapTupleIsValid(castTuple)) {
312 Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(castTuple);
313 if (OidIsValid(castForm->castfunc)) {
314 f->funcid = castForm->castfunc;
315 }
316 ReleaseSysCache(castTuple);
317 }
318
319 /* Retype the Var inside */
320 ((Var *)linitial(f->args))->vartype =
322
323 return (Node *)f;
324 }
325 }
326
327 if (IsA(node, Var)) {
328 Var *v = (Var *)node;
329
330 if (v->varno == context->varno && v->varattno == context->varattno) {
331 v->vartype = context->constants->OID_TYPE_AGG_TOKEN;
332 }
333 }
334 return expression_tree_mutator(node, aggregation_type_mutator, ctx);
335}
336
337/**
338 * @brief Retypes aggregation-result Vars in @p q from UUID to @c agg_token.
339 *
340 * After a subquery that contains @c provenance_aggregate is processed, its
341 * result type is @c agg_token rather than plain UUID. This mutator walks
342 * the outer query and updates the type of every @c Var referencing that
343 * result column so that subsequent type-checking passes correctly.
344 *
345 * @param constants Extension OID cache.
346 * @param q Outer query to patch.
347 * @param rteid Range-table index of the subquery in @p q.
348 * @param targetList Target list of the subquery (to locate provenance_aggregate columns).
349 */
350static void fix_type_of_aggregation_result(const constants_t *constants,
351 Query *q, Index rteid,
352 List *targetList) {
353 ListCell *lc;
354 aggregation_type_mutator_context context = {0, 0, constants};
355 Index attno = 1;
356
357 foreach (lc, targetList) {
358 TargetEntry *te = (TargetEntry *)lfirst(lc);
359 if (IsA(te->expr, FuncExpr)) {
360 FuncExpr *f = (FuncExpr *)te->expr;
361
362 if (f->funcid == constants->OID_FUNCTION_PROVENANCE_AGGREGATE) {
363 context.varno = rteid;
364 context.varattno = attno;
365 query_tree_mutator(q, aggregation_type_mutator, &context,
366 QTW_DONT_COPY_QUERY | QTW_IGNORE_RC_SUBQUERIES);
367
368 /* Check if the retyped column is used in ORDER BY or GROUP BY */
369 {
370 ListCell *lc2;
371 foreach (lc2, q->targetList) {
372 TargetEntry *outer_te = (TargetEntry *)lfirst(lc2);
373 if (IsA(outer_te->expr, Var)) {
374 Var *v = (Var *)outer_te->expr;
375 if (v->varno == rteid && v->varattno == attno &&
376 outer_te->ressortgroupref > 0) {
377 ListCell *lc3;
378 foreach (lc3, q->sortClause) {
379 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc3);
380 if (sgc->tleSortGroupRef == outer_te->ressortgroupref)
381 provsql_error("ORDER BY on aggregate results from "
382 "a subquery not supported");
383 }
384 foreach (lc3, q->groupClause) {
385 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc3);
386 if (sgc->tleSortGroupRef == outer_te->ressortgroupref)
387 provsql_error("GROUP BY on aggregate results from "
388 "a subquery not supported");
389 }
390 }
391 }
392 }
393 }
394 }
395 }
396 ++attno;
397 }
398}
399
400/**
401 * @brief Memo entry mapping a recursive-CTE name to its lowered scan subquery.
402 *
403 * A recursive CTE referenced more than once (e.g. in two arms of a top-level
404 * UNION) must be lowered -- and its backing temp table created by
405 * @c eval_recursive -- exactly once: re-running the fixpoint would
406 * @c DROP @c TABLE the temp table that an earlier reference's analyzed scan
407 * already bound to by OID, yielding "could not open relation with OID ...".
408 * @c inline_ctes_in_rtable records each lowering here and reuses it for
409 * subsequent references to the same CTE.
410 */
411typedef struct LoweredCte {
412 const char *name;
413 Query *subquery;
414#if PG_VERSION_NUM >= 150000
415 /* When the CTE was recognised as the (plain, single-column)
416 * reachability shape, the pieces the post-lowering aggregation
417 * planting needs to rebuild the gathering arguments; see
418 * plant_reach_aggregations(). */
419 bool reach_routed;
420 Oid edge_relid; /* InvalidOid for a subquery edge */
421 const char *src_name;
422 const char *dst_name;
423 const char *source_text; /* NULL for the multi-source form */
424 Oid source_relid;
425 const char *source_attname;
426 bool directed;
427 const char *edge_quals;
428 const char *edge_sql;
429#endif
430} LoweredCte;
431
432static Query *lookup_lowered_cte(List *lowered, const char *name) {
433 ListCell *lc;
434 foreach (lc, lowered) {
435 LoweredCte *e = (LoweredCte *)lfirst(lc);
436 if (strcmp(e->name, name) == 0)
437 return e->subquery;
438 }
439 return NULL;
440}
441
442#if PG_VERSION_NUM >= 150000
443/** @brief Output of @c detect_reachability_cte(): the recognised shape's pieces. */
444typedef struct ReachabilityShape {
445 Oid relid; /**< Edge relation. */
446 AttrNumber src_attno; /**< Source-vertex column of the edge relation. */
447 AttrNumber dst_attno; /**< Destination-vertex column. */
448 char *source_text; /**< Base arm's constant, rendered as text (constant form). */
449 Oid source_relid; /**< Base arm's source relation (multi-source form), or InvalidOid. */
450 AttrNumber source_attno; /**< Vertex column of the source relation. */
451 bool directed; /**< false for the undirected (CASE/IN) shape. */
452 char *edge_quals; /**< Deparsed extra quals over edge columns, or NULL. */
453 char *edge_sql; /**< Deparsed edge subquery (join-defined edges), or NULL. */
454 List *edge_rte_colnames; /**< Output column names of the edge subquery. */
455 int hop_bound; /**< Maximum walk length (hop-counting shape), or -1. */
456 int hop_seed; /**< Base arm's hop constant (hop-counting shape). */
457 int hops_position; /**< 1-based CTE position of the hop column, or 0. */
458 int node_position; /**< 1-based CTE position of the vertex column. */
459} ReachabilityShape;
460
461/** @brief Context for @c reach_varnos_walker(): set of varnos seen. */
462typedef struct ReachVarnosCtx {
463 Bitmapset *varnos; /**< Accumulated varnos (level-0 Vars). */
464 bool other; /**< Found an upper-level Var or other disqualifier. */
465} ReachVarnosCtx;
466
467/** @brief expression_tree_walker collecting level-0 varnos. */
468static bool reach_varnos_walker(Node *node, ReachVarnosCtx *ctx) {
469 if (node == NULL)
470 return false;
471 if (IsA(node, Var)) {
472 Var *v = (Var *) node;
473 if (v->varlevelsup != 0)
474 ctx->other = true;
475 else
476 ctx->varnos = bms_add_member(ctx->varnos, v->varno);
477 return false;
478 }
479 return expression_tree_walker(node, reach_varnos_walker, (void *) ctx);
480}
481
482/** @brief Strip @c RelabelType decorations off an expression. */
483static Node *reach_strip(Node *n) {
484 while (n != NULL && IsA(n, RelabelType))
485 n = (Node *) ((RelabelType *) n)->arg;
486 return n;
487}
488
489/**
490 * @brief Collect every qual of a join tree into @p quals, flattening AND.
491 *
492 * Only inner joins are in scope; any outer join flips @p ok to false.
493 */
494static void reach_collect_quals(Node *jtnode, List **quals, bool *ok) {
495 if (jtnode == NULL || !*ok)
496 return;
497 if (IsA(jtnode, FromExpr)) {
498 FromExpr *f = (FromExpr *) jtnode;
499 ListCell *lc;
500 foreach(lc, f->fromlist)
501 reach_collect_quals((Node *) lfirst(lc), quals, ok);
502 if (f->quals)
503 *quals = list_concat(*quals, make_ands_implicit((Expr *) f->quals));
504 } else if (IsA(jtnode, JoinExpr)) {
505 JoinExpr *j = (JoinExpr *) jtnode;
506 if (j->jointype != JOIN_INNER) {
507 *ok = false;
508 return;
509 }
510 reach_collect_quals(j->larg, quals, ok);
511 reach_collect_quals(j->rarg, quals, ok);
512 if (j->quals)
513 *quals = list_concat(*quals, make_ands_implicit((Expr *) j->quals));
514 }
515 /* RangeTblRef: nothing to collect */
516}
517
518/** @brief Return the single non-junk target entry of @p q, or NULL. */
519static TargetEntry *reach_single_tle(Query *q) {
520 TargetEntry *res = NULL;
521 ListCell *lc;
522 foreach(lc, q->targetList) {
523 TargetEntry *te = (TargetEntry *) lfirst(lc);
524 if (te->resjunk)
525 continue;
526 if (res != NULL)
527 return NULL;
528 res = te;
529 }
530 return res;
531}
532
533/** @brief Collect the two non-junk target entries of @p q by resno (1, 2). */
534static bool reach_two_tles(Query *q, TargetEntry *out[2]) {
535 ListCell *lc;
536 out[0] = out[1] = NULL;
537 foreach(lc, q->targetList) {
538 TargetEntry *te = (TargetEntry *) lfirst(lc);
539 if (te->resjunk)
540 continue;
541 if (te->resno < 1 || te->resno > 2 || out[te->resno - 1] != NULL)
542 return false;
543 out[te->resno - 1] = te;
544 }
545 return out[0] != NULL && out[1] != NULL;
546}
547
548/** @brief Read an integer Const of int2/int4/int8 type into @p value. */
549static bool reach_int_const(Node *n, int64 *value) {
550 Const *c = (Const *) reach_strip(n);
551 if (c == NULL || !IsA(c, Const) || c->constisnull)
552 return false;
553 switch (c->consttype) {
554 case INT2OID:
555 *value = DatumGetInt16(c->constvalue);
556 return true;
557 case INT4OID:
558 *value = DatumGetInt32(c->constvalue);
559 return true;
560 case INT8OID:
561 *value = DatumGetInt64(c->constvalue);
562 return true;
563 default:
564 return false;
565 }
566}
567
568/**
569 * @brief Recognise a hop-counter increment: @c r.hops + 1 over the
570 * recursive table's column @p resno (the counter must increment
571 * its own column).
572 */
573static bool reach_is_hop_increment(Node *n, Index cte_rti, AttrNumber resno) {
574 OpExpr *op = (OpExpr *) reach_strip(n);
575 Var *v = NULL;
576 int64 one;
577 char *opname;
578 bool is_plus;
579 if (op == NULL || !IsA(op, OpExpr) || list_length(op->args) != 2)
580 return false;
581 opname = get_opname(op->opno);
582 is_plus = opname != NULL && strcmp(opname, "+") == 0;
583 if (opname)
584 pfree(opname);
585 if (!is_plus)
586 return false;
587 if (reach_int_const((Node *) lsecond(op->args), &one))
588 v = (Var *) reach_strip((Node *) linitial(op->args));
589 else if (reach_int_const((Node *) linitial(op->args), &one))
590 v = (Var *) reach_strip((Node *) lsecond(op->args));
591 else
592 return false;
593 if (one != 1 || v == NULL || !IsA(v, Var))
594 return false;
595 return v->varno == cte_rti && v->varlevelsup == 0 && v->varattno == resno;
596}
597
598/**
599 * @brief Recognise a hop-bound qual -- @c r.hops < B or @c r.hops <= B
600 * (either orientation) over the recursive table's column
601 * @p hops_pos -- and return the bound and its strictness.
602 */
603static bool reach_is_hop_bound(Node *n, Index cte_rti, AttrNumber hops_pos,
604 int64 *bound, bool *strict) {
605 OpExpr *op = (OpExpr *) reach_strip(n);
606 Var *v;
607 char *opname;
608 bool var_first;
609 if (op == NULL || !IsA(op, OpExpr) || list_length(op->args) != 2)
610 return false;
611 v = (Var *) reach_strip((Node *) linitial(op->args));
612 if (v != NULL && IsA(v, Var) &&
613 reach_int_const((Node *) lsecond(op->args), bound))
614 var_first = true;
615 else {
616 v = (Var *) reach_strip((Node *) lsecond(op->args));
617 if (v == NULL || !IsA(v, Var) ||
618 !reach_int_const((Node *) linitial(op->args), bound))
619 return false;
620 var_first = false;
621 }
622 if (v->varno != cte_rti || v->varlevelsup != 0 || v->varattno != hops_pos)
623 return false;
624 opname = get_opname(op->opno);
625 if (opname == NULL)
626 return false;
627 /* var < B / var <= B; B > var / B >= var are the same bounds. */
628 if (strcmp(opname, var_first ? "<" : ">") == 0)
629 *strict = true;
630 else if (strcmp(opname, var_first ? "<=" : ">=") == 0)
631 *strict = false;
632 else {
633 pfree(opname);
634 return false;
635 }
636 pfree(opname);
637 return true;
638}
639
640/**
641 * @brief Recognise the linear reachability shape of a recursive CTE.
642 *
643 * Accepted (in either arm order, with either qual orientation):
644 *
645 * WITH RECURSIVE reach(v) AS (
646 * SELECT <constant>
647 * UNION
648 * SELECT e.<dst> FROM <edge> e JOIN reach r ON e.<src> = r.v
649 * )
650 *
651 * where @c <edge> is a provenance-tracked base relation (a @c provsql
652 * UUID column), the recursive arm has no other clauses, and the single
653 * join qual is a mergejoinable equality between an edge column and the
654 * CTE's (single) column. The caller has already checked the UNION
655 * (set) shape and the @c provsql.boolean_provenance gate.
656 *
657 * The *hop-counting* variant adds a counter column (in either CTE
658 * position):
659 *
660 * WITH RECURSIVE reach(v, hops) AS (
661 * SELECT <constant>, <int constant>
662 * UNION
663 * SELECT e.<dst>, r.hops + 1 FROM <edge> e JOIN reach r
664 * ON e.<src> = r.v WHERE r.hops < <int constant>
665 * )
666 *
667 * with @c <= accepted too; the bound qual is mandatory (an unbounded
668 * counter never reaches a fixpoint on cyclic data) and the maximum
669 * walk length it implies must not exceed the compiler's cap. The
670 * multi-source and undirected (CASE/IN) forms compose with it.
671 *
672 * @param cte The recursive CTE.
673 * @param cteq Its query (a UNION of two subquery arms).
674 * @param constants OID constants (for the UUID type check).
675 * @param out Output: the recognised pieces.
676 * @return Whether the shape was recognised.
677 */
678static bool detect_reachability_cte(CommonTableExpr *cte, Query *cteq,
679 const constants_t *constants,
680 ReachabilityShape *out) {
681 SetOperationStmt *so = (SetOperationStmt *) cteq->setOperations;
682 Query *arms[2];
683 Query *base = NULL, *rec = NULL;
684 Index edge_rti = 0, cte_rti = 0;
685 TargetEntry *tle;
686 Var *target_var;
687 List *quals = NIL;
688 bool ok = true;
689 Node *join_qual;
690 OpExpr *eq;
691 Var *va, *vb, *edge_var, *cte_var;
692 AttrNumber prov_attno;
693 int i;
694 ListCell *lc;
695 bool hops_mode;
696 AttrNumber node_pos = 1, hops_pos = 0;
697 TargetEntry *base_tles[2], *rec_tles[2];
698 int64 hop_seed = 0;
699
700 if (list_length(cte->ctecolnames) == 1)
701 hops_mode = false;
702 else if (list_length(cte->ctecolnames) == 2)
703 hops_mode = true;
704 else
705 return false;
706
707 if (!IsA(so->larg, RangeTblRef) || !IsA(so->rarg, RangeTblRef))
708 return false;
709 for (i = 0; i < 2; ++i) {
710 RangeTblRef *rtr = (RangeTblRef *) (i == 0 ? so->larg : so->rarg);
711 RangeTblEntry *r = rt_fetch(rtr->rtindex, cteq->rtable);
712 if (r->rtekind != RTE_SUBQUERY || r->subquery == NULL)
713 return false;
714 arms[i] = r->subquery;
715 }
716
717 /* Identify the recursive arm: the one with the self-reference (whose
718 * range-table index the hop analysis below needs early). */
719 {
720 Index rec_self_rti = 0;
721 for (i = 0; i < 2; ++i) {
722 bool has_self = false;
723 Index rti = 0, self_rti = 0;
724 foreach(lc, arms[i]->rtable) {
725 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
726 ++rti;
727 if (r->rtekind == RTE_CTE && r->self_reference &&
728 strcmp(r->ctename, cte->ctename) == 0) {
729 has_self = true;
730 self_rti = rti;
731 }
732 }
733 if (has_self) {
734 if (rec != NULL)
735 return false;
736 rec = arms[i];
737 rec_self_rti = self_rti;
738 } else {
739 if (base != NULL)
740 return false;
741 base = arms[i];
742 }
743 }
744 if (base == NULL || rec == NULL)
745 return false;
746
747 if (hops_mode) {
748 /* Identify the counter column: exactly one recursive-arm target
749 * entry of the form r.hops + 1 over its own column; the node
750 * column is the other one. The base arm seeds the counter with
751 * an integer constant in the matching position. */
752 int n_inc = 0;
753 if (!reach_two_tles(rec, rec_tles) || !reach_two_tles(base, base_tles))
754 return false;
755 for (i = 0; i < 2; ++i)
756 if (reach_is_hop_increment((Node *) rec_tles[i]->expr, rec_self_rti,
757 (AttrNumber) (i + 1))) {
758 hops_pos = (AttrNumber) (i + 1);
759 ++n_inc;
760 }
761 if (n_inc != 1)
762 return false;
763 node_pos = (AttrNumber) (3 - hops_pos);
764 if (!reach_int_const((Node *) base_tles[hops_pos - 1]->expr, &hop_seed))
765 return false;
766 if (hop_seed < PG_INT32_MIN/2 || hop_seed > PG_INT32_MAX/2)
767 return false;
768 }
769 }
770
771 /* Base arm: either SELECT <constant> (no FROM), or
772 * SELECT <column> FROM <relation> -- a (possibly probabilistic when
773 * tracked) source set; no other clauses in both forms. */
774 if (base->setOperations != NULL || base->hasAggs || base->hasSubLinks ||
775 base->hasTargetSRFs || base->groupClause != NIL ||
776 base->distinctClause != NIL || base->jointree == NULL ||
777 base->jointree->quals != NULL)
778 return false;
779 tle = hops_mode ? base_tles[node_pos - 1] : reach_single_tle(base);
780 if (tle == NULL)
781 return false;
782 if (base->jointree->fromlist == NIL) {
783 Node *bexpr = reach_strip((Node *) tle->expr);
784 Const *c;
785 Oid outfunc;
786 bool varlena;
787 if (bexpr == NULL || !IsA(bexpr, Const))
788 return false;
789 c = (Const *) bexpr;
790 if (c->constisnull)
791 return false;
792 getTypeOutputInfo(c->consttype, &outfunc, &varlena);
793 out->source_text = OidOutputFunctionCall(outfunc, c->constvalue);
794 } else {
795 RangeTblEntry *srel = NULL;
796 Index srel_rti = 0;
797 Var *sv;
798 Index rti = 0;
799 if (list_length(base->jointree->fromlist) != 1 ||
800 !IsA(linitial(base->jointree->fromlist), RangeTblRef))
801 return false;
802 foreach(lc, base->rtable) {
803 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
804 ++rti;
805 if (r->rtekind != RTE_RELATION || r->relkind != RELKIND_RELATION)
806 return false;
807 if (srel != NULL)
808 return false;
809 srel = r;
810 srel_rti = rti;
811 }
812 if (srel == NULL)
813 return false;
814 sv = (Var *) reach_strip((Node *) tle->expr);
815 if (sv == NULL || !IsA(sv, Var) || sv->varno != srel_rti ||
816 sv->varlevelsup != 0 || sv->varattno <= 0)
817 return false;
818 /* Do not take the provsql column itself as the vertex. */
819 {
820 AttrNumber sprov = get_attnum(srel->relid, PROVSQL_COLUMN_NAME);
821 if (sprov != InvalidAttrNumber && sv->varattno == sprov)
822 return false;
823 }
824 out->source_relid = srel->relid;
825 out->source_attno = sv->varattno;
826 }
827
828 /* Recursive arm: single tracked base relation joined with the CTE. */
829 if (rec->hasAggs || rec->hasWindowFuncs || rec->hasSubLinks ||
830 rec->hasTargetSRFs || rec->groupClause != NIL ||
831 rec->distinctClause != NIL || rec->sortClause != NIL ||
832 rec->havingQual != NULL || rec->limitOffset != NULL ||
833 rec->limitCount != NULL || rec->setOperations != NULL ||
834 rec->groupingSets != NIL)
835 return false;
836 i = 0;
837 {
838 RangeTblEntry *edge_rte = NULL;
839 foreach(lc, rec->rtable) {
840 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
841 ++i;
842 switch (r->rtekind) {
843 case RTE_RELATION:
844 if (edge_rti != 0 || r->relkind != RELKIND_RELATION)
845 return false;
846 edge_rti = i;
847 edge_rte = r;
848 out->relid = r->relid;
849 break;
850 case RTE_SUBQUERY:
851 /* A derived (join-defined) edge relation: deparsed and gathered
852 * as a subquery; its tokens are validated dynamically by the
853 * gathering (conjunctions of base tuples with pairwise disjoint
854 * supports), and any unsupported provenance shape falls back at
855 * run time. An inlined view lands here too. */
856 if (edge_rti != 0 || r->subquery == NULL)
857 return false;
858 edge_rti = i;
859 edge_rte = r;
860 out->relid = InvalidOid;
861 out->edge_sql = pg_get_querydef(copyObject(r->subquery), false);
862 break;
863 case RTE_CTE:
864 if (cte_rti != 0 || !r->self_reference ||
865 strcmp(r->ctename, cte->ctename) != 0)
866 return false;
867 cte_rti = i;
868 break;
869 case RTE_JOIN:
870 break;
871 default:
872 return false;
873 }
874 }
875 if (edge_rti == 0 || cte_rti == 0)
876 return false;
877
878 if (OidIsValid(out->relid)) {
879 /* The edge relation must be provenance-tracked (a provsql UUID
880 * column). */
881 prov_attno = get_attnum(out->relid, PROVSQL_COLUMN_NAME);
882 if (prov_attno == InvalidAttrNumber ||
883 get_atttype(out->relid, prov_attno) != constants->OID_TYPE_UUID)
884 return false;
885 } else {
886 prov_attno = InvalidAttrNumber;
887 /* Column names of the subquery's output, for the gathering. */
888 out->edge_rte_colnames = edge_rte->eref->colnames;
889 }
890 }
891
892 /* Quals: one qual touching the recursive table (the join: a single
893 * equality in the directed shape, a two-way disjunction in the
894 * undirected one), plus optionally deterministic filters over edge
895 * columns alone (folded into the edge gathering). */
896 reach_collect_quals((Node *) rec->jointree, &quals, &ok);
897 if (!ok)
898 return false;
899 {
900 List *edge_only = NIL;
901 bool have_bound = false;
902 int64 bound = 0;
903 bool strict = false;
904 join_qual = NULL;
905 foreach(lc, quals) {
906 Node *q = (Node *) lfirst(lc);
907 ReachVarnosCtx vctx = {NULL, false};
908 reach_varnos_walker(q, &vctx);
909 if (vctx.other)
910 return false;
911 if (bms_is_member(cte_rti, vctx.varnos)) {
912 /* Touches the recursive table: the hop bound (hop-counting
913 * shape, at most once) or THE join qual. */
914 if (hops_mode && !have_bound &&
915 reach_is_hop_bound(q, cte_rti, hops_pos, &bound, &strict))
916 have_bound = true;
917 else if (join_qual == NULL)
918 join_qual = q;
919 else
920 return false;
921 } else if (bms_is_subset(vctx.varnos, bms_make_singleton(edge_rti))) {
922 /* Edge columns (or constants) only: a row filter, kept if
923 * deterministic. */
924 if (contain_volatile_functions(q))
925 return false;
926 edge_only = lappend(edge_only, q);
927 } else
928 return false;
929 }
930 if (join_qual == NULL)
931 return false;
932 if (hops_mode) {
933 /* The bound is mandatory: an unbounded counter has no fixpoint on
934 * cyclic data. hops < B allows B - seed recursive steps,
935 * hops <= B one more; a bound below the seed allows none. */
936 int64 max_len;
937 if (!have_bound)
938 return false;
939 max_len = bound - hop_seed + (strict ? 0 : 1);
940 if (max_len < 0)
941 max_len = 0;
942 if (max_len > 62) /* ReachabilityCompiler::MAX_HOP_BOUND */
943 return false;
944 out->hop_bound = (int) max_len;
945 out->hop_seed = (int) hop_seed;
946 out->hops_position = hops_pos;
947 }
948 out->node_position = node_pos;
949 if (edge_only != NIL && !OidIsValid(out->relid))
950 return false; /* extra filters on a subquery edge: not deparsable here */
951 if (edge_only != NIL) {
952 /* Deparse the filters against the bare relation, for the
953 * edge-gathering query (whose FROM is the relation itself). */
954 Node *conj = (Node *) make_ands_explicit(edge_only);
955 List *dpcontext;
956 conj = copyObject(conj);
957 ChangeVarNodes(conj, edge_rti, 1, 0);
958 dpcontext = deparse_context_for(get_rel_name(out->relid), out->relid);
959 out->edge_quals = deparse_expression(conj, dpcontext, false, false);
960 }
961 }
962
963 /* Target + join qual, directed shape: SELECT e.dst ... ON e.src = r.v. */
964 tle = hops_mode ? rec_tles[node_pos - 1] : reach_single_tle(rec);
965 if (tle == NULL)
966 return false;
967 {
968 Node *texpr = reach_strip((Node *) tle->expr);
969 if (texpr != NULL && IsA(texpr, Var)) {
970 target_var = (Var *) texpr;
971 if (target_var->varno != edge_rti || target_var->varlevelsup != 0 ||
972 target_var->varattno <= 0 || target_var->varattno == prov_attno)
973 return false;
974 out->dst_attno = target_var->varattno;
975
976 if (!IsA(join_qual, OpExpr))
977 return false;
978 eq = (OpExpr *) join_qual;
979 if (list_length(eq->args) != 2)
980 return false;
981 va = (Var *) reach_strip((Node *) linitial(eq->args));
982 vb = (Var *) reach_strip((Node *) lsecond(eq->args));
983 if (va == NULL || vb == NULL || !IsA(va, Var) || !IsA(vb, Var) ||
984 va->varlevelsup != 0 || vb->varlevelsup != 0)
985 return false;
986 if (!op_mergejoinable(eq->opno, exprType((Node *) linitial(eq->args))))
987 return false;
988 if (va->varno == edge_rti && vb->varno == cte_rti)
989 edge_var = va, cte_var = vb;
990 else if (vb->varno == edge_rti && va->varno == cte_rti)
991 edge_var = vb, cte_var = va;
992 else
993 return false;
994 if (cte_var->varattno != node_pos || edge_var->varattno <= 0 ||
995 edge_var->varattno == prov_attno)
996 return false;
997 out->src_attno = edge_var->varattno;
998 out->directed = true;
999 return true;
1000 }
1001
1002 /* Undirected shape:
1003 * SELECT CASE WHEN e.a = r.v THEN e.b ELSE e.a END
1004 * ... ON r.v IN (e.a, e.b)
1005 * The join qual is the two-way disjunction (an OR of two equalities,
1006 * or a ScalarArrayOpExpr over an explicit two-element array). */
1007 if (texpr != NULL && IsA(texpr, CaseExpr)) {
1008 CaseExpr *ce = (CaseExpr *) texpr;
1009 CaseWhen *cw;
1010 OpExpr *weq;
1011 Var *wa, *wb, *wedge, *wcte, *res, *def;
1012 AttrNumber col_a, col_b;
1013
1014 if (ce->arg != NULL || list_length(ce->args) != 1 ||
1015 ce->defresult == NULL)
1016 return false;
1017 cw = (CaseWhen *) linitial(ce->args);
1018 if (!IsA(cw->expr, OpExpr))
1019 return false;
1020 weq = (OpExpr *) cw->expr;
1021 if (list_length(weq->args) != 2 ||
1022 !op_mergejoinable(weq->opno, exprType((Node *) linitial(weq->args))))
1023 return false;
1024 wa = (Var *) reach_strip((Node *) linitial(weq->args));
1025 wb = (Var *) reach_strip((Node *) lsecond(weq->args));
1026 if (wa == NULL || wb == NULL || !IsA(wa, Var) || !IsA(wb, Var) ||
1027 wa->varlevelsup != 0 || wb->varlevelsup != 0)
1028 return false;
1029 if (wa->varno == edge_rti && wb->varno == cte_rti)
1030 wedge = wa, wcte = wb;
1031 else if (wb->varno == edge_rti && wa->varno == cte_rti)
1032 wedge = wb, wcte = wa;
1033 else
1034 return false;
1035 if (wcte->varattno != node_pos)
1036 return false;
1037 res = (Var *) reach_strip((Node *) cw->result);
1038 def = (Var *) reach_strip((Node *) ce->defresult);
1039 if (res == NULL || def == NULL || !IsA(res, Var) || !IsA(def, Var) ||
1040 res->varno != edge_rti || def->varno != edge_rti ||
1041 res->varlevelsup != 0 || def->varlevelsup != 0)
1042 return false;
1043 /* WHEN e.X = r.v THEN e.Y ELSE e.X: the tested column is also the
1044 * default. */
1045 col_a = wedge->varattno; /* X */
1046 col_b = res->varattno; /* Y */
1047 if (def->varattno != col_a || col_a == col_b ||
1048 col_a <= 0 || col_b <= 0 ||
1049 col_a == prov_attno || col_b == prov_attno)
1050 return false;
1051
1052 /* The join disjunction must test r.v against exactly {e.X, e.Y}. */
1053 {
1054 AttrNumber got[2] = {0, 0};
1055 int n = 0;
1056 if (IsA(join_qual, BoolExpr) &&
1057 ((BoolExpr *) join_qual)->boolop == OR_EXPR &&
1058 list_length(((BoolExpr *) join_qual)->args) == 2) {
1059 ListCell *olc;
1060 foreach(olc, ((BoolExpr *) join_qual)->args) {
1061 OpExpr *oeq = (OpExpr *) lfirst(olc);
1062 Var *oa, *ob, *oedge, *octe;
1063 if (!IsA(oeq, OpExpr) || list_length(oeq->args) != 2 ||
1064 !op_mergejoinable(oeq->opno,
1065 exprType((Node *) linitial(oeq->args))))
1066 return false;
1067 oa = (Var *) reach_strip((Node *) linitial(oeq->args));
1068 ob = (Var *) reach_strip((Node *) lsecond(oeq->args));
1069 if (oa == NULL || ob == NULL || !IsA(oa, Var) || !IsA(ob, Var))
1070 return false;
1071 if (oa->varno == edge_rti && ob->varno == cte_rti)
1072 oedge = oa, octe = ob;
1073 else if (ob->varno == edge_rti && oa->varno == cte_rti)
1074 oedge = ob, octe = oa;
1075 else
1076 return false;
1077 if (octe->varattno != node_pos || n >= 2)
1078 return false;
1079 got[n++] = oedge->varattno;
1080 }
1081 } else if (IsA(join_qual, ScalarArrayOpExpr)) {
1082 ScalarArrayOpExpr *sao = (ScalarArrayOpExpr *) join_qual;
1083 Var *scte;
1084 ArrayExpr *arr;
1085 ListCell *alc;
1086 if (!sao->useOr || list_length(sao->args) != 2 ||
1087 !op_mergejoinable(sao->opno,
1088 exprType((Node *) linitial(sao->args))))
1089 return false;
1090 scte = (Var *) reach_strip((Node *) linitial(sao->args));
1091 if (scte == NULL || !IsA(scte, Var) || scte->varno != cte_rti ||
1092 scte->varattno != node_pos)
1093 return false;
1094 arr = (ArrayExpr *) reach_strip((Node *) lsecond(sao->args));
1095 if (arr == NULL || !IsA(arr, ArrayExpr) ||
1096 list_length(arr->elements) != 2)
1097 return false;
1098 foreach(alc, arr->elements) {
1099 Var *ev = (Var *) reach_strip((Node *) lfirst(alc));
1100 if (ev == NULL || !IsA(ev, Var) || ev->varno != edge_rti ||
1101 n >= 2)
1102 return false;
1103 got[n++] = ev->varattno;
1104 }
1105 } else
1106 return false;
1107 if (n != 2 ||
1108 !((got[0] == col_a && got[1] == col_b) ||
1109 (got[0] == col_b && got[1] == col_a)))
1110 return false;
1111 }
1112
1113 out->src_attno = col_a;
1114 out->dst_attno = col_b;
1115 out->directed = false;
1116 return true;
1117 }
1118 }
1119 return false;
1120}
1121#endif
1122
1123#if PG_VERSION_NUM >= 150000
1124/**
1125 * @brief Lower a recursive CTE to a provenance-aware fixpoint (PROTOTYPE).
1126 *
1127 * ProvSQL cannot rewrite @c WITH @c RECURSIVE in place: the recursive term
1128 * forbids the aggregate that provenance-merging needs. Instead, for a recursive
1129 * CTE whose body touches provenance-tracked relations, we deparse the body to
1130 * SQL, run @c provsql.eval_recursive over SPI now (at plan time) -- it evaluates
1131 * @c base @c UNION @c recursive to a fixpoint, letting ProvSQL's own rewriting
1132 * compute the join @c times gates, the untracked base branch's @c gate_one, and
1133 * the @c UNION @c plus merge -- and leaves a tracked temp table named after the
1134 * CTE holding @c (cols..., @c provsql). We then rewrite this CTE reference into
1135 * a plain scan of that table, which the rest of @c process_query handles as an
1136 * ordinary tracked relation.
1137 *
1138 * Returns @c true on success, @c false if the shape is unsupported (the caller
1139 * falls back to the normal error). Boolean provenance, acyclic data, and
1140 * UNION (set) recursion only; the driver guards non-termination. It performs
1141 * SPI work and temp-table creation during planning, and recognises only the
1142 * linear/UNION shape.
1143 */
1144static bool lower_recursive_cte(CommonTableExpr *cte, RangeTblEntry *r,
1145 LoweredCte *entry) {
1146 Query *cteq = (Query *) cte->ctequery;
1147 char *body_text;
1148 StringInfoData cols, coldef, call, scan;
1149 ListCell *lcn, *lct;
1150 bool first = true;
1151 int rc;
1152
1153 if (cteq == NULL || !IsA(cteq, Query))
1154 return false;
1155
1156 /* Only UNION (set) recursion is in scope. Reject UNION ALL (bag semantics,
1157 * which the set-fixpoint driver does not model -- and which is unbounded on a
1158 * graph with several paths) and anything that is not a plain UNION; the
1159 * caller then raises the usual "Recursive CTEs not supported". */
1160 if (cteq->setOperations == NULL ||
1161 !IsA(cteq->setOperations, SetOperationStmt) ||
1162 ((SetOperationStmt *) cteq->setOperations)->op != SETOP_UNION ||
1163 ((SetOperationStmt *) cteq->setOperations)->all)
1164 return false;
1165
1166 /* Reject a term whose target list contains a set-returning function
1167 * (e.g. SELECT unnest(...)). Such a CTE is not a provenance fixpoint we
1168 * can lower, and -- more importantly -- the per-round
1169 * INSERT ... SELECT ... UNION SELECT srf(...) the driver would build
1170 * crashes PostgreSQL's planner: the SRF tlist split leaves a NULL expr
1171 * in the PathTarget, which get_expr_width then dereferences. Bail out so
1172 * the caller raises the usual "Recursive CTEs not supported" error. */
1173 {
1174 ListCell *lc;
1175 foreach(lc, cteq->rtable) {
1176 RangeTblEntry *sub = (RangeTblEntry *) lfirst(lc);
1177 if (sub->rtekind == RTE_SUBQUERY && sub->subquery != NULL &&
1178 sub->subquery->hasTargetSRFs)
1179 return false;
1180 }
1181 }
1182
1183 /* Deparse the whole recursive CTE body to SQL. It references the working
1184 * relation by the CTE name; the driver creates a temp table of that name. */
1185 body_text = pg_get_querydef(cteq, false);
1186
1187 /* User column names (comma list) and column definitions (name type). */
1188 initStringInfo(&cols);
1189 initStringInfo(&coldef);
1190 forboth(lcn, cte->ctecolnames, lct, cte->ctecoltypes) {
1191 char *name = strVal(lfirst(lcn));
1192 Oid typid = lfirst_oid(lct);
1193 if (!first) {
1194 appendStringInfoString(&cols, ", ");
1195 appendStringInfoString(&coldef, ", ");
1196 }
1197 first = false;
1198 appendStringInfoString(&cols, quote_identifier(name));
1199 appendStringInfo(&coldef, "%s %s", quote_identifier(name), format_type_be(typid));
1200 }
1201
1202 if (provsql_verbose >= 20)
1203 provsql_notice("Lowering recursive CTE '%s':\n body = %s\n coldef = %s",
1204 cte->ctename, body_text, coldef.data);
1205
1206 /* Drive the fixpoint now, leaving a tracked temp table `ctename`.
1207 *
1208 * Under the 'absorptive' provenance class (or 'boolean', which
1209 * implies it), a CTE matching the linear reachability shape over a
1210 * tracked base edge relation routes to the decomposition-aligned
1211 * driver, which compiles one certified provenance circuit per
1212 * reachable vertex along a tree decomposition of the data graph
1213 * (linear-size for bounded data treewidth, cyclic data included) and
1214 * falls back to eval_recursive on any failure. The compiled circuit
1215 * is the exact Boolean function of the reachability lineage but only
1216 * the absorptive quotient of its (infinite) recursive semiring
1217 * provenance, hence the class gating; the materialised roots carry
1218 * the 'absorptive' assumption marker accordingly. */
1219 initStringInfo(&call);
1220 {
1221 ReachabilityShape shape = {InvalidOid, 0, 0, NULL, InvalidOid, 0, true,
1222 NULL, NULL, NIL, -1, 0, 0, 1};
1223 constants_t constants = get_constants(true);
1225 detect_reachability_cte(cte, cteq, &constants, &shape)) {
1226 char *src_name;
1227 char *dst_name;
1228 char *coltype = format_type_be(
1229 list_nth_oid(cte->ctecoltypes, shape.node_position - 1));
1230 StringInfoData relarg;
1231 if (OidIsValid(shape.relid)) {
1232 src_name = get_attname(shape.relid, shape.src_attno, false);
1233 dst_name = get_attname(shape.relid, shape.dst_attno, false);
1234 } else {
1235 src_name = strVal(list_nth(shape.edge_rte_colnames,
1236 shape.src_attno - 1));
1237 dst_name = strVal(list_nth(shape.edge_rte_colnames,
1238 shape.dst_attno - 1));
1239 }
1240 initStringInfo(&relarg);
1241 if (OidIsValid(shape.relid))
1242 appendStringInfo(&relarg, "%u::pg_catalog.regclass", shape.relid);
1243 else
1244 appendStringInfoString(&relarg, "NULL::pg_catalog.regclass");
1245 if (provsql_verbose >= 20)
1246 provsql_notice("Recursive CTE '%s' recognised as reachability over %s",
1247 cte->ctename,
1248 OidIsValid(shape.relid) ? get_rel_name(shape.relid)
1249 : "a join-defined edge query");
1250 /* Stash the pieces the post-lowering aggregation planting needs;
1251 * only the plain single-column shape is plantable (a hop-counting
1252 * working table has two columns and per-length tokens). */
1253 if (entry != NULL && shape.hop_bound < 0) {
1254 entry->reach_routed = true;
1255 entry->edge_relid = shape.relid;
1256 entry->src_name = pstrdup(src_name);
1257 entry->dst_name = pstrdup(dst_name);
1258 entry->source_text =
1259 shape.source_text ? pstrdup(shape.source_text) : NULL;
1260 entry->source_relid = shape.source_relid;
1261 entry->source_attname =
1262 OidIsValid(shape.source_relid)
1263 ? get_attname(shape.source_relid, shape.source_attno, false)
1264 : NULL;
1265 entry->directed = shape.directed;
1266 entry->edge_quals =
1267 shape.edge_quals ? pstrdup(shape.edge_quals) : NULL;
1268 entry->edge_sql = shape.edge_sql ? pstrdup(shape.edge_sql) : NULL;
1269 }
1270 appendStringInfo(&call,
1271 "SELECT provsql.eval_reachability(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
1272 relarg.data,
1273 quote_literal_cstr(src_name),
1274 quote_literal_cstr(dst_name),
1275 shape.source_text
1276 ? quote_literal_cstr(shape.source_text) : "NULL",
1277 shape.directed ? "true" : "false",
1278 quote_literal_cstr(cte->ctename),
1279 quote_literal_cstr(cols.data),
1280 quote_literal_cstr(coldef.data),
1281 quote_literal_cstr(coltype),
1282 quote_literal_cstr(body_text),
1283 shape.edge_quals
1284 ? quote_literal_cstr(shape.edge_quals) : "NULL");
1285 if (OidIsValid(shape.source_relid)) {
1286 char *satt = get_attname(shape.source_relid, shape.source_attno,
1287 false);
1288 appendStringInfo(&call, ", %u::pg_catalog.regclass, %s",
1289 shape.source_relid, quote_literal_cstr(satt));
1290 } else if (shape.edge_sql != NULL)
1291 appendStringInfoString(&call, ", NULL, NULL");
1292 if (shape.edge_sql != NULL)
1293 appendStringInfo(&call, ", %s", quote_literal_cstr(shape.edge_sql));
1294 if (shape.hop_bound >= 0)
1295 appendStringInfo(&call,
1296 ", hop_bound => %d, hop_seed => %d, hops_position => %d",
1297 shape.hop_bound, shape.hop_seed,
1298 shape.hops_position);
1299 appendStringInfoString(&call, ")");
1300 } else {
1301 appendStringInfo(&call, "SELECT provsql.eval_recursive(%s, %s, %s, %s)",
1302 quote_literal_cstr(body_text),
1303 quote_literal_cstr(cte->ctename),
1304 quote_literal_cstr(cols.data),
1305 quote_literal_cstr(coldef.data));
1306 }
1307 }
1308 if ((rc = SPI_connect()) != SPI_OK_CONNECT)
1309 provsql_error("Recursive CTE lowering: SPI_connect failed (%d)", rc);
1310 rc = SPI_execute(call.data, false, 0);
1311 SPI_finish();
1312 if (rc < 0)
1313 provsql_error("Recursive CTE lowering: eval_recursive failed (%d)", rc);
1314
1315 /* Replace the CTE reference with a scan of the populated table. */
1316 initStringInfo(&scan);
1317 appendStringInfo(&scan, "SELECT %s FROM %s",
1318 cols.data, quote_identifier(cte->ctename));
1319 {
1320 List *raw = pg_parse_query(scan.data);
1321 List *analyzed = pg_analyze_and_rewrite_fixedparams(
1322 linitial_node(RawStmt, raw), scan.data, NULL, 0, NULL);
1323 r->rtekind = RTE_SUBQUERY;
1324 r->subquery = linitial_node(Query, analyzed);
1325 r->ctename = NULL;
1326 r->ctelevelsup = 0;
1327 }
1328 return true;
1329}
1330#endif
1331
1332/**
1333 * @brief Inline CTE references as subqueries within a query.
1334 *
1335 * Replaces each non-recursive RTE_CTE entry in @p rtable with an
1336 * RTE_SUBQUERY containing a copy of the CTE's query, looking up
1337 * definitions in @p cteList. Recurses into newly inlined subqueries
1338 * to handle nested CTE references (ctelevelsup > 0).
1339 *
1340 * @param rtable Range table to scan for RTE_CTE entries.
1341 * @param cteList CTE definitions to look up names in.
1342 * @param lowered In/out memo of recursive CTEs already lowered (name ->
1343 * scan subquery), so a recursive CTE referenced more than
1344 * once is lowered exactly once and later references reuse
1345 * the first lowering instead of recreating its temp table.
1346 */
1347static void inline_ctes_in_rtable(List *rtable, List *cteList, List **lowered) {
1348 ListCell *lc;
1349 foreach (lc, rtable) {
1350 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
1351 if (r->rtekind == RTE_CTE) {
1352 ListCell *lc2;
1353 foreach (lc2, cteList) {
1354 CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc2);
1355 if (strcmp(cte->ctename, r->ctename) == 0) {
1356 if (cte->cterecursive) {
1357#if PG_VERSION_NUM >= 150000
1358 /* A recursive CTE referenced more than once (e.g. once per
1359 * UNION arm) must be lowered exactly once: re-running
1360 * eval_recursive would DROP and recreate its temp table,
1361 * invalidating the OID the first reference's analyzed scan
1362 * bound to. Reuse the earlier lowering when we have it. */
1363 Query *memo = lookup_lowered_cte(*lowered, cte->ctename);
1364 if (memo != NULL) {
1365 r->rtekind = RTE_SUBQUERY;
1366 r->subquery = copyObject(memo);
1367 r->ctename = NULL;
1368 r->ctelevelsup = 0;
1369 } else {
1370 LoweredCte *e = (LoweredCte *)palloc0(sizeof(LoweredCte));
1371 if (lower_recursive_cte(cte, r, e)) {
1372 /* Lowering succeeded; remember the scan subquery so any
1373 * further reference to this CTE reuses it. */
1374 e->name = pstrdup(cte->ctename);
1375 e->subquery = copyObject(r->subquery);
1376 *lowered = lappend(*lowered, e);
1377 } else
1378 /* Unsupported recursion shape (e.g. UNION ALL). */
1379 provsql_error("Recursive CTEs not supported (unsupported recursion shape)");
1380 }
1381#else
1382 provsql_error("Recursive CTEs not supported");
1383#endif
1384 } else {
1385 r->rtekind = RTE_SUBQUERY;
1386 r->subquery = copyObject((Query *)cte->ctequery);
1387 r->ctename = NULL;
1388 r->ctelevelsup = 0;
1389 /* Recurse: the inlined subquery may reference other CTEs
1390 * from the same cteList */
1391 inline_ctes_in_rtable(r->subquery->rtable, cteList, lowered);
1392 }
1393 break;
1394 }
1395 }
1396 } else if (r->rtekind == RTE_SUBQUERY && r->subquery != NULL) {
1397 /* Recurse into existing subqueries (e.g., UNION branches) to
1398 * inline CTE references they may contain */
1399 inline_ctes_in_rtable(r->subquery->rtable, cteList, lowered);
1400 }
1401 }
1402}
1403
1404#if PG_VERSION_NUM >= 150000
1405/** @brief Context for @c reach_member_local_walker. */
1406typedef struct {
1407 Index t_rti; /**< The member relation's RT index. */
1408 bool ok; /**< Cleared on any non-member-local node. */
1409} ReachMemberQualCtx;
1410
1411/**
1412 * @brief Expression walker: clear @c ok on any node that makes a qual
1413 * not a deterministic filter over the member relation alone --
1414 * a Var of another RTE (or an outer reference), a sublink, or a
1415 * placeholder parameter.
1416 */
1417static bool reach_member_local_walker(Node *node, ReachMemberQualCtx *ctx) {
1418 if (node == NULL)
1419 return false;
1420 if (IsA(node, Var)) {
1421 Var *v = (Var *) node;
1422 if (v->varlevelsup != 0 || v->varno != ctx->t_rti)
1423 ctx->ok = false;
1424 return false;
1425 }
1426 if (IsA(node, SubLink) || IsA(node, Param)) {
1427 ctx->ok = false;
1428 return false;
1429 }
1430 return expression_tree_walker(node, reach_member_local_walker, ctx);
1431}
1432
1433/**
1434 * @brief Whether @p qual is a deterministic filter over the member
1435 * relation @p t_rti alone (no CTE / join / outer references, no
1436 * sublinks or parameters). Volatility is checked separately.
1437 */
1438static bool reach_member_local_qual(Node *qual, Index t_rti) {
1439 ReachMemberQualCtx ctx = { t_rti, true };
1440 reach_member_local_walker(qual, &ctx);
1441 return ctx.ok;
1442}
1443
1444/** @brief One detected grouped-reachability aggregation (see below). */
1445typedef struct ReachAggCandidate {
1446 const char *ctename; /**< The recursive CTE being aggregated. */
1447 const char *node_colname; /**< Its (single) column name. */
1448 Oid member_relid; /**< The joined member relation T. */
1449 const char *member_attname; /**< T's join column. */
1450 const char *group_attname; /**< T's grouping column. */
1451 const char *member_quals; /**< Deparsed member-relation-local filter (against T's columns), or NULL. */
1452} ReachAggCandidate;
1453
1454/**
1455 * @brief Detect, before CTE lowering, the grouped-reachability
1456 * aggregation shape:
1457 *
1458 * WITH RECURSIVE reach(v) AS (...)
1459 * SELECT ... FROM reach r JOIN T ON r.v = T.<a> ... GROUP BY T.<g>
1460 *
1461 * The aggregation collapses each group's per-vertex reach tokens into
1462 * one @c provenance_plus -- an OR of *correlated* events (the vertices
1463 * share edges) that no per-vertex certificate covers. When the CTE is
1464 * later reachability-routed, @c plant_reach_aggregations() pre-creates,
1465 * at the canonical address of each group's token multiset, a certified
1466 * any-member-reachable circuit, so the natural aggregation stays on
1467 * the linear evaluation route. Detection is conservative: a single
1468 * GROUP BY column from a single joined relation, one join equality
1469 * against the CTE's single column, no other quals or range-table
1470 * entries; anything else simply skips the planting (the generic path
1471 * is always correct).
1472 *
1473 * @param q The outer query (CTE references still in place).
1474 * @return List of @c ReachAggCandidate.
1475 */
1476static List *detect_reach_aggregations(Query *q) {
1477 List *out = NIL;
1478 ListCell *lc;
1479 Index rti = 0, cte_rti = 0, t_rti = 0;
1480 RangeTblEntry *cte_rte = NULL, *t_rte = NULL;
1481 SortGroupClause *sgc;
1482 TargetEntry *gtle = NULL;
1483 Var *gvar;
1484 List *quals = NIL;
1485 List *member_quals = NIL;
1486 bool ok = true;
1487 OpExpr *eq;
1488 Var *cte_var, *t_var;
1489 CommonTableExpr *cte = NULL;
1490 ReachAggCandidate *cand;
1491
1492 if (q->setOperations != NULL || list_length(q->groupClause) != 1 ||
1493 q->groupingSets != NIL || q->cteList == NIL)
1494 return NIL;
1495
1496 foreach(lc, q->rtable) {
1497 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1498 ++rti;
1499 switch (r->rtekind) {
1500 case RTE_CTE:
1501 if (cte_rti != 0 || r->ctelevelsup != 0)
1502 return NIL;
1503 cte_rti = rti;
1504 cte_rte = r;
1505 break;
1506 case RTE_RELATION:
1507 if (t_rti != 0 || r->relkind != RELKIND_RELATION)
1508 return NIL;
1509 t_rti = rti;
1510 t_rte = r;
1511 break;
1512 case RTE_JOIN:
1513#if PG_VERSION_NUM >= 180000
1514 case RTE_GROUP:
1515 /* PG 18's synthetic grouping RTE; grouping Vars resolved below. */
1516#endif
1517 break;
1518 default:
1519 return NIL;
1520 }
1521 }
1522 if (cte_rti == 0 || t_rti == 0)
1523 return NIL;
1524
1525 /* The referenced CTE must be a recursive one of this query, with a
1526 * single column (the plain reachability shape; hop-counting working
1527 * tables carry per-length tokens and are not plantable). */
1528 foreach(lc, q->cteList) {
1529 CommonTableExpr *c = (CommonTableExpr *) lfirst(lc);
1530 if (strcmp(c->ctename, cte_rte->ctename) == 0) {
1531 cte = c;
1532 break;
1533 }
1534 }
1535 if (cte == NULL || !cte->cterecursive ||
1536 list_length(cte->ctecolnames) != 1)
1537 return NIL;
1538
1539 /* The grouping column: a bare Var of T. */
1540 sgc = (SortGroupClause *) linitial(q->groupClause);
1541 foreach(lc, q->targetList) {
1542 TargetEntry *te = (TargetEntry *) lfirst(lc);
1543 if (te->ressortgroupref == sgc->tleSortGroupRef) {
1544 gtle = te;
1545 break;
1546 }
1547 }
1548 if (gtle == NULL)
1549 return NIL;
1550 gvar = (Var *) reach_strip((Node *) gtle->expr);
1551#if PG_VERSION_NUM >= 180000
1552 /* On PG 18 the grouping TLE's Var points at the synthetic RTE_GROUP;
1553 * resolve it through the group RTE's groupexprs to the source Var. */
1554 if (q->hasGroupRTE && gvar != NULL && IsA(gvar, Var) &&
1555 gvar->varlevelsup == 0) {
1556 Index gidx = 1;
1557 foreach(lc, q->rtable) {
1558 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1559 if (r->rtekind == RTE_GROUP) {
1560 if (gvar->varno == gidx && gvar->varattno >= 1 &&
1561 gvar->varattno <= list_length(r->groupexprs))
1562 gvar = (Var *) reach_strip(
1563 (Node *) list_nth(r->groupexprs, gvar->varattno - 1));
1564 break;
1565 }
1566 ++gidx;
1567 }
1568 }
1569#endif
1570 if (gvar == NULL || !IsA(gvar, Var) || gvar->varno != t_rti ||
1571 gvar->varlevelsup != 0 || gvar->varattno <= 0)
1572 return NIL;
1573
1574 /* The quals: exactly one join equality (CTE column = T column), the
1575 * rest member-relation-local deterministic filters (which restrict
1576 * the members of each group, like the edge-column filters restrict
1577 * the edges -- they carry no provenance). Anything else (a qual
1578 * touching the CTE side, a sublink, a volatile function) forces
1579 * fallback. */
1580 cte_var = t_var = NULL;
1581 reach_collect_quals((Node *) q->jointree, &quals, &ok);
1582 if (!ok || quals == NIL)
1583 return NIL;
1584 foreach(lc, quals) {
1585 Node *qual = (Node *) lfirst(lc);
1586 /* Is this the join equality? An OpExpr with one CTE Var and one
1587 * T Var. */
1588 if (cte_var == NULL && IsA(qual, OpExpr)) {
1589 eq = (OpExpr *) qual;
1590 if (list_length(eq->args) == 2 &&
1591 op_mergejoinable(eq->opno, exprType((Node *) linitial(eq->args)))) {
1592 Var *ja = (Var *) reach_strip((Node *) linitial(eq->args));
1593 Var *jb = (Var *) reach_strip((Node *) lsecond(eq->args));
1594 if (ja != NULL && jb != NULL && IsA(ja, Var) && IsA(jb, Var) &&
1595 ja->varlevelsup == 0 && jb->varlevelsup == 0) {
1596 if (ja->varno == cte_rti && jb->varno == t_rti) {
1597 cte_var = ja;
1598 t_var = jb;
1599 continue;
1600 } else if (jb->varno == cte_rti && ja->varno == t_rti) {
1601 cte_var = jb;
1602 t_var = ja;
1603 continue;
1604 }
1605 }
1606 }
1607 }
1608 /* Otherwise it must be a member-relation-local deterministic
1609 * filter. */
1610 if (!reach_member_local_qual(qual, t_rti) ||
1611 contain_volatile_functions(qual))
1612 return NIL;
1613 member_quals = lappend(member_quals, qual);
1614 }
1615 if (cte_var == NULL)
1616 return NIL;
1617 if (cte_var->varattno != 1 || t_var->varattno <= 0)
1618 return NIL;
1619
1620 cand = (ReachAggCandidate *) palloc(sizeof(ReachAggCandidate));
1621 cand->ctename = pstrdup(cte->ctename);
1622 cand->node_colname = pstrdup(strVal(linitial(cte->ctecolnames)));
1623 cand->member_relid = t_rte->relid;
1624 cand->member_attname = get_attname(t_rte->relid, t_var->varattno, false);
1625 cand->group_attname = get_attname(t_rte->relid, gvar->varattno, false);
1626 cand->member_quals = NULL;
1627 if (member_quals != NIL) {
1628 /* Deparse with the member relation aliased "t" and column
1629 * references forced table-qualified: the planting applies the
1630 * filter as a WHERE on its member-gathering query, which joins the
1631 * working table with the member relation aliased "t" -- so
1632 * "t.<col>" resolves unambiguously there. */
1633 Node *conj = (Node *) make_ands_explicit(member_quals);
1634 List *dpcontext;
1635 conj = copyObject(conj);
1636 ChangeVarNodes(conj, t_rti, 1, 0);
1637 dpcontext = deparse_context_for("t", t_rte->relid);
1638 cand->member_quals = deparse_expression(conj, dpcontext, true, false);
1639 }
1640 out = lappend(out, cand);
1641 return out;
1642}
1643
1644/**
1645 * @brief Plant the certified any-member gates for the aggregations
1646 * detected by @c detect_reach_aggregations(), via
1647 * @c provsql.plant_reach_any_groups over SPI -- best-effort and
1648 * after lowering, so the working table exists and the
1649 * reachability shape's gathering arguments are known.
1650 */
1651static void plant_reach_aggregations(List *candidates, List *lowered) {
1652 ListCell *lc;
1653 foreach(lc, candidates) {
1654 ReachAggCandidate *cand = (ReachAggCandidate *) lfirst(lc);
1655 ListCell *ll;
1656 LoweredCte *entry = NULL;
1657 StringInfoData call;
1658 int rc;
1659 foreach(ll, lowered) {
1660 LoweredCte *e = (LoweredCte *) lfirst(ll);
1661 if (strcmp(e->name, cand->ctename) == 0) {
1662 entry = e;
1663 break;
1664 }
1665 }
1666 if (entry == NULL || !entry->reach_routed)
1667 continue;
1668
1669 initStringInfo(&call);
1670 appendStringInfo(&call,
1671 "SELECT provsql.plant_reach_any_groups(%s, %s, %u::pg_catalog.regclass, %s, %s, ",
1672 quote_literal_cstr(cand->ctename),
1673 quote_literal_cstr(cand->node_colname),
1674 cand->member_relid,
1675 quote_literal_cstr(cand->member_attname),
1676 quote_literal_cstr(cand->group_attname));
1677 if (OidIsValid(entry->edge_relid))
1678 appendStringInfo(&call, "%u::pg_catalog.regclass", entry->edge_relid);
1679 else
1680 appendStringInfoString(&call, "NULL::pg_catalog.regclass");
1681 appendStringInfo(&call, ", %s, %s, %s, %s, %s, ",
1682 quote_literal_cstr(entry->src_name),
1683 quote_literal_cstr(entry->dst_name),
1684 entry->source_text
1685 ? quote_literal_cstr(entry->source_text) : "NULL",
1686 entry->directed ? "true" : "false",
1687 entry->edge_quals
1688 ? quote_literal_cstr(entry->edge_quals) : "NULL");
1689 if (OidIsValid(entry->source_relid))
1690 appendStringInfo(&call, "%u::pg_catalog.regclass, %s, ",
1691 entry->source_relid,
1692 quote_literal_cstr(entry->source_attname));
1693 else
1694 appendStringInfoString(&call, "NULL, NULL, ");
1695 appendStringInfo(&call, "%s, %s)",
1696 entry->edge_sql
1697 ? quote_literal_cstr(entry->edge_sql) : "NULL",
1698 cand->member_quals
1699 ? quote_literal_cstr(cand->member_quals) : "NULL");
1700
1701 if ((rc = SPI_connect()) != SPI_OK_CONNECT)
1702 provsql_error("Reachability aggregation planting: SPI_connect failed (%d)", rc);
1703 rc = SPI_execute(call.data, false, 0);
1704 SPI_finish();
1705 if (rc < 0)
1706 provsql_error("Reachability aggregation planting failed (%d)", rc);
1707 }
1708}
1709
1710/** @brief One detected reachability self-join conjunction (see below). */
1711typedef struct ReachConjCandidate {
1712 const char *ctename; /**< The self-joined recursive CTE. */
1713 const char *node_colname; /**< Its (single) column name. */
1714 List *const_texts; /**< The constant node bindings, as text
1715 * (multiset, one per reference). */
1716} ReachConjCandidate;
1717
1718/**
1719 * @brief Detect, before CTE lowering, the reachability self-join
1720 * conjunction shape:
1721 *
1722 * WITH RECURSIVE reach(v) AS (...)
1723 * SELECT ... FROM reach r1, ..., reach rk
1724 * WHERE r1.v = c1 AND ... AND rk.v = ck
1725 *
1726 * The row's provenance is the @c provenance_times() of the per-vertex
1727 * reach tokens -- a conjunction of *correlated* events (the vertices
1728 * share edges) that entangles the per-vertex certificates. When the
1729 * CTE is later reachability-routed, @c plant_reach_conjunctions()
1730 * pre-creates, at the times-canonical address of that token multiset,
1731 * a certified all-members-reachable circuit, so the natural "are
1732 * these k vertices all reachable" query stays on the linear
1733 * evaluation route (and min-plus evaluation prices the cheapest
1734 * *joint* covering subgraph). Detection is conservative: every
1735 * range-table entry references the same single-column recursive CTE,
1736 * each constrained by exactly one equality against a constant, no
1737 * other quals, grouping, aggregation or DISTINCT; anything else
1738 * simply skips the planting (the generic path is always correct).
1739 *
1740 * @param q The outer query (CTE references still in place).
1741 * @return List of @c ReachConjCandidate.
1742 */
1743static List *detect_reach_conjunctions(Query *q) {
1744 ListCell *lc;
1745 Index rti = 0;
1746 int nb_refs = 0;
1747 RangeTblEntry *cte_rte = NULL;
1748 CommonTableExpr *cte = NULL;
1749 List *quals = NIL;
1750 bool ok = true;
1751 Node **bound; /* per-RTE constant binding (1-based rti) */
1752 List *const_texts = NIL;
1753 ReachConjCandidate *cand;
1754
1755 if (q->setOperations != NULL || q->groupClause != NIL ||
1756 q->groupingSets != NIL || q->distinctClause != NIL ||
1757 q->havingQual != NULL || q->hasAggs || q->hasWindowFuncs ||
1758 q->hasSubLinks || q->cteList == NIL)
1759 return NIL;
1760
1761 foreach(lc, q->rtable) {
1762 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1763 ++rti;
1764 switch (r->rtekind) {
1765 case RTE_CTE:
1766 if (r->ctelevelsup != 0)
1767 return NIL;
1768 if (cte_rte == NULL)
1769 cte_rte = r;
1770 else if (strcmp(cte_rte->ctename, r->ctename) != 0)
1771 return NIL;
1772 ++nb_refs;
1773 break;
1774 case RTE_JOIN:
1775 break;
1776 default:
1777 return NIL;
1778 }
1779 }
1780 if (nb_refs < 2)
1781 return NIL;
1782
1783 /* The referenced CTE must be a recursive one of this query, with a
1784 * single column (the plain reachability shape). */
1785 foreach(lc, q->cteList) {
1786 CommonTableExpr *c = (CommonTableExpr *) lfirst(lc);
1787 if (strcmp(c->ctename, cte_rte->ctename) == 0) {
1788 cte = c;
1789 break;
1790 }
1791 }
1792 if (cte == NULL || !cte->cterecursive ||
1793 list_length(cte->ctecolnames) != 1)
1794 return NIL;
1795
1796 /* The quals: exactly one constant equality per CTE reference,
1797 * nothing else. */
1798 reach_collect_quals((Node *) q->jointree, &quals, &ok);
1799 if (!ok || list_length(quals) != nb_refs)
1800 return NIL;
1801 bound = (Node **) palloc0(sizeof(Node *) * (list_length(q->rtable) + 1));
1802 foreach(lc, quals) {
1803 OpExpr *eq;
1804 Node *na, *nb;
1805 Var *v;
1806 Const *c;
1807 if (!IsA(lfirst(lc), OpExpr))
1808 return NIL;
1809 eq = (OpExpr *) lfirst(lc);
1810 if (list_length(eq->args) != 2 ||
1811 !op_mergejoinable(eq->opno, exprType((Node *) linitial(eq->args))))
1812 return NIL;
1813 na = reach_strip((Node *) linitial(eq->args));
1814 nb = reach_strip((Node *) lsecond(eq->args));
1815 if (na != NULL && IsA(na, Var) && nb != NULL && IsA(nb, Const)) {
1816 v = (Var *) na;
1817 c = (Const *) nb;
1818 } else if (nb != NULL && IsA(nb, Var) && na != NULL && IsA(na, Const)) {
1819 v = (Var *) nb;
1820 c = (Const *) na;
1821 } else
1822 return NIL;
1823 if (v->varlevelsup != 0 || v->varattno != 1 ||
1824 v->varno < 1 || v->varno > (Index) list_length(q->rtable) ||
1825 list_nth_node(RangeTblEntry, q->rtable, v->varno - 1)->rtekind
1826 != RTE_CTE)
1827 return NIL;
1828 if (c->constisnull || bound[v->varno] != NULL)
1829 return NIL;
1830 bound[v->varno] = (Node *) c;
1831 }
1832
1833 /* Every reference bound: collect the constants as text, in
1834 * range-table order (the canonical recipe sorts anyway). */
1835 rti = 0;
1836 foreach(lc, q->rtable) {
1837 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1838 ++rti;
1839 if (r->rtekind != RTE_CTE)
1840 continue;
1841 if (bound[rti] == NULL)
1842 return NIL;
1843 {
1844 Const *c = (Const *) bound[rti];
1845 Oid out_func;
1846 bool is_varlena;
1847 getTypeOutputInfo(c->consttype, &out_func, &is_varlena);
1848 const_texts = lappend(const_texts,
1849 OidOutputFunctionCall(out_func, c->constvalue));
1850 }
1851 }
1852
1853 cand = (ReachConjCandidate *) palloc(sizeof(ReachConjCandidate));
1854 cand->ctename = pstrdup(cte->ctename);
1855 cand->node_colname = pstrdup(strVal(linitial(cte->ctecolnames)));
1856 cand->const_texts = const_texts;
1857 return list_make1(cand);
1858}
1859
1860/**
1861 * @brief Plant the certified all-members gates for the conjunctions
1862 * detected by @c detect_reach_conjunctions(), via
1863 * @c provsql.plant_reach_cover over SPI -- best-effort and
1864 * after lowering, so the working table exists and the
1865 * reachability shape's gathering arguments are known.
1866 */
1867static void plant_reach_conjunctions(List *candidates, List *lowered) {
1868 ListCell *lc;
1869 foreach(lc, candidates) {
1870 ReachConjCandidate *cand = (ReachConjCandidate *) lfirst(lc);
1871 ListCell *ll;
1872 LoweredCte *entry = NULL;
1873 StringInfoData call;
1874 bool first = true;
1875 int rc;
1876 foreach(ll, lowered) {
1877 LoweredCte *e = (LoweredCte *) lfirst(ll);
1878 if (strcmp(e->name, cand->ctename) == 0) {
1879 entry = e;
1880 break;
1881 }
1882 }
1883 if (entry == NULL || !entry->reach_routed)
1884 continue;
1885
1886 initStringInfo(&call);
1887 appendStringInfo(&call,
1888 "SELECT provsql.plant_reach_cover(%s, %s, ",
1889 quote_literal_cstr(cand->ctename),
1890 quote_literal_cstr(cand->node_colname));
1891 if (OidIsValid(entry->edge_relid))
1892 appendStringInfo(&call, "%u::pg_catalog.regclass", entry->edge_relid);
1893 else
1894 appendStringInfoString(&call, "NULL::pg_catalog.regclass");
1895 appendStringInfo(&call, ", %s, %s, %s, %s, ",
1896 quote_literal_cstr(entry->src_name),
1897 quote_literal_cstr(entry->dst_name),
1898 entry->source_text
1899 ? quote_literal_cstr(entry->source_text) : "NULL",
1900 entry->directed ? "true" : "false");
1901 appendStringInfoString(&call, "ARRAY[");
1902 foreach(ll, cand->const_texts) {
1903 appendStringInfo(&call, "%s%s", first ? "" : ", ",
1904 quote_literal_cstr((const char *) lfirst(ll)));
1905 first = false;
1906 }
1907 appendStringInfo(&call, "]::text[], %s, ",
1908 entry->edge_quals
1909 ? quote_literal_cstr(entry->edge_quals) : "NULL");
1910 if (OidIsValid(entry->source_relid))
1911 appendStringInfo(&call, "%u::pg_catalog.regclass, %s, ",
1912 entry->source_relid,
1913 quote_literal_cstr(entry->source_attname));
1914 else
1915 appendStringInfoString(&call, "NULL, NULL, ");
1916 appendStringInfo(&call, "%s)",
1917 entry->edge_sql
1918 ? quote_literal_cstr(entry->edge_sql) : "NULL");
1919
1920 if ((rc = SPI_connect()) != SPI_OK_CONNECT)
1921 provsql_error("Reachability conjunction planting: SPI_connect failed (%d)", rc);
1922 rc = SPI_execute(call.data, false, 0);
1923 SPI_finish();
1924 if (rc < 0)
1925 provsql_error("Reachability conjunction planting failed (%d)", rc);
1926 }
1927}
1928#endif
1929
1930/**
1931 * @brief Inline all CTE references in @p q as subqueries.
1932 */
1933static void inline_ctes(Query *q) {
1934 List *lowered = NIL;
1935#if PG_VERSION_NUM >= 150000
1936 List *reach_aggs = NIL;
1937 List *reach_conjs = NIL;
1938#endif
1939 if (q->cteList == NIL)
1940 return;
1941#if PG_VERSION_NUM >= 150000
1942 /* Grouped-reachability aggregations and self-join conjunctions are
1943 * detected before lowering (the CTE reference is still
1944 * recognisable) and planted after (the working table then
1945 * exists). */
1946 reach_aggs = detect_reach_aggregations(q);
1947 reach_conjs = detect_reach_conjunctions(q);
1948#endif
1949 inline_ctes_in_rtable(q->rtable, q->cteList, &lowered);
1950#if PG_VERSION_NUM >= 150000
1951 plant_reach_aggregations(reach_aggs, lowered);
1952 plant_reach_conjunctions(reach_conjs, lowered);
1953#endif
1954 q->cteList = NIL;
1955}
1956
1957/**
1958 * @brief Collect all provenance Var nodes reachable from @p q's range table.
1959 *
1960 * Walks every RTE in @p q->rtable:
1961 * - @c RTE_RELATION: looks for a column named @c provsql of type UUID.
1962 * - @c RTE_SUBQUERY: recursively calls @c process_query and splices the
1963 * resulting provenance column back into the parent's column list, also
1964 * patching outer Var attribute numbers if inner columns were removed.
1965 * - @c RTE_CTE: non-recursive CTEs are inlined as @c RTE_SUBQUERY before
1966 * the main loop, then processed as above. Recursive CTEs raise an error.
1967 * - @c RTE_FUNCTION: handled when the function returns a single UUID column
1968 * named @c provsql.
1969 * - @c RTE_JOIN / @c RTE_VALUES / @c RTE_GROUP: handled passively (the
1970 * underlying base-table RTEs supply the tokens).
1971 *
1972 * @param constants Extension OID cache.
1973 * @param q Query whose range table is scanned (subquery RTEs are
1974 * modified in place by the recursive call).
1975 * @param in_boolean_rewrite True when @p q lies under a safe-query (boolean)
1976 * rewrite; threaded into the subquery recursion so the
1977 * joint-width recogniser defers throughout the subtree.
1978 * @param inv_ctx Inversion-free marker context for @p q, or @c NULL; its
1979 * per-subquery child context is threaded into each recursive
1980 * @c process_query call so a flattened view's base inputs
1981 * receive their order markers.
1982 * @return List of @c Var nodes, one per provenance source; @c NIL if the
1983 * query has no provenance-bearing relation.
1984 */
1985static List *get_provenance_attributes(const constants_t *constants, Query *q,
1986 bool in_boolean_rewrite,
1987 const InvFreeMarkerCtx *inv_ctx) {
1988 List *prov_atts = NIL;
1989
1990 for(Index rteid = 1; rteid <= q->rtable->length; ++rteid) {
1991 RangeTblEntry *r = list_nth_node(RangeTblEntry, q->rtable, rteid-1);
1992
1993 if (r->rtekind == RTE_RELATION) {
1994 ListCell *lc;
1995 AttrNumber attid = 1;
1996
1997 /* PG 14 and 15 leave the OLD/NEW rule-placeholder RTEs (relkind
1998 * = RELKIND_VIEW, inFromCl = false) in the rewritten range table
1999 * for any view body. PG 16+ removes them. They are never
2000 * scanned and the planner does not build a RelOptInfo for them,
2001 * so any Var we point at them later fails find_base_rel().
2002 * Filter them out here; any post-rewrite RTE_RELATION whose
2003 * relkind is still a view is one of these artifacts. */
2004 if (r->relkind == RELKIND_VIEW)
2005 continue;
2006
2007 foreach (lc, r->eref->colnames) {
2008 const char *v = strVal(lfirst(lc));
2009
2010 if (!strcmp(v, PROVSQL_COLUMN_NAME) &&
2011 get_atttype(r->relid, attid) == constants->OID_TYPE_UUID) {
2012 prov_atts =
2013 lappend(prov_atts,
2014 make_provenance_attribute(constants, q, r, rteid, attid));
2015 }
2016
2017 ++attid;
2018 }
2019 } else if (r->rtekind == RTE_SUBQUERY) {
2020 bool *inner_removed = NULL;
2021 int old_targetlist_length =
2022 r->subquery->targetList ? r->subquery->targetList->length : 0;
2023 Query *new_subquery =
2024 process_query(constants, r->subquery, &inner_removed, false, false,
2025 in_boolean_rewrite,
2026 (inv_ctx && rteid - 1 < (Index) inv_ctx->natoms)
2027 ? inv_ctx->sub[rteid - 1] : NULL);
2028 if (new_subquery != NULL) {
2029 int i = 0;
2030 int *offset = (int *)palloc(old_targetlist_length * sizeof(int));
2031 unsigned varattnoprovsql;
2032 ListCell *cell, *prev;
2033
2034 r->subquery = new_subquery;
2035
2036 if (inner_removed != NULL) {
2037 for (cell = list_head(r->eref->colnames), prev = NULL;
2038 cell != NULL;) {
2039 if (inner_removed[i]) {
2040 r->eref->colnames =
2041 my_list_delete_cell(r->eref->colnames, cell, prev);
2042 if (prev)
2043 cell = my_lnext(r->eref->colnames, prev);
2044 else
2045 cell = list_head(r->eref->colnames);
2046 } else {
2047 prev = cell;
2048 cell = my_lnext(r->eref->colnames, cell);
2049 }
2050 ++i;
2051 }
2052 for (i = 0; i < old_targetlist_length; ++i) {
2053 offset[i] =
2054 (i == 0 ? 0 : offset[i - 1]) - (inner_removed[i] ? 1 : 0);
2055 }
2056
2057 reduce_varattno_by_offset(q->targetList, rteid, offset);
2058 }
2059
2060 varattnoprovsql = 0;
2061 for (cell = list_head(new_subquery->targetList); cell != NULL;
2062 cell = my_lnext(new_subquery->targetList, cell)) {
2063 TargetEntry *te = (TargetEntry *)lfirst(cell);
2064 ++varattnoprovsql;
2065 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME))
2066 break;
2067 }
2068
2069 /* In a UNION, every branch must expose a provsql column so the set
2070 * operation's columns line up. A branch with no provenance source
2071 * (constant rows, or an untracked relation) is returned unchanged by
2072 * process_query above and has no provsql column; such rows are present
2073 * unconditionally, so their provenance is the multiplicative identity.
2074 * Append a gate_one() provsql column. Set-operation branches never
2075 * carry resjunk entries (those are rejected by the planner), so the
2076 * column lands last -- the same ordinal position as the provsql column
2077 * the provenance-bearing branches get. */
2078 if (cell == NULL && q->setOperations != NULL &&
2079 IsA(q->setOperations, SetOperationStmt) &&
2080 ((SetOperationStmt *)q->setOperations)->op == SETOP_UNION) {
2081 FuncExpr *one_expr = makeNode(FuncExpr);
2082 TargetEntry *one_te;
2083 one_expr->funcid = constants->OID_FUNCTION_GATE_ONE;
2084 one_expr->funcresulttype = constants->OID_TYPE_UUID;
2085 one_expr->args = NIL;
2086 one_expr->location = -1;
2087 one_te = makeTargetEntry((Expr *)one_expr,
2088 list_length(new_subquery->targetList) + 1,
2089 pstrdup(PROVSQL_COLUMN_NAME), false);
2090 new_subquery->targetList = lappend(new_subquery->targetList, one_te);
2091 varattnoprovsql = list_length(new_subquery->targetList);
2092 cell = list_tail(new_subquery->targetList);
2093 }
2094
2095 if (cell != NULL) {
2096 r->eref->colnames = list_insert_nth(r->eref->colnames, varattnoprovsql-1,
2097 makeString(pstrdup(PROVSQL_COLUMN_NAME)));
2098 prov_atts =
2099 lappend(prov_atts, make_provenance_attribute(
2100 constants, q, r, rteid, varattnoprovsql));
2101 }
2102 fix_type_of_aggregation_result(constants, q, rteid,
2103 r->subquery->targetList);
2104 }
2105 } else if (r->rtekind == RTE_JOIN) {
2106 if (r->jointype == JOIN_INNER || r->jointype == JOIN_LEFT ||
2107 r->jointype == JOIN_FULL || r->jointype == JOIN_RIGHT) {
2108 // Nothing to do, there will also be RTE entries for the tables
2109 // that are part of the join, from which we will extract the
2110 // provenance information
2111 } else { // Semijoin (should be feasible, but check whether the second
2112 // provenance information is available) Antijoin (feasible with
2113 // negation)
2114 provsql_error("JOIN type not supported");
2115 }
2116 } else if (r->rtekind == RTE_FUNCTION) {
2117 ListCell *lc;
2118 AttrNumber attid = 1;
2119
2120 foreach (lc, r->functions) {
2121 RangeTblFunction *func = (RangeTblFunction *)lfirst(lc);
2122
2123 if (func->funccolcount == 1) {
2124 FuncExpr *expr = (FuncExpr *)func->funcexpr;
2125 if (expr->funcresulttype == constants->OID_TYPE_UUID &&
2126 !strcmp(get_rte_attribute_name(r, attid), PROVSQL_COLUMN_NAME)) {
2127 prov_atts = lappend(prov_atts, make_provenance_attribute(
2128 constants, q, r, rteid, attid));
2129 }
2130 } else {
2131 provsql_error("FROM function with multiple output "
2132 "attributes not supported");
2133 }
2134
2135 attid += func->funccolcount;
2136 }
2137 } else if (r->rtekind == RTE_VALUES) {
2138 // Nothing to do, no provenance attribute in literal values
2139#if PG_VERSION_NUM >= 120000
2140 } else if (r->rtekind == RTE_RESULT) {
2141 // Empty-FROM RTE (no provenance). Also what the outer-join lowering
2142 // leaves behind when it neutralises an orphaned subquery arm.
2143#endif
2144#if PG_VERSION_NUM >= 180000
2145 } else if (r->rtekind == RTE_GROUP) {
2146 // Introduced in PostgreSQL 18, we already handle group by from
2147 // groupClause
2148#endif
2149 } else {
2150 provsql_error("FROM clause not supported");
2151 }
2152 }
2153
2154 return prov_atts;
2155}
2156
2157/* -------------------------------------------------------------------------
2158 * Target-list surgery
2159 * ------------------------------------------------------------------------- */
2160
2161/**
2162 * @brief Strip provenance UUID columns from @p q's SELECT list.
2163 *
2164 * Scans the target list and removes every @c Var entry whose column name is
2165 * @c provsql and whose type is UUID. The remaining entries have their
2166 * @c resno values decremented to fill the gaps.
2167 *
2168 * @param constants Extension OID cache.
2169 * @param q Query to modify in place.
2170 * @param removed Out-param: allocated boolean array (length =
2171 * original target list length) where @c true means the
2172 * corresponding entry was removed. The caller must
2173 * @c pfree this array when done.
2174 * @return Bitmapset of @c ressortgroupref values whose entries were
2175 * removed (so the caller can clean up GROUP BY / ORDER BY).
2176 */
2177static Bitmapset *
2179 bool **removed) {
2180 int nbRemoved = 0;
2181 int i = 0;
2182 Bitmapset *ressortgrouprefs = NULL;
2183 ListCell *cell, *prev;
2184 *removed = (bool *)palloc(q->targetList->length * sizeof(bool));
2185
2186 for (cell = list_head(q->targetList), prev = NULL; cell != NULL;) {
2187 TargetEntry *rt = (TargetEntry *)lfirst(cell);
2188 (*removed)[i] = false;
2189
2190 if (rt->expr->type == T_Var) {
2191 Var *v = (Var *)rt->expr;
2192
2193 if (v->vartype == constants->OID_TYPE_UUID) {
2194 const char *colname;
2195
2196 if (rt->resname)
2197 colname = rt->resname;
2198 else {
2199 /* This case occurs, for example, when grouping by a column
2200 * that is projected out */
2201 RangeTblEntry *r = (RangeTblEntry *)list_nth(q->rtable, v->varno - 1);
2202 colname = strVal(list_nth(r->eref->colnames, v->varattno - 1));
2203 }
2204
2205 if (!strcmp(colname, PROVSQL_COLUMN_NAME)) {
2206 q->targetList = my_list_delete_cell(q->targetList, cell, prev);
2207
2208 (*removed)[i] = true;
2209 ++nbRemoved;
2210
2211 if (rt->ressortgroupref > 0)
2212 ressortgrouprefs =
2213 bms_add_member(ressortgrouprefs, rt->ressortgroupref);
2214 }
2215 }
2216 }
2217
2218 if ((*removed)[i]) {
2219 if (prev) {
2220 cell = my_lnext(q->targetList, prev);
2221 } else {
2222 cell = list_head(q->targetList);
2223 }
2224 } else {
2225 rt->resno -= nbRemoved;
2226 prev = cell;
2227 cell = my_lnext(q->targetList, cell);
2228 }
2229
2230 ++i;
2231 }
2232
2233 return ressortgrouprefs;
2234}
2235
2236/**
2237 * @brief Strip @c given(evidence) whole-tuple conditioning markers from the
2238 * visible projection, returning the captured evidence expressions.
2239 *
2240 * Walks @p q's target list for visible (non-resjunk) entries whose expression
2241 * is a @c provsql.given(uuid) @c FuncExpr -- the consumed marker emitted by the
2242 * prefix @c | operator / @c given() call. Each match is removed from the
2243 * projection (its @c resno renumbered like @c remove_provenance_attributes_
2244 * select), and its single argument (the per-row evidence token) is collected
2245 * into the returned list, in target-list order. The caller wraps the query's
2246 * output provenance in @c cond(row_provenance, evidence) for each captured
2247 * expression, so multiple markers accumulate as a conjunction of evidence
2248 * (cond folds @c "(X|A)|B = X|(A∧B)").
2249 *
2250 * Returns @c NIL when the query carries no marker (the common case, no cost
2251 * beyond the walk). @p q's target list is modified in place.
2252 */
2253static List *strip_given_markers(const constants_t *constants, Query *q) {
2254 List *evidence = NIL;
2255 int nbRemoved = 0;
2256 ListCell *cell, *prev;
2257
2258 if (!OidIsValid(constants->OID_FUNCTION_GIVEN) || q->targetList == NIL)
2259 return NIL;
2260
2261 for (cell = list_head(q->targetList), prev = NULL; cell != NULL;) {
2262 TargetEntry *rt = (TargetEntry *)lfirst(cell);
2263 bool is_given = false;
2264 List *args = NIL;
2265
2266 /* The marker reaches the rewriter as either a given(...) FuncExpr (the
2267 * function-call spelling) or a prefix `| c` OpExpr (the operator
2268 * spelling, whose opfuncid is given's OID). */
2269 if (!rt->resjunk && IsA(rt->expr, FuncExpr) &&
2270 ((FuncExpr *)rt->expr)->funcid == constants->OID_FUNCTION_GIVEN)
2271 args = ((FuncExpr *)rt->expr)->args;
2272 else if (!rt->resjunk && IsA(rt->expr, OpExpr) &&
2273 ((OpExpr *)rt->expr)->opfuncid == constants->OID_FUNCTION_GIVEN)
2274 args = ((OpExpr *)rt->expr)->args;
2275
2276 if (args != NIL) {
2277 if (list_length(args) != 1)
2278 provsql_error("provsql.given expects exactly one argument");
2279 is_given = true;
2280 evidence = lappend(evidence, linitial(args));
2281 q->targetList = my_list_delete_cell(q->targetList, cell, prev);
2282 ++nbRemoved;
2283 }
2284
2285 if (is_given) {
2286 cell = prev ? my_lnext(q->targetList, prev) : list_head(q->targetList);
2287 } else {
2288 rt->resno -= nbRemoved;
2289 prev = cell;
2290 cell = my_lnext(q->targetList, cell);
2291 }
2292 }
2293
2294 return evidence;
2295}
2296
2297/**
2298 * @brief Semiring operation used to combine provenance tokens.
2299 *
2300 * @c SR_TIMES corresponds to the multiplicative operation (joins, Cartesian
2301 * products), @c SR_PLUS to the additive operation (duplicate elimination), and
2302 * @c SR_MONUS to the monus / set-difference operation (EXCEPT).
2303 *
2304 * @see https://provsql.org/lean-docs/Provenance/QueryRewriting.html
2305 * Lean 4 formalization of rewriting rules (R1)--(R5) and correctness
2306 * theorem @c Query.rewriting_valid.
2307 */
2308typedef enum {
2309 SR_PLUS, ///< Semiring addition (UNION, SELECT DISTINCT)
2310 SR_MONUS, ///< Semiring monus / set difference (EXCEPT)
2311 SR_TIMES ///< Semiring multiplication (JOIN, Cartesian product)
2313
2314/* -------------------------------------------------------------------------
2315 * Semiring expression builders
2316 * ------------------------------------------------------------------------- */
2317
2318/**
2319 * @brief Wrap @p toExpr in a @c provenance_eq gate if @p fromOpExpr is an
2320 * equality between two tracked columns.
2321 *
2322 * Used for where-provenance: each equijoin condition (and some WHERE
2323 * equalities) introduces an @c eq gate that records which attribute positions
2324 * were compared. Because this function is also called for WHERE predicates,
2325 * it applies extra guards and silently returns @p toExpr unchanged when the
2326 * expression does not match the expected shape (both sides must be @c Var
2327 * nodes, possibly wrapped in a @c RelabelType).
2328 *
2329 * @param constants Extension OID cache.
2330 * @param fromOpExpr The equality @c OpExpr to inspect.
2331 * @param toExpr Existing provenance expression to wrap.
2332 * @param columns Per-RTE column-numbering array. EQ gate positions
2333 * carry the same sequential-number caveat as PROJECT
2334 * gate positions (see @c build_column_map()); they are
2335 * only correct when each operand's RTE is either a join
2336 * RTE or a subquery, not a bare provenance-tracked base
2337 * table.
2338 * @return @p toExpr wrapped in @c provenance_eq(toExpr, col1, col2), or
2339 * @p toExpr unchanged if the shape is unsupported.
2340 */
2341static Expr *add_eq_from_OpExpr_to_Expr(const constants_t *constants,
2342 OpExpr *fromOpExpr, Expr *toExpr,
2343 int **columns) {
2344 Datum first_arg;
2345 Datum second_arg;
2346 FuncExpr *fc;
2347 Const *c1;
2348 Const *c2;
2349 Var *v1;
2350 Var *v2;
2351
2352 if (my_lnext(fromOpExpr->args, list_head(fromOpExpr->args))) {
2353 /* Sometimes Var is nested within a RelabelType */
2354 if (IsA(linitial(fromOpExpr->args), Var)) {
2355 v1 = linitial(fromOpExpr->args);
2356 } else if (IsA(linitial(fromOpExpr->args), RelabelType)) {
2357 /* In the WHERE case it can be a Const */
2358 RelabelType *rt1 = linitial(fromOpExpr->args);
2359 if (IsA(rt1->arg, Var)) { /* Can be Param in the WHERE case */
2360 v1 = (Var *)rt1->arg;
2361 } else
2362 return toExpr;
2363 } else
2364 return toExpr;
2365 if (!columns[v1->varno - 1])
2366 return toExpr;
2367 first_arg = Int16GetDatum(columns[v1->varno - 1][v1->varattno - 1]);
2368
2369 if (IsA(lsecond(fromOpExpr->args), Var)) {
2370 v2 = lsecond(fromOpExpr->args);
2371 } else if (IsA(lsecond(fromOpExpr->args), RelabelType)) {
2372 /* In the WHERE case it can be a Const */
2373 RelabelType *rt2 = lsecond(fromOpExpr->args);
2374 if (IsA(rt2->arg, Var)) { /* Can be Param in the WHERE case */
2375 v2 = (Var *)rt2->arg;
2376 } else
2377 return toExpr;
2378 } else
2379 return toExpr;
2380 if (!columns[v2->varno - 1])
2381 return toExpr;
2382 second_arg = Int16GetDatum(columns[v2->varno - 1][v2->varattno - 1]);
2383
2384 fc = makeNode(FuncExpr);
2385 fc->funcid = constants->OID_FUNCTION_PROVENANCE_EQ;
2386 fc->funcvariadic = false;
2387 fc->funcresulttype = constants->OID_TYPE_UUID;
2388 fc->location = -1;
2389
2390 c1 = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int16),
2391 first_arg, false, true);
2392
2393 c2 = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int16),
2394 second_arg, false, true);
2395
2396 fc->args = list_make3(toExpr, c1, c2);
2397 return (Expr *)fc;
2398 }
2399 return toExpr;
2400}
2401
2402/**
2403 * @brief Walk a join-condition or WHERE quals node and add @c eq gates for
2404 * every equality it contains.
2405 *
2406 * Dispatches to @c add_eq_from_OpExpr_to_Expr for simple @c OpExpr nodes
2407 * and iterates over the arguments of an AND @c BoolExpr. OR/NOT inside a
2408 * join ON clause are rejected with an error.
2409 *
2410 * @param constants Extension OID cache.
2411 * @param quals Root of the quals tree (@c OpExpr or @c BoolExpr), or
2412 * @c NULL (in which case @p result is returned unchanged).
2413 * @param result Provenance expression to wrap.
2414 * @param columns Per-RTE column-numbering array.
2415 * @return Updated provenance expression with zero or more @c eq gates added.
2416 */
2417static Expr *add_eq_from_Quals_to_Expr(const constants_t *constants,
2418 Node *quals, Expr *result,
2419 int **columns) {
2420 OpExpr *oe;
2421
2422 if (!quals)
2423 return result;
2424
2425 if (IsA(quals, OpExpr)) {
2426 oe = (OpExpr *)quals;
2427 result = add_eq_from_OpExpr_to_Expr(constants, oe, result, columns);
2428 } /* Sometimes OpExpr is nested within a BoolExpr */
2429 else if (IsA(quals, BoolExpr)) {
2430 BoolExpr *be = (BoolExpr *)quals;
2431 /* In some cases, there can be an OR or a NOT specified with ON clause */
2432 if (be->boolop == OR_EXPR || be->boolop == NOT_EXPR) {
2433 provsql_error("Boolean operators OR and NOT in a join...on "
2434 "clause are not supported");
2435 } else {
2436 ListCell *lc2;
2437 foreach (lc2, be->args) {
2438 if (IsA(lfirst(lc2), OpExpr)) {
2439 oe = (OpExpr *)lfirst(lc2);
2440 result = add_eq_from_OpExpr_to_Expr(constants, oe, result, columns);
2441 }
2442 }
2443 }
2444 } else { /* Handle other cases */
2445 }
2446 return result;
2447}
2448
2449/**
2450 * @brief Build the per-row provenance token for an aggregate rewrite.
2451 *
2452 * Used by both @c make_aggregation_expression (for the agg_token /
2453 * @c provenance_semimod path) and @c make_rv_aggregate_expression (for
2454 * the inline RV-aggregate path). Combines @p prov_atts via
2455 * @c provenance_times (under @c SR_TIMES) or @c provenance_monus
2456 * (under @c SR_MONUS); a single @c prov_att is returned as-is.
2457 *
2458 * @return An @c Expr returning UUID; never @c NULL.
2459 */
2460static Expr *combine_prov_atts(const constants_t *constants,
2461 List *prov_atts, semiring_operation op) {
2462 FuncExpr *combine;
2463
2464 if (my_lnext(prov_atts, list_head(prov_atts)) == NULL)
2465 return (Expr *)linitial(prov_atts);
2466
2467 combine = makeNode(FuncExpr);
2468 if (op == SR_TIMES) {
2469 ArrayExpr *array = makeNode(ArrayExpr);
2470
2471 combine->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
2472 combine->funcvariadic = true;
2473
2474 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
2475 array->element_typeid = constants->OID_TYPE_UUID;
2476 array->elements = prov_atts;
2477 array->location = -1;
2478
2479 combine->args = list_make1(array);
2480 } else { // SR_MONUS
2481 combine->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
2482 combine->args = prov_atts;
2483 }
2484 combine->funcresulttype = constants->OID_TYPE_UUID;
2485 combine->location = -1;
2486 return (Expr *)combine;
2487}
2488
2489/**
2490 * @brief Inline rewrite of an RV-returning aggregate into the same
2491 * aggregate over provenance-wrapped per-row arguments.
2492 *
2493 * Handles any aggregate whose result type is @c random_variable
2494 * (e.g. @c provsql.sum, @c provsql.avg). Replaces @c agg(@c x) with an
2495 * Aggref whose per-row argument is lifted through @c rv_aggregate_semimod
2496 * to attach the row's provenance: each row contributes
2497 * @c mixture(prov_token, X_i, as_random(0)). The aggregate itself
2498 * (@c aggfnoid) is preserved verbatim, so its SFUNC / FFUNC decide what
2499 * gate shape to build from the per-row mixtures. In particular:
2500 * - @c sum(random_variable) collects the mixtures into a single
2501 * @c gate_arith @c PLUS root, realising
2502 * @f$\mathrm{SUM}(x) = \sum_i \mathbf{1}\{\varphi_i\} \cdot X_i@f$;
2503 * - @c avg(random_variable) walks each mixture to recover @c prov_i
2504 * and emits @c gate_arith(DIV, @c sum(mixture(p_i,x_i,0)),
2505 * @c sum(mixture(p_i,1,0))), the natural lift of "AVG = SUM / COUNT"
2506 * into the @c random_variable algebra.
2507 *
2508 * Routing happens at @c make_aggregation_expression on
2509 * @c agg_ref->aggtype @c == @c OID_TYPE_RANDOM_VARIABLE, so any future
2510 * RV-returning aggregate inherits the same per-row provenance wrap
2511 * without further C-side dispatch.
2512 *
2513 * SR_PLUS (UNION outer level) is handled by the caller; this builder
2514 * never runs for SR_PLUS.
2515 */
2516static Expr *make_rv_aggregate_expression(const constants_t *constants,
2517 Aggref *agg_ref, List *prov_atts,
2518 semiring_operation op) {
2519 Expr *prov_expr = combine_prov_atts(constants, prov_atts, op);
2520 Expr *rv_arg = ((TargetEntry *)linitial(agg_ref->args))->expr;
2521 FuncExpr *wrap;
2522 Aggref *new_agg;
2523 TargetEntry *te;
2524
2525 /* Wrap the per-row RV in mixture(prov, rv, as_random(0)) via the
2526 * rv_aggregate_semimod SQL helper. Going through the helper avoids
2527 * having to look up the specific (uuid, random_variable,
2528 * random_variable) overload of mixture and the (double precision)
2529 * overload of as_random at OID-cache time. */
2530 wrap = makeNode(FuncExpr);
2531 wrap->funcid = constants->OID_FUNCTION_RV_AGGREGATE_SEMIMOD;
2532 wrap->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
2533 wrap->args = list_make2(prov_expr, rv_arg);
2534 wrap->location = -1;
2535
2536 /* Rebuild an Aggref calling the SAME aggregate (sum_rv, avg_rv, ...),
2537 * but with the argument now wrapped. Inherit the original Aggref's
2538 * clause positioning; aggargtypes / aggtype stay random_variable. */
2539 te = makeNode(TargetEntry);
2540 te->resno = 1;
2541 te->expr = (Expr *)wrap;
2542
2543 new_agg = makeNode(Aggref);
2544 new_agg->aggfnoid = agg_ref->aggfnoid;
2545 new_agg->aggtype = constants->OID_TYPE_RANDOM_VARIABLE;
2546 new_agg->aggargtypes = list_make1_oid(constants->OID_TYPE_RANDOM_VARIABLE);
2547 new_agg->aggkind = AGGKIND_NORMAL;
2548 new_agg->aggtranstype = InvalidOid;
2549 new_agg->args = list_make1(te);
2550 new_agg->location = agg_ref->location;
2551#if PG_VERSION_NUM >= 140000
2552 new_agg->aggno = new_agg->aggtransno = -1;
2553#endif
2554
2555 return (Expr *)new_agg;
2556}
2557
2558/**
2559 * @brief Build the provenance expression for a single aggregate function.
2560 *
2561 * For @c SR_PLUS (union context) returns the first provenance attribute
2562 * directly. For @c SR_TIMES or @c SR_MONUS, constructs:
2563 * @code
2564 * provenance_aggregate(fn_oid, result_type,
2565 * original_aggref,
2566 * array_agg(provenance_semimod(arg, times_or_monus_token)))
2567 * @endcode
2568 * COUNT(*) and COUNT(expr) are remapped to SUM so that the semimodule
2569 * semantics (scalar × token → token) work correctly.
2570 *
2571 * @param constants Extension OID cache.
2572 * @param agg_ref The original @c Aggref node from the query.
2573 * @param prov_atts List of provenance @c Var nodes.
2574 * @param op Semiring operation (determines how tokens are combined).
2575 * @param is_scalar Aggregation has no GROUP BY (single always-present row).
2576 * @return Provenance expression of type @c agg_token.
2577 */
2578static Expr *make_aggregation_expression(const constants_t *constants,
2579 Aggref *agg_ref, List *prov_atts,
2580 semiring_operation op, bool is_scalar) {
2581 Expr *result;
2582 FuncExpr *expr, *expr_s;
2583 Aggref *agg = makeNode(Aggref);
2584 FuncExpr *plus = makeNode(FuncExpr);
2585 TargetEntry *te_inner = makeNode(TargetEntry);
2586 Const *fn = makeNode(Const);
2587 Const *typ = makeNode(Const);
2588
2589 if (op == SR_PLUS) {
2590 result = linitial(prov_atts);
2591 } else {
2592 Oid aggregation_function = agg_ref->aggfnoid;
2593
2594 /* Aggregates that return random_variable (sum_rv, avg_rv, and any
2595 * future RV-returning aggregate) get a different rewrite: instead
2596 * of going through provenance_semimod (which builds a gate_value
2597 * from CAST(val AS VARCHAR), nonsensical for an RV), each per-row
2598 * argument is wrapped in mixture(prov, rv, as_random(0)) and the
2599 * original aggregate's SFUNC / FFUNC decide what gate shape to
2600 * build from the resulting mixtures. */
2601 if (OidIsValid(constants->OID_TYPE_RANDOM_VARIABLE) &&
2602 agg_ref->aggtype == constants->OID_TYPE_RANDOM_VARIABLE) {
2603 return make_rv_aggregate_expression(constants, agg_ref, prov_atts, op);
2604 }
2605
2606 if (my_lnext(prov_atts, list_head(prov_atts)) == NULL)
2607 expr = linitial(prov_atts);
2608 else {
2609 expr = makeNode(FuncExpr);
2610 if (op == SR_TIMES) {
2611 ArrayExpr *array = makeNode(ArrayExpr);
2612
2613 expr->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
2614 expr->funcvariadic = true;
2615
2616 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
2617 array->element_typeid = constants->OID_TYPE_UUID;
2618 array->elements = prov_atts;
2619 array->location = -1;
2620
2621 expr->args = list_make1(array);
2622 } else { // SR_MONUS
2623 expr->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
2624 expr->args = prov_atts;
2625 }
2626 expr->funcresulttype = constants->OID_TYPE_UUID;
2627 expr->location = -1;
2628 }
2629
2630 // semimodule function
2631 expr_s = makeNode(FuncExpr);
2632 expr_s->funcid = constants->OID_FUNCTION_PROVENANCE_SEMIMOD;
2633 expr_s->funcresulttype = constants->OID_TYPE_UUID;
2634
2635 // check the particular case of count
2636 if (aggregation_function == F_COUNT_) // count(*): counts every row
2637 {
2638 Const *one = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
2639 sizeof(int32), Int32GetDatum(1), false, true);
2640 expr_s->args = list_make2(one, expr);
2641 aggregation_function = F_SUM_INT4;
2642 } else if (aggregation_function == F_COUNT_ANY) // count(expr)
2643 {
2644 /* count(expr) counts only rows where expr IS NOT NULL, but -- unlike the
2645 * other aggregates -- an all-NULL group still has a defined result of 0
2646 * (not NULL), so the row must stay PRESENT in the aggregate to carry the
2647 * group's existence; it just contributes 0. Pass the per-row value
2648 * CASE WHEN expr IS NOT NULL THEN 1 ELSE 0 END: a NULL expr (e.g. the
2649 * NULL-padded rows a LEFT JOIN manufactures) contributes 0 to the count
2650 * yet keeps the group alive, so HAVING count(expr)=0 is correctly true.
2651 * count(*) keeps the constant 1 above. */
2652 Expr *arg = ((TargetEntry *)linitial(agg_ref->args))->expr;
2653 CaseExpr *ce = makeNode(CaseExpr);
2654 CaseWhen *cw = makeNode(CaseWhen);
2655 NullTest *nt = makeNode(NullTest);
2656
2657 nt->arg = (Expr *)arg;
2658 nt->nulltesttype = IS_NOT_NULL;
2659 nt->argisrow = false;
2660 nt->location = -1;
2661
2662 cw->expr = (Expr *)nt;
2663 cw->result = (Expr *)makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
2664 sizeof(int32), Int32GetDatum(1), false,
2665 true);
2666 cw->location = -1;
2667
2668 ce->casetype = constants->OID_TYPE_INT;
2669 ce->casecollid = InvalidOid;
2670 ce->arg = NULL;
2671 ce->args = list_make1(cw);
2672 ce->defresult = (Expr *)makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
2673 sizeof(int32), Int32GetDatum(0), false,
2674 true);
2675 ce->location = -1;
2676
2677 expr_s->args = list_make2(ce, expr);
2678 /* Keep the gate's aggfnoid as count (NOT normalised to F_SUM_INT4 like
2679 * count(*) is): the per-row CASE already makes the value 0/1 so the
2680 * VALUE is the SUM of those, but preserving the COUNT identity tells the
2681 * HAVING evaluators that the empty-group result is 0 (a real, comparable
2682 * value) rather than NULL as a genuine sum would be. This is what lets a
2683 * scalar true-on-empty predicate (count(col)=0, <k, <=k) keep the
2684 * all-absent world: enumerate_valid_worlds routes a COUNT with non-unit
2685 * (0/1) values to the value-aware sum_dp with the empty world retained,
2686 * while count(*) (all-unit) keeps using count_enum. */
2687 } else {
2688 expr_s->args =
2689 list_make2(((TargetEntry *)linitial(agg_ref->args))->expr, expr);
2690 }
2691
2692 expr_s->location = -1;
2693
2694 // aggregating all semirings in an array
2695 te_inner->resno = 1;
2696 te_inner->expr = (Expr *)expr_s;
2697 agg->aggfnoid = constants->OID_FUNCTION_ARRAY_AGG;
2698 agg->aggtype = constants->OID_TYPE_UUID_ARRAY;
2699 agg->args = list_make1(te_inner);
2700 agg->aggkind = AGGKIND_NORMAL;
2701 agg->location = -1;
2702#if PG_VERSION_NUM >= 140000
2703 agg->aggno = agg->aggtransno = -1;
2704#endif
2705
2706 agg->aggargtypes = list_make1_oid(constants->OID_TYPE_UUID);
2707
2708 /* An ordered aggregate (e.g. array_agg(x ORDER BY k)) is order-sensitive:
2709 * the HAVING array_agg evaluator matches the per-row values against the
2710 * constant array in children order, which is the order this token
2711 * aggregate collects rows. Carry the original ORDER BY over (sort keys
2712 * re-attached as junk arguments) so that order is the user's on every
2713 * PostgreSQL version; otherwise it is plan order, which only coincides
2714 * when PG >= 16 pre-sorts the ordered-aggregate input
2715 * (enable_presorted_aggregate). */
2716 if (agg_ref->aggorder != NIL) {
2717 AttrNumber sort_resno = 2;
2718 ListCell *lc;
2719 foreach (lc, agg_ref->args) {
2720 TargetEntry *arg_te = (TargetEntry *)lfirst(lc);
2721 TargetEntry *te_sort;
2722 if (arg_te->ressortgroupref == 0)
2723 continue;
2724 te_sort = makeTargetEntry((Expr *)copyObject(arg_te->expr),
2725 sort_resno++, NULL, true);
2726 te_sort->ressortgroupref = arg_te->ressortgroupref;
2727 agg->args = lappend(agg->args, te_sort);
2728 }
2729 agg->aggorder = (List *)copyObject((Node *)agg_ref->aggorder);
2730 }
2731
2732 // final aggregation function
2733 plus->funcid = constants->OID_FUNCTION_PROVENANCE_AGGREGATE;
2734
2735 fn = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
2736 Int32GetDatum(aggregation_function), false, true);
2737
2738 /* The aggregate result-type OID is passed clean (it is also the cast target
2739 * when an agg_token is used in arithmetic, see wrap_agg_token_with_cast).
2740 * The scalar-aggregation flag travels as a separate boolean argument;
2741 * provenance_aggregate sets the high bit of the gate's info2 and folds the
2742 * flag into the gate's content UUID. */
2743 typ = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
2744 Int32GetDatum(agg_ref->aggtype), false, true);
2745
2746 plus->funcresulttype = constants->OID_TYPE_AGG_TOKEN;
2747 plus->args = list_make5(fn, typ, agg_ref, agg,
2748 makeConst(BOOLOID, -1, InvalidOid, sizeof(bool),
2749 BoolGetDatum(is_scalar), false, true));
2750 plus->location = -1;
2751
2752 result = (Expr *)plus;
2753 }
2754
2755 return result;
2756}
2757
2758/* -------------------------------------------------------------------------
2759 * HAVING / WHERE-on-aggregates rewriting
2760 * ------------------------------------------------------------------------- */
2761
2762/* Forward declaration needed because having_BoolExpr_to_provenance and
2763 * having_Expr_to_provenance_cmp are mutually recursive. */
2764static FuncExpr *having_Expr_to_provenance_cmp(Expr *expr, const constants_t *constants, bool negated);
2765
2766/* Forward declaration: defined alongside the other tree walkers
2767 * further down in the file. */
2768static bool needs_having_lift(Node *havingQual, const constants_t *constants);
2769static Node *normalize_bool_agg_having(Node *n);
2770static Node *peel_agg_casts(Node *n);
2771static Node *try_swap_agg_arith(OpExpr *op, const constants_t *constants);
2772
2773/* ----------------------------------------------------------------------
2774 * Normalising constant arithmetic over an aggregate in a comparison.
2775 *
2776 * A comparison such as `sum(x)+1 > 15`, `sum(x)*2 > 30`, or `-sum(x) > 5`
2777 * is rewritten so the aggregate stands alone and the constant arithmetic is
2778 * folded into the threshold (and the operator flipped where the arithmetic
2779 * is monotone-decreasing): `sum(x) > 14`, `sum(x) > 15`, `sum(x) < -5`.
2780 * The aggregate-comparison evaluators then resolve it as usual. This is the
2781 * "push arithmetic to data values" half of the agg_token arithmetic story.
2782 * -------------------------------------------------------------------- */
2783
2784/* Forward declaration: the regular-leaf handling in the HAVING / RV
2785 * converters needs to recognise an rv-free sub-expression, but
2786 * expr_contains_rv_cmp is defined further down with the rest of the RV
2787 * helpers. */
2788static bool expr_contains_rv_cmp(Node *node, const constants_t *constants);
2789
2790/** @brief Context for @c contains_agg_walker. */
2795
2796static bool contains_agg_walker(Node *node, contains_agg_ctx *ctx) {
2797 if (node == NULL)
2798 return false;
2799 if (IsA(node, Var) &&
2800 ((Var *)node)->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
2801 ctx->found = true;
2802 return true;
2803 }
2804 if (IsA(node, FuncExpr) &&
2805 ((FuncExpr *)node)->funcid ==
2807 ctx->found = true;
2808 return true;
2809 }
2810 return expression_tree_walker(node, contains_agg_walker, ctx);
2811}
2812
2813/** @brief Whether an expression subtree references an aggregate (a bare
2814 * provenance_aggregate call or an agg_token Var). */
2815static bool expr_contains_agg(Node *node, const constants_t *constants) {
2816 contains_agg_ctx ctx = {constants, false};
2817 contains_agg_walker(node, &ctx);
2818 return ctx.found;
2819}
2820
2821/** @brief Numeric value of a (possibly cast-wrapped) @c Const; false if the
2822 * node is not a non-NULL @c Const. */
2823static bool const_as_double(Node *n, double *out) {
2824 Const *c;
2825 Oid outfunc;
2826 bool isvarlena;
2827 char *s;
2828
2829 n = peel_agg_casts(n);
2830 if (n == NULL || !IsA(n, Const) || ((Const *)n)->constisnull)
2831 return false;
2832 c = (Const *)n;
2833 getTypeOutputInfo(c->consttype, &outfunc, &isvarlena);
2834 s = OidOutputFunctionCall(outfunc, c->constvalue);
2835 *out = atof(s);
2836 pfree(s);
2837 return true;
2838}
2839
2840/** @brief Build `l <op> r`, resolving the operator by name. */
2841static Node *build_binop(const char *op, Node *l, Node *r) {
2842 ParseState *p = make_parsestate(NULL);
2843 Node *e = (Node *)make_op(p, list_make1(makeString(pstrdup(op))),
2844 l, r, NULL, -1);
2845 free_parsestate(p);
2846 return e;
2847}
2848
2849/**
2850 * @brief Fold constant arithmetic over an aggregate into the comparison
2851 * threshold.
2852 *
2853 * Given a comparison @c OpExpr one of whose sides is an aggregate wrapped in
2854 * constant arithmetic (the other side being aggregate-free), returns an
2855 * equivalent @c "bare_agg <op'> threshold'" @c OpExpr. Returns @c NULL when
2856 * the comparison has no aggregate, has aggregates on both sides (which cannot
2857 * be folded into a scalar threshold), or uses an arithmetic shape we do not
2858 * fold (e.g. a constant divided by the aggregate).
2859 */
2860static OpExpr *normalize_agg_comparison(OpExpr *cmp,
2861 const constants_t *constants) {
2862 Node *a, *b, *agg_side, *thr;
2863 bool a_agg, b_agg;
2864 Oid opno;
2865 OpExpr *res;
2866
2867 if (list_length(cmp->args) != 2)
2868 return NULL;
2869 a = (Node *)linitial(cmp->args);
2870 b = (Node *)lsecond(cmp->args);
2871 a_agg = expr_contains_agg(a, constants);
2872 b_agg = expr_contains_agg(b, constants);
2873 if (a_agg == b_agg) /* none, or aggregate on both sides */
2874 return NULL;
2875
2876 opno = cmp->opno;
2877 if (a_agg) {
2878 agg_side = a; thr = b;
2879 } else {
2880 agg_side = b; thr = a;
2881 opno = get_commutator(opno); /* orient: aggregate on the left */
2882 if (!OidIsValid(opno))
2883 return NULL;
2884 }
2885
2886 /* Conceptually `agg_side <opno> thr`; peel arithmetic from agg_side into
2887 * thr until agg_side is a bare aggregate. */
2888 for (;;) {
2889 Node *peeled = peel_agg_casts(agg_side);
2890 OpExpr *inner;
2891 char *iname;
2892 int nin;
2893 bool flip = false;
2894
2895 if (peeled == NULL || !IsA(peeled, OpExpr)) {
2896 agg_side = peeled;
2897 break;
2898 }
2899 inner = (OpExpr *)peeled;
2900 iname = get_opname(inner->opno);
2901 if (iname == NULL)
2902 return NULL;
2903 nin = list_length(inner->args);
2904
2905 if (nin == 1 && strcmp(iname, "-") == 0) { /* prefix -agg */
2906 thr = build_binop("-", NULL, thr); /* -thr */
2907 flip = true;
2908 agg_side = (Node *)linitial(inner->args);
2909 } else if (nin == 2) {
2910 Node *x = (Node *)linitial(inner->args);
2911 Node *y = (Node *)lsecond(inner->args);
2912 bool x_agg = expr_contains_agg(x, constants);
2913 bool y_agg = expr_contains_agg(y, constants);
2914 Node *c;
2915 bool agg_left;
2916
2917 if (x_agg == y_agg) /* aggregate on both / neither side of inner op */
2918 return NULL;
2919 if (x_agg) { agg_side = x; c = y; agg_left = true; }
2920 else { agg_side = y; c = x; agg_left = false; }
2921
2922 if (strcmp(iname, "+") == 0) {
2923 thr = build_binop("-", thr, c); /* agg+c: thr-c */
2924 } else if (strcmp(iname, "-") == 0) {
2925 if (agg_left)
2926 thr = build_binop("+", thr, c); /* agg-c: thr+c */
2927 else {
2928 thr = build_binop("-", c, thr); /* c-agg: c-thr */
2929 flip = true;
2930 }
2931 } else if (strcmp(iname, "/") == 0) {
2932 /* agg/c <op> thr <=> agg <op> thr*c -- valid for REAL division
2933 * only. An integer (floored) division does not satisfy this
2934 * equivalence, so leave it unfolded and let the possible-worlds
2935 * enumeration evaluate it with the correct integer-division semantics.
2936 * c/agg cannot be folded either way. */
2937 Oid divtype = exprType(peeled);
2938 double cv;
2939 if (divtype == INT2OID || divtype == INT4OID || divtype == INT8OID)
2940 return NULL;
2941 if (!agg_left || !const_as_double(c, &cv) || cv == 0.0)
2942 return NULL;
2943 thr = build_binop("*", thr, c);
2944 if (cv < 0.0)
2945 flip = true;
2946 } else if (strcmp(iname, "*") == 0) {
2947 /* agg*c <op> thr <=> agg <op> thr/c (exact numeric division, so a
2948 * non-integer threshold like 15.5 is preserved). The aggregate
2949 * comparison evaluator handles fractional and high-scale thresholds
2950 * (minimal-scale via trailing-zero trimming on the fast DP path, and
2951 * an exact-enumeration fallback otherwise). */
2952 double cv;
2953 Node *thr_num;
2954 if (!const_as_double(c, &cv) || cv == 0.0)
2955 return NULL;
2956 thr_num = coerce_to_target_type(NULL, thr, exprType(thr),
2957 NUMERICOID, -1, COERCION_EXPLICIT,
2958 COERCE_EXPLICIT_CAST, -1);
2959 if (thr_num == NULL)
2960 return NULL;
2961 thr = build_binop("/", thr_num, c);
2962 if (cv < 0.0)
2963 flip = true;
2964 } else {
2965 return NULL;
2966 }
2967 } else {
2968 return NULL;
2969 }
2970
2971 if (flip) {
2972 opno = get_commutator(opno);
2973 if (!OidIsValid(opno))
2974 return NULL;
2975 }
2976 }
2977
2978 res = makeNode(OpExpr);
2979 res->opno = opno;
2980 res->opresulttype = BOOLOID;
2981 res->opretset = false;
2982 res->opcollid = InvalidOid;
2983 res->inputcollid = cmp->inputcollid;
2984 res->location = cmp->location;
2985 res->args = list_make2(agg_side, thr); /* aggregate on the left */
2986 set_opfuncid(res);
2987 return res;
2988}
2989
2990/**
2991 * @brief Convert a comparison @c OpExpr on aggregate results into a
2992 * @c provenance_cmp gate expression.
2993 *
2994 * Each argument of @p opExpr must be one of:
2995 * - A @c Var of type @c agg_token (or a @c FuncExpr implicit-cast wrapper
2996 * around one) → cast to UUID via @c agg_token_to_uuid.
2997 * - A scalar @c Const, or a bare grouped-column @c Var (necessarily a GROUP BY
2998 * key in a HAVING clause, hence constant within each group) → wrapped in
2999 * @c provenance_semimod(value, gate_one()).
3000 *
3001 * If @p negated is true the operator OID is replaced by its negator so that
3002 * NOT(a < b) becomes a >= b at the provenance level.
3003 *
3004 * @param opExpr The comparison expression from the HAVING clause.
3005 * @param constants Extension OID cache.
3006 * @param negated Whether the expression appears under a NOT.
3007 * @return A @c provenance_cmp(lhs, op_oid, rhs) @c FuncExpr.
3008 */
3009static FuncExpr *having_OpExpr_to_provenance_cmp(OpExpr *opExpr, const constants_t *constants, bool negated) {
3010 FuncExpr *cmpExpr;
3011 Node *arguments[2];
3012 Const *oid;
3013 Oid opno;
3014
3015 /* Fold any constant arithmetic over the aggregate into the threshold
3016 * (e.g. sum(x)+1 > 15 -> sum(x) > 14), so the comparison reduces to the
3017 * bare-aggregate form the rest of this function and the evaluators expect. */
3018 {
3019 OpExpr *norm = normalize_agg_comparison(opExpr, constants);
3020 if (norm != NULL)
3021 opExpr = norm;
3022 }
3023 opno = opExpr->opno;
3024
3025 for (unsigned i = 0; i < 2; ++i) {
3026 Node *node = (Node *)lfirst(list_nth_cell(opExpr->args, i));
3027 Node *agg_node = NULL;
3028
3029 if (IsA(node, FuncExpr)) {
3030 FuncExpr *fe = (FuncExpr *)node;
3031 if (fe->funcformat == COERCE_IMPLICIT_CAST ||
3032 fe->funcformat == COERCE_EXPLICIT_CAST) {
3033 if (fe->args->length == 1)
3034 node = lfirst(list_head(fe->args));
3035 }
3036 }
3037
3038 // Identify the aggregate side. It is either already agg_token-typed (a
3039 // bare provenance_aggregate / agg_token Var, or agg_token arithmetic on a
3040 // materialised column), or an arithmetic OpExpr over aggregates that still
3041 // needs lowering to the native agg_token operators (e.g. the int8-typed
3042 // sum(x)*sum(y) of a live HAVING) -- which try_swap_agg_arith turns into a
3043 // gate_arith. Either way the result agg_token is cast to its UUID, so the
3044 // gate_cmp wraps the aggregate (or the gate_arith over aggregates); the
3045 // possible-worlds evaluator resolves it.
3046 if (exprType(node) == constants->OID_TYPE_AGG_TOKEN) {
3047 agg_node = node;
3048 } else if (IsA(node, OpExpr) && expr_contains_agg(node, constants)) {
3049 Node *swapped = try_swap_agg_arith((OpExpr *)node, constants);
3050 if (swapped != NULL &&
3051 exprType(swapped) == constants->OID_TYPE_AGG_TOKEN)
3052 agg_node = swapped;
3053 }
3054
3055 if (agg_node != NULL) {
3056 // The aggregate side: add an explicit cast of the agg_token to UUID.
3057 FuncExpr *castToUUID = makeNode(FuncExpr);
3058
3059 castToUUID->funcid = constants->OID_FUNCTION_AGG_TOKEN_UUID;
3060 castToUUID->funcresulttype = constants->OID_TYPE_UUID;
3061 castToUUID->args = list_make1(agg_node);
3062 castToUUID->location = -1;
3063
3064 arguments[i] = (Node *)castToUUID;
3065 } else if (!expr_contains_agg(node, constants)) {
3066 // The value side: a literal, a bare grouped-column Var, or a constant
3067 // arithmetic expression folded from the aggregate side by
3068 // normalize_agg_comparison (e.g. the `15 - col` of sum(x)+col > 15).
3069 // A non-agg_token Var in HAVING is necessarily a GROUP BY key, hence
3070 // constant within each group, so any such aggregate-free expression is
3071 // wrapped like a literal in a value gate carrying the (per-group) datum
3072 // with certain provenance.
3073 FuncExpr *oneExpr = makeNode(FuncExpr);
3074 FuncExpr *semimodExpr = makeNode(FuncExpr);
3075
3076 // gate_one() expression
3077 oneExpr->funcid = constants->OID_FUNCTION_GATE_ONE;
3078 oneExpr->funcresulttype = constants->OID_TYPE_UUID;
3079 oneExpr->args = NIL;
3080 oneExpr->location = -1;
3081
3082 // provenance_semimod(value, gate_one())
3083 semimodExpr->funcid = constants->OID_FUNCTION_PROVENANCE_SEMIMOD;
3084 semimodExpr->funcresulttype = constants->OID_TYPE_UUID;
3085 semimodExpr->args = list_make2((Expr *)node, (Expr *)oneExpr);
3086 semimodExpr->location = -1;
3087
3088 arguments[i] = (Node *)semimodExpr;
3089 } else {
3090 provsql_error("cannot handle complex HAVING expressions");
3091 }
3092 }
3093
3094 if (negated) {
3095 opno = get_negator(opno);
3096 if (!opno)
3097 provsql_error("Missing negator");
3098 }
3099
3100 oid = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
3101 Int32GetDatum(opno), false, true);
3102
3103 cmpExpr = makeNode(FuncExpr);
3104 cmpExpr->funcid = constants->OID_FUNCTION_PROVENANCE_CMP;
3105 cmpExpr->funcresulttype = constants->OID_TYPE_UUID;
3106 cmpExpr->args = list_make3(arguments[0], oid, arguments[1]);
3107 cmpExpr->location = opExpr->location;
3108
3109 return cmpExpr;
3110}
3111
3112/**
3113 * @brief Build @c "⊕(array_agg(K) FILTER (WHERE V IS [NOT] NULL))" -- the
3114 * per-row provenance @c ⊕ over just the value rows (@p filter @c =
3115 * @c IS_NOT_NULL) or just the null-valued rows (@p filter @c = @c IS_NULL)
3116 * of an aggregate's group.
3117 *
3118 * @p base_arr is the aggregate's @c array_agg(provenance_semimod(V, K)) Aggref;
3119 * we copy it, swap its argument to @c K, and add the @c FILTER on @p V (the
3120 * per-row aggregated value, NULL exactly when the row does not contribute). The
3121 * filtered @c array_agg is @c NULL (not an empty array) for a group with no row
3122 * of the requested kind, so it is wrapped in @c COALESCE(..., '{}') -- the STRICT
3123 * @c provenance_plus then yields @c gate_zero rather than NULL.
3124 */
3125static FuncExpr *having_null_filtered_plus(const constants_t *constants,
3126 Aggref *base_arr, Node *V, Node *K,
3127 NullTestType filter) {
3128 Aggref *arr = (Aggref *)copyObject(base_arr);
3129 TargetEntry *te = (TargetEntry *)linitial(arr->args);
3130 NullTest *flt = makeNode(NullTest);
3131 ArrayExpr *empty = makeNode(ArrayExpr);
3132 CoalesceExpr *coal = makeNode(CoalesceExpr);
3133 FuncExpr *plus = makeNode(FuncExpr);
3134
3135 te->expr = (Expr *)copyObject(K); /* array_agg(K) instead of the semimod */
3136
3137 /* The provenance array_agg never carries a user FILTER (make_aggregation_
3138 * expression builds a fresh Aggref), so setting aggfilter directly is safe. */
3139 flt->arg = (Expr *)copyObject(V);
3140 flt->nulltesttype = filter;
3141 flt->argisrow = false;
3142 flt->location = -1;
3143 arr->aggfilter = (Expr *)flt;
3144
3145 empty->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3146 empty->array_collid = InvalidOid;
3147 empty->element_typeid = constants->OID_TYPE_UUID;
3148 empty->elements = NIL;
3149 empty->multidims = false;
3150 empty->location = -1;
3151
3152 coal->coalescetype = constants->OID_TYPE_UUID_ARRAY;
3153 coal->coalescecollid = InvalidOid;
3154 coal->args = list_make2(arr, empty);
3155 coal->location = -1;
3156
3157 plus->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
3158 plus->funcresulttype = constants->OID_TYPE_UUID;
3159 plus->funcvariadic = true;
3160 plus->args = list_make1(coal);
3161 plus->location = -1;
3162 return plus;
3163}
3164
3165/**
3166 * @brief Convert a @c NullTest on an aggregate (@c agg IS [NOT] NULL) into a
3167 * provenance expression.
3168 *
3169 * @c sum / @c avg / @c min / @c max / @c array_agg / @c choose are NULL exactly
3170 * when no value row contributes (every aggregated value absent or NULL). Split
3171 * the group's rows into value rows (@c V @c IS @c NOT @c NULL → tokens @c Kn, the
3172 * rows the aggregate is defined over) and null-valued rows (@c V @c IS @c NULL →
3173 * tokens @c Kz, present but not contributing). Then:
3174 *
3175 * - @c IS @c NOT @c NULL → @c δ(⊕Kn): a value row is present.
3176 * - @c IS @c NULL, scalar (no GROUP BY) → @c "1 ⊖ ⊕Kn": the single result row
3177 * always exists and is NULL exactly when no value row is present.
3178 * - @c IS @c NULL, grouped → @c "δ(⊕Kz) ⊗ (1 ⊖ ⊕Kn)": the group is present via a
3179 * null-valued row while no value row is present, so the aggregate is NULL.
3180 * (When the group has no null-valued rows @c Kz is empty and this collapses to
3181 * @c gate_zero, matching the pre-fix behaviour; the all-NULL-valued group, once
3182 * an unsupported edge, is now handled.)
3183 *
3184 * Splitting on @p V (not the whole-group @c ⊕) is what fixes both directions when
3185 * null-valued rows are present: the old code used @c ⊕ over every row, so
3186 * @c IS @c NOT @c NULL over-counted (a null-only world looked non-NULL) and
3187 * grouped @c IS @c NULL was dropped entirely.
3188 *
3189 * The aggregate must be a direct @c provenance_aggregate call (an @c agg_token
3190 * @c Var coming from a subquery exposes no token array and is rejected).
3191 */
3192static FuncExpr *having_NullTest_to_provenance(NullTest *nt,
3193 const constants_t *constants,
3194 bool negated) {
3195 Node *arg = (Node *)nt->arg;
3196 FuncExpr *pa, *plusKn;
3197 Node *V, *K;
3198 Aggref *base_arr;
3199 bool is_scalar;
3200 NullTestType ntt;
3201
3202 /* Unwrap a single-argument implicit/explicit cast around the aggregate. */
3203 if (IsA(arg, FuncExpr)) {
3204 FuncExpr *fe = (FuncExpr *)arg;
3205 if ((fe->funcformat == COERCE_IMPLICIT_CAST ||
3206 fe->funcformat == COERCE_EXPLICIT_CAST) &&
3207 list_length(fe->args) == 1)
3208 arg = (Node *)linitial(fe->args);
3209 }
3210 if (!IsA(arg, FuncExpr) ||
3211 ((FuncExpr *)arg)->funcid != constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
3212 provsql_error("HAVING IS [NOT] NULL is only supported directly on an "
3213 "aggregate of a provenance-tracked relation");
3214 pa = (FuncExpr *)arg;
3215
3216 /* provenance_aggregate(aggfnoid, aggtype, Aggref, array_agg(semimods),
3217 * is_scalar): the 5th argument is the scalar flag, the 4th is
3218 * array_agg(provenance_semimod(V, K)). Extract the per-row aggregated value V
3219 * and provenance token K (the semimod's two arguments) -- V drives the value /
3220 * null split, K is what we ⊕. We need K rather than the semimod (which carries
3221 * a value gate the probability and Boolean evaluators cannot walk). */
3222 is_scalar = DatumGetBool(((Const *)list_nth(pa->args, 4))->constvalue);
3223 {
3224 Aggref *arr = (Aggref *)list_nth(pa->args, 3);
3225 TargetEntry *te;
3226 FuncExpr *sm;
3227 if (!IsA(arr, Aggref) || list_length(arr->args) != 1)
3228 provsql_error("unexpected aggregate shape in HAVING IS [NOT] NULL");
3229 te = (TargetEntry *)linitial(arr->args);
3230 if (!IsA(te->expr, FuncExpr) ||
3231 ((FuncExpr *)te->expr)->funcid != constants->OID_FUNCTION_PROVENANCE_SEMIMOD)
3232 provsql_error("unexpected aggregate shape in HAVING IS [NOT] NULL");
3233 sm = (FuncExpr *)te->expr;
3234 V = (Node *)list_nth(sm->args, 0); /* per-row aggregated value */
3235 K = (Node *)list_nth(sm->args, 1); /* per-row provenance token */
3236 base_arr = arr;
3237 }
3238
3239 ntt = nt->nulltesttype;
3240 if (negated)
3241 ntt = (ntt == IS_NULL) ? IS_NOT_NULL : IS_NULL;
3242
3243 /* ⊕Kn: the OR of the value-row tokens (V IS NOT NULL). */
3244 plusKn = having_null_filtered_plus(constants, base_arr, V, K, IS_NOT_NULL);
3245
3246 if (ntt == IS_NOT_NULL) {
3247 /* δ(⊕Kn): a value row is present. */
3248 FuncExpr *delta = makeNode(FuncExpr);
3249 delta->funcid = constants->OID_FUNCTION_PROVENANCE_DELTA;
3250 delta->funcresulttype = constants->OID_TYPE_UUID;
3251 delta->args = list_make1(plusKn);
3252 delta->location = -1;
3253 return delta;
3254 }
3255
3256 /* IS NULL: no value row present, i.e. 1 ⊖ ⊕Kn. */
3257 {
3258 FuncExpr *one = makeNode(FuncExpr);
3259 FuncExpr *monus = makeNode(FuncExpr);
3260 one->funcid = constants->OID_FUNCTION_GATE_ONE;
3261 one->funcresulttype = constants->OID_TYPE_UUID;
3262 one->args = NIL;
3263 one->location = -1;
3264 monus->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
3265 monus->funcresulttype = constants->OID_TYPE_UUID;
3266 monus->args = list_make2(one, plusKn); /* 1 ⊖ ⊕Kn */
3267 monus->location = -1;
3268
3269 if (is_scalar)
3270 return monus; /* the single result row always exists */
3271
3272 /* Grouped: the group must also be present, which -- given no value row --
3273 * means a null-valued row is present: δ(⊕Kz) ⊗ (1 ⊖ ⊕Kn). */
3274 {
3275 FuncExpr *plusKz =
3276 having_null_filtered_plus(constants, base_arr, V, K, IS_NULL);
3277 FuncExpr *deltaKz = makeNode(FuncExpr);
3278 FuncExpr *times = makeNode(FuncExpr);
3279 ArrayExpr *factors = makeNode(ArrayExpr);
3280
3281 deltaKz->funcid = constants->OID_FUNCTION_PROVENANCE_DELTA;
3282 deltaKz->funcresulttype = constants->OID_TYPE_UUID;
3283 deltaKz->args = list_make1(plusKz);
3284 deltaKz->location = -1;
3285
3286 factors->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3287 factors->array_collid = InvalidOid;
3288 factors->element_typeid = constants->OID_TYPE_UUID;
3289 factors->elements = list_make2(deltaKz, monus);
3290 factors->multidims = false;
3291 factors->location = -1;
3292
3293 times->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
3294 times->funcresulttype = constants->OID_TYPE_UUID;
3295 times->funcvariadic = true;
3296 times->args = list_make1(factors);
3297 times->location = -1;
3298 return times;
3299 }
3300 }
3301}
3302
3303/**
3304 * @brief Build the deterministic indicator gate for an ordinary (regular)
3305 * comparison: @c regular_indicator(cond) (@c gate_one when @p cond
3306 * holds, @c gate_zero otherwise).
3307 *
3308 * Used for the regular leaves of a MIXED predicate (one that also carries a
3309 * probabilistic comparison), in both the conditioning rewrite and the
3310 * WHERE / HAVING Boolean analysis -- the @c χ case of the HAVING-provenance
3311 * semantics. Under negation, @c χ(¬ψ) = 𝟙 ⊖ χ(ψ): wrap @p expr in @c NOT.
3312 */
3313static FuncExpr *make_regular_indicator(const constants_t *constants,
3314 Expr *expr, bool negated) {
3315 FuncExpr *ind = makeNode(FuncExpr);
3316 if (!OidIsValid(constants->OID_FUNCTION_REGULAR_INDICATOR))
3317 provsql_error("a regular comparison in a probabilistic predicate requires "
3318 "provsql.regular_indicator (schema too old)");
3319 ind->funcid = constants->OID_FUNCTION_REGULAR_INDICATOR;
3320 ind->funcresulttype = constants->OID_TYPE_UUID;
3321 ind->funcretset = false;
3322 ind->funcvariadic = false;
3323 ind->funcformat = COERCE_EXPLICIT_CALL;
3324 ind->funccollid = InvalidOid;
3325 ind->inputcollid = InvalidOid;
3326 ind->args = list_make1(negated
3327 ? (Expr *) makeBoolExpr(NOT_EXPR, list_make1(expr), -1)
3328 : expr);
3329 ind->location = -1;
3330 return ind;
3331}
3332
3333/**
3334 * @brief Convert a Boolean combination of HAVING comparisons into a
3335 * @c provenance_times / @c provenance_plus gate expression.
3336 *
3337 * Applies De Morgan duality when @p negated is true: AND becomes
3338 * @c provenance_plus (OR) and vice-versa. NOT is handled by flipping
3339 * @p negated and delegating to @c having_Expr_to_provenance_cmp.
3340 *
3341 * @param be Boolean expression from the HAVING clause.
3342 * @param constants Extension OID cache.
3343 * @param negated Whether the expression appears under a NOT.
3344 * @return A @c FuncExpr combining the sub-expressions.
3345 */
3346static FuncExpr *having_BoolExpr_to_provenance(BoolExpr *be, const constants_t *constants, bool negated) {
3347 if(be->boolop == NOT_EXPR) {
3348 Expr *expr = (Expr *) lfirst(list_head(be->args));
3349 return having_Expr_to_provenance_cmp(expr, constants, !negated);
3350 } else {
3351 FuncExpr *result;
3352 List *l = NULL;
3353 ListCell *lc;
3354 ArrayExpr *array = makeNode(ArrayExpr);
3355
3356 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3357 array->element_typeid = constants->OID_TYPE_UUID;
3358 array->location = -1;
3359
3360 result = makeNode(FuncExpr);
3361 result->funcresulttype = constants->OID_TYPE_UUID;
3362 result->funcvariadic = true;
3363 result->location = be->location;
3364 result->args = list_make1(array);
3365
3366 if ((be->boolop == AND_EXPR && !negated) || (be->boolop == OR_EXPR && negated))
3367 result->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
3368 else if ((be->boolop == AND_EXPR && negated) || (be->boolop == OR_EXPR && !negated))
3369 result->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
3370 else
3371 provsql_error("Unknown Boolean operator");
3372
3373 foreach (lc, be->args) {
3374 Expr *expr = (Expr *)lfirst(lc);
3375 FuncExpr *arg = having_Expr_to_provenance_cmp(expr, constants, negated);
3376 l = lappend(l, arg);
3377 }
3378
3379 array->elements = l;
3380
3381 return result;
3382 }
3383}
3384
3385/**
3386 * @brief Dispatch a HAVING sub-expression to the appropriate converter.
3387 *
3388 * Entry point for the mutual recursion between
3389 * @c having_BoolExpr_to_provenance and @c having_OpExpr_to_provenance_cmp.
3390 *
3391 * @param expr Sub-expression to convert (@c BoolExpr or @c OpExpr).
3392 * @param constants Extension OID cache.
3393 * @param negated Whether the expression appears under a NOT.
3394 * @return Converted @c FuncExpr.
3395 */
3396static FuncExpr *having_Expr_to_provenance_cmp(Expr *expr, const constants_t *constants, bool negated)
3397{
3398 /* A sub-expression with no aggregate is an ordinary (regular) condition --
3399 * a grouping-column comparison mixed into the HAVING predicate. Its
3400 * predicate-provenance is the deterministic indicator (the χ case), so a
3401 * mixed HAVING such as "SUM(x) > 5 OR region = 'north'" is supported. */
3402 if (!expr_contains_agg((Node *)expr, constants))
3403 return make_regular_indicator(constants, expr, negated);
3404 if (IsA(expr, BoolExpr))
3405 return having_BoolExpr_to_provenance((BoolExpr *)expr, constants, negated);
3406 else if (IsA(expr, OpExpr))
3407 return having_OpExpr_to_provenance_cmp((OpExpr *)expr, constants, negated);
3408 else if (IsA(expr, NullTest))
3409 return having_NullTest_to_provenance((NullTest *)expr, constants, negated);
3410 else
3411 provsql_error("Unknown structure within Boolean expression");
3412}
3413
3414/* -------------------------------------------------------------------------
3415 * Random-variable WHERE-clause rewriting
3416 *
3417 * Mirror of the HAVING trio above. An OpExpr whose @c opfuncid matches
3418 * one of the @c random_variable_{eq,ne,le,lt,ge,gt} procedures is an
3419 * RV comparison; the planner hook lifts it out of @c jointree->quals,
3420 * builds an equivalent @c provenance_cmp(left_uuid, op_oid, right_uuid)
3421 * @c FuncExpr, and conjoins the resulting UUID into the row's
3422 * provenance via @c provenance_times. The lifted WHERE conjunct is
3423 * removed (or the whole WHERE replaced by @c NULL when only RV cmps
3424 * were present); what remains is purely Boolean and the executor
3425 * evaluates it in the usual way. The RV-cmp operators themselves are
3426 * boolean placeholders -- their procedure raises if reached, which can
3427 * happen only when the planner hook is bypassed (e.g. provsql.active
3428 * off).
3429 * ------------------------------------------------------------------------- */
3430
3431/**
3432 * @brief Test whether @p funcoid is one of the @c random_variable_*
3433 * comparison procedures, and if so return its
3434 * @c ComparisonOperator index.
3435 *
3436 * @param constants Extension OID cache.
3437 * @param funcoid Procedure OID to test (typically @c OpExpr->opfuncid).
3438 * @return Index in @c [0..6) on match, @c -1 otherwise. Match indices
3439 * line up with @c ComparisonOperator (EQ=0, NE=1, LE=2, LT=3,
3440 * GE=4, GT=5).
3441 */
3442static int rv_cmp_index(const constants_t *constants, Oid funcoid)
3443{
3444 for (int i = 0; i < 6; ++i) {
3445 if (funcoid == constants->OID_FUNCTION_RV_CMP[i])
3446 return i;
3447 }
3448 return -1;
3449}
3450
3451/**
3452 * @brief Wrap an expression returning @c random_variable in a
3453 * binary-coercible cast to @c uuid.
3454 *
3455 * Operand of the comparison may be a Var, a constant lifted by an
3456 * implicit cast, or another OpExpr (e.g. <tt>a + b</tt>).
3457 * @c random_variable and @c uuid share the same byte layout, so we
3458 * emit a @c RelabelType node -- the planner sees a zero-cost type
3459 * relabel, the executor never dispatches through a runtime
3460 * conversion function.
3461 */
3462static Expr *
3463wrap_random_variable_uuid(Node *operand, const constants_t *constants)
3464{
3465 RelabelType *rt = makeNode(RelabelType);
3466 rt->arg = (Expr *) operand;
3467 rt->resulttype = constants->OID_TYPE_UUID;
3468 rt->resulttypmod = -1;
3469 rt->resultcollid = InvalidOid;
3470 rt->relabelformat = COERCE_IMPLICIT_CAST;
3471 rt->location = -1;
3472 return (Expr *) rt;
3473}
3474
3475/* Forward declaration: the BoolExpr and Expr walkers below are mutually
3476 * recursive (BoolExpr recurses into Expr for each AND/OR child). */
3477static FuncExpr *rv_Expr_to_provenance(Expr *expr,
3478 const constants_t *constants,
3479 bool negated);
3480
3481/**
3482 * @brief Convert a single RV-comparison @c OpExpr into a
3483 * @c provenance_cmp() FuncExpr returning UUID.
3484 *
3485 * If @p negated is true the operator OID is replaced by its negator
3486 * (so <tt>NOT (a &gt; b)</tt> becomes <tt>a &le; b</tt> at the
3487 * provenance level), exactly as @c having_OpExpr_to_provenance_cmp
3488 * does.
3489 *
3490 * @param opExpr The comparison expression from the WHERE clause.
3491 * Must satisfy @c rv_cmp_index(opExpr->opfuncid) &ge; 0;
3492 * callers are responsible for the type check.
3493 * @param constants Extension OID cache.
3494 * @param negated Whether the expression appears under a NOT.
3495 */
3496static FuncExpr *
3497rv_OpExpr_to_provenance_cmp(OpExpr *opExpr, const constants_t *constants,
3498 bool negated)
3499{
3500 FuncExpr *cmpExpr;
3501 Const *oid_const;
3502 Oid opno = opExpr->opno;
3503 Node *left = (Node *)linitial(opExpr->args);
3504 Node *right = (Node *)lsecond(opExpr->args);
3505
3506 if (negated) {
3507 opno = get_negator(opno);
3508 if (!opno)
3509 provsql_error("Missing negator for random_variable comparison");
3510 }
3511
3512 oid_const = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
3513 sizeof(int32), Int32GetDatum(opno), false, true);
3514
3515 cmpExpr = makeNode(FuncExpr);
3516 cmpExpr->funcid = constants->OID_FUNCTION_PROVENANCE_CMP;
3517 cmpExpr->funcresulttype = constants->OID_TYPE_UUID;
3518 cmpExpr->args = list_make3(
3519 wrap_random_variable_uuid(left, constants),
3520 oid_const,
3521 wrap_random_variable_uuid(right, constants));
3522 cmpExpr->location = opExpr->location;
3523
3524 return cmpExpr;
3525}
3526
3527/**
3528 * @brief Convert a Boolean combination of RV comparisons into a
3529 * @c provenance_times / @c provenance_plus expression.
3530 *
3531 * Same De Morgan handling as @c having_BoolExpr_to_provenance: under
3532 * negation, AND ↔ OR (which means PROVENANCE_TIMES ↔ PROVENANCE_PLUS).
3533 * NOT flips @c negated and recurses.
3534 */
3535static FuncExpr *
3536rv_BoolExpr_to_provenance(BoolExpr *be, const constants_t *constants,
3537 bool negated)
3538{
3539 FuncExpr *result;
3540 ArrayExpr *array;
3541 List *l = NIL;
3542 ListCell *lc;
3543
3544 if (be->boolop == NOT_EXPR) {
3545 Expr *child = (Expr *)linitial(be->args);
3546 return rv_Expr_to_provenance(child, constants, !negated);
3547 }
3548
3549 array = makeNode(ArrayExpr);
3550 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3551 array->element_typeid = constants->OID_TYPE_UUID;
3552 array->location = -1;
3553
3554 result = makeNode(FuncExpr);
3555 result->funcresulttype = constants->OID_TYPE_UUID;
3556 result->funcvariadic = true;
3557 result->location = be->location;
3558 result->args = list_make1(array);
3559
3560 if ((be->boolop == AND_EXPR && !negated) ||
3561 (be->boolop == OR_EXPR && negated))
3562 result->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
3563 else if ((be->boolop == AND_EXPR && negated) ||
3564 (be->boolop == OR_EXPR && !negated))
3565 result->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
3566 else
3567 provsql_error("Unknown Boolean operator in random_variable WHERE clause");
3568
3569 foreach (lc, be->args) {
3570 FuncExpr *arg = rv_Expr_to_provenance((Expr *)lfirst(lc),
3571 constants, negated);
3572 l = lappend(l, arg);
3573 }
3574 array->elements = l;
3575
3576 return result;
3577}
3578
3579/**
3580 * @brief Dispatch a WHERE sub-expression to the appropriate RV converter.
3581 *
3582 * Entry point for the mutual recursion between
3583 * @c rv_BoolExpr_to_provenance and @c rv_OpExpr_to_provenance_cmp.
3584 */
3585static FuncExpr *
3586rv_Expr_to_provenance(Expr *expr, const constants_t *constants, bool negated)
3587{
3588 /* A sub-expression with no RV comparison is an ordinary (regular)
3589 * per-tuple condition mixed into the predicate. Its predicate-provenance
3590 * is the deterministic indicator (the χ case), so a mixed WHERE such as
3591 * "X > 3 OR region = 'north'" is supported. */
3592 if (!expr_contains_rv_cmp((Node *)expr, constants))
3593 return make_regular_indicator(constants, expr, negated);
3594 if (IsA(expr, BoolExpr))
3595 return rv_BoolExpr_to_provenance((BoolExpr *)expr, constants, negated);
3596 if (IsA(expr, OpExpr)) {
3597 OpExpr *opExpr = (OpExpr *)expr;
3598 if (rv_cmp_index(constants, opExpr->opfuncid) >= 0)
3599 return rv_OpExpr_to_provenance_cmp(opExpr, constants, negated);
3600 }
3601 provsql_error("Unsupported sub-expression in random_variable WHERE clause "
3602 "(only Boolean combinations of RV comparisons, optionally "
3603 "mixed with ordinary comparisons, are accepted)");
3604 return NULL; /* unreachable, silences -Wreturn-type */
3605}
3606
3607/**
3608 * @brief Walker: does @p node contain a probabilistic (random_variable or
3609 * aggregate) comparison?
3610 *
3611 * Distinguishes a conditioning predicate (which has at least one such
3612 * comparison) from a purely-regular one (an ordinary filter, which is NOT a
3613 * conditioning event and is rejected by the @c "X | (predicate)" rewrite).
3614 */
3615static bool expr_has_probabilistic_cmp(Node *node, void *data) {
3616 const constants_t *constants = (const constants_t *)data;
3617 if (node == NULL)
3618 return false;
3619 if (IsA(node, OpExpr)) {
3620 OpExpr *op = (OpExpr *)node;
3621 if (rv_cmp_index(constants, op->opfuncid) >= 0 ||
3622 expr_contains_agg((Node *)op, constants))
3623 return true;
3624 }
3625 return expression_tree_walker(node, expr_has_probabilistic_cmp, data);
3626}
3627
3628/**
3629 * @brief Convert a Boolean predicate into a provenance condition gate.
3630 *
3631 * Carrier-independent counterpart of @c rv_Expr_to_provenance, used by the
3632 * @c "X | (predicate)" rewrite: the predicate is a Boolean combination
3633 * (AND / OR / NOT) of comparisons. A probabilistic comparison -- a
3634 * random_variable comparison (@c "X > 3", lowered by
3635 * @c rv_OpExpr_to_provenance_cmp) or an agg_token comparison (@c "SUM(x) > 5",
3636 * lowered by @c having_OpExpr_to_provenance_cmp) -- becomes its gate. A
3637 * purely-regular SUB-expression (no probabilistic comparison, e.g.
3638 * @c "region = 'north'") becomes the deterministic indicator
3639 * @c regular_indicator(cond) (@c χ: @c gate_one when it holds, @c gate_zero
3640 * otherwise), so a MIXED predicate is supported per the HAVING-provenance
3641 * semantics. Returns the @c uuid gate representing "the predicate holds":
3642 * AND maps to @c provenance_times, OR to @c provenance_plus, NOT flips
3643 * @c negated (De Morgan), mirroring @c rv_BoolExpr_to_provenance.
3644 */
3645static FuncExpr *predicate_to_condition_gate(Expr *expr,
3646 const constants_t *constants,
3647 bool negated) {
3648 /* A purely-regular sub-expression: a single deterministic indicator (not
3649 * decomposed -- a regular OR must not become a sum of indicators). Under
3650 * negation, χ(¬ψ) = 𝟙 ⊖ χ(ψ), i.e. the indicator of the negated comparison,
3651 * so wrap the operand in NOT. */
3652 if (!expr_has_probabilistic_cmp((Node *)expr, (void *)constants)) {
3653 FuncExpr *ind = makeNode(FuncExpr);
3654 if (!OidIsValid(constants->OID_FUNCTION_REGULAR_INDICATOR))
3655 provsql_error("conditioning on an ordinary (regular) comparison requires "
3656 "provsql.regular_indicator (schema too old)");
3657 ind->funcid = constants->OID_FUNCTION_REGULAR_INDICATOR;
3658 ind->funcresulttype = constants->OID_TYPE_UUID;
3659 ind->funcretset = false;
3660 ind->funcvariadic = false;
3661 ind->funcformat = COERCE_EXPLICIT_CALL;
3662 ind->funccollid = InvalidOid;
3663 ind->inputcollid = InvalidOid;
3664 ind->args = list_make1(negated
3665 ? (Expr *) makeBoolExpr(NOT_EXPR, list_make1(expr), -1)
3666 : expr);
3667 ind->location = -1;
3668 return ind;
3669 }
3670
3671 if (IsA(expr, BoolExpr)) {
3672 BoolExpr *be = (BoolExpr *)expr;
3673 ArrayExpr *array;
3674 FuncExpr *result;
3675 List *l = NIL;
3676 ListCell *lc;
3677
3678 if (be->boolop == NOT_EXPR)
3679 return predicate_to_condition_gate((Expr *)linitial(be->args),
3680 constants, !negated);
3681
3682 array = makeNode(ArrayExpr);
3683 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3684 array->element_typeid = constants->OID_TYPE_UUID;
3685 array->location = -1;
3686
3687 result = makeNode(FuncExpr);
3688 result->funcresulttype = constants->OID_TYPE_UUID;
3689 result->funcvariadic = true;
3690 result->location = be->location;
3691 result->args = list_make1(array);
3692 if ((be->boolop == AND_EXPR && !negated) ||
3693 (be->boolop == OR_EXPR && negated))
3694 result->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
3695 else
3696 result->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
3697
3698 foreach (lc, be->args)
3699 l = lappend(l, predicate_to_condition_gate((Expr *)lfirst(lc),
3700 constants, negated));
3701 array->elements = l;
3702 return result;
3703 }
3704
3705 if (IsA(expr, OpExpr)) {
3706 OpExpr *op = (OpExpr *)expr;
3707 if (rv_cmp_index(constants, op->opfuncid) >= 0)
3708 return rv_OpExpr_to_provenance_cmp(op, constants, negated);
3709 if (expr_contains_agg((Node *)op, constants))
3710 return having_OpExpr_to_provenance_cmp(op, constants, negated);
3711 }
3712
3713 provsql_error("The right operand of the conditioning operator | must be a "
3714 "Boolean combination of random_variable or aggregate "
3715 "comparisons (e.g. \"X | (X > 3)\")");
3716 return NULL; /* unreachable */
3717}
3718
3719/**
3720 * @brief Carrier-routing for an @c "X | (predicate)" placeholder OpExpr.
3721 *
3722 * Maps the placeholder's @c opfuncid to the conditioning constructor to emit
3723 * and its result type; the prefix whole-tuple form (@c given_predicate) maps
3724 * to @c given, whose single argument is the gate (no left operand). Returns
3725 * @c false if @p opfuncid is not a conditioning placeholder.
3726 */
3727static bool cond_predicate_target(const constants_t *constants, Oid opfuncid,
3728 Oid *cond_fn, Oid *result_type,
3729 bool *is_prefix) {
3730 *is_prefix = false;
3731 if (opfuncid == constants->OID_FUNCTION_COND_PREDICATE) {
3732 *cond_fn = constants->OID_FUNCTION_COND;
3733 *result_type = constants->OID_TYPE_UUID;
3734 } else if (opfuncid == constants->OID_FUNCTION_RV_COND_PREDICATE) {
3735 *cond_fn = constants->OID_FUNCTION_RV_COND;
3736 *result_type = constants->OID_TYPE_RANDOM_VARIABLE;
3737 } else if (opfuncid == constants->OID_FUNCTION_AGG_COND_PREDICATE) {
3738 *cond_fn = constants->OID_FUNCTION_AGG_COND;
3739 *result_type = constants->OID_TYPE_AGG_TOKEN;
3740 } else if (opfuncid == constants->OID_FUNCTION_GIVEN_PREDICATE) {
3741 *cond_fn = constants->OID_FUNCTION_GIVEN;
3742 *result_type = constants->OID_TYPE_UUID;
3743 *is_prefix = true;
3744 } else
3745 return false;
3746 return OidIsValid(*cond_fn);
3747}
3748
3749/**
3750 * @brief Mutator: rewrite @c "X | (predicate)" into the carrier's @c cond.
3751 *
3752 * The @c "|" on @c "(carrier, boolean)" -- and the prefix @c "| (boolean)" --
3753 * parses to an @c OpExpr over a conditioning placeholder, whose Boolean
3754 * operand is a combination of probabilistic comparisons. This mutator builds
3755 * the condition gate from that operand (@c predicate_to_condition_gate) and
3756 * replaces the node with @c "cond(X, gate)" for the carrier (or @c given(gate)
3757 * for the prefix whole-tuple form), so the natural @c "X | (X > 3)" /
3758 * @c "SUM(x) | (SUM(x) > 5)" / prefix @c "| (sensor > k)" syntax resolves to
3759 * the existing conditioning surface. The left operand is recursively mutated
3760 * so nested forms (@c "(X | p1) | p2") compose.
3761 */
3762static Node *rewrite_cond_predicate_mutator(Node *node, void *data) {
3763 const constants_t *constants = (const constants_t *)data;
3764 if (node == NULL)
3765 return NULL;
3766 if (IsA(node, OpExpr)) {
3767 OpExpr *op = (OpExpr *)node;
3768 Oid cond_fn, result_type;
3769 bool is_prefix;
3770 if (cond_predicate_target(constants, op->opfuncid, &cond_fn, &result_type,
3771 &is_prefix)) {
3772 FuncExpr *gate, *cond;
3773 Expr *pred = (Expr *)llast(op->args); /* the Boolean predicate operand */
3774 /* A conditioning predicate must carry at least one probabilistic
3775 * (random_variable / aggregate) comparison. A purely-regular predicate
3776 * is an ordinary deterministic filter, not a conditioning event: reject
3777 * it (the regular-indicator leaf only fires for regular comparisons
3778 * MIXED with probabilistic ones). */
3779 if (!expr_has_probabilistic_cmp((Node *)pred, (void *)constants))
3780 provsql_error("the conditioning operator | needs a predicate with at "
3781 "least one random_variable / aggregate comparison; a "
3782 "purely regular condition is an ordinary filter -- use a "
3783 "WHERE clause instead");
3784 gate = predicate_to_condition_gate(pred, constants, false);
3785 cond = makeNode(FuncExpr);
3786 cond->funcid = cond_fn;
3787 cond->funcresulttype = result_type;
3788 cond->funcretset = false;
3789 cond->funcvariadic = false;
3790 cond->funcformat = COERCE_EXPLICIT_CALL;
3791 cond->funccollid = InvalidOid;
3792 cond->inputcollid = InvalidOid;
3793 if (is_prefix)
3794 cond->args = list_make1(gate); /* given(gate): no left operand */
3795 else {
3796 Expr *target = (Expr *)expression_tree_mutator(
3797 (Node *)linitial(op->args), rewrite_cond_predicate_mutator, data);
3798 cond->args = list_make2(target, gate);
3799 }
3800 cond->location = op->location;
3801 return (Node *) cond;
3802 }
3803 }
3804 return expression_tree_mutator(node, rewrite_cond_predicate_mutator, data);
3805}
3806
3807/**
3808 * @brief Rewrite every @c "X | (predicate)" in @p q's own clauses.
3809 *
3810 * Runs early in @c process_query (before the FROM-less early return, the
3811 * given()-marker strip and the probabilistic-qual migration), over the target
3812 * list, WHERE and HAVING. No-op on a schema predating the placeholders.
3813 */
3814static void rewrite_cond_predicates(const constants_t *constants, Query *q) {
3815 if (!OidIsValid(constants->OID_FUNCTION_RV_COND_PREDICATE))
3816 return;
3817 q->targetList = (List *)expression_tree_mutator(
3818 (Node *)q->targetList, rewrite_cond_predicate_mutator, (void *)constants);
3819 if (q->jointree && q->jointree->quals)
3820 q->jointree->quals = expression_tree_mutator(
3821 q->jointree->quals, rewrite_cond_predicate_mutator, (void *)constants);
3822 if (q->havingQual)
3823 q->havingQual = expression_tree_mutator(
3824 q->havingQual, rewrite_cond_predicate_mutator, (void *)constants);
3825}
3826
3827/**
3828 * @brief Test whether an Expr (sub-)tree contains any RV comparison.
3829 *
3830 * Used by the WHERE-clause extractor to decide whether a top-level
3831 * conjunct mentions any random_variable comparator and therefore
3832 * needs lifting (or, if the conjunct mixes RV and non-RV operators
3833 * in a way we cannot rewrite, errors).
3834 */
3835static bool
3836expr_contains_rv_cmp(Node *node, const constants_t *constants)
3837{
3838 if (node == NULL)
3839 return false;
3840 if (IsA(node, OpExpr)) {
3841 OpExpr *opExpr = (OpExpr *)node;
3842 if (rv_cmp_index(constants, opExpr->opfuncid) >= 0)
3843 return true;
3844 }
3845 if (IsA(node, BoolExpr)) {
3846 BoolExpr *be = (BoolExpr *)node;
3847 ListCell *lc;
3848 foreach (lc, be->args) {
3849 if (expr_contains_rv_cmp(lfirst(lc), constants))
3850 return true;
3851 }
3852 return false;
3853 }
3854 return false;
3855}
3856
3857/**
3858 * @brief Test whether @p expr is a Boolean combination of @em only
3859 * random_variable comparisons (no other leaves allowed).
3860 *
3861 * Mirrors @c check_expr_on_aggregate / @c check_boolexpr_on_aggregate
3862 * for the agg_token WHERE-to-HAVING migration path. Recursively
3863 * accepts:
3864 * - @c BoolExpr (AND/OR/NOT) all of whose children pass; and
3865 * - @c OpExpr matching one of the @c random_variable_* comparators.
3866 *
3867 * Anything else (a non-RV @c OpExpr, a @c Var, a @c Const, a non-cmp
3868 * @c FuncExpr) makes the expression mixed and unsupportable by the
3869 * RV-only walker, so the function returns @c false and the caller
3870 * raises a clear error.
3871 */
3872static bool
3873check_expr_on_rv(Expr *expr, const constants_t *constants)
3874{
3875 if (expr == NULL)
3876 return false;
3877 /* An rv-free sub-expression is an ordinary comparison: supported as a
3878 * deterministic indicator (the χ case), so a mix of RV and ordinary
3879 * comparisons is accepted. */
3880 if (!expr_contains_rv_cmp((Node *)expr, constants))
3881 return true;
3882 if (IsA(expr, OpExpr))
3883 return rv_cmp_index(constants, ((OpExpr *)expr)->opfuncid) >= 0;
3884 if (IsA(expr, BoolExpr)) {
3885 BoolExpr *be = (BoolExpr *)expr;
3886 ListCell *lc;
3887 foreach (lc, be->args) {
3888 if (!check_expr_on_rv((Expr *)lfirst(lc), constants))
3889 return false;
3890 }
3891 return true;
3892 }
3893 return false;
3894}
3895
3896/* WHERE conjuncts comparing @c random_variable values are classified
3897 * by the unified classifier @c migrate_probabilistic_quals further down
3898 * in this file; both the agg_token and the random_variable migration
3899 * paths are special cases of one walk over @c q->jointree->quals. See
3900 * the comment on @c qual_class for the routing matrix. */
3901
3902/**
3903 * @brief Build the @c ucq_joint_provenance(descriptor) call substituted for
3904 * a recognised unsafe UCQ's existence provenance.
3905 *
3906 * The descriptor (built by @c provsql_joint_width_descriptor from the
3907 * query's syntax) is wrapped as a @c jsonb @c Const; the resulting
3908 * provenance token is the joint-width compiler's certified d-D, so the
3909 * standard probability / Shapley evaluators answer the @c \#P-hard UCQ
3910 * through the one pipeline. Returns @c NULL if the function cannot be
3911 * resolved (e.g. an older schema without it), leaving the normal path.
3912 */
3914 const char *desc, Expr *fallback)
3915{
3916 FuncCandidateList fcl = FuncnameGetCandidates(
3917 list_make2(makeString("provsql"), makeString("ucq_joint_provenance")),
3918 2, NIL, false, false,
3919#if PG_VERSION_NUM >= 140000
3920 false,
3921#endif
3922 false);
3923 FuncExpr *fe;
3924 Const *c;
3925 Datum jb;
3926
3927 if (fcl == NULL)
3928 return NULL;
3929
3930 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
3931 c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
3932
3933 fe = makeNode(FuncExpr);
3934 fe->funcid = fcl->oid;
3935 fe->funcresulttype = constants->OID_TYPE_UUID;
3936 fe->funcretset = false;
3937 fe->funcvariadic = false;
3938 /* (descriptor, fallback token): the joint-width compiler is tried at
3939 * execution; on any failure the fallback (the normal provenance) is
3940 * returned, so the query never fails. */
3941 fe->args = list_make2(c, fallback);
3942 fe->location = -1;
3943 return (Expr *) fe;
3944}
3945
3946/**
3947 * @brief Build the @c ucq_mobius_provenance(descriptor, fallback) call.
3948 *
3949 * The Möbius-inversion route (safe-UCQ Möbius cancellation, the last missing
3950 * exact route of the Dalvi-Suciu dichotomy) shares the joint-width descriptor.
3951 * It is wired as the runtime fallback of the joint-width call (see
3952 * @c make_provenance_expression): the joint-width compiler is tried first
3953 * (strict priority -- it is more general on its inputs), and only on its
3954 * decline (e.g. the joint treewidth exceeds the cap, as for q9 on adversarial
3955 * data) does the Möbius compiler run; on its own decline the @p fallback (the
3956 * normal provenance) is returned, so the query never fails. Returns @c NULL
3957 * if the function cannot be resolved (older schema), leaving @p fallback.
3958 */
3959static Expr *build_mobius_provenance_expr(const constants_t *constants,
3960 const char *desc, Expr *fallback)
3961{
3962 FuncCandidateList fcl = FuncnameGetCandidates(
3963 list_make2(makeString("provsql"), makeString("ucq_mobius_provenance")),
3964 2, NIL, false, false,
3965#if PG_VERSION_NUM >= 140000
3966 false,
3967#endif
3968 false);
3969 FuncExpr *fe;
3970 Const *c;
3971 Datum jb;
3972
3973 if (fcl == NULL)
3974 return fallback;
3975
3976 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
3977 c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
3978
3979 fe = makeNode(FuncExpr);
3980 fe->funcid = fcl->oid;
3981 fe->funcresulttype = constants->OID_TYPE_UUID;
3982 fe->funcretset = false;
3983 fe->funcvariadic = false;
3984 fe->args = list_make2(c, fallback);
3985 fe->location = -1;
3986 return (Expr *) fe;
3987}
3988
3989/**
3990 * @brief Build the per-answer @c ucq_joint_provenance_answer(...) call for a
3991 * recognised non-Boolean UCQ (head variables exposed in the output).
3992 *
3993 * Per output group the head variables are bound to their values; the
3994 * substituted call materialises the head-pinned certified d-D for that
3995 * answer (@c head_vals is @c ARRAY[head Vars], evaluated per group at
3996 * execution). @c fallback (the normal per-answer provenance) is returned
3997 * on any decline. Heads are int4 Vars (the recogniser's restriction), so
3998 * the value array is a plain @c int4[]. Returns @c NULL if the function
3999 * cannot be resolved or there are no heads.
4000 */
4001static Expr *build_joint_width_answer_expr(const constants_t *constants,
4002 const char *desc, List *head_var_idx,
4003 List *head_exprs, Expr *fallback)
4004{
4005 FuncCandidateList fcl = FuncnameGetCandidates(
4006 list_make2(makeString("provsql"), makeString("ucq_joint_provenance_answer")),
4007 4, NIL, false, false,
4008#if PG_VERSION_NUM >= 140000
4009 false,
4010#endif
4011 false);
4012 FuncExpr *fe;
4013 Const *desc_c, *hv_c;
4014 ArrayExpr *vals;
4015 Datum jb;
4016 Datum *hd;
4017 int n = list_length(head_var_idx), i;
4018 ListCell *lc;
4019 ArrayType *arr;
4020
4021 if (fcl == NULL || head_var_idx == NIL)
4022 return NULL;
4023
4024 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
4025 desc_c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
4026
4027 /* The head variables' query-variable indices as an int4[] Const. */
4028 hd = palloc(n * sizeof(Datum));
4029 i = 0;
4030 foreach (lc, head_var_idx)
4031 hd[i++] = Int32GetDatum(lfirst_int(lc));
4032 arr = construct_array(hd, n, INT4OID, sizeof(int32), true, TYPALIGN_INT);
4033 hv_c = makeConst(INT4ARRAYOID, -1, InvalidOid, -1,
4034 PointerGetDatum(arr), false, false);
4035
4036 /* The head values: ARRAY[head Vars cast to text] (text[]), bound per
4037 * group at run time. The cast is the type's output function (CoerceViaIO),
4038 * matching the (col)::text the gather uses for the element dictionary, so
4039 * a head of any type pins correctly. */
4040 vals = makeNode(ArrayExpr);
4041 vals->array_typeid = TEXTARRAYOID;
4042 vals->element_typeid = TEXTOID;
4043 vals->multidims = false;
4044 vals->elements = NIL;
4045 foreach (lc, head_exprs) {
4046 CoerceViaIO *cio = makeNode(CoerceViaIO);
4047 cio->arg = (Expr *) lfirst(lc);
4048 cio->resulttype = TEXTOID;
4049 cio->resultcollid = DEFAULT_COLLATION_OID;
4050 cio->coerceformat = COERCE_IMPLICIT_CAST;
4051 cio->location = -1;
4052 vals->elements = lappend(vals->elements, (Node *) cio);
4053 }
4054 vals->location = -1;
4055
4056 fe = makeNode(FuncExpr);
4057 fe->funcid = fcl->oid;
4058 fe->funcresulttype = constants->OID_TYPE_UUID;
4059 fe->funcretset = false;
4060 fe->funcvariadic = false;
4061 fe->args = list_make4(desc_c, hv_c, (Expr *) vals, fallback);
4062 fe->location = -1;
4063 return (Expr *) fe;
4064}
4065
4066/**
4067 * @brief Build the per-answer @c ucq_mobius_provenance_answer(...) call,
4068 * identical in shape to @c build_joint_width_answer_expr but for the
4069 * Möbius route. Wired as the runtime fallback of the joint-width
4070 * per-answer call, so the joint-width single-DP keeps priority and the
4071 * Möbius head-pinned compile runs only on its decline. Returns
4072 * @p fallback if the function cannot be resolved.
4073 */
4074static Expr *build_mobius_answer_expr(const constants_t *constants,
4075 const char *desc, List *head_var_idx,
4076 List *head_exprs, Expr *fallback)
4077{
4078 FuncCandidateList fcl = FuncnameGetCandidates(
4079 list_make2(makeString("provsql"), makeString("ucq_mobius_provenance_answer")),
4080 4, NIL, false, false,
4081#if PG_VERSION_NUM >= 140000
4082 false,
4083#endif
4084 false);
4085 FuncExpr *fe;
4086 Const *desc_c, *hv_c;
4087 ArrayExpr *vals;
4088 Datum jb;
4089 Datum *hd;
4090 int n = list_length(head_var_idx), i;
4091 ListCell *lc;
4092 ArrayType *arr;
4093
4094 if (fcl == NULL || head_var_idx == NIL)
4095 return fallback;
4096
4097 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
4098 desc_c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
4099
4100 hd = palloc(n * sizeof(Datum));
4101 i = 0;
4102 foreach (lc, head_var_idx)
4103 hd[i++] = Int32GetDatum(lfirst_int(lc));
4104 arr = construct_array(hd, n, INT4OID, sizeof(int32), true, TYPALIGN_INT);
4105 hv_c = makeConst(INT4ARRAYOID, -1, InvalidOid, -1,
4106 PointerGetDatum(arr), false, false);
4107
4108 vals = makeNode(ArrayExpr);
4109 vals->array_typeid = TEXTARRAYOID;
4110 vals->element_typeid = TEXTOID;
4111 vals->multidims = false;
4112 vals->elements = NIL;
4113 foreach (lc, head_exprs) {
4114 CoerceViaIO *cio = makeNode(CoerceViaIO);
4115 cio->arg = (Expr *) lfirst(lc);
4116 cio->resulttype = TEXTOID;
4117 cio->resultcollid = DEFAULT_COLLATION_OID;
4118 cio->coerceformat = COERCE_IMPLICIT_CAST;
4119 cio->location = -1;
4120 vals->elements = lappend(vals->elements, (Node *) cio);
4121 }
4122 vals->location = -1;
4123
4124 fe = makeNode(FuncExpr);
4125 fe->funcid = fcl->oid;
4126 fe->funcresulttype = constants->OID_TYPE_UUID;
4127 fe->funcretset = false;
4128 fe->funcvariadic = false;
4129 fe->args = list_make4(desc_c, hv_c, (Expr *) vals, fallback);
4130 fe->location = -1;
4131 return (Expr *) fe;
4132}
4133
4134/**
4135 * @brief Wrap a Möbius call in @c mobius_or_null(...): the token if it roots a
4136 * @c gate_mobius (a Möbius success), else NULL (a Möbius decline returns
4137 * the lineage, never a @c gate_mobius). Returns @p mobius_call
4138 * unwrapped if the helper cannot be resolved (older schema).
4139 */
4140static Expr *wrap_mobius_or_null(const constants_t *constants, Expr *mobius_call)
4141{
4142 FuncCandidateList fcl = FuncnameGetCandidates(
4143 list_make2(makeString("provsql"), makeString("mobius_or_null")),
4144 1, NIL, false, false,
4145#if PG_VERSION_NUM >= 140000
4146 false,
4147#endif
4148 false);
4149 FuncExpr *fe;
4150
4151 if (fcl == NULL)
4152 return mobius_call;
4153
4154 fe = makeNode(FuncExpr);
4155 fe->funcid = fcl->oid;
4156 fe->funcresulttype = constants->OID_TYPE_UUID;
4157 fe->funcretset = false;
4158 fe->funcvariadic = false;
4159 fe->args = list_make1(mobius_call);
4160 fe->location = -1;
4161 return (Expr *) fe;
4162}
4163
4164/**
4165 * @brief Combine the Möbius and joint-width routes under Möbius precedence.
4166 *
4167 * Builds @c COALESCE(mobius_or_null(mobius), joint) -- a SHORT-CIRCUITING
4168 * choice: the safe-UCQ Möbius cancellation route (a *guaranteed* PTIME
4169 * \f$O(|D|^k)\f$ exact route for its class -- TID, self-join-free, safe) is
4170 * tried first; on success it roots a @c gate_mobius and @c COALESCE returns it
4171 * without ever evaluating -- hence ever running -- the joint-width compiler.
4172 * Only when Möbius declines (correlated inputs, self-joins, or an unsafe shape:
4173 * @c mobius_or_null then yields NULL) does the joint-width compiler run, with
4174 * the literal @p lineage as its own fallback. Möbius is preferred not because
4175 * joint-width is provably worse -- whether the Möbius class has bounded joint
4176 * treewidth is open (no polynomial d-D is *known* for q9, but none is proved
4177 * impossible for the general d-D class either) -- but because Möbius is a
4178 * guaranteed-terminating route for its class whereas the joint-width compiler
4179 * may grind to its state cap before declining. @p mobius_call / @p joint_call
4180 * are pre-built route expressions (either may be NULL when its debug GUC is
4181 * off); @p lineage is the normal provenance, the final fallback.
4182 */
4183static Expr *combine_safe_routes(const constants_t *constants,
4184 Expr *mobius_call, Expr *joint_call,
4185 Expr *lineage)
4186{
4187 Expr *first;
4188 CoalesceExpr *ce;
4189
4190 if (mobius_call == NULL)
4191 return (joint_call != NULL) ? joint_call : lineage;
4192
4193 first = wrap_mobius_or_null(constants, mobius_call);
4194
4195 ce = makeNode(CoalesceExpr);
4196 ce->coalescetype = constants->OID_TYPE_UUID;
4197 ce->coalescecollid = InvalidOid;
4198 /* On a Möbius decline fall through to joint-width if enabled, else straight
4199 * to the literal lineage. */
4200 ce->args = list_make2(first, (joint_call != NULL) ? joint_call : lineage);
4201 ce->location = -1;
4202 return (Expr *) ce;
4203}
4204
4205/**
4206 * @brief Build the combined provenance expression to be added to the SELECT list.
4207 *
4208 * Combines the tokens in @p prov_atts according to @p op:
4209 * - @c SR_PLUS → use the first token directly (union branch; the outer
4210 * @c array_agg / @c provenance_plus is added later if needed).
4211 * - @c SR_TIMES → wrap all tokens in @c provenance_times(...).
4212 * - @c SR_MONUS → wrap all tokens in @c provenance_monus(...).
4213 *
4214 * When @p aggregation or @p group_by_rewrite is true, wraps the result in
4215 * @c array_agg + @c provenance_plus to collapse groups. A @c provenance_delta
4216 * gate is added for plain aggregations without a HAVING clause.
4217 *
4218 * If a HAVING clause is present it is removed from @p q->havingQual and
4219 * converted into a provenance expression via @c having_Expr_to_provenance_cmp.
4220 *
4221 * If @c provsql_where_provenance is enabled, equality gates (@c provenance_eq)
4222 * are prepended for join conditions and WHERE equalities, and a projection gate
4223 * is appended if the output columns form a proper subset of the input columns.
4224 *
4225 * @param constants Extension OID cache.
4226 * @param q Query being rewritten (HAVING is cleared if present).
4227 * @param prov_atts List of provenance @c Var nodes.
4228 * @param aggregation True if the query contains aggregate functions.
4229 * @param group_by_rewrite True if a GROUP BY requires the plus-aggregate wrapper.
4230 * @param op Semiring operation to use for combining tokens.
4231 * @param columns Per-RTE column-numbering array (for where-provenance).
4232 * For provenance-tracked @c RTE_RELATION entries, the
4233 * -1 sentinel is used to identify them; the PROJECT
4234 * gate positions for their columns use @c varattno
4235 * rather than the query-order-dependent sequential
4236 * numbers (see @c build_column_map() for the
4237 * rationale).
4238 * @param nbcols Total number of non-provenance output columns.
4239 * @param wrap_assumed If true, wrap the result in
4240 * @c assume_boolean so downstream
4241 * probability evaluators may treat it as Boolean.
4242 * @param in_boolean_rewrite True when this query lies under a safe-query
4243 * (boolean) rewrite; the joint-width substitution
4244 * declines so it never pre-empts the read-once form.
4245 * @param inv_cert If non-NULL, a serialised inversion-free certificate
4246 * to attach to the per-row root via @c provsql.annotate
4247 * (transparent for every evaluator; read back by the
4248 * probability dispatcher). Mutually compatible with
4249 * @c wrap_assumed only in principle -- the
4250 * inversion-free path never sets the latter.
4251 * @return The provenance @c Expr to be appended to the target list.
4252 */
4253static Expr *make_provenance_expression(const constants_t *constants, Query *q,
4254 List *prov_atts, bool aggregation,
4255 bool group_by_rewrite,
4256 semiring_operation op, int **columns,
4257 int nbcols, bool wrap_assumed,
4258 bool in_boolean_rewrite,
4259 const char *inv_cert) {
4260 Expr *result;
4261 ListCell *lc_v;
4262 /* Recognise the joint-width substitution BEFORE the aggregation branch
4263 * mutates q (it sets q->hasAggs); the descriptor is applied at the end. */
4264 char *jw_desc = NULL;
4265 bool jw_all_exist = false;
4266 List *jw_head_idx = NIL;
4267 List *jw_head_exprs = NIL;
4268 /* Joint-width is the fallback for the genuinely #P-hard UCQs. It must NOT
4269 * pre-empt a query another route already certifies: @c in_boolean_rewrite is
4270 * set throughout a safe-query rewrite's subtree (so jw defers to it even one
4271 * level down a subquery, where @c wrap_assumed alone is lost), and @c inv_cert
4272 * marks an inversion-free certificate. In both cases the lineage is already
4273 * tractable, so decline. */
4274 /* The Möbius and joint-width routes share this UCQ-existence recognition and
4275 * its descriptor, but are INDEPENDENT: neither GUC gates the other. Build
4276 * the descriptor when EITHER route is enabled; combine_safe_routes then
4277 * wires whichever are on (Möbius first, with precedence). Turning both off
4278 * compares against the literal lineage. */
4280 op != SR_PLUS && (aggregation || group_by_rewrite) &&
4281 !in_boolean_rewrite && inv_cert == NULL)
4282 jw_desc = provsql_joint_width_descriptor(constants, q, &jw_all_exist,
4283 &jw_head_idx, &jw_head_exprs);
4284
4285 if (op == SR_PLUS) {
4286 result = linitial(prov_atts);
4287 } else {
4288 if (my_lnext(prov_atts, list_head(prov_atts)) == NULL) {
4289 result = linitial(prov_atts);
4290 } else {
4291 FuncExpr *expr = makeNode(FuncExpr);
4292 if (op == SR_TIMES) {
4293 ArrayExpr *array = makeNode(ArrayExpr);
4294
4295 expr->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
4296 expr->funcvariadic = true;
4297
4298 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
4299 array->element_typeid = constants->OID_TYPE_UUID;
4300 array->elements = prov_atts;
4301 array->location = -1;
4302
4303 expr->args = list_make1(array);
4304 } else { // SR_MONUS
4305 expr->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
4306 expr->args = prov_atts;
4307 }
4308 expr->funcresulttype = constants->OID_TYPE_UUID;
4309 expr->location = -1;
4310
4311 result = (Expr *)expr;
4312 }
4313
4314 if (group_by_rewrite || aggregation) {
4315 Aggref *agg = makeNode(Aggref);
4316 FuncExpr *plus = makeNode(FuncExpr);
4317 TargetEntry *te_inner = makeNode(TargetEntry);
4318
4319 q->hasAggs = true;
4320
4321 te_inner->resno = 1;
4322 te_inner->expr = (Expr *)result;
4323
4324 agg->aggfnoid = constants->OID_FUNCTION_ARRAY_AGG;
4325 agg->aggtype = constants->OID_TYPE_UUID_ARRAY;
4326 agg->args = list_make1(te_inner);
4327 agg->aggkind = AGGKIND_NORMAL;
4328 agg->location = -1;
4329#if PG_VERSION_NUM >= 140000
4330 agg->aggno = agg->aggtransno = -1;
4331#endif
4332
4333 agg->aggargtypes = list_make1_oid(constants->OID_TYPE_UUID);
4334
4335 plus->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
4336 plus->args = list_make1(agg);
4337 plus->funcresulttype = constants->OID_TYPE_UUID;
4338 plus->location = -1;
4339
4340 result = (Expr *)plus;
4341 }
4342
4343 /* HAVING quals come in two flavours. A qual that references an
4344 * agg_token Var or a provenance_aggregate() wrapper must be lifted
4345 * into a provenance_cmp gate so the per-group truth value is
4346 * carried by the provenance circuit (and the corresponding gate_agg
4347 * remains evaluable). Anything else -- a deterministic scalar
4348 * predicate, or one over random_variable aggregates collapsed by
4349 * expected() / variance() / moment() to a plain double -- is left
4350 * in q->havingQual for PostgreSQL to evaluate natively, and the
4351 * per-group provenance still gets a delta wrapper. */
4352 {
4353 bool lift_having = q->havingQual != NULL &&
4354 needs_having_lift((Node *) q->havingQual, constants);
4355
4356 if (aggregation && !lift_having) {
4357 if (q->groupClause == NIL && q->groupingSets == NIL) {
4358 /* Scalar aggregation (no GROUP BY): the single result row always
4359 * exists -- even over an empty input (count 0, sum/min/max NULL) -- so
4360 * its existence provenance is gate_one (certain, 1_K in every semiring),
4361 * NOT δ(⊕ tuples) which reads as "the input is non-empty". The per-row
4362 * value provenance lives in the agg_token; the "is this aggregate
4363 * non-empty" condition is recovered separately (the agg_token moment /
4364 * support functions exclude the empty world for NULL-on-empty
4365 * aggregates). δ collapses multiplicity for a *grouped* row, where the
4366 * empty group is no row; for a scalar row that distinction does not
4367 * apply. */
4368 FuncExpr *oneExpr = makeNode(FuncExpr);
4369 oneExpr->funcid = constants->OID_FUNCTION_GATE_ONE;
4370 oneExpr->funcresulttype = constants->OID_TYPE_UUID;
4371 oneExpr->args = NIL;
4372 oneExpr->location = -1;
4373 result = (Expr *)oneExpr;
4374 } else {
4375 FuncExpr *deltaExpr = makeNode(FuncExpr);
4376
4377 // adding the delta gate to the provenance circuit
4378 deltaExpr->funcid = constants->OID_FUNCTION_PROVENANCE_DELTA;
4379 deltaExpr->args = list_make1(result);
4380 deltaExpr->funcresulttype = constants->OID_TYPE_UUID;
4381 deltaExpr->location = -1;
4382
4383 result = (Expr *)deltaExpr;
4384 }
4385 }
4386
4387 if (lift_having) {
4388 result = (Expr*) having_Expr_to_provenance_cmp((Expr*)q->havingQual, constants, false);
4389 q->havingQual = NULL;
4390 }
4391 }
4392 }
4393
4394 /* Part to handle eq gates used for where-provenance.
4395 * Placed before projection gates because they need
4396 * to be deeper in the provenance tree. */
4397 if (provsql_where_provenance && q->jointree) {
4398 ListCell *lc;
4399 foreach (lc, q->jointree->fromlist) {
4400 if (IsA(lfirst(lc), JoinExpr)) {
4401 JoinExpr *je = (JoinExpr *)lfirst(lc);
4402 /* Study equalities coming from From clause */
4403 result =
4404 add_eq_from_Quals_to_Expr(constants, je->quals, result, columns);
4405 }
4406 }
4407 /* Study equalities coming from WHERE clause */
4408 result = add_eq_from_Quals_to_Expr(constants, q->jointree->quals, result,
4409 columns);
4410 }
4411
4413 ArrayExpr *array = makeNode(ArrayExpr);
4414 FuncExpr *fe = makeNode(FuncExpr);
4415 bool projection = false;
4416 int nb_column = 0;
4417 /* Cumulative offset of each RTE within the TIMES gate's concatenated
4418 * locator vector. WhereCircuit::evaluate(TIMES) appends the locator
4419 * vector of each child input in q->rtable order, so a column at
4420 * varattno k of the i-th provenance-tracked base RTE lands at
4421 * prov_offset[i] + k in the concat. varattno alone (the recent fix
4422 * documented at the top of this file) is correct only when there is a
4423 * single provenance-tracked input; for multi-input joins it omits the
4424 * preceding inputs' nb_user_cols and the project gate then reads from
4425 * the wrong table's locator slice.
4426 *
4427 * 1-indexed by rteid for direct indexing via Var->varno; entry 0 is
4428 * unused. Length q->rtable->length + 1. */
4429 int *prov_offset = (int *)palloc0((q->rtable->length + 1) * sizeof(int));
4430 int cum = 0;
4431 Index r;
4432
4433 fe->funcid = constants->OID_FUNCTION_PROVENANCE_PROJECT;
4434 fe->funcvariadic = true;
4435 fe->funcresulttype = constants->OID_TYPE_UUID;
4436 fe->location = -1;
4437
4438 array->array_typeid = constants->OID_TYPE_INT_ARRAY;
4439 array->element_typeid = constants->OID_TYPE_INT;
4440 array->elements = NIL;
4441 array->location = -1;
4442
4443 for (r = 1; r <= (Index)q->rtable->length; ++r) {
4444 prov_offset[r] = cum;
4445 if (columns[r-1]) {
4446 RangeTblEntry *rte_r = (RangeTblEntry *)list_nth(q->rtable, r-1);
4447 int ncols = list_length(rte_r->eref->colnames);
4448 bool is_prov = false;
4449 int nb_user = 0;
4450 int k;
4451 for (k = 0; k < ncols; ++k) {
4452 if (columns[r-1][k] == -1) is_prov = true;
4453 else if (columns[r-1][k] > 0) nb_user++;
4454 }
4455 if (is_prov) cum += nb_user;
4456 }
4457 }
4458
4459 foreach (lc_v, q->targetList) {
4460 TargetEntry *te_v = (TargetEntry *)lfirst(lc_v);
4461 if (IsA(te_v->expr, Var)) {
4462 Var *vte_v = (Var *)te_v->expr;
4463 RangeTblEntry *rte_v =
4464 (RangeTblEntry *)lfirst(list_nth_cell(q->rtable, vte_v->varno - 1));
4465 int value_v;
4466#if PG_VERSION_NUM >= 180000
4467 if (rte_v->rtekind == RTE_GROUP) {
4468 Expr *ge = lfirst(list_nth_cell(rte_v->groupexprs, vte_v->varattno - 1));
4469 if(IsA(ge, Var)) {
4470 Var *v = (Var *) ge;
4471 value_v = columns[v->varno - 1] ?
4472 columns[v->varno - 1][v->varattno - 1] : 0;
4473 } else {
4474 Const *ce = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
4475 sizeof(int32), Int32GetDatum(0), false, true);
4476
4477 array->elements = lappend(array->elements, ce);
4478 value_v = 0;
4479 }
4480 } else
4481#endif
4482 if (rte_v->rtekind != RTE_JOIN) { // Normal RTE
4483 if (rte_v->rtekind == RTE_RELATION && columns[vte_v->varno - 1]) {
4484 /* Determine whether this base table is provenance-tracked by
4485 * scanning for the sentinel -1 entry that build_column_map()
4486 * assigns to the provsql column. */
4487 bool is_prov = false;
4488 int ncols_rte = list_length(rte_v->eref->colnames);
4489 for (int k = 0; k < ncols_rte; k++) {
4490 if (columns[vte_v->varno - 1][k] == -1) {
4491 is_prov = true;
4492 break;
4493 }
4494 }
4495 if (is_prov) {
4496 int raw = columns[vte_v->varno - 1][vte_v->varattno - 1];
4497 /* Local position within this table is `varattno` (the
4498 * provsql column is appended last by add_provenance(), so
4499 * user columns occupy 1..nb_user_cols exactly matching the
4500 * IN gate's Locator vector). We then shift by
4501 * prov_offset[varno] to land in the right slice of the
4502 * TIMES gate's concatenated locator vector when the query
4503 * joins multiple provenance-tracked relations. */
4504 value_v = (raw == -1) ? -1
4505 : (int)vte_v->varattno
4506 + prov_offset[vte_v->varno];
4507 } else {
4508 /* Non-provenance base table: no IN gate exists for it, so
4509 * the position would be out of range regardless. Explicitly
4510 * record 0 so evaluate() returns an empty locator set and
4511 * the positions array stays in sync with the output column
4512 * count. */
4513 Const *ce =
4514 makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
4515 sizeof(int32), Int32GetDatum(0), false, true);
4516 array->elements = lappend(array->elements, ce);
4517 projection = true;
4518 continue;
4519 }
4520 } else {
4521 /* RTE_SUBQUERY and others: the sequential number equals the
4522 * column's 1-indexed position in the subquery's output list,
4523 * which matches what the child gate's evaluate() expects. */
4524 value_v = columns[vte_v->varno - 1] ?
4525 columns[vte_v->varno - 1][vte_v->varattno - 1] : 0;
4526 }
4527 } else { // Join RTE
4528 Var *jav_v = (Var *)lfirst(
4529 list_nth_cell(rte_v->joinaliasvars, vte_v->varattno - 1));
4530 if (jav_v && IsA(jav_v, Var) && columns[jav_v->varno - 1]) {
4531 RangeTblEntry *jrte_v = (RangeTblEntry *)lfirst(
4532 list_nth_cell(q->rtable, jav_v->varno - 1));
4533 if (jrte_v->rtekind == RTE_RELATION) {
4534 /* Provenance-tracking check and varattno fix – same rationale
4535 * as the RTE_RELATION branch above. */
4536 bool is_prov = false;
4537 int ncols_jrte = list_length(jrte_v->eref->colnames);
4538 for (int k = 0; k < ncols_jrte; k++) {
4539 if (columns[jav_v->varno - 1][k] == -1) {
4540 is_prov = true;
4541 break;
4542 }
4543 }
4544 if (is_prov) {
4545 int raw = columns[jav_v->varno - 1][jav_v->varattno - 1];
4546 value_v = (raw == -1) ? -1
4547 : (int)jav_v->varattno
4548 + prov_offset[jav_v->varno];
4549 } else {
4550 Const *ce =
4551 makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
4552 sizeof(int32), Int32GetDatum(0), false, true);
4553 array->elements = lappend(array->elements, ce);
4554 projection = true;
4555 continue;
4556 }
4557 } else {
4558 value_v = columns[jav_v->varno - 1][jav_v->varattno - 1];
4559 }
4560 } else {
4561 value_v = 0;
4562 }
4563 }
4564
4565 /* If this is a valid column */
4566 if (value_v > 0) {
4567 Const *ce =
4568 makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
4569 Int32GetDatum(value_v), false, true);
4570
4571 array->elements = lappend(array->elements, ce);
4572
4573 if (value_v != ++nb_column)
4574 projection = true;
4575 } else {
4576 if (value_v != -1)
4577 projection = true;
4578 }
4579 } else { // we have a function in target
4580 Const *ce = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
4581 sizeof(int32), Int32GetDatum(0), false, true);
4582
4583 array->elements = lappend(array->elements, ce);
4584 projection = true;
4585 }
4586 }
4587
4588 if (nb_column != nbcols)
4589 projection = true;
4590
4591 if (projection) {
4592 fe->args = list_make2(result, array);
4593 result = (Expr *)fe;
4594 } else {
4595 pfree(array);
4596 pfree(fe);
4597 }
4598 }
4599
4600 /* Wrap the finished per-row root in a @c gate_assumed when
4601 * our caller (the safe-query rewrite path in @c process_query) asks
4602 * for it. Wrapping here -- before @c add_to_select and
4603 * @c replace_provenance_function_by_expression -- means every
4604 * per-row root reference in the final target list carries the
4605 * marker uniformly. Subqueries that this same Query body opens
4606 * (per-atom DISTINCT projections inserted by the rewriter) are
4607 * handled by their own deeper @c process_query / @c make_provenance_expression
4608 * calls with @c wrap_assumed = false, so the marker sits
4609 * only at the outermost root that surfaces as the user-visible
4610 * row provenance. */
4611 if (wrap_assumed &&
4612 OidIsValid(constants->OID_FUNCTION_ASSUME_BOOLEAN))
4613 result = wrap_in_assume_boolean(constants, result);
4614
4615 /* Attach the inversion-free tractability certificate to the per-row root.
4616 * The annotation gate is transparent for every evaluator; the probability
4617 * dispatcher reads the certificate back from its extra. */
4618 if (inv_cert != NULL &&
4619 OidIsValid(constants->OID_FUNCTION_ANNOTATE))
4620 result = wrap_in_annotate(constants, result, inv_cert);
4621
4622 /* Joint-width substitution (debug GUC provsql.joint_width, on by
4623 * default). When the Boolean-provenance query forms the *existence*
4624 * of a recognised unsafe UCQ -- a DISTINCT / GROUP-BY that ORs the
4625 * witnesses, the #P-hard case the Dalvi-Suciu dichotomy rules out from
4626 * lifted inference -- wrap the just-built normal provenance so that, at
4627 * execution, the joint-width compiler's certified d-D is used instead
4628 * whenever it applies, and the normal provenance is the fallback on any
4629 * failure (unsupported gate type, width too large, ...). Both give the
4630 * exact same probability; joint-width only makes the #P-hard ones
4631 * tractable. The normal provenance is still built (it is the
4632 * fallback), so this never makes a query fail. */
4633 if (jw_desc != NULL && jw_all_exist) {
4634 /* Möbius precedence (see combine_safe_routes): the safe-UCQ Möbius
4635 * cancellation route -- a guaranteed PTIME exact route for its class -- is
4636 * tried first and, on success, short-circuits past the joint-width
4637 * compiler (which may otherwise grind to its state cap before declining on
4638 * exactly these queries). The joint-width compiler runs only on a Möbius
4639 * decline (correlated inputs, self-joins, unsafe shape), with the normal
4640 * provenance the final fallback, so a recognised query never fails. Both
4641 * give the same probability. */
4642 Expr *mob = provsql_mobius
4643 ? build_mobius_provenance_expr(constants, jw_desc, result)
4644 : NULL;
4645 Expr *jw = provsql_joint_width
4646 ? build_joint_width_provenance_expr(constants, jw_desc, result)
4647 : NULL;
4648 result = combine_safe_routes(constants, mob, jw, result);
4649 } else if (jw_desc != NULL && jw_head_idx != NIL && inv_cert == NULL) {
4650 /* Per-answer (non-Boolean) UCQ, and the inversion-free certifier has
4651 * declined (an inv_cert would be carried on the normal provenance and is
4652 * consumed by the 'inversion-free' method, which the joint-width d-D does
4653 * not provide): same Möbius-precedence dispatch per output group, head
4654 * variables pinned per group, the normal per-answer provenance the final
4655 * fallback. */
4656 Expr *mob = provsql_mobius
4657 ? build_mobius_answer_expr(constants, jw_desc, jw_head_idx,
4658 jw_head_exprs, result)
4659 : NULL;
4660 Expr *jw = provsql_joint_width
4661 ? build_joint_width_answer_expr(constants, jw_desc, jw_head_idx,
4662 jw_head_exprs, result)
4663 : NULL;
4664 result = combine_safe_routes(constants, mob, jw, result);
4665 }
4666
4667 return result;
4668}
4669
4670/* -------------------------------------------------------------------------
4671 * Set-operation & DISTINCT rewriting
4672 * ------------------------------------------------------------------------- */
4673
4674#if PG_VERSION_NUM >= 180000
4675typedef struct {
4676 Index group_rtindex;
4677 List *groupexprs;
4678} resolve_group_rte_ctx;
4679
4680static Node *
4681resolve_group_rte_vars_mutator(Node *node, void *raw_ctx) {
4682 resolve_group_rte_ctx *ctx = (resolve_group_rte_ctx *)raw_ctx;
4683 if (node == NULL)
4684 return NULL;
4685 if (IsA(node, Var)) {
4686 Var *v = (Var *)node;
4687 if (v->varno == ctx->group_rtindex) {
4688 Node *resolved = copyObject(list_nth(ctx->groupexprs, v->varattno - 1));
4689#if PG_VERSION_NUM >= 160000
4690 /* Clear varnullingrels: the group-step nulling bits reference the
4691 * group_rtindex RTE which does not exist in the fresh inner query.
4692 * Leaving them set causes the planner to access simple_rel_array at
4693 * group_rtindex (which has no RelOptInfo), triggering
4694 * "unrecognized RTE kind: 9". */
4695 if (IsA(resolved, Var))
4696 ((Var *)resolved)->varnullingrels = NULL;
4697#endif
4698 return resolved;
4699 }
4700 }
4701 return expression_tree_mutator(node, resolve_group_rte_vars_mutator, raw_ctx);
4702}
4703
4704/**
4705 * @brief Strip PG 18's virtual @c RTE_GROUP entry from @p q in place.
4706 *
4707 * @c parseCheckAggregates() appends an @c RTE_GROUP entry at the end of
4708 * @c q->rtable whenever the query has a @c GROUP @c BY clause; references
4709 * to grouped columns in @c targetList and @c jointree->quals point at that
4710 * synthetic RTE rather than the underlying base tables. ProvSQL's
4711 * rewriters need a flat range-table to do their own index arithmetic, so
4712 * we remove the @c RTE_GROUP and resolve every @c Var(@c group_rtindex,
4713 * @c i) back to its base-table expression before going further.
4714 *
4715 * Idempotent: when @c q->hasGroupRTE is already false, returns without
4716 * doing anything.
4717 */
4718void strip_group_rte_pg18(Query *q) {
4719 resolve_group_rte_ctx grp_ctx;
4720 bool found = false;
4721 ListCell *lc;
4722 Index idx = 1;
4723 int rte_len = 0;
4724
4725 if (!q->hasGroupRTE)
4726 return;
4727
4728 foreach (lc, q->rtable) {
4729 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
4730 if (r->rtekind == RTE_GROUP) {
4731 grp_ctx.group_rtindex = idx;
4732 grp_ctx.groupexprs = r->groupexprs;
4733 found = true;
4734 rte_len = idx - 1;
4735 break;
4736 }
4737 idx++;
4738 }
4739
4740 if (!found)
4741 return;
4742
4743 q->rtable = list_truncate(q->rtable, rte_len);
4744 q->hasGroupRTE = false;
4745
4746 foreach (lc, q->targetList) {
4747 TargetEntry *te = (TargetEntry *) lfirst(lc);
4748 te->expr = (Expr *) resolve_group_rte_vars_mutator(
4749 (Node *) te->expr, &grp_ctx);
4750 }
4751 if (q->jointree && q->jointree->quals)
4752 q->jointree->quals = resolve_group_rte_vars_mutator(
4753 q->jointree->quals, &grp_ctx);
4754 /* HAVING too: when the GROUP BY key is a constant (e.g. GROUP BY 1, or
4755 * any literal grouping expression), PostgreSQL 18 rewrites a matching
4756 * literal on the other side of a HAVING comparison (HAVING count(*) = 1)
4757 * into a grouped Var referencing the RTE_GROUP entry. Left unresolved
4758 * it reaches having_OpExpr_to_provenance_cmp as a bare Var and trips the
4759 * "cannot handle complex HAVING expressions" bail; resolving it back to
4760 * the underlying grouping expression restores the Const the converter
4761 * expects. */
4762 if (q->havingQual)
4763 q->havingQual = resolve_group_rte_vars_mutator(q->havingQual, &grp_ctx);
4764}
4765#endif
4766
4767/* Forward declaration – defined later but needed by rewrite_agg_distinct */
4768static bool provenance_function_walker(Node *node, void *data);
4769
4770/**
4771 * @brief Build the inner GROUP-BY subquery for one @c AGG(DISTINCT key).
4772 *
4773 * Produces:
4774 * @code
4775 * SELECT key_expr, gb_col1, gb_col2, ...
4776 * FROM <same tables as q>
4777 * GROUP BY key_expr, gb_col1, gb_col2, ...
4778 * @endcode
4779 *
4780 * @param q Original query (supplies FROM / WHERE).
4781 * @param key_expr The DISTINCT argument expression.
4782 * @param groupby_tes Non-aggregate target entries that are GROUP BY columns.
4783 * @return Fresh inner @c Query.
4784 */
4785static Query *build_inner_for_distinct_key(Query *q, Expr *key_expr,
4786 List *groupby_tes) {
4787 Query *inner;
4788 List *new_tl = NIL;
4789 List *new_gc = NIL;
4790 ListCell *lc;
4791 int resno = 1, sgref = 1;
4792
4793 inner = copyObject(q);
4794
4795 inner->hasAggs = false;
4796 inner->sortClause = NIL;
4797 inner->limitCount = NULL;
4798 inner->limitOffset = NULL;
4799 inner->distinctClause = NIL;
4800 inner->hasDistinctOn = false;
4801 inner->havingQual = NULL;
4802
4803 /* First column: the DISTINCT key */
4804 {
4805 TargetEntry *kte = makeNode(TargetEntry);
4806 SortGroupClause *sgc = makeNode(SortGroupClause);
4807
4808 kte->expr = copyObject(key_expr);
4809 kte->resno = resno++;
4810 kte->resname = "key";
4811 sgc->tleSortGroupRef = kte->ressortgroupref = sgref++;
4812 get_sort_group_operators(exprType((Node *)kte->expr), true, true, false,
4813 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
4814 new_gc = list_make1(sgc);
4815 new_tl = list_make1(kte);
4816 }
4817
4818 /* Remaining columns: GROUP BY columns from the original query */
4819 foreach (lc, groupby_tes) {
4820 TargetEntry *gyte = copyObject((TargetEntry *)lfirst(lc));
4821 SortGroupClause *sgc = makeNode(SortGroupClause);
4822
4823 gyte->resno = resno++;
4824 gyte->resjunk = false;
4825 sgc->tleSortGroupRef = gyte->ressortgroupref = sgref++;
4826 get_sort_group_operators(exprType((Node *)gyte->expr), true, true, false,
4827 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
4828 new_gc = lappend(new_gc, sgc);
4829 new_tl = lappend(new_tl, gyte);
4830 }
4831
4832 inner->targetList = new_tl;
4833 inner->groupClause = new_gc;
4834 return inner;
4835}
4836
4837/**
4838 * @brief Wrap @p inner in an outer query that applies the original aggregate.
4839 *
4840 * Produces:
4841 * @code
4842 * SELECT AGG(key_col), gb_col1, gb_col2, ...
4843 * FROM inner
4844 * GROUP BY gb_col1, gb_col2, ...
4845 * @endcode
4846 * The DISTINCT flag is cleared; @p inner provides exactly one row per
4847 * (key, group-by) combination, so the plain aggregate gives the right count.
4848 *
4849 * @param orig_agg_te Original @c TargetEntry containing @c AGG(DISTINCT key).
4850 * @param inner Inner query from @c build_inner_for_distinct_key.
4851 * @param n_gb Number of GROUP BY columns (trailing entries in @p inner).
4852 * @param constants Extension OID cache.
4853 * @return Fresh outer @c Query.
4854 */
4855static Query *build_outer_for_distinct_key(TargetEntry *orig_agg_te,
4856 Query *inner, int n_gb,
4857 const constants_t *constants) {
4858 Query *outer = makeNode(Query);
4859 RangeTblEntry *rte = makeNode(RangeTblEntry);
4860 Alias *alias = makeNode(Alias), *eref = makeNode(Alias);
4861 RangeTblRef *rtr = makeNode(RangeTblRef);
4862 FromExpr *jt = makeNode(FromExpr);
4863 List *new_tl = NIL, *new_gc = NIL;
4864 ListCell *lc;
4865 int resno = 1, sgref = 1;
4866 int inner_len = list_length(inner->targetList);
4867 int attno;
4868
4869 /* Wrap inner in a subquery RTE */
4870 alias->aliasname = eref->aliasname = "d";
4871 eref->colnames = NIL;
4872 foreach (lc, inner->targetList) {
4873 TargetEntry *te = lfirst(lc);
4874 eref->colnames = lappend(eref->colnames,
4875 makeString(te->resname ? pstrdup(te->resname) : ""));
4876 }
4877 rte->alias = alias;
4878 rte->eref = eref;
4879 rte->rtekind = RTE_SUBQUERY;
4880 rte->subquery = inner;
4881 rte->inFromCl = true;
4882#if PG_VERSION_NUM < 160000
4883 rte->requiredPerms = ACL_SELECT;
4884#endif
4885
4886 rtr->rtindex = 1;
4887 jt->fromlist = list_make1(rtr);
4888
4889 outer->commandType = CMD_SELECT;
4890 outer->canSetTag = true;
4891 outer->rtable = list_make1(rte);
4892 outer->jointree = jt;
4893 outer->hasAggs = true;
4894
4895 /* First output column: the aggregate over the key (col 1 of inner) */
4896 {
4897 TargetEntry *agg_te = copyObject(orig_agg_te);
4898 Aggref *ar = (Aggref *)agg_te->expr;
4899 Var *key_var = makeNode(Var);
4900 TargetEntry *arg_te = makeNode(TargetEntry);
4901
4902 key_var->varno = 1;
4903 key_var->varattno = 1; /* key is first column of inner */
4904 key_var->vartype = linitial_oid(ar->aggargtypes);
4905 key_var->varcollid = exprCollation((Node *)((TargetEntry *)linitial(ar->args))->expr);
4906 key_var->vartypmod = -1;
4907 key_var->location = -1;
4908 arg_te->resno = 1;
4909 arg_te->expr = (Expr *)key_var;
4910
4911 ar->args = list_make1(arg_te);
4912 ar->aggdistinct = NIL;
4913 agg_te->resno = resno++;
4914 new_tl = list_make1(agg_te);
4915 }
4916
4917 /* Remaining output columns: GROUP BY cols (trailing cols of inner) */
4918 for (attno = inner_len - n_gb + 1; attno <= inner_len; attno++) {
4919 TargetEntry *inner_te = list_nth(inner->targetList, attno - 1);
4920 Var *gb_var = makeNode(Var);
4921 TargetEntry *gb_te = makeNode(TargetEntry);
4922 SortGroupClause *sgc = makeNode(SortGroupClause);
4923
4924 gb_var->varno = 1;
4925 gb_var->varattno = attno;
4926 gb_var->vartype = exprType((Node *)inner_te->expr);
4927 gb_var->varcollid = exprCollation((Node *)inner_te->expr);
4928 gb_var->vartypmod = -1;
4929 gb_var->location = -1;
4930
4931 gb_te->resno = resno++;
4932 gb_te->expr = (Expr *)gb_var;
4933 gb_te->resname = inner_te->resname;
4934
4935 sgc->tleSortGroupRef = gb_te->ressortgroupref = sgref++;
4936 sgc->nulls_first = false;
4937 get_sort_group_operators(gb_var->vartype, true, true, false,
4938 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
4939 new_gc = lappend(new_gc, sgc);
4940 new_tl = lappend(new_tl, gb_te);
4941 }
4942
4943 outer->targetList = new_tl;
4944 outer->groupClause = new_gc;
4945 return outer;
4946}
4947
4948/** @brief Collector for @c AGG(DISTINCT) Aggrefs inside a HAVING clause. */
4949typedef struct having_distinct_ctx {
4950 List *aggs; ///< Aggref* nodes carrying @c aggdistinct, in traversal order
4952
4953/**
4954 * @brief Walker that collects @c AGG(DISTINCT) Aggrefs from an expression.
4955 *
4956 * Does not descend into an @c Aggref's own arguments, so the traversal order
4957 * matches @c replace_having_distinct_mutator below (both stop at every
4958 * @c Aggref), keeping the per-aggregate outer-subquery indices aligned.
4959 */
4960static bool collect_having_distinct_walker(Node *node, void *ctx) {
4961 if (node == NULL)
4962 return false;
4963 if (IsA(node, Aggref)) {
4964 Aggref *ar = (Aggref *) node;
4965 if (list_length(ar->aggdistinct) > 0)
4966 ((having_distinct_ctx *) ctx)->aggs =
4967 lappend(((having_distinct_ctx *) ctx)->aggs, ar);
4968 return false; /* don't recurse into aggregate arguments */
4969 }
4970 return expression_tree_walker(node, collect_having_distinct_walker, ctx);
4971}
4972
4973/** @brief Context for @c replace_having_distinct_mutator: next outer RT index. */
4977
4978/**
4979 * @brief Mutator that replaces each @c AGG(DISTINCT) Aggref in a HAVING
4980 * clause with @c Var(next_rtindex++, 1) -- the deduped count column of
4981 * its outer subquery (built in the same order by
4982 * @c rewrite_agg_distinct). The @c Var is typed as the aggregate's
4983 * result so the surrounding comparison is intercepted by the HAVING
4984 * provenance path exactly as a non-DISTINCT count would be.
4985 */
4986static Node *replace_having_distinct_mutator(Node *node, void *ctx) {
4987 if (node == NULL)
4988 return NULL;
4989 if (IsA(node, Aggref)) {
4990 Aggref *ar = (Aggref *) node;
4991 if (list_length(ar->aggdistinct) > 0) {
4993 Var *v = makeNode(Var);
4994 v->varno = c->next_rtindex++;
4995 v->varattno = 1; /* agg result is col 1 of each outer */
4996 v->vartype = ar->aggtype;
4997 v->vartypmod = -1;
4998 v->varcollid = ar->aggcollid;
4999 v->location = -1;
5000 return (Node *) v;
5001 }
5002 return node; /* non-DISTINCT aggregate: leave for the normal HAVING path */
5003 }
5004 return expression_tree_mutator(node, replace_having_distinct_mutator, ctx);
5005}
5006
5007/**
5008 * @brief Rewrite every @c AGG(DISTINCT key) in @p q using independent subqueries.
5009 *
5010 * For a single DISTINCT aggregate, produces a subquery:
5011 * @code
5012 * SELECT AGG(key), gb... FROM (SELECT key, gb... FROM t GROUP BY key, gb...) GROUP BY gb...
5013 * @endcode
5014 * For multiple DISTINCT aggregates with different keys, produces an JOIN
5015 * of one such subquery per aggregate, joined on the GROUP BY columns.
5016 * Non-DISTINCT aggregates are left untouched.
5017 *
5018 * @c AGG(DISTINCT) aggregates appearing in the @c HAVING clause are handled
5019 * the same way (one deduped outer per aggregate) and the @c HAVING Aggref is
5020 * replaced by a @c Var to its outer's count column, so the comparison's
5021 * provenance is built over the per-distinct-value rows rather than the raw
5022 * tuples.
5023 *
5024 * @param q Query to inspect and possibly rewrite.
5025 * @param constants Extension OID cache.
5026 * @return Rewritten query, or @c NULL if no @c AGG(DISTINCT) was found.
5027 */
5028static Query *rewrite_agg_distinct(Query *q, const constants_t *constants) {
5029 List *distinct_agg_tes = NIL;
5030 List *groupby_tes = NIL;
5031 ListCell *lc;
5032 having_distinct_ctx hctx = { NIL };
5033
5034#if PG_VERSION_NUM >= 180000
5035 /* In PostgreSQL 18, parseCheckAggregates() injects a virtual RTE_GROUP
5036 * entry at the END of the range table. GROUP BY column Vars in the
5037 * SELECT list point to this entry (varno == group_rtindex) instead of
5038 * the underlying base-table RTE.
5039 *
5040 * Strip that entry now, before we do any index arithmetic (fll, rtr->rtindex,
5041 * agg_idx) or copy q->targetList into groupby_tes. Once removed:
5042 * - q->rtable contains only real RTEs, so appending outer-subquery RTEs
5043 * lands at the correct indices.
5044 * - groupby_tes will carry resolved (base-table) Var expressions, so
5045 * the WHERE equalities and the inner-query target list are correct.
5046 * We also resolve the Var(group_rtindex) refs in q's own targetList and
5047 * WHERE clause so the final query doesn't reference the stripped entry. */
5049#endif
5050
5051 /* Extract AGG(DISTINCT) and GROUP BY targets from the target list.
5052 * Regular AGG() aggregations and expressions containing provenance()
5053 * are left untouched. */
5054 foreach (lc, q->targetList) {
5055 TargetEntry *te = lfirst(lc);
5056 if (IsA(te->expr, Aggref)) {
5057 Aggref *ar = (Aggref *)te->expr;
5058 if (list_length(ar->aggdistinct) > 0)
5059 distinct_agg_tes = lappend(distinct_agg_tes, te);
5060 } else if (provenance_function_walker((Node *)te->expr,
5061 (void *)constants)) {
5062 /* Expression contains provenance() – skip it, it will be
5063 * handled later by the provenance rewriter */
5064 } else {
5065 /* Non-aggregate column – treat as GROUP BY key */
5066 TargetEntry *te_copy = copyObject(te);
5067 te_copy->resjunk = false;
5068 groupby_tes = lappend(groupby_tes, te_copy);
5069 }
5070 }
5071
5072 /* Also collect AGG(DISTINCT) aggregates from the HAVING clause; they are
5073 * not TargetEntries, so they get their own list and a Var-replacement
5074 * mutator below. */
5075 if (q->havingQual != NULL)
5076 collect_having_distinct_walker(q->havingQual, &hctx);
5077
5078 if (distinct_agg_tes == NIL && hctx.aggs == NIL)
5079 return NULL;
5080
5081 {
5082 int n_having = list_length(hctx.aggs);
5083 int n_aggs = list_length(distinct_agg_tes) + n_having;
5084 int n_gb = list_length(groupby_tes);
5085 List *outer_queries = NIL;
5086
5087 /* -----------------------------------------------------------------------
5088 * For each DISTINCT aggregate, build:
5089 * inner_i: SELECT key_i, gb... FROM original... GROUP BY key_i, gb...
5090 * outer_i: SELECT AGG(key_i) ASS agg_i, gb... FROM inner_i GROUP BY gb...
5091 *
5092 * Then produce a final query:
5093 * SELECT gb..., agg_0, ..., agg_{N-1}
5094 * FROM original... JOIN outer_0 ON gb... = gb... [JOIN ...]
5095 * keeping the same order for the output columns.
5096 *
5097 * Column order in the final target list follows q->targetList:
5098 * - DISTINCT agg i → Var(n+i, 1) (agg col of outer_i)
5099 * ----------------------------------------------------------------------- */
5100
5101 /* Build one inner + one outer query per DISTINCT aggregate */
5102 foreach (lc, distinct_agg_tes) {
5103 TargetEntry *agg_te = lfirst(lc);
5104 Aggref *ar = (Aggref *)agg_te->expr;
5105 if(list_length(ar->args) != 1)
5106 provsql_error("AGG(DISTINCT) with more than one argument is not supported");
5107 else {
5108 Expr *key_expr = (Expr *)((TargetEntry *)linitial(ar->args))->expr;
5109 Query *inner = build_inner_for_distinct_key(q, key_expr, groupby_tes);
5110 Query *outer = build_outer_for_distinct_key(agg_te, inner, n_gb, constants);
5111 outer_queries = lappend(outer_queries, outer);
5112 }
5113 }
5114
5115 /* Build one inner + one outer query per HAVING-clause DISTINCT aggregate,
5116 * appended after the target-list ones so their RT indices are the last
5117 * n_having entries of the final from-list (matched by the mutator below). */
5118 foreach (lc, hctx.aggs) {
5119 Aggref *ar = lfirst(lc);
5120 if(list_length(ar->args) != 1)
5121 provsql_error("AGG(DISTINCT) with more than one argument is not supported");
5122 else {
5123 TargetEntry *syn = makeNode(TargetEntry);
5124 Expr *key_expr = (Expr *)((TargetEntry *)linitial(ar->args))->expr;
5125 Query *inner = build_inner_for_distinct_key(q, key_expr, groupby_tes);
5126 Query *outer;
5127 syn->expr = (Expr *) copyObject(ar);
5128 syn->resno = 1;
5129 outer = build_outer_for_distinct_key(syn, inner, n_gb, constants);
5130 outer_queries = lappend(outer_queries, outer);
5131 }
5132 }
5133
5134 {
5135 /* One subquery RTE per outer query. They are appended to q->rtable, so
5136 * their range-table indices start at the current rtable length -- which is
5137 * NOT the from-list length when the FROM contains a JoinExpr (an outer
5138 * join is one from-list item but several rtable slots). Index everything
5139 * off rtable_base, or the RangeTblRefs / Vars below would point at the base
5140 * relations the join spans (e.g. "rel 2 already exists" for the join's
5141 * right arm). */
5142 int rtable_base = list_length(q->rtable);
5143 int i = 0;
5144 foreach (lc, outer_queries) {
5145 Query *oq = lfirst(lc);
5146 RangeTblEntry *rte = makeNode(RangeTblEntry);
5147 Alias *alias = makeNode(Alias), *eref = makeNode(Alias);
5148 ListCell *lc2;
5149 char buf[16];
5150
5151 snprintf(buf, sizeof(buf), "d%d", i + 1);
5152 alias->aliasname = eref->aliasname = pstrdup(buf);
5153 eref->colnames = NIL;
5154 foreach (lc2, oq->targetList) {
5155 TargetEntry *te = lfirst(lc2);
5156 eref->colnames = lappend(eref->colnames,
5157 makeString(te->resname ? pstrdup(te->resname) : ""));
5158 }
5159 rte->alias = alias;
5160 rte->eref = eref;
5161 rte->rtekind = RTE_SUBQUERY;
5162 rte->subquery = oq;
5163 rte->inFromCl = true;
5164#if PG_VERSION_NUM < 160000
5165 rte->requiredPerms = ACL_SELECT;
5166#endif
5167 q->rtable = lappend(q->rtable, rte);
5168 i++;
5169 }
5170
5171 /* Build FROM list and WHERE conditions for the implicit join.
5172 * Use a simple FROM original..., outer_i, ... WHERE original.gb_j = outer_i.gb_j */
5173 {
5174 FromExpr *jt = q->jointree;
5175 List *from_list = jt->fromlist;
5176 List *where_args = NIL;
5177
5178 for (i = rtable_base + 1; i <= rtable_base + n_aggs; i++) {
5179 RangeTblRef *rtr = makeNode(RangeTblRef);
5180 ListCell *lc2;
5181 unsigned j=0;
5182
5183 rtr->rtindex = i;
5184 from_list = lappend(from_list, rtr);
5185
5186 /* outer_0.gb_j = outer_i.gb_j for each GROUP BY column j */
5187 foreach(lc2, groupby_tes) {
5188 TargetEntry *gb_te = lfirst(lc2);
5189 int gb_attno = ++j + 1; /* col 1 = agg, cols 2+ = GB */
5190 Oid ytype = exprType((Node *)gb_te->expr);
5191 Oid opno = find_equality_operator(ytype, ytype);
5192 Operator opInfo = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
5193 Form_pg_operator opform;
5194 OpExpr *oe = makeNode(OpExpr);
5195 Expr *le = copyObject(gb_te->expr);
5196 Var *rv = makeNode(Var);
5197 Oid collation=exprCollation((Node*) le);
5198
5199 if (!HeapTupleIsValid(opInfo))
5200 provsql_error("could not find equality operator for type %u",
5201 ytype);
5202 opform = (Form_pg_operator)GETSTRUCT(opInfo);
5203
5204 oe->opno = opno;
5205 oe->opfuncid = opform->oprcode;
5206 oe->opresulttype = opform->oprresult;
5207 oe->opcollid = InvalidOid;
5208 oe->inputcollid = collation;
5209 oe->location = -1;
5210 ReleaseSysCache(opInfo);
5211
5212 rv->varno = i; rv->varattno = gb_attno;
5213 rv->vartype = ytype; rv->varcollid = collation;
5214 rv->vartypmod = -1; rv->location = -1;
5215
5216 oe->args = list_make2(le, rv);
5217 where_args = lappend(where_args, oe);
5218 }
5219 }
5220
5221 if (list_length(where_args) == 0) {
5222 jt->quals = NULL;
5223 } else if (list_length(where_args) == 1) {
5224 jt->quals = linitial(where_args);
5225 } else {
5226 BoolExpr *be = makeNode(BoolExpr);
5227 be->boolop = AND_EXPR;
5228 be->args = where_args;
5229 be->location = -1;
5230 jt->quals = (Node *)be;
5231 }
5232 }
5233
5234 /* Build final target list in original column order.
5235 * DISTINCT agg i → Var(i+1, 1); GROUP BY col j → Var(1, 2+j). */
5236 {
5237 int agg_idx = rtable_base + 1;
5238 ListCell *lc2;
5239
5240 foreach (lc2, q->targetList) {
5241 TargetEntry *te = lfirst(lc2);
5242
5243 if (IsA(te->expr, Aggref) &&
5244 ((Aggref *)te->expr)->aggdistinct != NIL) {
5245 Var *v = makeNode(Var);
5246 v->varno = agg_idx++; /* outer_{agg_idx} RTE */
5247 v->varattno = 1; /* agg result is col 1 of each outer */
5248 v->vartypmod = -1;
5249 v->location = -1;
5250 te->expr = (Expr*)v;
5251 }
5252 }
5253 }
5254
5255 /* Replace HAVING-clause DISTINCT aggregates with Vars to their outer
5256 * subqueries -- the last n_having entries of the from-list, in the same
5257 * order collect_having_distinct_walker visited them. */
5258 if (n_having > 0) {
5260 /* HAVING outers are appended after the target-list ones. */
5261 hrc.next_rtindex = rtable_base + (n_aggs - n_having) + 1;
5262 q->havingQual = replace_having_distinct_mutator(q->havingQual, &hrc);
5263 }
5264
5265 return q;
5266 }
5267 }
5268}
5269
5270
5271/* -------------------------------------------------------------------------
5272 * Aggregation replacement mutator
5273 * ------------------------------------------------------------------------- */
5274
5275/** @brief Context for the @c aggregation_mutator tree walker. */
5277 List *prov_atts; ///< List of provenance Var nodes
5278 semiring_operation op; ///< Semiring operation for combining tokens
5279 const constants_t *constants; ///< Extension OID cache
5280 bool is_scalar; ///< Aggregation has no GROUP BY (single always-present row)
5282
5283/**
5284 * @brief Tree-mutator that replaces Aggrefs with provenance-aware aggregates.
5285 * @param node Current expression tree node.
5286 * @param ctx Pointer to an @c aggregation_mutator_context (prov_atts,
5287 * op, and constants).
5288 * @return Possibly modified node.
5289 */
5290static Node *aggregation_mutator(Node *node, void *ctx) {
5292 if (node == NULL)
5293 return NULL;
5294
5295 if (IsA(node, Aggref)) {
5296 Aggref *ar_v = (Aggref *)node;
5297 return (Node *)make_aggregation_expression(context->constants, ar_v,
5298 context->prov_atts, context->op,
5299 context->is_scalar);
5300 }
5301
5302 return expression_tree_mutator(node, aggregation_mutator, ctx);
5303}
5304
5305/**
5306 * @brief Wrap a @c provenance_aggregate FuncExpr with a cast to the
5307 * original aggregate return type.
5308 *
5309 * @param prov_agg The provenance_aggregate FuncExpr to wrap.
5310 * @param constants Extension OID cache.
5311 * @return Cast FuncExpr wrapping @p prov_agg.
5312 */
5313static Node *wrap_agg_token_with_cast(FuncExpr *prov_agg,
5314 const constants_t *constants) {
5315 Const *typ_const = (Const *)lsecond(prov_agg->args);
5316 Oid target_type = DatumGetObjectId(typ_const->constvalue);
5317 CoercionPathType pathtype;
5318 Oid castfuncid;
5319
5320 pathtype = find_coercion_pathway(target_type,
5321 constants->OID_TYPE_AGG_TOKEN,
5322 COERCION_EXPLICIT, &castfuncid);
5323 if (pathtype == COERCION_PATH_FUNC && OidIsValid(castfuncid)) {
5324 FuncExpr *cast = makeNode(FuncExpr);
5325 cast->funcid = castfuncid;
5326 cast->funcresulttype = target_type;
5327 cast->funcretset = false;
5328 cast->funcvariadic = false;
5329 cast->funcformat = COERCE_IMPLICIT_CAST;
5330 cast->args = list_make1(prov_agg);
5331 cast->location = -1;
5332 return (Node *)cast;
5333 }
5334
5335 provsql_error("no cast from agg_token to %s for arithmetic on aggregate",
5336 format_type_be(target_type));
5337 return (Node *)prov_agg; /* unreachable */
5338}
5339
5340/**
5341 * @brief Wrap an @c agg_token expression in a cast to @p target_type.
5342 *
5343 * Companion to @c wrap_agg_token_with_cast for @c agg_token values that are
5344 * not a bare @c provenance_aggregate call (e.g. the result of @c agg_token
5345 * arithmetic): the original aggregate type is not recoverable from the node,
5346 * so we cast to the type the consuming context requires.
5347 */
5348static Node *cast_agg_token_to_type(Node *arg, Oid target_type,
5349 const constants_t *constants) {
5350 CoercionPathType pathtype;
5351 Oid castfuncid;
5352
5353 pathtype = find_coercion_pathway(target_type, constants->OID_TYPE_AGG_TOKEN,
5354 COERCION_EXPLICIT, &castfuncid);
5355 if (pathtype == COERCION_PATH_FUNC && OidIsValid(castfuncid)) {
5356 FuncExpr *cast = makeNode(FuncExpr);
5357 cast->funcid = castfuncid;
5358 cast->funcresulttype = target_type;
5359 cast->funcretset = false;
5360 cast->funcvariadic = false;
5361 cast->funcformat = COERCE_IMPLICIT_CAST;
5362 cast->args = list_make1(arg);
5363 cast->location = -1;
5364 return (Node *)cast;
5365 }
5366
5367 provsql_error("no cast from agg_token to %s for arithmetic on aggregate",
5368 format_type_be(target_type));
5369 return arg; /* unreachable */
5370}
5371
5372/**
5373 * @brief Cast @c provenance_aggregate arguments of an operator or
5374 * function when the formal parameter type requires it.
5375 *
5376 * For each argument in @p args that is a @c provenance_aggregate call,
5377 * check the corresponding formal parameter type of the parent function
5378 * @p parent_funcid. If the formal type is polymorphic or @c agg_token
5379 * itself, the argument is left alone. Otherwise a cast to the original
5380 * aggregate return type is inserted.
5381 *
5382 * @param args Argument list to inspect (modified in place).
5383 * @param parent_funcid OID of the parent function / operator implementor.
5384 * @param constants Extension OID cache.
5385 */
5386static void maybe_cast_agg_token_args(List *args, Oid parent_funcid,
5387 const constants_t *constants) {
5388 HeapTuple tp;
5389 Form_pg_proc procForm;
5390 ListCell *lc;
5391 int i;
5392
5393 tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(parent_funcid));
5394 if (!HeapTupleIsValid(tp))
5395 return;
5396 procForm = (Form_pg_proc) GETSTRUCT(tp);
5397
5398 i = 0;
5399 foreach(lc, args) {
5400 Node *arg = lfirst(lc);
5401
5402 /* Any agg_token-typed argument (a bare provenance_aggregate, OR the
5403 * result of agg_token arithmetic produced by try_swap_agg_arith) must
5404 * be cast back to a scalar when the consuming function/operator does not
5405 * itself accept agg_token: this is the boundary where an agg_token
5406 * "bubbling up" through arithmetic meets a scalar context (e.g. ROUND,
5407 * ORDER BY) and its provenance can no longer be carried. A bare
5408 * provenance_aggregate is cast to its own aggregate type; a swapped
5409 * arithmetic result is cast to whatever the parent expects. */
5410 if (i < procForm->pronargs && exprType(arg) == constants->OID_TYPE_AGG_TOKEN) {
5411 Oid formal_type = procForm->proargtypes.values[i];
5412
5413 if (formal_type != constants->OID_TYPE_AGG_TOKEN &&
5414 !IsPolymorphicType(formal_type)) {
5415 if (IsA(arg, FuncExpr) &&
5416 ((FuncExpr *)arg)->funcid == constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
5417 lfirst(lc) = wrap_agg_token_with_cast((FuncExpr *)arg, constants);
5418 else
5419 lfirst(lc) = cast_agg_token_to_type(arg, formal_type, constants);
5420 }
5421 }
5422 i++;
5423 }
5424
5425 ReleaseSysCache(tp);
5426}
5427
5428/**
5429 * @brief Peel implicit/explicit cast FuncExprs and RelabelTypes that wrap a
5430 * single argument, returning the underlying expression.
5431 *
5432 * Used to see through the coercions the parser inserts around an aggregate
5433 * (e.g. the @c int8->numeric cast in @c count(*)/2.0) so the underlying
5434 * @c agg_token / @c provenance_aggregate can be recognised.
5435 */
5436static Node *peel_agg_casts(Node *n) {
5437 for (;;) {
5438 if (n != NULL && IsA(n, FuncExpr)) {
5439 FuncExpr *fe = (FuncExpr *)n;
5440 if ((fe->funcformat == COERCE_IMPLICIT_CAST ||
5441 fe->funcformat == COERCE_EXPLICIT_CAST) &&
5442 list_length(fe->args) == 1) {
5443 n = (Node *)linitial(fe->args);
5444 continue;
5445 }
5446 } else if (n != NULL && IsA(n, RelabelType)) {
5447 n = (Node *)((RelabelType *)n)->arg;
5448 continue;
5449 }
5450 return n;
5451 }
5452}
5453
5454/**
5455 * @brief Rebuild an arithmetic operator over an aggregate so the result
5456 * stays an @c agg_token (provenance preserved).
5457 *
5458 * When an arithmetic operator (@c + @c - @c * @c /, or prefix unary @c -)
5459 * has an @c agg_token operand (after peeling the parser's coercions), the
5460 * default rewriting would cast that @c agg_token to its scalar aggregate
5461 * type, silently dropping provenance. Instead, we re-resolve the operator
5462 * against the @c agg_token operand via @c make_op, which selects the native
5463 * @c agg_token arithmetic operators -- the arithmetic is then recorded
5464 * symbolically as a @c gate_arith over the operand provenance, exactly like
5465 * arithmetic on @c random_variable. Returns the rebuilt @c agg_token
5466 * expression, or @c NULL if @p op is not arithmetic over an aggregate.
5467 */
5468static Node *try_swap_agg_arith(OpExpr *op, const constants_t *constants) {
5469 char *opname;
5470 bool is_arith;
5471 int nargs = list_length(op->args);
5472 Node *l, *r, *lp, *rp;
5473 ParseState *pstate;
5474 Expr *newop;
5475
5476 if (nargs < 1 || nargs > 2)
5477 return NULL;
5478 opname = get_opname(op->opno);
5479 if (opname == NULL)
5480 return NULL;
5481 is_arith = strcmp(opname, "+") == 0 || strcmp(opname, "-") == 0 ||
5482 strcmp(opname, "*") == 0 || strcmp(opname, "/") == 0;
5483 if (!is_arith) {
5484 pfree(opname);
5485 return NULL;
5486 }
5487
5488 if (nargs == 2) {
5489 l = (Node *)linitial(op->args);
5490 r = (Node *)lsecond(op->args);
5491 } else { /* prefix unary minus */
5492 l = NULL;
5493 r = (Node *)linitial(op->args);
5494 }
5495 lp = l ? peel_agg_casts(l) : NULL;
5496 rp = peel_agg_casts(r);
5497
5498 if (!((lp && exprType(lp) == constants->OID_TYPE_AGG_TOKEN) ||
5499 exprType(rp) == constants->OID_TYPE_AGG_TOKEN)) {
5500 pfree(opname);
5501 return NULL;
5502 }
5503
5504 /* Feed make_op the peeled (uncast) operand wherever it exposes an
5505 * agg_token, so resolution picks the agg_token operator; keep the
5506 * original node for the non-agg operand to preserve its own coercions. */
5507 if (lp && exprType(lp) == constants->OID_TYPE_AGG_TOKEN)
5508 l = lp;
5509 if (exprType(rp) == constants->OID_TYPE_AGG_TOKEN)
5510 r = rp;
5511
5512 pstate = make_parsestate(NULL);
5513 newop = make_op(pstate, list_make1(makeString(opname)), l, r, NULL, -1);
5514 free_parsestate(pstate);
5515 pfree(opname);
5516 return (Node *)newop;
5517}
5518
5519/**
5520 * @brief Tree-mutator that casts @c provenance_aggregate results back
5521 * to the original aggregate return type where needed.
5522 *
5523 * After the aggregation mutator replaces Aggrefs with
5524 * @c provenance_aggregate calls (returning @c agg_token), this
5525 * post-processing step inserts casts where the surrounding expression
5526 * expects a different type (e.g. a non-arithmetic function over an
5527 * aggregate). Arithmetic over an aggregate is instead kept as an
5528 * @c agg_token via @c try_swap_agg_arith so its provenance survives;
5529 * arguments to functions that accept @c agg_token or polymorphic types
5530 * are left alone.
5531 *
5532 * @param node Current expression tree node.
5533 * @param ctx Pointer to the @c constants_t OID cache.
5534 * @return Possibly modified node.
5535 */
5536static Node *cast_agg_token_mutator(Node *node, void *ctx) {
5537 const constants_t *constants = (const constants_t *)ctx;
5538 Node *result;
5539
5540 if (node == NULL)
5541 return NULL;
5542
5543 /* Recurse first, then fix up arguments at this level. */
5544 result = expression_tree_mutator(node, cast_agg_token_mutator, ctx);
5545
5546 if (IsA(result, OpExpr)) {
5547 OpExpr *op = (OpExpr *)result;
5548 Node *swapped = try_swap_agg_arith(op, constants);
5549 if (swapped != NULL)
5550 return swapped;
5551 set_opfuncid(op);
5552 maybe_cast_agg_token_args(op->args, op->opfuncid, constants);
5553 } else if (IsA(result, FuncExpr)) {
5554 FuncExpr *fe = (FuncExpr *)result;
5555 if (fe->funcid != constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
5556 maybe_cast_agg_token_args(fe->args, fe->funcid, constants);
5557 }
5558
5559 return result;
5560}
5561
5562/**
5563 * @brief Push distributive constant arithmetic into an aggregate's argument.
5564 *
5565 * Rewrites `f(x) <op> c` to `f(x <op'> c)` when @c f distributes over the
5566 * arithmetic, so the result is a clean aggregate over transformed per-row
5567 * values rather than a @c gate_arith wrapping the aggregate. Run before the
5568 * aggregate is lowered, so the provenance machinery then builds an ordinary
5569 * @c gate_agg. Only the cases that distribute without flipping the aggregate
5570 * and without integer-division rounding are handled (the rest fall through to
5571 * the gate_arith path):
5572 * - sum, avg: @c *c (either side), unary @c -; avg also @c +c / @c -c.
5573 * - min, max: @c +c (either side), @c -c (aggregate on the left).
5574 * The transformed argument must keep the original argument's type (so the
5575 * aggregate's function/type stay valid); otherwise no push happens. Returns
5576 * the rewritten @c Aggref, or @c NULL when @p op is not such a case.
5577 */
5578static Node *try_push_into_aggref(OpExpr *op, const constants_t *constants) {
5579 char *opname = get_opname(op->opno);
5580 int nargs = list_length(op->args);
5581 Node *l = NULL, *r = NULL, *aggn = NULL, *cn = NULL, *old_arg, *new_arg = NULL;
5582 Aggref *ar, *newar;
5583 char *aggnm;
5584 bool agg_left = true, plus, minus, times;
5585 bool is_sum, is_avg, is_min, is_max;
5586
5587 if (opname == NULL)
5588 return NULL;
5589 plus = strcmp(opname, "+") == 0;
5590 minus = strcmp(opname, "-") == 0;
5591 times = strcmp(opname, "*") == 0;
5592 if (!(plus || minus || times)) /* division is skipped (rounding) */
5593 return NULL;
5594
5595 if (nargs == 1) { /* prefix unary minus */
5596 if (!minus)
5597 return NULL;
5598 aggn = peel_agg_casts((Node *)linitial(op->args));
5599 } else if (nargs == 2) {
5600 l = peel_agg_casts((Node *)linitial(op->args));
5601 r = peel_agg_casts((Node *)lsecond(op->args));
5602 if (IsA(l, Aggref) && IsA(r, Const)) { aggn = l; cn = r; agg_left = true; }
5603 else if (IsA(r, Aggref) && IsA(l, Const)) { aggn = r; cn = l; agg_left = false; }
5604 else return NULL;
5605 } else
5606 return NULL;
5607
5608 if (!IsA(aggn, Aggref))
5609 return NULL;
5610 ar = (Aggref *)aggn;
5611 /* Need a single ordinary argument: skip count(*) (aggstar), DISTINCT /
5612 * FILTER / ORDER BY aggregates, and RV-returning aggregates. */
5613 if (ar->aggstar || list_length(ar->args) != 1 ||
5614 ar->aggdistinct != NIL || ar->aggfilter != NULL || ar->aggorder != NIL)
5615 return NULL;
5616 if (OidIsValid(constants->OID_TYPE_RANDOM_VARIABLE) &&
5617 ar->aggtype == constants->OID_TYPE_RANDOM_VARIABLE)
5618 return NULL;
5619
5620 aggnm = get_func_name(ar->aggfnoid);
5621 if (aggnm == NULL)
5622 return NULL;
5623 is_sum = strcmp(aggnm, "sum") == 0; is_avg = strcmp(aggnm, "avg") == 0;
5624 is_min = strcmp(aggnm, "min") == 0; is_max = strcmp(aggnm, "max") == 0;
5625 pfree(aggnm);
5626 if (!(is_sum || is_avg || is_min || is_max))
5627 return NULL;
5628
5629 old_arg = (Node *)((TargetEntry *)linitial(ar->args))->expr;
5630
5631 if (nargs == 1) { /* -f(x) */
5632 if (is_sum || is_avg)
5633 new_arg = build_binop("-", NULL, old_arg); /* -x */
5634 } else if (times) { /* f(x)*c, c*f(x) */
5635 if (is_sum || is_avg)
5636 new_arg = agg_left ? build_binop("*", old_arg, cn)
5637 : build_binop("*", cn, old_arg);
5638 } else if (plus) { /* f(x)+c, c+f(x) */
5639 if (is_avg || is_min || is_max)
5640 new_arg = agg_left ? build_binop("+", old_arg, cn)
5641 : build_binop("+", cn, old_arg);
5642 } else /* minus */ {
5643 if (agg_left) { /* f(x)-c */
5644 if (is_avg || is_min || is_max)
5645 new_arg = build_binop("-", old_arg, cn);
5646 } else { /* c-f(x): only avg (no flip) */
5647 if (is_avg)
5648 new_arg = build_binop("-", cn, old_arg);
5649 }
5650 }
5651 if (new_arg == NULL)
5652 return NULL;
5653
5654 /* Keep the aggregate's argument type, so its function/return type stay valid. */
5655 if (exprType(new_arg) != exprType(old_arg))
5656 return NULL;
5657
5658 newar = (Aggref *)copyObject(ar);
5659 ((TargetEntry *)linitial(newar->args))->expr = (Expr *)new_arg;
5660 return (Node *)newar;
5661}
5662
5663/** @brief Tree-mutator applying @c try_push_into_aggref bottom-up. */
5664static Node *push_arith_into_agg_mutator(Node *node, void *ctx) {
5665 if (node == NULL)
5666 return NULL;
5667 node = expression_tree_mutator(node, push_arith_into_agg_mutator, ctx);
5668 if (IsA(node, OpExpr)) {
5669 Node *pushed = try_push_into_aggref((OpExpr *)node, (const constants_t *)ctx);
5670 if (pushed != NULL)
5671 return pushed;
5672 }
5673 return node;
5674}
5675
5676/**
5677 * @brief Replace every @c Aggref in @p q with a provenance-aware aggregate.
5678 *
5679 * Walks the query tree and substitutes each @c Aggref node with the result
5680 * of @c make_aggregation_expression, which wraps the original aggregate in
5681 * the semimodule machinery (@c provenance_semimod + @c array_agg +
5682 * @c provenance_aggregate).
5683 *
5684 * @param constants Extension OID cache.
5685 * @param q Query to mutate in place.
5686 * @param prov_atts List of provenance @c Var nodes.
5687 * @param op Semiring operation for combining tokens across rows.
5688 */
5689static void
5691 Query *q, List *prov_atts,
5692 semiring_operation op) {
5693
5694 /* A scalar aggregation (no GROUP BY / GROUPING SETS) yields a single,
5695 * always-present result row; mark its agg gates so the value-aware evaluators
5696 * treat the empty-input world as real (vs the "no row" of a grouped query). */
5697 bool is_scalar = (q->groupClause == NIL && q->groupingSets == NIL);
5698 aggregation_mutator_context context = {prov_atts, op, constants, is_scalar};
5699 ListCell *lc;
5700
5701 /* First push distributive constant arithmetic into aggregate arguments
5702 * (sum(x)*2 -> sum(2*x)), so those become clean aggregates rather than a
5703 * gate_arith over the aggregate. */
5704 query_tree_mutator(q, push_arith_into_agg_mutator, (void *)constants,
5705 QTW_DONT_COPY_QUERY | QTW_IGNORE_RT_SUBQUERIES);
5706
5707 query_tree_mutator(q, aggregation_mutator, &context,
5708 QTW_DONT_COPY_QUERY | QTW_IGNORE_RT_SUBQUERIES);
5709
5710 /* Post-processing: for target-list entries where a provenance_aggregate
5711 * result is nested inside an outer expression (e.g. SUM(id)+1),
5712 * insert a cast from agg_token back to the original aggregate return
5713 * type. Standalone provenance_aggregate entries are left as agg_token
5714 * so they display as "value (*)". */
5715 foreach(lc, q->targetList) {
5716 TargetEntry *te = (TargetEntry *)lfirst(lc);
5717 if (te->expr == NULL)
5718 continue;
5719 /* Skip standalone provenance_aggregate calls */
5720 if (IsA(te->expr, FuncExpr) &&
5721 ((FuncExpr *)te->expr)->funcid == constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
5722 continue;
5723 te->expr = (Expr *)cast_agg_token_mutator((Node *)te->expr,
5724 (void *)constants);
5725 }
5726}
5727
5728/**
5729 * @brief Append the provenance expression to @p q's target list.
5730 *
5731 * Inserts a new @c TargetEntry named @c provsql immediately before any
5732 * @c resjunk entries (which must remain last) and adjusts the @c resno
5733 * of subsequent entries accordingly.
5734 *
5735 * @param q Query to modify in place.
5736 * @param provenance Expression to add (becomes the @c provsql output column).
5737 */
5738static void add_to_select(Query *q, Expr *provenance) {
5739 TargetEntry *newte = makeNode(TargetEntry);
5740 bool inserted = false;
5741 unsigned resno = 0;
5742
5743 newte->expr = provenance;
5744 newte->resname = (char *)PROVSQL_COLUMN_NAME;
5745
5746 if (IsA(provenance, Var)) {
5747 RangeTblEntry *rte = list_nth(q->rtable, ((Var *)provenance)->varno - 1);
5748 newte->resorigtbl = rte->relid;
5749 newte->resorigcol = ((Var *)provenance)->varattno;
5750 }
5751
5752 /* Make sure to insert before all resjunk Target Entry */
5753 for (ListCell *cell = list_head(q->targetList); cell != NULL;) {
5754 TargetEntry *te = (TargetEntry *)lfirst(cell);
5755
5756 if (!inserted)
5757 ++resno;
5758
5759 if (te->resjunk) {
5760 if (!inserted) {
5761 newte->resno = resno;
5762 q->targetList = list_insert_nth(q->targetList, resno - 1, newte);
5763 cell = list_nth_cell(q->targetList, resno);
5764 te = (TargetEntry *)lfirst(cell);
5765 inserted = true;
5766 }
5767
5768 ++te->resno;
5769 }
5770
5771 cell = my_lnext(q->targetList, cell);
5772 }
5773
5774 if (!inserted) {
5775 newte->resno = resno + 1;
5776 q->targetList = lappend(q->targetList, newte);
5777 }
5778}
5779
5780/* -------------------------------------------------------------------------
5781 * Provenance function replacement
5782 * ------------------------------------------------------------------------- */
5783
5784/** @brief Context for the @c provenance_mutator tree walker. */
5786 Expr *provsql; ///< Provenance expression to substitute for provenance() calls
5787 const constants_t *constants; ///< Extension OID cache
5788 bool provsql_has_aggref; ///< @c true when @c provsql contains an @c Aggref (set once by @c replace_provenance_function_by_expression). When @c true, a @c provenance() substitution that lands inside another @c Aggref's argument tree would produce a nested same-level aggregate -- @c parse_agg.c forbids that shape, the planner's @c preprocess_aggrefs_walker does not recurse through @c Aggref boundaries, and the inner @c Aggref's @c aggno stays at the @c -1 sentinel and crashes @c ExecInterpExpr on @c ecxt_aggvalues[-1].
5789 bool inside_aggref; ///< @c true while descending the argument tree of an @c Aggref node.
5791
5792/**
5793 * @brief @c expression_tree_walker predicate: returns @c true on the first
5794 * @c Aggref it encounters.
5795 *
5796 * Used to decide whether the provenance expression about to be substituted
5797 * would inject a nested aggregate when a @c provenance() call lives inside
5798 * another @c Aggref's argument tree.
5799 */
5800static bool
5801expr_contains_aggref_walker(Node *node, void *context) {
5802 if (node == NULL)
5803 return false;
5804 if (IsA(node, Aggref))
5805 return true;
5806 return expression_tree_walker(node, expr_contains_aggref_walker, context);
5807}
5808
5809/**
5810 * @brief Tree-mutator that replaces provenance() calls with the actual provenance expression.
5811 * @param node Current expression tree node.
5812 * @param ctx Pointer to a @c provenance_mutator_context (provenance
5813 * expression and constants).
5814 * @return Possibly modified node.
5815 */
5816static Node *provenance_mutator(Node *node, void *ctx) {
5818 if (node == NULL)
5819 return NULL;
5820
5821 if (IsA(node, Aggref)) {
5822 /* Descend into the Aggref's arguments with @c inside_aggref set so we
5823 * can refuse substitutions that would create a nested same-level
5824 * aggregate. Save and restore the flag so sibling sub-expressions
5825 * outside this Aggref see the original value. */
5826 bool saved = context->inside_aggref;
5827 Node *result;
5828 context->inside_aggref = true;
5829 result = expression_tree_mutator(node, provenance_mutator, ctx);
5830 context->inside_aggref = saved;
5831 return result;
5832 }
5833
5834 if (IsA(node, FuncExpr)) {
5835 FuncExpr *f = (FuncExpr *)node;
5836
5837 if (f->funcid == context->constants->OID_FUNCTION_PROVENANCE) {
5838 if (context->inside_aggref && context->provsql_has_aggref) {
5840 "applying an SQL aggregate on top of a ProvSQL-introduced "
5841 "aggregation is not supported: the inner provenance() would "
5842 "be substituted with an expression containing an aggregate, "
5843 "producing a nested same-level aggregate that PostgreSQL "
5844 "rejects. Evaluate the per-row provenance in a subquery "
5845 "and aggregate the resulting scalar outside, or drop the "
5846 "surrounding aggregate.");
5847 }
5848 return (Node *)copyObject(context->provsql);
5849 }
5850 } else if (IsA(node, RangeTblEntry) || IsA(node, RangeTblFunction)) {
5851 // A provenance() expression in a From (not within a subquery) is
5852 // non-sensical
5853 return node;
5854 }
5855
5856 return expression_tree_mutator(node, provenance_mutator, ctx);
5857}
5858
5859/**
5860 * @brief Replace every explicit @c provenance() call in @p q with @p provsql.
5861 *
5862 * Users can write @c provenance() in the target list or WHERE to refer to the
5863 * provenance token of the current tuple. This mutator substitutes those calls
5864 * with the actual computed provenance expression.
5865 *
5866 * @param constants Extension OID cache.
5867 * @param q Query to mutate in place.
5868 * @param provsql Provenance expression to substitute.
5869 */
5870static void
5872 Query *q, Expr *provsql) {
5874
5875 context.provsql = provsql;
5876 context.constants = constants;
5877 context.provsql_has_aggref =
5878 expr_contains_aggref_walker((Node *) provsql, NULL);
5879 context.inside_aggref = false;
5880
5881 query_tree_mutator(q, provenance_mutator, &context,
5882 QTW_DONT_COPY_QUERY | QTW_IGNORE_RT_SUBQUERIES);
5883}
5884
5885/**
5886 * @brief Convert a SELECT DISTINCT into an equivalent GROUP BY.
5887 *
5888 * ProvSQL cannot handle DISTINCT directly (it would collapse provenance
5889 * tokens that should remain separate). This function moves every entry
5890 * from @p q->distinctClause into @p q->groupClause (skipping any that are
5891 * already there) and clears @p q->distinctClause.
5892 *
5893 * @param q Query to modify in place.
5894 */
5896 // First check which are already in the group by clause
5897 // Should be either none or all as "SELECT DISTINCT a, b ... GROUP BY a"
5898 // is invalid
5899 Bitmapset *already_in_group_by = NULL;
5900 ListCell *lc;
5901 foreach (lc, q->groupClause) {
5902 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc);
5903 already_in_group_by =
5904 bms_add_member(already_in_group_by, sgc->tleSortGroupRef);
5905 }
5906
5907 foreach (lc, q->distinctClause) {
5908 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc);
5909 if (!bms_is_member(sgc->tleSortGroupRef, already_in_group_by)) {
5910 q->groupClause = lappend(q->groupClause, sgc);
5911 }
5912 }
5913
5914 q->distinctClause = NULL;
5915}
5916
5917/**
5918 * @brief Normalise a supported @c SELECT @c DISTINCT into a @c GROUP @c BY.
5919 *
5920 * Wraps @c transform_distinct_into_group_by() with the validity guards
5921 * (DISTINCT ON and DISTINCT-on-aggregate-results stay rejected; a
5922 * DISTINCT not covering the whole target list is inconsistent).
5923 *
5924 * Called twice on the main rewrite path: once *before* @c inline_ctes()
5925 * so the recursive-reachability detectors see the @c GROUP @c BY form
5926 * (a @c SELECT @c DISTINCT region aggregation is provenance-identical
5927 * to its @c GROUP @c BY twin), and once at the late site --
5928 * idempotent, since the first call clears @c distinctClause, so the
5929 * second is a no-op for any query the first already normalised. The
5930 * target list carries only the user's columns at both call sites (the
5931 * provsql output column is spliced later), so the length guard reads
5932 * the same either way.
5933 *
5934 * @param q Query to normalise in place.
5935 */
5937 if (!q->distinctClause)
5938 return;
5939 if (q->hasDistinctOn)
5940 provsql_error("DISTINCT ON not supported");
5941 else if (q->hasAggs)
5942 provsql_error("DISTINCT on aggregate results not supported");
5943 else if (list_length(q->distinctClause) < list_length(q->targetList))
5944 provsql_error("Inconsistent DISTINCT and GROUP BY clauses not "
5945 "supported");
5946 else
5948}
5949
5950/**
5951 * @brief Remove sort/group references that belonged to removed provenance columns.
5952 *
5953 * After @c remove_provenance_attributes_select strips provenance entries from
5954 * the target list, any GROUP BY, ORDER BY, or DISTINCT clause that referenced
5955 * them by @c tleSortGroupRef must be cleaned up.
5956 *
5957 * @param q Query to modify in place.
5958 * @param removed_sortgrouprefs Bitmapset of @c ressortgroupref values to remove.
5959 */
5960static void
5962 const Bitmapset *removed_sortgrouprefs) {
5963 List **lists[3] = {&q->groupClause, &q->distinctClause, &q->sortClause};
5964 int i = 0;
5965
5966 for (i = 0; i < 3; ++i) {
5967 ListCell *cell, *prev;
5968
5969 for (cell = list_head(*lists[i]), prev = NULL; cell != NULL;) {
5970 SortGroupClause *sgc = (SortGroupClause *)lfirst(cell);
5971 if (bms_is_member(sgc->tleSortGroupRef, removed_sortgrouprefs)) {
5972 *lists[i] = my_list_delete_cell(*lists[i], cell, prev);
5973
5974 if (prev) {
5975 cell = my_lnext(*lists[i], prev);
5976 } else {
5977 cell = list_head(*lists[i]);
5978 }
5979 } else {
5980 prev = cell;
5981 cell = my_lnext(*lists[i], cell);
5982 }
5983 }
5984 }
5985}
5986
5987/**
5988 * @brief Strip the provenance column's type info from a set-operation node.
5989 *
5990 * When a provenance column is removed from a UNION/EXCEPT query's target list,
5991 * the matching entries in the @c SetOperationStmt's @c colTypes, @c colTypmods,
5992 * and @c colCollations lists must also be removed.
5993 *
5994 * @param q Query containing @c setOperations.
5995 * @param removed Boolean array (from @c remove_provenance_attributes_select)
5996 * indicating which columns were removed.
5997 */
5998static void remove_provenance_attribute_setoperations(Query *q, bool *removed) {
5999 SetOperationStmt *so = (SetOperationStmt *)q->setOperations;
6000 List **lists[3] = {&so->colTypes, &so->colTypmods, &so->colCollations};
6001 int i = 0;
6002
6003 for (i = 0; i < 3; ++i) {
6004 ListCell *cell, *prev;
6005 int j;
6006
6007 for (cell = list_head(*lists[i]), prev = NULL, j = 0; cell != NULL; ++j) {
6008 if (removed[j]) {
6009 *lists[i] = my_list_delete_cell(*lists[i], cell, prev);
6010
6011 if (prev) {
6012 cell = my_lnext(*lists[i], prev);
6013 } else {
6014 cell = list_head(*lists[i]);
6015 }
6016 } else {
6017 prev = cell;
6018 cell = my_lnext(*lists[i], cell);
6019 }
6020 }
6021 }
6022}
6023
6024/**
6025 * @brief Wrap a non-ALL set operation in an outer GROUP BY query.
6026 *
6027 * UNION / EXCEPT (without ALL) would deduplicate tuples before ProvSQL can
6028 * attach provenance tokens. To avoid this, the set operation is converted to
6029 * UNION ALL / EXCEPT ALL and a new outer query is built that groups the results
6030 * by all non-provenance columns, collecting tokens into an array for the
6031 * @c provenance_plus evaluation.
6032 *
6033 * After this rewrite the recursive call to @c process_query handles the
6034 * now-ALL inner set operation normally.
6035 *
6036 * @param q Query whose @c setOperations is non-ALL (modified to ALL in place).
6037 * @return New outer query that wraps @p q as a subquery RTE.
6038 */
6040 Query *new_query = makeNode(Query);
6041 RangeTblEntry *rte = makeNode(RangeTblEntry);
6042 FromExpr *jointree = makeNode(FromExpr);
6043 RangeTblRef *rtr = makeNode(RangeTblRef);
6044
6045 SetOperationStmt *stmt = (SetOperationStmt *)q->setOperations;
6046
6047 ListCell *lc;
6048 int sortgroupref = 0;
6049
6050 stmt->all = true;
6051 // we might leave sub nodes of the SetOperationsStmt tree with all = false
6052 // but only for recursive trees of operators and only union can be recursive
6053 // https://doxygen.postgresql.org/prepunion_8c_source.html#l00479
6054 // we will set therefore set them later in process_set_operation_union
6055
6056 rte->rtekind = RTE_SUBQUERY;
6057 rte->subquery = q;
6058 rte->eref = copyObject(((RangeTblEntry *)linitial(q->rtable))->eref);
6059 rte->inFromCl = true;
6060#if PG_VERSION_NUM < 160000
6061 // For PG_VERSION_NUM >= 160000, rte->perminfoindex==0 so no need to
6062 // care about permissions
6063 rte->requiredPerms = ACL_SELECT;
6064#endif
6065
6066 rtr->rtindex = 1;
6067 jointree->fromlist = list_make1(rtr);
6068
6069 new_query->commandType = CMD_SELECT;
6070 new_query->canSetTag = true;
6071 new_query->rtable = list_make1(rte);
6072 new_query->jointree = jointree;
6073 new_query->targetList = copyObject(q->targetList);
6074
6075 if (new_query->targetList) {
6076 foreach (lc, new_query->targetList) {
6077 TargetEntry *te = (TargetEntry *)lfirst(lc);
6078 SortGroupClause *sgc = makeNode(SortGroupClause);
6079
6080 sgc->tleSortGroupRef = te->ressortgroupref = ++sortgroupref;
6081
6082 get_sort_group_operators(exprType((Node *)te->expr), false, true, false,
6083 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
6084
6085 new_query->groupClause = lappend(new_query->groupClause, sgc);
6086 }
6087 } else {
6088 GroupingSet *gs = makeNode(GroupingSet);
6089 gs->kind = GROUPING_SET_EMPTY;
6090 gs->content = 0;
6091 gs->location = -1;
6092 new_query->groupingSets = list_make1(gs);
6093 }
6094
6095 return new_query;
6096}
6097
6098/* -------------------------------------------------------------------------
6099 * Detection walkers
6100 * ------------------------------------------------------------------------- */
6101
6102/**
6103 * @brief Tree walker that returns true if any @c provenance() call is found.
6104 *
6105 * Used to detect whether a query explicitly calls @c provenance(), which
6106 * triggers the substitution in @c replace_provenance_function_by_expression.
6107 * @param node Current expression tree node.
6108 * @param data Pointer to @c constants_t (cast from @c void*).
6109 * @return @c true if a @c provenance() call is found anywhere in @p node.
6110 */
6111static bool provenance_function_walker(Node *node, void *data) {
6112 const constants_t *constants = (const constants_t *)data;
6113 if (node == NULL)
6114 return false;
6115
6116 if (IsA(node, FuncExpr)) {
6117 FuncExpr *f = (FuncExpr *)node;
6118
6119 if (f->funcid == constants->OID_FUNCTION_PROVENANCE)
6120 return true;
6121 }
6122
6123 return expression_tree_walker(node, provenance_function_walker, data);
6124}
6125
6126/**
6127 * @brief Check whether a @c provenance() call appears in the GROUP BY list.
6128 *
6129 * When the user writes @c GROUP BY provenance(), ProvSQL must not add its own
6130 * group-by wrapper (the query is already grouping on the token).
6131 *
6132 * @param constants Extension OID cache.
6133 * @param q Query to inspect.
6134 * @return True if any GROUP BY key contains a @c provenance() call.
6135 */
6137 Query *q) {
6138 ListCell *lc;
6139
6140 /* Build the set of ressortgrouprefs that are actually in GROUP BY
6141 * (not ORDER BY or DISTINCT, which also set ressortgroupref). */
6142 Bitmapset *group_refs = NULL;
6143 foreach (lc, q->groupClause) {
6144 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc);
6145 group_refs = bms_add_member(group_refs, sgc->tleSortGroupRef);
6146 }
6147
6148 foreach (lc, q->targetList) {
6149 TargetEntry *te = (TargetEntry *)lfirst(lc);
6150 if (te->ressortgroupref > 0 &&
6151 bms_is_member(te->ressortgroupref, group_refs)) {
6152 if(expression_tree_walker((Node *)te, provenance_function_walker,
6153 (void *)constants)) {
6154 return true;
6155 }
6156
6157#if PG_VERSION_NUM >= 180000
6158 // Starting from PostgreSQL 18, the content of the GROUP BY is not
6159 // in the groupClause but in an associated RTE_GROUP RangeTblEntry
6160 if(IsA(te->expr, Var)) {
6161 Var *v = (Var *) te->expr;
6162 RangeTblEntry *r = (RangeTblEntry *)list_nth(q->rtable, v->varno - 1);
6163 if(r->rtekind == RTE_GROUP)
6164 if(expression_tree_walker((Node *) r->groupexprs, provenance_function_walker,
6165 (void *)constants)) {
6166 return true;
6167 }
6168 }
6169#endif
6170 }
6171 }
6172
6173 return false;
6174}
6175
6176/**
6177 * @brief Tree walker that detects any provenance-bearing relation or provenance() call.
6178 * @param node Current expression tree node.
6179 * @param data Pointer to @c constants_t (cast from @c void*).
6180 * @return @c true if provenance rewriting is needed for this node.
6181 */
6182/**
6183 * @brief Recursive helper for @c has_provenance_walker that detects
6184 * rv_cmp @c OpExpr and @c provenance() @c FuncExpr in
6185 * expression subtrees.
6186 *
6187 * Stops at Query boundaries: @c SubLink subselects (used as
6188 * scalar/array subqueries in expressions) are not rewritten by the
6189 * outer planner_hook pass, so a tracked relation inside one must not
6190 * cause the OUTER query's gate to engage. Only the @c testexpr of a
6191 * SubLink is followed (it lives in the outer's evaluation scope).
6192 */
6193static bool has_rv_or_provenance_call(Node *node, void *data) {
6194 const constants_t *constants = (const constants_t *)data;
6195 if (node == NULL)
6196 return false;
6197
6198 if (IsA(node, OpExpr)) {
6199 OpExpr *op = (OpExpr *)node;
6200 if (rv_cmp_index(constants, op->opfuncid) >= 0)
6201 return true;
6202 }
6203
6204 if (IsA(node, FuncExpr)) {
6205 FuncExpr *f = (FuncExpr *)node;
6206 if (f->funcid == constants->OID_FUNCTION_PROVENANCE)
6207 return true;
6208 }
6209
6210 if (IsA(node, SubLink)) {
6211 SubLink *sl = (SubLink *)node;
6212 return has_rv_or_provenance_call((Node *)sl->testexpr, data);
6213 }
6214
6215 /* Query nodes are opaque here; expression_tree_walker returns false
6216 * on them. Explicit short-circuit just makes the intent obvious. */
6217 if (IsA(node, Query))
6218 return false;
6219
6220 return expression_tree_walker(node, has_rv_or_provenance_call, data);
6221}
6222
6223/**
6224 * @brief Walker (this query level only): true if an @c EXPR_SUBLINK whose body
6225 * is a decorrelatable value subquery over a provenance-tracked base
6226 * relation appears in an expression.
6227 *
6228 * Lets the planner gate engage for a scalar subquery over a tracked relation
6229 * even when the OUTER query has no tracked relation -- decorrelate_scalar_
6230 * sublinks then handles it (wrapping the untracked outer with a certain
6231 * gate_one() provenance and warning that its tuple provenance is lost). The
6232 * shape conditions mirror decorrelate's subselect validation, so engagement
6233 * implies the decorrelation succeeds (no engage-then-error regression); a
6234 * non-decorrelatable scalar subquery still leaves the gate untouched and runs
6235 * as plain SQL. Does not descend into nested Query / SubLink subselects.
6236 */
6237/* -------------------------------------------------------------------------
6238 * (A) Inert provenance() fetches.
6239 *
6240 * A scalar SubLink whose subselect's *sole* visible output is a bare
6241 * provenance() call -- e.g. `(SELECT provenance() FROM tests WHERE ...)` --
6242 * is an *inert* read of a tuple's identity token: it must yield the token
6243 * as a plain uuid value without coupling that relation into the
6244 * surrounding row's lineage (the basis of the conditioning operator's
6245 * evidence). Such a SubLink's subselect is processed early
6246 * (provenance() resolved to that scope's token, no provsql column
6247 * appended) and recorded here, so the coupling-time detectors
6248 * (has_provenance, the tracked-sublink and decorrelation-value walkers)
6249 * treat it as an untracked value subquery. A subselect that brings along
6250 * any other column stays correlated (ordinary behaviour).
6251 *
6252 * The list holds the processed subselect Query pointers (stable: processed
6253 * in place). Saved/restored around provsql_planner for re-entrancy.
6254 * ------------------------------------------------------------------------- */
6255static List *provsql_inert_subselects = NIL;
6256
6257/** @brief Is @p q a recorded inert provenance()-fetch subselect? */
6258static bool is_inert_subselect(Query *q) {
6259 return q != NULL && list_member_ptr(provsql_inert_subselects, q);
6260}
6261
6262/** @brief Does @p sl wrap a recorded inert provenance()-fetch subselect?
6263 * Such a SubLink is an untracked scalar value: the decorrelation /
6264 * move-to-FROM passes must leave it alone (moving or aggregating it would
6265 * couple its relation into the outer lineage and wrap the token in an
6266 * aggregate gate). */
6267static bool sublink_is_inert(SubLink *sl) {
6268 return sl != NULL && sl->subselect && IsA(sl->subselect, Query) &&
6269 is_inert_subselect((Query *)sl->subselect);
6270}
6271
6272/**
6273 * @brief Whether @p sub's sole non-junk output is a bare provenance() call.
6274 *
6275 * Checked before resolution (the target is still the raw provenance()
6276 * FuncExpr). The supported shape is deliberately narrow -- a plain scan
6277 * projecting only provenance(): exactly one visible target entry, and it
6278 * is provenance(), with no aggregation / grouping / HAVING / set-op /
6279 * DISTINCT / window (those carry probabilistic-provenance semantics an
6280 * inert, physically-evaluated fetch would not honour, so they stay on the
6281 * ordinary path). A subselect bringing any other column likewise stays
6282 * correlated.
6283 */
6285 Query *sub) {
6286 TargetEntry *only = NULL;
6287 ListCell *lc;
6288 if (sub == NULL || !IsA(sub, Query))
6289 return false;
6290 foreach (lc, sub->targetList) {
6291 TargetEntry *te = (TargetEntry *)lfirst(lc);
6292 if (te->resjunk)
6293 continue;
6294 if (only != NULL)
6295 return false; /* a second visible column -> stays correlated */
6296 only = te;
6297 }
6298 return only != NULL && IsA(only->expr, FuncExpr) &&
6299 ((FuncExpr *)only->expr)->funcid == constants->OID_FUNCTION_PROVENANCE;
6300}
6301
6302/** @brief Walker: set found if an inert provenance()-fetch SubLink is present
6303 * in this query's *own* clauses (not descending into other scopes). */
6304static bool inert_fetch_sublink_walker(Node *node, void *data) {
6305 if (node == NULL)
6306 return false;
6307 if (IsA(node, SubLink)) {
6308 SubLink *sl = (SubLink *)node;
6309 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
6310 IsA(sl->subselect, Query) &&
6312 (Query *)sl->subselect))
6313 return true;
6314 return expression_tree_walker((Node *)sl->testexpr,
6316 }
6317 if (IsA(node, Query))
6318 return false; /* a nested scope handles its own */
6319 return expression_tree_walker(node, inert_fetch_sublink_walker, data);
6320}
6321
6322/** @brief Does @p q's own target list / jointree / HAVING contain an inert
6323 * provenance()-fetch SubLink? Such a query must be rewritten so the early
6324 * inert pass resolves the fetch, even with an otherwise-untracked outer. */
6325static bool query_has_inert_fetch(const constants_t *constants, Query *q) {
6326 if (inert_fetch_sublink_walker((Node *)q->targetList, (void *)constants))
6327 return true;
6328 if (q->jointree &&
6329 inert_fetch_sublink_walker((Node *)q->jointree, (void *)constants))
6330 return true;
6331 if (q->havingQual &&
6332 inert_fetch_sublink_walker(q->havingQual, (void *)constants))
6333 return true;
6334 return false;
6335}
6336
6337static bool decorr_value_sublink_walker(Node *node, void *data) {
6338 const constants_t *constants = (const constants_t *)data;
6339 if (node == NULL)
6340 return false;
6341 if (IsA(node, SubLink)) {
6342 SubLink *sl = (SubLink *)node;
6343 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
6344 IsA(sl->subselect, Query) &&
6345 is_inert_subselect((Query *)sl->subselect))
6346 return false; /* inert fetch: an untracked value, do not decorrelate */
6347 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
6348 IsA(sl->subselect, Query)) {
6349 Query *sub = (Query *)sl->subselect;
6350 if (!sub->hasAggs && !sub->groupClause && !sub->groupingSets &&
6351 !sub->distinctClause && !sub->setOperations && !sub->hasWindowFuncs &&
6352 !sub->hasSubLinks && !sub->limitCount && !sub->limitOffset &&
6353 !sub->cteList && list_length(sub->rtable) == 1 &&
6354 list_length(sub->targetList) == 1 && sub->jointree &&
6355 list_length(sub->jointree->fromlist) == 1 &&
6356 IsA(linitial(sub->jointree->fromlist), RangeTblRef)) {
6357 RangeTblEntry *qr = (RangeTblEntry *)linitial(sub->rtable);
6358 if (qr->rtekind == RTE_RELATION) {
6359 ListCell *lc;
6360 AttrNumber a = 0;
6361 foreach (lc, qr->eref->colnames) {
6362 ++a;
6363 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME) &&
6364 get_atttype(qr->relid, a) == constants->OID_TYPE_UUID)
6365 return true;
6366 }
6367 }
6368 }
6369 }
6370 return false; /* do not descend into the subselect */
6371 }
6372 if (IsA(node, Query))
6373 return false; /* nested queries are handled by has_provenance_walker */
6374 return expression_tree_walker(node, decorr_value_sublink_walker, data);
6375}
6376
6377static bool has_provenance_walker(Node *node, void *data) {
6378 const constants_t *constants = (const constants_t *)data;
6379 if (node == NULL)
6380 return false;
6381
6382 if (IsA(node, Query)) {
6383 Query *q = (Query *)node;
6384 ListCell *rc;
6385
6386 /* Walk into CTE subqueries explicitly: they will be inlined as
6387 * subqueries by the rewriter, so a tracked-table inside one (or
6388 * an rv_cmp / provenance() call) matters for this query. */
6389 foreach (rc, q->cteList) {
6390 CommonTableExpr *cte = (CommonTableExpr *)lfirst(rc);
6391 if (has_provenance_walker((Node *)cte->ctequery, data))
6392 return true;
6393 }
6394
6395 /* Walk this query's own expressions for rv_cmp OpExpr and
6396 * provenance() FuncExpr. Use the SubLink-aware walker so we
6397 * don't descend into expression-context subqueries (they get
6398 * planned standalone; an rv_cmp inside one matters only to
6399 * that planning pass).
6400 *
6401 * This intentionally replaces a single query_tree_walker call:
6402 * that helper recurses with the passed walker into BOTH rtable
6403 * RTEs (RTE_SUBQUERY) and SubLink subselects, which would erase
6404 * the SubLink/RTE_SUBQUERY distinction we need. */
6405 if (has_rv_or_provenance_call((Node *)q->targetList, data))
6406 return true;
6407 if (has_rv_or_provenance_call((Node *)q->jointree, data))
6408 return true;
6409 if (has_rv_or_provenance_call((Node *)q->havingQual, data))
6410 return true;
6411 if (has_rv_or_provenance_call((Node *)q->returningList, data))
6412 return true;
6413
6414 /* A decorrelatable value scalar subquery over a tracked relation engages
6415 * the gate even with an untracked outer (handled with a warning). */
6416 if (decorr_value_sublink_walker((Node *)q->targetList, data))
6417 return true;
6418 if (q->jointree &&
6419 decorr_value_sublink_walker((Node *)q->jointree->quals, data))
6420 return true;
6421
6422 /* An (unresolved) inert provenance()-fetch sublink also engages the
6423 * gate, at every level -- including a FROM subquery whose only
6424 * provenance is the fetch -- so the early inert pass resolves it.
6425 * (After resolution the subselect no longer reads as a pure fetch,
6426 * so this does not re-fire.) */
6427 if (query_has_inert_fetch(constants, q))
6428 return true;
6429
6430 foreach (rc, q->rtable) {
6431 RangeTblEntry *r = (RangeTblEntry *)lfirst(rc);
6432 if (r->rtekind == RTE_RELATION) {
6433 ListCell *lc;
6434 AttrNumber attid = 1;
6435
6436 foreach (lc, r->eref->colnames) {
6437 const char *v = strVal(lfirst(lc));
6438
6439 if (!strcmp(v, PROVSQL_COLUMN_NAME) &&
6440 get_atttype(r->relid, attid) == constants->OID_TYPE_UUID) {
6441 return true;
6442 }
6443
6444 ++attid;
6445 }
6446 } else if (r->rtekind == RTE_FUNCTION) {
6447 ListCell *lc;
6448 AttrNumber attid = 1;
6449
6450 foreach (lc, r->functions) {
6451 RangeTblFunction *func = (RangeTblFunction *)lfirst(lc);
6452
6453 if (func->funccolcount == 1) {
6454 FuncExpr *expr = (FuncExpr *)func->funcexpr;
6455 if (expr->funcresulttype == constants->OID_TYPE_UUID &&
6456 !strcmp(get_rte_attribute_name(r, attid),
6458 return true;
6459 }
6460 }
6461
6462 attid += func->funccolcount;
6463 }
6464 } else if (r->rtekind == RTE_SUBQUERY && r->subquery != NULL) {
6465 /* A FROM-source subquery contributes its provenance to ours;
6466 * process_query recurses on it explicitly, so we must detect
6467 * tracked relations / rv_cmp / provenance() inside it. */
6468 if (has_provenance_walker((Node *)r->subquery, data))
6469 return true;
6470 }
6471 }
6472 }
6473
6474 /* For non-Query nodes, use the expression-only walker. It detects
6475 * rv_cmp OpExpr and provenance() FuncExpr inside arbitrary
6476 * sub-expressions (BoolExpr around an rv comparison, RV cmp under
6477 * IS-DISTINCT-FROM, ...) but stops at Query boundaries so a sibling
6478 * subquery's tracked rtable doesn't make THIS query's gate engage
6479 * (subqueries have their own planner_hook pass). */
6480 return has_rv_or_provenance_call(node, data);
6481}
6482
6483/**
6484 * @brief Return true if @p q involves any provenance-bearing relation or
6485 * contains an explicit @c provenance() call.
6486 *
6487 * This is the gate condition checked by @c provsql_planner before doing any
6488 * rewriting: if neither condition holds the query is passed through unchanged.
6489 *
6490 * @param constants Extension OID cache.
6491 * @param q Query to inspect.
6492 * @return True if provenance rewriting is needed.
6493 */
6494static bool has_provenance(const constants_t *constants, Query *q) {
6495 /* An already-processed inert provenance() fetch is an untracked value
6496 * subquery: it contributes no lineage to whatever references it.
6497 * (Detection of an *unresolved* inert fetch -- which must engage the
6498 * gate -- lives in has_provenance_walker, so it fires at every level.) */
6499 if (is_inert_subselect(q))
6500 return false;
6501 return has_provenance_walker((Node *)q, (void *)constants);
6502}
6503
6504/** @brief Context for @c sublink_over_tracked_walker. */
6505typedef struct {
6507 bool found;
6509
6510/** @brief Walker: set @c found if a @c SubLink whose subselect (transitively)
6511 * involves a provenance-tracked relation is reached. */
6512static bool sublink_over_tracked_walker(Node *node, void *cx) {
6514 if (node == NULL || c->found)
6515 return false;
6516 if (IsA(node, SubLink)) {
6517 SubLink *sl = (SubLink *)node;
6518 if (IsA(sl->subselect, Query) &&
6519 has_provenance(c->constants, (Query *)sl->subselect)) {
6520 c->found = true;
6521 return true;
6522 }
6523 /* Not tracked at this level: fall through to descend (the subselect, for
6524 * nested sublinks, and the testexpr). */
6525 }
6526 if (IsA(node, Query))
6527 return query_tree_walker((Query *)node, sublink_over_tracked_walker, cx, 0);
6528 return expression_tree_walker(node, sublink_over_tracked_walker, cx);
6529}
6530
6531/**
6532 * @brief Does any @c SubLink in @p q's own clauses have a subselect that
6533 * (transitively) involves a provenance-tracked relation?
6534 *
6535 * Distinguishes the @c "Subqueries not supported" cases (a sublink over a tracked
6536 * @c Q, which needs the rewrite passes) from a harmless one whose body touches no
6537 * tracked relation -- a deterministic filter/value (untracked data is certain, so
6538 * the same in every possible world) that Postgres can evaluate directly, leaving
6539 * the row's provenance unchanged. Only @p q's own expressions are inspected, not
6540 * its range table (the outer relation is tracked, and FROM subqueries get their
6541 * own @c process_query pass).
6542 */
6543static bool query_has_tracked_sublink(const constants_t *constants, Query *q) {
6545 c.constants = constants;
6546 c.found = false;
6547 sublink_over_tracked_walker((Node *)q->targetList, &c);
6548 if (!c.found && q->jointree)
6549 sublink_over_tracked_walker((Node *)q->jointree, &c);
6550 if (!c.found && q->havingQual)
6551 sublink_over_tracked_walker(q->havingQual, &c);
6552 return c.found;
6553}
6554
6555/**
6556 * @brief Collect @c SubLink nodes sitting in a "direct", decorrelatable position:
6557 * a target-list entry that @e is the sublink, or a WHERE/HAVING boolean
6558 * factor or a direct operand of a comparison.
6559 *
6560 * These are exactly the positions the rewrite passes (@c rewrite_predicate_sublinks,
6561 * @c decorrelate_scalar_sublinks…) consume. A tracked sublink still in such a
6562 * position after those passes is a genuinely unsupported @e direct form (a
6563 * @c GROUP @c BY body, a multi-relation @c EXISTS…) that must raise the clean
6564 * error. A tracked sublink anywhere @e else is nested inside an expression
6565 * (arithmetic, a function argument); those are let through with a warning instead
6566 * -- Postgres evaluates the sublink normally (correct value), the row keeps the
6567 * outer relation's provenance, and the subquery's data is treated as certain.
6568 */
6569static void collect_direct_qual_sublinks(Node *node, List **out) {
6570 if (node == NULL)
6571 return;
6572 if (IsA(node, SubLink)) {
6573 /* A bare sublink boolean factor (EXISTS / IN / NOT …). */
6574 *out = lappend(*out, node);
6575 return;
6576 }
6577 if (IsA(node, BoolExpr)) {
6578 ListCell *lc;
6579 foreach (lc, ((BoolExpr *)node)->args)
6580 collect_direct_qual_sublinks((Node *)lfirst(lc), out);
6581 return;
6582 }
6583 if (IsA(node, OpExpr)) {
6584 /* A comparison whose direct operand is the sublink (a coercion in between is
6585 * fine, but arithmetic is not -- that makes the sublink nested). */
6586 ListCell *lc;
6587 foreach (lc, ((OpExpr *)node)->args) {
6588 Node *a = (Node *)lfirst(lc);
6589 if (IsA(a, RelabelType))
6590 a = (Node *)((RelabelType *)a)->arg;
6591 if (IsA(a, SubLink))
6592 *out = lappend(*out, a);
6593 }
6594 return;
6595 }
6596}
6597
6598/** @brief Context for @c sublink_classify_walker. */
6599typedef struct {
6601 List *direct; /* sublinks in a decorrelatable position */
6602 List *nested; /* tracked EXPR_SUBLINKs nested in an expression */
6603 bool has_unsupported_direct; /* a tracked sublink in a direct position remains */
6605
6606/**
6607 * @brief Walker classifying each tracked @c SubLink of a query as either a
6608 * still-unsupported @e direct form or an @e arithmetic-nested one.
6609 *
6610 * Stops descending at a tracked sublink (its subselect is Postgres' business once
6611 * we decide to pass it through); keeps descending through untracked sublinks so a
6612 * tracked one nested deeper is still found.
6613 */
6614static bool sublink_classify_walker(Node *node, void *cx) {
6616 if (node == NULL)
6617 return false;
6618 if (IsA(node, SubLink)) {
6619 SubLink *sl = (SubLink *)node;
6620 if (IsA(sl->subselect, Query) &&
6621 has_provenance(c->constants, (Query *)sl->subselect)) {
6622 if (list_member_ptr(c->direct, sl) || sl->subLinkType != EXPR_SUBLINK)
6623 c->has_unsupported_direct = true;
6624 else
6625 c->nested = lappend(c->nested, sl);
6626 return false; /* do not descend into a tracked sublink */
6627 }
6628 /* untracked: fall through to descend (a tracked one may be nested inside) */
6629 }
6630 if (IsA(node, Query))
6631 return query_tree_walker((Query *)node, sublink_classify_walker, cx, 0);
6632 return expression_tree_walker(node, sublink_classify_walker, cx);
6633}
6634
6635/**
6636 * @brief Partition @p q's remaining tracked sublinks into unsupported-direct vs
6637 * arithmetic-nested. Returns the list of nested @c SubLink nodes (for
6638 * warnings) and sets @p *has_direct if any unsupported direct form remains.
6639 */
6640static List *classify_remaining_sublinks(const constants_t *constants, Query *q,
6641 bool *has_direct) {
6643 ListCell *lc;
6644
6645 c.constants = constants;
6646 c.direct = NIL;
6647 c.nested = NIL;
6648 c.has_unsupported_direct = false;
6649
6650 /* Direct positions: a target entry that IS the sublink (a coercion allowed). */
6651 foreach (lc, q->targetList) {
6652 Node *e = (Node *)((TargetEntry *)lfirst(lc))->expr;
6653 if (e && IsA(e, RelabelType))
6654 e = (Node *)((RelabelType *)e)->arg;
6655 if (e && IsA(e, SubLink))
6656 c.direct = lappend(c.direct, e);
6657 }
6658 if (q->jointree && q->jointree->quals)
6659 collect_direct_qual_sublinks(q->jointree->quals, &c.direct);
6660 if (q->havingQual)
6661 collect_direct_qual_sublinks(q->havingQual, &c.direct);
6662
6663 sublink_classify_walker((Node *)q->targetList, &c);
6664 if (q->jointree)
6665 sublink_classify_walker((Node *)q->jointree, &c);
6666 if (q->havingQual)
6667 sublink_classify_walker(q->havingQual, &c);
6668
6669 *has_direct = c.has_unsupported_direct;
6670 return c.nested;
6671}
6672
6673/**
6674 * @brief Walker: true if @p node (descending through nested queries) contains
6675 * an explicit @c provenance() call.
6676 */
6677static bool calls_provenance_walker(Node *node, void *data) {
6678 if (node == NULL)
6679 return false;
6680 if (IsA(node, FuncExpr) &&
6681 ((FuncExpr *)node)->funcid ==
6682 ((const constants_t *)data)->OID_FUNCTION_PROVENANCE)
6683 return true;
6684 if (IsA(node, Query))
6685 return query_tree_walker((Query *)node, calls_provenance_walker, data, 0);
6686 return expression_tree_walker(node, calls_provenance_walker, data);
6687}
6688
6689/**
6690 * @brief Walker: true if a @c SubLink subselect calls @c provenance().
6691 *
6692 * A @c SubLink subselect (scalar / @c IN / @c EXISTS) is planned standalone, so
6693 * it never goes through this hook -- a @c provenance() call inside one is never
6694 * rewritten and falls through to its runtime stub (NULL or a misleading error).
6695 * ProvSQL does not propagate provenance through a @c SubLink, so we detect the
6696 * @c provenance() use up front and raise a clear error instead.
6697 *
6698 * Only the explicit @c provenance() call is flagged, not a mere read of a
6699 * tracked relation's columns: @c (SELECT @c array_agg(provsql) @c FROM @c t) and
6700 * other plain column reads inside a @c SubLink are legitimate and must keep
6701 * working. Tracked relations reached through the @c FROM clause
6702 * (@c RTE_SUBQUERY) are fully supported and never reach this walker's @c SubLink
6703 * arm.
6704 */
6705static bool provenance_in_sublink_walker(Node *node, void *data) {
6706 if (node == NULL)
6707 return false;
6708 if (IsA(node, Query))
6709 return query_tree_walker((Query *)node, provenance_in_sublink_walker, data, 0);
6710 if (IsA(node, SubLink)) {
6711 SubLink *sl = (SubLink *)node;
6712 if (sl->subselect && IsA(sl->subselect, Query) &&
6713 calls_provenance_walker(sl->subselect, data)) {
6714 /* A scalar sublink whose sole output is provenance() is an inert
6715 * token fetch, handled by process_query; allow it. Any other
6716 * provenance() in a sublink is the unsupported form. */
6717 if (sl->subLinkType == EXPR_SUBLINK &&
6719 (Query *)sl->subselect))
6720 return false;
6721 return true;
6722 }
6723 }
6724 return expression_tree_walker(node, provenance_in_sublink_walker, data);
6725}
6726
6727/**
6728 * @brief Remove the auto-added @c provsql output column from a rewritten query.
6729 *
6730 * The inverse of @c add_to_select: drops the @c TargetEntry named
6731 * @c PROVSQL_COLUMN_NAME and decrements the @c resno of every later entry, so
6732 * the column numbering stays contiguous. Used when a query was rewritten for
6733 * its own provenance semantics (HAVING lifting, @c provenance() resolution) but
6734 * the caller cannot store the provenance -- e.g. an @c INSERT @c ... @c SELECT
6735 * whose target table has no provsql column.
6736 */
6737static void remove_provsql_from_select(Query *q) {
6738 ListCell *lc;
6739 ListCell *prev = NULL;
6740 int removed_resno = -1;
6741
6742 foreach (lc, q->targetList) {
6743 TargetEntry *te = (TargetEntry *)lfirst(lc);
6744 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME)) {
6745 removed_resno = te->resno;
6746 q->targetList = my_list_delete_cell(q->targetList, lc, prev);
6747 break;
6748 }
6749 prev = lc;
6750 }
6751
6752 if (removed_resno < 0)
6753 return;
6754
6755 foreach (lc, q->targetList) {
6756 TargetEntry *te = (TargetEntry *)lfirst(lc);
6757 if (te->resno > removed_resno)
6758 --te->resno;
6759 }
6760}
6761
6762/**
6763 * @brief Tree walker that detects any Var of type agg_token.
6764 * @param node Current expression tree node.
6765 * @param data Pointer to a @c constants_t (extension OID cache).
6766 * @return @c true if an agg_token Var is found in @p node.
6767 */
6768static bool aggtoken_walker(Node *node, void *data) {
6769 const constants_t *constants = (const constants_t *) data;
6770 if (node == NULL)
6771 return false;
6772
6773 if (IsA(node, Var)) {
6774 Var *v = (Var *) node;
6775 if(v->vartype == constants->OID_TYPE_AGG_TOKEN)
6776 return true;
6777 }
6778
6779 return expression_tree_walker(node, aggtoken_walker, data);
6780}
6781
6782/**
6783 * @brief Return true if @p node contains a @c Var of type @c agg_token.
6784 *
6785 * Used to detect whether a WHERE clause references an aggregate result
6786 * (which must be moved to HAVING).
6787 *
6788 * @param node Expression tree to inspect.
6789 * @param constants Extension OID cache.
6790 * @return True if an @c agg_token @c Var is found anywhere in @p node.
6791 */
6792static bool has_aggtoken(Node *node, const constants_t *constants) {
6793 return expression_tree_walker(node, aggtoken_walker, (void*) constants);
6794}
6795
6796/**
6797 * @brief Walker for @c needs_having_lift: detect any operand shape that
6798 * the HAVING-lift rewriter (@c having_OpExpr_to_provenance_cmp)
6799 * needs to handle specially.
6800 *
6801 * Returns @c true on:
6802 * - a @c Var of type @c agg_token; or
6803 * - a @c FuncExpr whose @c funcid is @c provenance_aggregate (the
6804 * wrapper the planner-hook puts around aggregates over tracked
6805 * non-RV columns -- yields @c agg_token).
6806 *
6807 * Anything else (deterministic scalars, plain @c Const, @c FuncExpr
6808 * over @c random_variable like @c expected / @c variance / @c moment,
6809 * comparisons of those) is left for PostgreSQL to evaluate natively;
6810 * the HAVING-lift never needs to touch it.
6811 */
6812static bool having_lift_walker(Node *node, void *data) {
6813 const constants_t *constants = (const constants_t *) data;
6814 if (node == NULL)
6815 return false;
6816
6817 if (IsA(node, Var)) {
6818 Var *v = (Var *) node;
6819 if (v->vartype == constants->OID_TYPE_AGG_TOKEN)
6820 return true;
6821 }
6822
6823 if (IsA(node, FuncExpr)) {
6824 FuncExpr *fe = (FuncExpr *) node;
6825 if (fe->funcid == constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
6826 return true;
6827 }
6828
6829 return expression_tree_walker(node, having_lift_walker, data);
6830}
6831
6832/**
6833 * @brief Return true if @p havingQual contains anything the HAVING-lift
6834 * path needs to handle (an @c agg_token Var or a
6835 * @c provenance_aggregate wrapper). A qual that returns @c false
6836 * is left in place for PostgreSQL to evaluate, while the
6837 * per-group provenance still gets a @c gate_delta wrapper.
6838 *
6839 * This is what lets a HAVING like @c expected(avg(rv)) > 20 work
6840 * directly: @c provsql.avg returns @c random_variable (not
6841 * @c agg_token), @c expected collapses to a scalar @c double, and the
6842 * surrounding comparison is a plain Boolean that PostgreSQL can filter
6843 * groups by without any provenance-side rewriting.
6844 */
6845static bool needs_having_lift(Node *havingQual, const constants_t *constants) {
6846 return expression_tree_walker(havingQual, having_lift_walker,
6847 (void *) constants);
6848}
6849
6850/* bool_or / bool_and / its every() alias -- the boolean aggregates that the
6851 * HAVING boolean-domain evaluator handles. */
6852static bool is_supported_bool_agg(Oid aggfnoid) {
6853 char *name = get_func_name(aggfnoid);
6854 bool yes;
6855 if (!name)
6856 return false;
6857 yes = strcmp(name, "bool_or") == 0 || strcmp(name, "bool_and") == 0 ||
6858 strcmp(name, "every") == 0;
6859 pfree(name);
6860 return yes;
6861}
6862
6863/* Normalise a bare boolean aggregate used directly as a HAVING condition --
6864 * @c "HAVING bool_or(x)", @c "HAVING NOT(every(x))" -- into the explicit
6865 * @c "agg = true" comparison, so the existing aggregate-comparison recognition
6866 * (@c needs_having_lift) and the boolean-domain HAVING evaluator handle it.
6867 * Descends the Boolean structure (AND / OR / NOT) but not into comparison
6868 * operands: an aggregate already inside a comparison is left untouched. */
6869static Node *normalize_bool_agg_having(Node *n) {
6870 if (n == NULL)
6871 return NULL;
6872 if (IsA(n, BoolExpr)) {
6873 BoolExpr *be = (BoolExpr *) n;
6874 ListCell *lc;
6875 foreach (lc, be->args)
6876 lfirst(lc) = normalize_bool_agg_having((Node *) lfirst(lc));
6877 return n;
6878 }
6879 if (IsA(n, Aggref)) {
6880 Aggref *ar = (Aggref *) n;
6881 if (ar->aggtype == BOOLOID && is_supported_bool_agg(ar->aggfnoid)) {
6882 OpExpr *eq = makeNode(OpExpr);
6883 eq->opno = BooleanEqualOperator;
6884 eq->opfuncid = get_opcode(BooleanEqualOperator);
6885 eq->opresulttype = BOOLOID;
6886 eq->opretset = false;
6887 eq->opcollid = InvalidOid;
6888 eq->inputcollid = InvalidOid;
6889 eq->args = list_make2(ar, makeBoolConst(true, false));
6890 eq->location = -1;
6891 return (Node *) eq;
6892 }
6893 }
6894 return n;
6895}
6896
6897/**
6898 * @brief Rewrite an EXCEPT query into a LEFT JOIN with monus provenance.
6899 *
6900 * EXCEPT cannot be handled directly because it deduplicates. This function
6901 * transforms:
6902 * @code
6903 * SELECT … FROM A EXCEPT SELECT … FROM B
6904 * @endcode
6905 * into a LEFT JOIN of A and B on equality of all non-provenance columns,
6906 * clears @c setOperations, and leaves the monus token combination to
6907 * @c make_provenance_expression (which will see @c SR_MONUS).
6908 *
6909 * Only simple (non-chained) EXCEPT is supported; chained EXCEPT raises an
6910 * error.
6911 *
6912 * @param constants Extension OID cache.
6913 * @param q Query to rewrite in place.
6914 * @return Always true (errors out on unsupported cases).
6915 */
6916static bool transform_except_into_join(const constants_t *constants, Query *q) {
6917 SetOperationStmt *setOps = (SetOperationStmt *)q->setOperations;
6918 RangeTblEntry *rte = makeNode(RangeTblEntry);
6919 FromExpr *fe = makeNode(FromExpr);
6920 JoinExpr *je = makeNode(JoinExpr);
6921 BoolExpr *expr = makeNode(BoolExpr);
6922 ListCell *lc;
6923 int attno = 1;
6924
6925 if (!IsA(setOps->larg, RangeTblRef) || !IsA(setOps->rarg, RangeTblRef)) {
6926 provsql_error("Unsupported chain of EXCEPT operations");
6927 }
6928
6929 expr->boolop = AND_EXPR;
6930 expr->location = -1;
6931 expr->args = NIL;
6932
6933 foreach (lc, q->targetList) {
6934 TargetEntry *te = (TargetEntry *)lfirst(lc);
6935 Var *v;
6936
6937 if (!IsA(te->expr, Var))
6938 provsql_error("EXCEPT query format not supported");
6939
6940 v = (Var *)te->expr;
6941
6942 if (v->vartype != constants->OID_TYPE_UUID) {
6943 OpExpr *oe = makeNode(OpExpr);
6944 Oid opno = find_equality_operator(v->vartype, v->vartype);
6945 Operator opInfo = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
6946 Form_pg_operator opform;
6947 Var *leftArg, *rightArg;
6948
6949 if (!HeapTupleIsValid(opInfo))
6950 provsql_error("could not find operator with OID %u to compare variables of type %u",
6951 opno, v->vartype);
6952
6953 opform = (Form_pg_operator)GETSTRUCT(opInfo);
6954 leftArg = makeNode(Var);
6955 rightArg = makeNode(Var);
6956
6957 oe->opno = opno;
6958 oe->opfuncid = opform->oprcode;
6959 oe->opresulttype = opform->oprresult;
6960 oe->opcollid = InvalidOid;
6961 oe->inputcollid = DEFAULT_COLLATION_OID;
6962
6963 leftArg->varno = ((RangeTblRef *)setOps->larg)->rtindex;
6964 rightArg->varno = ((RangeTblRef *)setOps->rarg)->rtindex;
6965 leftArg->varattno = rightArg->varattno = attno;
6966
6967#if PG_VERSION_NUM >= 130000
6968 leftArg->varnosyn = rightArg->varnosyn = 0;
6969 leftArg->varattnosyn = rightArg->varattnosyn = 0;
6970#else
6971 leftArg->varnoold = leftArg->varno;
6972 rightArg->varnoold = rightArg->varno;
6973 leftArg->varoattno = rightArg->varoattno = attno;
6974#endif
6975
6976 leftArg->vartype = rightArg->vartype = v->vartype;
6977 leftArg->varcollid = rightArg->varcollid = InvalidOid;
6978 leftArg->vartypmod = rightArg->vartypmod = -1;
6979 leftArg->location = rightArg->location = -1;
6980
6981 oe->args = list_make2(leftArg, rightArg);
6982 oe->location = -1;
6983 expr->args = lappend(expr->args, oe);
6984
6985 ReleaseSysCache(opInfo);
6986 }
6987
6988 ++attno;
6989 }
6990
6991 /* Populate the JOIN RTE's eref / joinaliasvars / joinleftcols /
6992 * joinrightcols by walking the larg and rarg subqueries' targetLists.
6993 * Execution doesn't need these (outer Vars reference the input RTEs
6994 * directly), but PostgreSQL's ruleutils deparser walks them when
6995 * pg_get_querydef / EXPLAIN VERBOSE traverse the rewritten tree and
6996 * segfaults on NULL eref. Non-USING LEFT JOIN: joinmergedcols = 0,
6997 * output is left columns followed by right columns. */
6998 {
6999 RangeTblRef *larg_ref = (RangeTblRef *)setOps->larg;
7000 RangeTblRef *rarg_ref = (RangeTblRef *)setOps->rarg;
7001 RangeTblEntry *larg_rte =
7002 (RangeTblEntry *)list_nth(q->rtable, larg_ref->rtindex - 1);
7003 RangeTblEntry *rarg_rte =
7004 (RangeTblEntry *)list_nth(q->rtable, rarg_ref->rtindex - 1);
7005 List *aliasvars = NIL;
7006 List *leftcols = NIL;
7007 List *rightcols = NIL;
7008 List *colnames = NIL;
7009 ListCell *lc_te;
7010 int colno;
7011
7012 colno = 1;
7013 foreach (lc_te, larg_rte->subquery->targetList) {
7014 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
7015 if (te->resjunk) {
7016 colno++;
7017 continue;
7018 }
7019 aliasvars = lappend(aliasvars,
7020 makeVar(larg_ref->rtindex, colno,
7021 exprType((Node *)te->expr),
7022 exprTypmod((Node *)te->expr),
7023 exprCollation((Node *)te->expr),
7024 0));
7025 leftcols = lappend_int(leftcols, colno);
7026 rightcols = lappend_int(rightcols, 0);
7027 colnames = lappend(colnames,
7028 makeString(pstrdup(te->resname ? te->resname
7029 : "?column?")));
7030 colno++;
7031 }
7032 colno = 1;
7033 foreach (lc_te, rarg_rte->subquery->targetList) {
7034 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
7035 if (te->resjunk) {
7036 colno++;
7037 continue;
7038 }
7039 aliasvars = lappend(aliasvars,
7040 makeVar(rarg_ref->rtindex, colno,
7041 exprType((Node *)te->expr),
7042 exprTypmod((Node *)te->expr),
7043 exprCollation((Node *)te->expr),
7044 0));
7045 leftcols = lappend_int(leftcols, 0);
7046 rightcols = lappend_int(rightcols, colno);
7047 colnames = lappend(colnames,
7048 makeString(pstrdup(te->resname ? te->resname
7049 : "?column?")));
7050 colno++;
7051 }
7052
7053 rte->alias = NULL;
7054 rte->eref = makeAlias("unnamed_join", colnames);
7055 rte->joinaliasvars = aliasvars;
7056#if PG_VERSION_NUM >= 130000
7057 rte->joinleftcols = leftcols;
7058 rte->joinrightcols = rightcols;
7059 rte->joinmergedcols = 0;
7060#else
7061 (void) leftcols;
7062 (void) rightcols;
7063#endif
7064 }
7065
7066 rte->rtekind = RTE_JOIN;
7067 rte->jointype = JOIN_LEFT;
7068
7069 q->rtable = lappend(q->rtable, rte);
7070
7071 je->jointype = JOIN_LEFT;
7072
7073 je->larg = setOps->larg;
7074 je->rarg = setOps->rarg;
7075 je->quals = (Node *)expr;
7076 je->rtindex = list_length(q->rtable);
7077
7078 fe->fromlist = list_make1(je);
7079
7080 q->jointree = fe;
7081
7082 // TODO: Add group by in the right-side table
7083
7084 q->setOperations = 0;
7085
7086 return true;
7087}
7088
7089/* -------------------------------------------------------------------------
7090 * Outer-join lowering (LEFT JOIN)
7091 *
7092 * ProvSQL builds provenance by annotating the all-present instance, which is
7093 * sound for monotone SPJU but WRONG for the non-monotone outer join: the
7094 * null-padded row (r, NULL) of a LEFT JOIN appears only in the *smaller*
7095 * worlds where the right side has no match for r, so for a left row that does
7096 * match in the actual instance ProvSQL has nothing to annotate. The
7097 * RTE_JOIN arm of process_query treats LEFT/FULL/RIGHT exactly
7098 * like INNER, emitting only the matched branch.
7099 *
7100 * The fix is a structural transform applied in the planner hook before
7101 * provenance discovery. R ⟕_θ S is rewritten as
7102 *
7103 * ( SELECT R.cols, S.cols FROM R JOIN S ON θ ) -- matched (⊗)
7104 * UNION ALL -- ⊎ (plus)
7105 * ( SELECT R.cols, NULL,…,NULL
7106 * FROM ( SELECT R.cols FROM R
7107 * EXCEPT ALL -- ProvSQL's −
7108 * SELECT R.cols FROM R JOIN S ON θ ) ) -- R(r)⊗(1⊖⊕match)
7109 *
7110 * Both UNION ALL and EXCEPT ALL → − are native (process_set_operation_union /
7111 * transform_except_into_join), so this code is pure parse-tree construction
7112 * plus an outer Var remap: the recursive process_query passes over the
7113 * constructed subqueries do all the provenance work. The antijoin provenance
7114 * R(r) ⊖ ⊕_match (R(r)⊗S(s)) that ProvSQL's EXCEPT (NOT-IN semantics) builds
7115 * equals R(r) ⊗ (1 ⊖ ⊕_match S(s)), exactly the paper's null-padded branch.
7116 * ------------------------------------------------------------------------- */
7117
7118/**
7119 * @brief Rename the @c provsql column in @p rel's @c eref so a later
7120 * @c get_provenance_attributes pass does not re-detect @p rel as a
7121 * provenance source.
7122 *
7123 * Used when a relation's provenance has already been captured elsewhere -- by
7124 * an explode-style subquery (the aggregation rewrite) or, in the outer-join
7125 * lowering, by the replacement UNION subquery, leaving the original base
7126 * relation orphaned in the range table. Renaming only the (unreferenced)
7127 * @c eref entry is enough: detection matches on the @c eref colname.
7128 */
7129static void hide_provsql_colname(RangeTblEntry *rel) {
7130 ListCell *lc;
7131 foreach (lc, rel->eref->colnames) {
7132 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME)) {
7133 lfirst(lc) = makeString(pstrdup("_provsql_inner"));
7134 break;
7135 }
7136 }
7137}
7138
7139/** @brief Per-relation user-column descriptor for the outer-join lowering. */
7140typedef struct oj_cols {
7141 int n; ///< number of user (non-provsql, non-dropped) columns
7142 AttrNumber *attno; ///< original attribute number in the base relation
7143 Oid *type; ///< column type OID
7144 int32 *typmod; ///< column typmod
7145 Oid *coll; ///< column collation OID
7146 char **name; ///< column name
7147} oj_cols;
7148
7149/** @brief Collect the user columns (skipping @c provsql and dropped columns)
7150 * of an outer-join arm: a base relation or a subquery. For a relation
7151 * the column @c attno is its catalog attribute number; for a subquery
7152 * it is the target entry's @c resno. */
7153static void oj_collect_cols(const constants_t *constants, RangeTblEntry *rel,
7154 oj_cols *out) {
7155 ListCell *lc;
7156
7157 if (rel->rtekind == RTE_SUBQUERY) {
7158 int cap = list_length(rel->subquery->targetList);
7159 out->attno = (AttrNumber *)palloc(cap * sizeof(AttrNumber));
7160 out->type = (Oid *)palloc(cap * sizeof(Oid));
7161 out->typmod = (int32 *)palloc(cap * sizeof(int32));
7162 out->coll = (Oid *)palloc(cap * sizeof(Oid));
7163 out->name = (char **)palloc(cap * sizeof(char *));
7164 out->n = 0;
7165 foreach (lc, rel->subquery->targetList) {
7166 TargetEntry *te = (TargetEntry *)lfirst(lc);
7167 if (te->resjunk)
7168 continue;
7169 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME))
7170 continue;
7171 out->attno[out->n] = te->resno;
7172 out->type[out->n] = exprType((Node *)te->expr);
7173 out->typmod[out->n] = exprTypmod((Node *)te->expr);
7174 out->coll[out->n] = exprCollation((Node *)te->expr);
7175 out->name[out->n] = pstrdup(te->resname ? te->resname : "?column?");
7176 ++out->n;
7177 }
7178 return;
7179 }
7180
7181 {
7182 AttrNumber attid = 0;
7183 int cap = list_length(rel->eref->colnames);
7184 out->attno = (AttrNumber *)palloc(cap * sizeof(AttrNumber));
7185 out->type = (Oid *)palloc(cap * sizeof(Oid));
7186 out->typmod = (int32 *)palloc(cap * sizeof(int32));
7187 out->coll = (Oid *)palloc(cap * sizeof(Oid));
7188 out->name = (char **)palloc(cap * sizeof(char *));
7189 out->n = 0;
7190 foreach (lc, rel->eref->colnames) {
7191 const char *v = strVal(lfirst(lc));
7192 Oid t;
7193 int32 tm;
7194 Oid c;
7195 ++attid;
7196 if (v[0] == '\0') /* dropped column */
7197 continue;
7198 if (!strcmp(v, PROVSQL_COLUMN_NAME))
7199 continue;
7200 get_atttypetypmodcoll(rel->relid, attid, &t, &tm, &c);
7201 out->attno[out->n] = attid;
7202 out->type[out->n] = t;
7203 out->typmod[out->n] = tm;
7204 out->coll[out->n] = c;
7205 out->name[out->n] = pstrdup(v);
7206 ++out->n;
7207 }
7208 }
7209}
7210
7211/** @brief True if @p rel contributes provenance: a base relation with a
7212 * @c provsql UUID column, or a subquery over tracked relations. */
7213static bool oj_rte_has_provsql(const constants_t *constants,
7214 RangeTblEntry *rel) {
7215 ListCell *lc;
7216 AttrNumber attid = 0;
7217
7218 if (rel->rtekind == RTE_SUBQUERY) {
7219 if (rel->subquery == NULL)
7220 return false;
7221 if (has_provenance(constants, rel->subquery))
7222 return true;
7223 /* Also tracked if the subquery already exposes a provsql UUID column
7224 * (e.g. the synthetic gate_one() column the wrap adds for an untracked
7225 * outer). */
7226 foreach (lc, rel->subquery->targetList) {
7227 TargetEntry *te = (TargetEntry *)lfirst(lc);
7228 if (!te->resjunk && te->resname &&
7229 !strcmp(te->resname, PROVSQL_COLUMN_NAME) &&
7230 exprType((Node *)te->expr) == constants->OID_TYPE_UUID)
7231 return true;
7232 }
7233 return false;
7234 }
7235
7236 foreach (lc, rel->eref->colnames) {
7237 ++attid;
7238 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME) &&
7239 get_atttype(rel->relid, attid) == constants->OID_TYPE_UUID)
7240 return true;
7241 }
7242 return false;
7243}
7244
7245/** @brief Wrap a constructed @c Query as an @c RTE_SUBQUERY, building its
7246 * @c eref->colnames from the (non-junk) target list. */
7247static RangeTblEntry *oj_make_subquery_rte(Query *sub) {
7248 RangeTblEntry *rte = makeNode(RangeTblEntry);
7249 List *colnames = NIL;
7250 ListCell *lc;
7251
7252 foreach (lc, sub->targetList) {
7253 TargetEntry *te = (TargetEntry *)lfirst(lc);
7254 if (te->resjunk)
7255 continue;
7256 colnames = lappend(colnames,
7257 makeString(pstrdup(te->resname ? te->resname
7258 : "?column?")));
7259 }
7260
7261 rte->rtekind = RTE_SUBQUERY;
7262 rte->subquery = sub;
7263 rte->alias = NULL;
7264 rte->eref = makeAlias("unnamed_subquery", colnames);
7265 rte->lateral = false;
7266 rte->inFromCl = true;
7267#if PG_VERSION_NUM < 160000
7268 rte->requiredPerms = 0;
7269#endif
7270 return rte;
7271}
7272
7273/** @brief Copy an outer-join arm RTE into the range table of subquery @p sub.
7274 * A base relation carries its permission info (PG 16+); a subquery has no
7275 * direct permissions (its inner query keeps its own rteperminfos). */
7276static RangeTblEntry *oj_copy_rel(Query *outer, Query *sub,
7277 RangeTblEntry *orig) {
7278 RangeTblEntry *c = copyObject(orig);
7279#if PG_VERSION_NUM >= 160000
7280 if (orig->rtekind == RTE_RELATION && orig->perminfoindex != 0) {
7281 RTEPermissionInfo *pi = getRTEPermissionInfo(outer->rteperminfos, orig);
7282 sub->rteperminfos = lappend(sub->rteperminfos, copyObject(pi));
7283 c->perminfoindex = list_length(sub->rteperminfos);
7284 } else {
7285 c->perminfoindex = 0;
7286 }
7287#else
7288 (void)outer;
7289 (void)sub;
7290#endif
7291 return c;
7292}
7293
7294/** @brief Neutralise an outer-join arm RTE left orphaned after the lowering so
7295 * get_provenance_attributes does not re-pick it up as a provenance source: a
7296 * base relation has its provsql column renamed; a subquery (which would still
7297 * be processed) is turned into an inert RTE_RESULT. */
7298static void oj_neutralize_orphan_arm(RangeTblEntry *rel) {
7299 if (rel->rtekind == RTE_SUBQUERY) {
7300#if PG_VERSION_NUM >= 120000
7301 rel->rtekind = RTE_RESULT;
7302 rel->subquery = NULL;
7303#else
7304 /* No RTE_RESULT before PostgreSQL 12: leave an inert zero-column
7305 * subquery (a bare SELECT) instead. get_provenance_attributes
7306 * recurses into it, finds no relations, and adds nothing. */
7307 Query *empty = makeNode(Query);
7308 empty->commandType = CMD_SELECT;
7309 empty->canSetTag = true;
7310 empty->jointree = makeFromExpr(NIL, NULL);
7311 rel->subquery = empty;
7312#endif
7313 rel->eref = makeAlias("*RESULT*", NIL);
7314#if PG_VERSION_NUM >= 160000
7315 rel->perminfoindex = 0;
7316#endif
7317 return;
7318 }
7320}
7321
7322/** @brief Var-renumber context: map @c varno @c from[i] → @c to[i]. */
7323typedef struct oj_renum_ctx {
7325 Index from[2];
7326 Index to[2];
7327} oj_renum_ctx;
7328
7329static Node *oj_renum_mut(Node *node, void *cx) {
7330 oj_renum_ctx *c = (oj_renum_ctx *)cx;
7331 if (node == NULL)
7332 return NULL;
7333 if (IsA(node, Var)) {
7334 Var *v = (Var *)node;
7335 if (v->varlevelsup == 0) {
7336 int i;
7337 for (i = 0; i < c->npairs; ++i)
7338 if (v->varno == c->from[i]) {
7339 v = (Var *)copyObject(v);
7340 v->varno = c->to[i];
7341#if PG_VERSION_NUM >= 160000
7342 v->varnullingrels = NULL;
7343#endif
7344#if PG_VERSION_NUM >= 130000
7345 v->varnosyn = 0;
7346 v->varattnosyn = 0;
7347#endif
7348 return (Node *)v;
7349 }
7350 }
7351 return node;
7352 }
7353 return expression_tree_mutator(node, oj_renum_mut, cx);
7354}
7355
7356/** @brief Build the inner-join scan subquery
7357 * @c "SELECT [R.cols][, S.cols] FROM R JOIN S ON θ".
7358 *
7359 * Projects R's columns when @p select_r and S's columns when @p select_s, in
7360 * R-then-S order. R is copied at index 1, S at index 2, the synthetic join
7361 * RTE at index 3; θ is copied and its base-relation varnos remapped
7362 * (R_idx→1, S_idx→2). */
7363static Query *oj_build_join_query(const constants_t *constants, Query *outer,
7364 RangeTblEntry *R, RangeTblEntry *S,
7365 Index R_idx, Index S_idx, oj_cols *Rc,
7366 oj_cols *Sc, Node *theta, bool select_r,
7367 bool select_s) {
7368 Query *sub = makeNode(Query);
7369 RangeTblEntry *Rcopy, *Scopy, *jrte = makeNode(RangeTblEntry);
7370 JoinExpr *je = makeNode(JoinExpr);
7371 RangeTblRef *lr = makeNode(RangeTblRef), *rr = makeNode(RangeTblRef);
7372 FromExpr *fe = makeNode(FromExpr);
7373 List *tl = NIL, *av = NIL, *lcols = NIL, *rcols = NIL, *cn = NIL;
7374 Node *theta2;
7375 oj_renum_ctx rctx;
7376 int i;
7377
7378 sub->commandType = CMD_SELECT;
7379 sub->canSetTag = true;
7380 Rcopy = oj_copy_rel(outer, sub, R);
7381 Scopy = oj_copy_rel(outer, sub, S);
7382
7383 /* Synthetic join RTE: eref / joinaliasvars / joinleftcols / joinrightcols
7384 * kept consistent so the ruleutils deparser does not segfault. */
7385 for (i = 0; i < Rc->n; ++i) {
7386 av = lappend(av, makeVar(1, Rc->attno[i], Rc->type[i], Rc->typmod[i],
7387 Rc->coll[i], 0));
7388 lcols = lappend_int(lcols, Rc->attno[i]);
7389 rcols = lappend_int(rcols, 0);
7390 cn = lappend(cn, makeString(pstrdup(Rc->name[i])));
7391 }
7392 for (i = 0; i < Sc->n; ++i) {
7393 av = lappend(av, makeVar(2, Sc->attno[i], Sc->type[i], Sc->typmod[i],
7394 Sc->coll[i], 0));
7395 lcols = lappend_int(lcols, 0);
7396 rcols = lappend_int(rcols, Sc->attno[i]);
7397 cn = lappend(cn, makeString(pstrdup(Sc->name[i])));
7398 }
7399 jrte->rtekind = RTE_JOIN;
7400 jrte->jointype = JOIN_INNER;
7401 jrte->alias = NULL;
7402 jrte->eref = makeAlias("unnamed_join", cn);
7403 jrte->joinaliasvars = av;
7404#if PG_VERSION_NUM >= 130000
7405 jrte->joinleftcols = lcols;
7406 jrte->joinrightcols = rcols;
7407 jrte->joinmergedcols = 0;
7408#endif
7409 jrte->inFromCl = true;
7410
7411 sub->rtable = list_make3(Rcopy, Scopy, jrte);
7412
7413 rctx.npairs = 2;
7414 rctx.from[0] = R_idx; rctx.to[0] = 1;
7415 rctx.from[1] = S_idx; rctx.to[1] = 2;
7416 theta2 = oj_renum_mut(copyObject(theta), &rctx);
7417
7418 lr->rtindex = 1;
7419 rr->rtindex = 2;
7420 je->jointype = JOIN_INNER;
7421 je->larg = (Node *)lr;
7422 je->rarg = (Node *)rr;
7423 je->quals = theta2;
7424 je->isNatural = false;
7425 je->usingClause = NIL;
7426 je->rtindex = 3;
7427 fe->fromlist = list_make1(je);
7428 sub->jointree = fe;
7429
7430 if (select_r)
7431 for (i = 0; i < Rc->n; ++i) {
7432 Var *v = makeVar(1, Rc->attno[i], Rc->type[i], Rc->typmod[i],
7433 Rc->coll[i], 0);
7434 tl = lappend(tl, makeTargetEntry((Expr *)v, list_length(tl) + 1,
7435 pstrdup(Rc->name[i]), false));
7436 }
7437 if (select_s)
7438 for (i = 0; i < Sc->n; ++i) {
7439 Var *v = makeVar(2, Sc->attno[i], Sc->type[i], Sc->typmod[i],
7440 Sc->coll[i], 0);
7441 tl = lappend(tl, makeTargetEntry((Expr *)v, list_length(tl) + 1,
7442 pstrdup(Sc->name[i]), false));
7443 }
7444 sub->targetList = tl;
7445
7446 return sub;
7447}
7448
7449/** @brief Build the plain-scan subquery @c "SELECT R.cols FROM R". */
7450static Query *oj_build_rel_query(const constants_t *constants, Query *outer,
7451 RangeTblEntry *R, oj_cols *Rc) {
7452 Query *sub = makeNode(Query);
7453 RangeTblEntry *Rcopy;
7454 RangeTblRef *rtr = makeNode(RangeTblRef);
7455 FromExpr *fe = makeNode(FromExpr);
7456 List *tl = NIL;
7457 int i;
7458
7459 sub->commandType = CMD_SELECT;
7460 sub->canSetTag = true;
7461 Rcopy = oj_copy_rel(outer, sub, R);
7462 sub->rtable = list_make1(Rcopy);
7463 rtr->rtindex = 1;
7464 fe->fromlist = list_make1(rtr);
7465 sub->jointree = fe;
7466
7467 for (i = 0; i < Rc->n; ++i) {
7468 Var *v = makeVar(1, Rc->attno[i], Rc->type[i], Rc->typmod[i], Rc->coll[i],
7469 0);
7470 tl = lappend(tl, makeTargetEntry((Expr *)v, i + 1, pstrdup(Rc->name[i]),
7471 false));
7472 }
7473 sub->targetList = tl;
7474 return sub;
7475}
7476
7477/** @brief Build the difference subquery for the kept side of an outer join:
7478 * @c "SELECT X.cols FROM X EXCEPT ALL SELECT X.cols FROM R JOIN S ON θ",
7479 * where X = R when @p keep_left, else S.
7480 *
7481 * Processed natively as an EXCEPT (→ ProvSQL's −), yielding per distinct kept
7482 * tuple x the monus provenance X(x) ⊖ ⊕_match (R(r)⊗S(s)) =
7483 * X(x) ⊗ (1 ⊖ ⊕_match Y(y)) -- the null-padded antijoin branch of the join. */
7484static Query *oj_build_diff(const constants_t *constants, Query *outer,
7485 RangeTblEntry *R, RangeTblEntry *S, Index R_idx,
7486 Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta,
7487 bool keep_left) {
7488 RangeTblEntry *kept_rel = keep_left ? R : S;
7489 oj_cols *Kc = keep_left ? Rc : Sc;
7490 Query *ls = oj_build_rel_query(constants, outer, kept_rel, Kc);
7491 /* Matched-projection arm projects the kept side's columns only. */
7492 Query *mp = oj_build_join_query(constants, outer, R, S, R_idx, S_idx, Rc, Sc,
7493 theta, keep_left, !keep_left);
7494 RangeTblEntry *ls_rte = oj_make_subquery_rte(ls);
7495 RangeTblEntry *mp_rte = oj_make_subquery_rte(mp);
7496 Query *D = makeNode(Query);
7497 SetOperationStmt *so = makeNode(SetOperationStmt);
7498 RangeTblRef *l = makeNode(RangeTblRef), *r = makeNode(RangeTblRef);
7499 FromExpr *fe = makeNode(FromExpr);
7500 List *tl = NIL;
7501 int i;
7502
7503 D->commandType = CMD_SELECT;
7504 D->canSetTag = true;
7505 D->rtable = list_make2(ls_rte, mp_rte);
7506
7507 l->rtindex = 1;
7508 r->rtindex = 2;
7509 so->op = SETOP_EXCEPT;
7510 /* EXCEPT ALL = the pure multiset difference q₁−q₂ (NOT IN), which keeps every
7511 * kept-side row with its multiplicity -- exactly the antijoin's null-padded
7512 * rows. group_set_difference_right_arm groups the matched-projection arm so
7513 * the monus is X(x) ⊖ ⊕(R(r)⊗S(s)) = X(x) ⊗ (1 ⊖ ⊕ Y(y)). */
7514 so->all = true;
7515 so->larg = (Node *)l;
7516 so->rarg = (Node *)r;
7517 for (i = 0; i < Kc->n; ++i) {
7518 so->colTypes = lappend_oid(so->colTypes, Kc->type[i]);
7519 so->colTypmods = lappend_int(so->colTypmods, Kc->typmod[i]);
7520 so->colCollations = lappend_oid(so->colCollations, Kc->coll[i]);
7521 }
7522 D->setOperations = (Node *)so;
7523 fe->fromlist = NIL;
7524 D->jointree = fe;
7525
7526 for (i = 0; i < Kc->n; ++i) {
7527 Var *v = makeVar(1, i + 1, Kc->type[i], Kc->typmod[i], Kc->coll[i], 0);
7528 tl = lappend(tl, makeTargetEntry((Expr *)v, i + 1, pstrdup(Kc->name[i]),
7529 false));
7530 }
7531 D->targetList = tl;
7532 return D;
7533}
7534
7535/** @brief Build a null-padded antijoin arm in R-then-S column order.
7536 *
7537 * For @p keep_left it emits the left-unmatched rows
7538 * @c "SELECT D.cols, NULL,… FROM (R EXCEPT ALL R⋈S) D" (S columns NULL); for
7539 * the right side it emits @c "SELECT NULL,…, D.cols FROM (S EXCEPT ALL R⋈S) D"
7540 * (R columns NULL). The kept side's columns come from the difference @c D
7541 * (which also carries the antijoin provenance); the other side is typed NULL
7542 * constants. */
7543static Query *oj_build_antijoin(const constants_t *constants, Query *outer,
7544 RangeTblEntry *R, RangeTblEntry *S,
7545 Index R_idx, Index S_idx, oj_cols *Rc,
7546 oj_cols *Sc, Node *theta, bool keep_left) {
7547 Query *D = oj_build_diff(constants, outer, R, S, R_idx, S_idx, Rc, Sc, theta,
7548 keep_left);
7549 RangeTblEntry *D_rte = oj_make_subquery_rte(D);
7550 Query *A = makeNode(Query);
7551 RangeTblRef *rtr = makeNode(RangeTblRef);
7552 FromExpr *fe = makeNode(FromExpr);
7553 List *tl = NIL;
7554 int i, kept = 0; /* next column position in the difference D */
7555
7556 A->commandType = CMD_SELECT;
7557 A->canSetTag = true;
7558 A->rtable = list_make1(D_rte);
7559 rtr->rtindex = 1;
7560 fe->fromlist = list_make1(rtr);
7561 A->jointree = fe;
7562
7563 /* R columns: from D when keep_left, else typed NULL. */
7564 for (i = 0; i < Rc->n; ++i) {
7565 Expr *e;
7566 if (keep_left)
7567 e = (Expr *)makeVar(1, ++kept, Rc->type[i], Rc->typmod[i], Rc->coll[i],
7568 0);
7569 else
7570 e = (Expr *)makeNullConst(Rc->type[i], Rc->typmod[i], Rc->coll[i]);
7571 tl = lappend(tl, makeTargetEntry(e, list_length(tl) + 1,
7572 pstrdup(Rc->name[i]), false));
7573 }
7574 /* S columns: typed NULL when keep_left, else from D. */
7575 for (i = 0; i < Sc->n; ++i) {
7576 Expr *e;
7577 if (keep_left)
7578 e = (Expr *)makeNullConst(Sc->type[i], Sc->typmod[i], Sc->coll[i]);
7579 else
7580 e = (Expr *)makeVar(1, ++kept, Sc->type[i], Sc->typmod[i], Sc->coll[i],
7581 0);
7582 tl = lappend(tl, makeTargetEntry(e, list_length(tl) + 1,
7583 pstrdup(Sc->name[i]), false));
7584 }
7585 A->targetList = tl;
7586 return A;
7587}
7588
7589/** @brief Build the column-type lists (R-then-S, user columns only) shared by
7590 * every set-operation node of the replacement union. */
7591static void oj_build_coltype_lists(oj_cols *Rc, oj_cols *Sc, List **types,
7592 List **typmods, List **collations) {
7593 int i;
7594 *types = *typmods = *collations = NIL;
7595 for (i = 0; i < Rc->n; ++i) {
7596 *types = lappend_oid(*types, Rc->type[i]);
7597 *typmods = lappend_int(*typmods, Rc->typmod[i]);
7598 *collations = lappend_oid(*collations, Rc->coll[i]);
7599 }
7600 for (i = 0; i < Sc->n; ++i) {
7601 *types = lappend_oid(*types, Sc->type[i]);
7602 *typmods = lappend_int(*typmods, Sc->typmod[i]);
7603 *collations = lappend_oid(*collations, Sc->coll[i]);
7604 }
7605}
7606
7607/** @brief Build the UNION-ALL of the matched arm and the outer join's
7608 * antijoin arm(s): the full outer-join relation in R-then-S column
7609 * order with one combined @c provsql column.
7610 *
7611 * @p jointype selects which null-padded antijoin branches are added:
7612 * @c JOIN_LEFT adds the left (R-kept) branch, @c JOIN_RIGHT the right
7613 * (S-kept) branch, @c JOIN_FULL both. */
7614static Query *oj_build_union(const constants_t *constants, Query *outer,
7615 RangeTblEntry *R, RangeTblEntry *S, Index R_idx,
7616 Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta,
7617 JoinType jointype) {
7618 List *arms = NIL; /* list of arm Query* */
7619 List *types, *typmods, *collations;
7620 Query *Q = makeNode(Query);
7621 FromExpr *fe = makeNode(FromExpr);
7622 List *tl = NIL;
7623 Node *tree;
7624 ListCell *lc;
7625 int i, pos = 0, k;
7626
7627 /* Matched arm (R ⋈ S), then the requested antijoin branches. */
7628 arms = lappend(arms, oj_build_join_query(constants, outer, R, S, R_idx,
7629 S_idx, Rc, Sc, theta, true, true));
7630 if (jointype == JOIN_LEFT || jointype == JOIN_FULL)
7631 arms = lappend(arms, oj_build_antijoin(constants, outer, R, S, R_idx,
7632 S_idx, Rc, Sc, theta, true));
7633 if (jointype == JOIN_RIGHT || jointype == JOIN_FULL)
7634 arms = lappend(arms, oj_build_antijoin(constants, outer, R, S, R_idx,
7635 S_idx, Rc, Sc, theta, false));
7636
7637 Q->commandType = CMD_SELECT;
7638 Q->canSetTag = true;
7639 foreach (lc, arms)
7640 Q->rtable = lappend(Q->rtable, oj_make_subquery_rte((Query *)lfirst(lc)));
7641
7642 oj_build_coltype_lists(Rc, Sc, &types, &typmods, &collations);
7643
7644 /* Left-deep UNION ALL tree over the arm RTEs (indices 1..n). Every
7645 * SetOperationStmt node carries its own colTypes lists -- process_set_
7646 * operation_union appends the UUID type to each node in place. */
7647 {
7648 RangeTblRef *first = makeNode(RangeTblRef);
7649 first->rtindex = 1;
7650 tree = (Node *)first;
7651 }
7652 for (k = 2; k <= list_length(arms); ++k) {
7653 SetOperationStmt *so = makeNode(SetOperationStmt);
7654 RangeTblRef *rtr = makeNode(RangeTblRef);
7655 rtr->rtindex = k;
7656 so->op = SETOP_UNION;
7657 so->all = true;
7658 so->larg = tree;
7659 so->rarg = (Node *)rtr;
7660 so->colTypes = list_copy(types);
7661 so->colTypmods = list_copy(typmods);
7662 so->colCollations = list_copy(collations);
7663 tree = (Node *)so;
7664 }
7665 Q->setOperations = tree;
7666 fe->fromlist = NIL;
7667 Q->jointree = fe;
7668
7669 /* Leader target list: one Var per user column, referencing the first arm. */
7670 for (i = 0; i < Rc->n; ++i) {
7671 Var *v = makeVar(1, ++pos, Rc->type[i], Rc->typmod[i], Rc->coll[i], 0);
7672 tl = lappend(tl, makeTargetEntry((Expr *)v, pos, pstrdup(Rc->name[i]),
7673 false));
7674 }
7675 for (i = 0; i < Sc->n; ++i) {
7676 Var *v = makeVar(1, ++pos, Sc->type[i], Sc->typmod[i], Sc->coll[i], 0);
7677 tl = lappend(tl, makeTargetEntry((Expr *)v, pos, pstrdup(Sc->name[i]),
7678 false));
7679 }
7680 Q->targetList = tl;
7681 return Q;
7682}
7683
7684/** @brief Walker context: detect a Var referencing the join RTE index. */
7685typedef struct oj_joinref_ctx {
7688
7689static bool oj_joinref_walker(Node *node, void *cx) {
7690 oj_joinref_ctx *c = (oj_joinref_ctx *)cx;
7691 if (node == NULL)
7692 return false;
7693 if (IsA(node, Var)) {
7694 Var *v = (Var *)node;
7695 return (v->varlevelsup == 0 && v->varno == c->join_idx);
7696 }
7697 return expression_tree_walker(node, oj_joinref_walker, cx);
7698}
7699
7700/** @brief True if any outer Var references the join RTE directly (USING /
7701 * whole-row / alias.col references the conservative remap cannot
7702 * resolve through @c joinaliasvars yet). */
7703static bool oj_refs_join_index(Query *q, Index join_idx) {
7705 c.join_idx = join_idx;
7706 if (oj_joinref_walker((Node *)q->targetList, &c))
7707 return true;
7708 if (q->jointree && q->jointree->quals &&
7709 oj_joinref_walker(q->jointree->quals, &c))
7710 return true;
7711 if (q->havingQual && oj_joinref_walker(q->havingQual, &c))
7712 return true;
7713 return false;
7714}
7715
7716/** @brief Outer Var remap context for the LEFT-join lowering: base-relation
7717 * Vars (R_idx / S_idx) are retargeted to the new subquery (new_idx)
7718 * with their attribute number mapped to the subquery column position. */
7719typedef struct oj_outer_ctx {
7721 AttrNumber *R_map, *S_map;
7722} oj_outer_ctx;
7723
7724static Node *oj_outer_remap(Node *node, void *cx) {
7725 oj_outer_ctx *c = (oj_outer_ctx *)cx;
7726 if (node == NULL)
7727 return NULL;
7728 if (IsA(node, Var)) {
7729 Var *v = (Var *)node;
7730 if (v->varlevelsup == 0 &&
7731 (v->varno == c->R_idx || v->varno == c->S_idx)) {
7732 v = (Var *)copyObject(v);
7733 if ((Index)((Var *)node)->varno == c->R_idx)
7734 v->varattno = c->R_map[v->varattno];
7735 else
7736 v->varattno = c->S_map[v->varattno];
7737 v->varno = c->new_idx;
7738#if PG_VERSION_NUM >= 160000
7739 v->varnullingrels = NULL;
7740#endif
7741#if PG_VERSION_NUM >= 130000
7742 v->varnosyn = 0;
7743 v->varattnosyn = 0;
7744#endif
7745 return (Node *)v;
7746 }
7747 return node;
7748 }
7749 return expression_tree_mutator(node, oj_outer_remap, cx);
7750}
7751
7752/**
7753 * @brief Lower a top-level outer @c JOIN of two base relations into the
7754 * UNION-ALL of its matched and null-padded antijoin arms.
7755 *
7756 * Fires only on @c jointree->fromlist ==
7757 * @c [JoinExpr(JOIN_LEFT|JOIN_RIGHT|JOIN_FULL, RTR, RTR)] whose arms are
7758 * provenance-tracked base relations and where no outer Var references the join
7759 * RTE directly. Everything else falls through unchanged. Returns @c true if
7760 * the query was rewritten.
7761 */
7762static bool lower_outer_joins(const constants_t *constants, Query *q) {
7763 JoinExpr *je;
7764 RangeTblRef *lref, *rref;
7765 Index R_idx, S_idx, join_idx;
7766 RangeTblEntry *R_rte, *S_rte;
7767 oj_cols Rc, Sc;
7768 Node *theta;
7769 Query *Q;
7770 AttrNumber *R_map, *S_map;
7771 int ncolR, ncolS, i;
7772 oj_outer_ctx octx;
7773
7774 if (q->commandType != CMD_SELECT)
7775 return false;
7776 if (!q->jointree || list_length(q->jointree->fromlist) != 1)
7777 return false;
7778 if (!IsA(linitial(q->jointree->fromlist), JoinExpr))
7779 return false;
7780 je = (JoinExpr *)linitial(q->jointree->fromlist);
7781 if (je->jointype != JOIN_LEFT && je->jointype != JOIN_RIGHT &&
7782 je->jointype != JOIN_FULL)
7783 return false;
7784 if (!IsA(je->larg, RangeTblRef) || !IsA(je->rarg, RangeTblRef))
7785 return false;
7786
7787 lref = (RangeTblRef *)je->larg;
7788 rref = (RangeTblRef *)je->rarg;
7789 R_idx = lref->rtindex;
7790 S_idx = rref->rtindex;
7791 join_idx = je->rtindex;
7792 R_rte = list_nth_node(RangeTblEntry, q->rtable, R_idx - 1);
7793 S_rte = list_nth_node(RangeTblEntry, q->rtable, S_idx - 1);
7794
7795 if ((R_rte->rtekind != RTE_RELATION && R_rte->rtekind != RTE_SUBQUERY) ||
7796 (S_rte->rtekind != RTE_RELATION && S_rte->rtekind != RTE_SUBQUERY))
7797 return false;
7798 if ((R_rte->rtekind == RTE_SUBQUERY && R_rte->lateral) ||
7799 (S_rte->rtekind == RTE_SUBQUERY && S_rte->lateral))
7800 return false;
7801 if (!oj_rte_has_provsql(constants, R_rte) ||
7802 !oj_rte_has_provsql(constants, S_rte))
7803 return false;
7804 if (oj_refs_join_index(q, join_idx))
7805 return false;
7806
7807#if PG_VERSION_NUM >= 180000
7808 /* Flatten PG 18's synthetic RTE_GROUP so grouped-column Vars are base-
7809 * relation Vars again, which the remap below can retarget. */
7811#endif
7812
7813 theta = je->quals;
7814 oj_collect_cols(constants, R_rte, &Rc);
7815 oj_collect_cols(constants, S_rte, &Sc);
7816 ncolR = (R_rte->rtekind == RTE_SUBQUERY)
7817 ? list_length(R_rte->subquery->targetList)
7818 : list_length(R_rte->eref->colnames);
7819 ncolS = (S_rte->rtekind == RTE_SUBQUERY)
7820 ? list_length(S_rte->subquery->targetList)
7821 : list_length(S_rte->eref->colnames);
7822
7823 Q = oj_build_union(constants, q, R_rte, S_rte, R_idx, S_idx, &Rc, &Sc, theta,
7824 je->jointype);
7825
7826 /* The combined provenance now lives in the replacement subquery Q. The
7827 * original arm RTEs are left orphaned in the outer range table; neutralise
7828 * them so get_provenance_attributes does not pick them up again. */
7831
7832 R_map = (AttrNumber *)palloc0((ncolR + 1) * sizeof(AttrNumber));
7833 S_map = (AttrNumber *)palloc0((ncolS + 1) * sizeof(AttrNumber));
7834 for (i = 0; i < Rc.n; ++i)
7835 R_map[Rc.attno[i]] = i + 1;
7836 for (i = 0; i < Sc.n; ++i)
7837 S_map[Sc.attno[i]] = Rc.n + i + 1;
7838
7839 /* Replace the JOIN RTE slot in place with the new subquery, reusing the
7840 * join's range-table index for the outer reference. Reusing the *join*
7841 * slot (rather than the left relation's) leaves no orphaned RTE_JOIN in the
7842 * range table -- an orphaned join RTE without a matching JoinExpr trips the
7843 * planner ("so where are the outer joins?"). The two base-relation RTEs are
7844 * left orphaned, which the planner tolerates (they are simply not scanned). */
7845 {
7846 RangeTblEntry *J_rte =
7847 list_nth_node(RangeTblEntry, q->rtable, join_idx - 1);
7848 List *cn = NIL;
7849
7850 J_rte->rtekind = RTE_SUBQUERY;
7851 J_rte->subquery = Q;
7852 J_rte->jointype = JOIN_INNER;
7853 J_rte->joinaliasvars = NIL;
7854#if PG_VERSION_NUM >= 130000
7855 J_rte->joinleftcols = NIL;
7856 J_rte->joinrightcols = NIL;
7857 J_rte->joinmergedcols = 0;
7858#endif
7859 J_rte->relid = InvalidOid;
7860 J_rte->relkind = 0;
7861#if PG_VERSION_NUM >= 120000
7862 J_rte->rellockmode = 0;
7863#endif
7864 J_rte->inh = false;
7865 J_rte->lateral = false;
7866 J_rte->tablesample = NULL;
7867#if PG_VERSION_NUM >= 160000
7868 J_rte->perminfoindex = 0;
7869#else
7870 J_rte->selectedCols = NULL;
7871 J_rte->insertedCols = NULL;
7872 J_rte->updatedCols = NULL;
7873 J_rte->requiredPerms = ACL_SELECT;
7874#endif
7875 for (i = 0; i < Rc.n; ++i)
7876 cn = lappend(cn, makeString(pstrdup(Rc.name[i])));
7877 for (i = 0; i < Sc.n; ++i)
7878 cn = lappend(cn, makeString(pstrdup(Sc.name[i])));
7879 J_rte->eref = makeAlias("unnamed_subquery", cn);
7880 }
7881
7882 {
7883 RangeTblRef *newr = makeNode(RangeTblRef);
7884 newr->rtindex = join_idx;
7885 q->jointree->fromlist = list_make1(newr);
7886 }
7887
7888 octx.R_idx = R_idx;
7889 octx.S_idx = S_idx;
7890 octx.new_idx = join_idx;
7891 octx.R_map = R_map;
7892 octx.S_map = S_map;
7893 q->targetList = (List *)oj_outer_remap((Node *)q->targetList, &octx);
7894 if (q->jointree->quals)
7895 q->jointree->quals = oj_outer_remap(q->jointree->quals, &octx);
7896 if (q->havingQual)
7897 q->havingQual = oj_outer_remap(q->havingQual, &octx);
7898
7899 return true;
7900}
7901
7902/* -------------------------------------------------------------------------
7903 * Scalar-subquery decorrelation
7904 *
7905 * A correlated scalar subquery (SELECT Q.x FROM Q WHERE corr), used as a
7906 * top-level target-list entry of a query whose FROM is a single tracked base
7907 * relation R, is decorrelated to a LEFT JOIN:
7908 *
7909 * SELECT R.cols, choose(Q.x)
7910 * FROM R LEFT JOIN Q ON corr
7911 * GROUP BY R.cols
7912 * HAVING count(Q.key) <= 1
7913 *
7914 * The corrected outer-join lowering (lower_outer_joins, which runs next)
7915 * supplies the 0-match NULL row, choose() picks the single matched value, and
7916 * the count<=1 HAVING gates out the (SQL-illegal) >=2-match worlds -- no
7917 * gate-level special case. Anything outside this shape returns false and the
7918 * caller's "Subqueries not supported" error still fires.
7919 * ------------------------------------------------------------------------- */
7920
7921/** @brief Mutator: lift a scalar subquery's body into the outer query level.
7922 * Var(level 0, varno @c q_old) -> Var(level 0, varno @c q_new) [the pulled-up
7923 * Q]; the correlated outer Var(level 1) -> Var(level 0). */
7924typedef struct oj_decorr_ctx {
7925 Index q_old;
7926 Index q_new;
7928
7929static Node *oj_decorr_var_mut(Node *node, void *cx) {
7930 oj_decorr_ctx *c = (oj_decorr_ctx *)cx;
7931 if (node == NULL)
7932 return NULL;
7933 if (IsA(node, Var)) {
7934 Var *v = (Var *)node;
7935 if (v->varlevelsup == 1) {
7936 v = (Var *)copyObject(v);
7937 v->varlevelsup = 0;
7938 return (Node *)v;
7939 }
7940 if (v->varlevelsup == 0 && v->varno == c->q_old) {
7941 v = (Var *)copyObject(v);
7942 v->varno = c->q_new;
7943#if PG_VERSION_NUM >= 130000
7944 v->varnosyn = 0;
7945 v->varattnosyn = 0;
7946#endif
7947 return (Node *)v;
7948 }
7949 return node;
7950 }
7951 return expression_tree_mutator(node, oj_decorr_var_mut, cx);
7952}
7953
7954/** @brief Walker: count SubLink nodes (capturing the first), and capture a Var
7955 * referencing varno @p target_varno (level 0) -- used to find a Q column for
7956 * the count() key. */
7957typedef struct oj_sublink_scan {
7960 Index target_varno; /* find any level-0 Var on this rel */
7963
7964static bool oj_sublink_scan_walker(Node *node, void *cx) {
7966 if (node == NULL)
7967 return false;
7968 if (IsA(node, SubLink) && !sublink_is_inert((SubLink *)node)) {
7969 s->n_sublinks++;
7970 if (s->found_sublink == NULL)
7971 s->found_sublink = (SubLink *)node;
7972 }
7973 if (IsA(node, Var)) {
7974 Var *v = (Var *)node;
7975 if (s->found_var == NULL && v->varlevelsup == 0 &&
7976 v->varno == s->target_varno)
7977 s->found_var = v;
7978 }
7979 return expression_tree_walker(node, oj_sublink_scan_walker, cx);
7980}
7981
7982/** @brief Mutator: replace the specific @c SubLink node @p target (by pointer)
7983 * with @p replacement. */
7984typedef struct oj_sl_replace_ctx {
7985 SubLink *target;
7988
7989static Node *oj_sl_replace_mut(Node *node, void *cx) {
7991 if (node == NULL)
7992 return NULL;
7993 if (node == (Node *)c->target)
7994 return c->replacement;
7995 return expression_tree_mutator(node, oj_sl_replace_mut, cx);
7996}
7997
7998/** @brief Walker: true if the subtree contains the specific SubLink @p cx. */
7999static bool oj_contains_sublink_walker(Node *node, void *cx) {
8000 if (node == NULL)
8001 return false;
8002 if (node == (Node *)cx)
8003 return true;
8004 return expression_tree_walker(node, oj_contains_sublink_walker, cx);
8005}
8006
8007/** @brief Build an @c Aggref for a single-argument aggregate. */
8008static Aggref *oj_make_aggref(Oid aggfnoid, Oid aggtype, Oid argtype,
8009 Expr *arg) {
8010 Aggref *agg = makeNode(Aggref);
8011 TargetEntry *te = makeNode(TargetEntry);
8012 te->resno = 1;
8013 te->expr = arg;
8014 agg->aggfnoid = aggfnoid;
8015 agg->aggtype = aggtype;
8016 agg->aggtranstype = InvalidOid;
8017 agg->aggargtypes = list_make1_oid(argtype);
8018 agg->args = list_make1(te);
8019 agg->aggkind = AGGKIND_NORMAL;
8020 agg->aggsplit = AGGSPLIT_SIMPLE;
8021 agg->location = -1;
8022#if PG_VERSION_NUM >= 140000
8023 agg->aggno = agg->aggtransno = -1;
8024#endif
8025 return agg;
8026}
8027
8028/**
8029 * @brief Build @c "count(Q.key) <op> n" over the decorrelated LEFT-JOIN group.
8030 *
8031 * @p found_var is some Q column from the correlation (NULL on the null-padded
8032 * antijoin rows, so it counts only genuine matches); it is re-pointed to the
8033 * pulled-up Q at @p q_idx. Used for the scalar-subquery at-most-one-row gate
8034 * (@c "<= 1") and the WHERE-comparison non-empty gate (@c ">= 1").
8035 */
8036static OpExpr *oj_count_cmp(Var *found_var, Index q_idx, const char *opstr,
8037 int64 n) {
8038 Var *qkey = (Var *)copyObject((Node *)found_var);
8039 Aggref *cnt;
8040 OpExpr *op = makeNode(OpExpr);
8041 Oid o;
8042
8043 qkey->varno = q_idx;
8044#if PG_VERSION_NUM >= 130000
8045 qkey->varnosyn = 0;
8046 qkey->varattnosyn = 0;
8047#endif
8048 cnt = oj_make_aggref(F_COUNT_ANY, INT8OID, qkey->vartype, (Expr *)qkey);
8049
8050 o = OpernameGetOprid(list_make1(makeString((char *)opstr)), INT8OID, INT8OID);
8051 op->opno = o;
8052 op->opfuncid = get_opcode(o);
8053 op->opresulttype = BOOLOID;
8054 op->opcollid = InvalidOid;
8055 op->inputcollid = InvalidOid;
8056 op->args = list_make2(cnt, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
8057 Int64GetDatum(n), false, FLOAT8PASSBYVAL));
8058 op->location = -1;
8059 return op;
8060}
8061
8062/** @brief Build @c "count(DISTINCT v) <op> n" -- the at-most-one-DISTINCT-value
8063 * gate of a @c "SELECT DISTINCT v" body (NULLs, on the null-padded antijoin
8064 * rows, are ignored by @c count, so an empty group counts 0). */
8065static OpExpr *oj_count_distinct_cmp(Expr *valexpr, const char *opstr,
8066 int64 n) {
8067 Aggref *cnt = makeNode(Aggref);
8068 TargetEntry *arg = makeTargetEntry((Expr *)copyObject((Node *)valexpr), 1,
8069 NULL, false);
8070 SortGroupClause *sgc = makeNode(SortGroupClause);
8071 OpExpr *op = makeNode(OpExpr);
8072 Oid o;
8073
8074 arg->ressortgroupref = 1;
8075 sgc->tleSortGroupRef = 1;
8076 get_sort_group_operators(exprType((Node *)valexpr), false, true, false,
8077 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
8078
8079 cnt->aggfnoid = F_COUNT_ANY;
8080 cnt->aggtype = INT8OID;
8081 cnt->aggtranstype = InvalidOid;
8082 cnt->aggargtypes = list_make1_oid(exprType((Node *)valexpr));
8083 cnt->args = list_make1(arg);
8084 cnt->aggdistinct = list_make1(sgc);
8085 cnt->aggkind = AGGKIND_NORMAL;
8086 cnt->aggsplit = AGGSPLIT_SIMPLE;
8087 cnt->location = -1;
8088#if PG_VERSION_NUM >= 140000
8089 cnt->aggno = cnt->aggtransno = -1;
8090#endif
8091
8092 o = OpernameGetOprid(list_make1(makeString((char *)opstr)), INT8OID, INT8OID);
8093 op->opno = o;
8094 op->opfuncid = get_opcode(o);
8095 op->opresulttype = BOOLOID;
8096 op->opcollid = InvalidOid;
8097 op->inputcollid = InvalidOid;
8098 op->args = list_make2(cnt, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
8099 Int64GetDatum(n), false, FLOAT8PASSBYVAL));
8100 op->location = -1;
8101 return op;
8102}
8103
8104/** @brief Var-remap context for the FROM-wrapping pre-step: a Var at
8105 * @c target_level on relation @c varno / attribute @c varattno is retargeted to
8106 * @c newidx column @c pos[varno][varattno]. Descent into @c skip (the
8107 * SubLink) is suppressed. */
8108typedef struct oj_wrap_ctx {
8110 Index newidx;
8112 int **pos; /* pos[varno][varattno] -> R' column (1-based), or 0 */
8113 SubLink *skip;
8114} oj_wrap_ctx;
8115
8116static Node *oj_wrap_remap_mut(Node *node, void *cx) {
8117 oj_wrap_ctx *c = (oj_wrap_ctx *)cx;
8118 if (node == NULL)
8119 return NULL;
8120 if (c->skip && node == (Node *)c->skip)
8121 return node;
8122 if (IsA(node, Var)) {
8123 Var *v = (Var *)node;
8124 if ((int)v->varlevelsup == c->target_level && (int)v->varno >= 1 &&
8125 (int)v->varno <= c->rtlen && c->pos[v->varno] != NULL &&
8126 v->varattno >= 1 && c->pos[v->varno][v->varattno] > 0) {
8127 Var *nv = (Var *)copyObject(v);
8128 nv->varno = c->newidx;
8129 nv->varattno = c->pos[v->varno][v->varattno];
8130#if PG_VERSION_NUM >= 130000
8131 nv->varnosyn = 0;
8132 nv->varattnosyn = 0;
8133#endif
8134 return (Node *)nv;
8135 }
8136 return node;
8137 }
8138 return expression_tree_mutator(node, oj_wrap_remap_mut, cx);
8139}
8140
8141/**
8142 * @brief Wrap a non-single-relation outer FROM into a derived subquery R' so a
8143 * scalar subquery can be decorrelated onto it.
8144 *
8145 * Builds R' = the outer FROM (all its base relations + join RTEs) with the
8146 * non-subquery WHERE conjuncts, exposing every base-relation user column. The
8147 * outer query is rewritten to @c "FROM R'" with all references (the target
8148 * list, the SubLink's correlation at level 1, and -- for a WHERE SubLink -- the
8149 * conjunct that will move to HAVING) retargeted to R''s columns. The FROM must
8150 * consist only of base relations and join RTEs (no nested subqueries / VALUES /
8151 * functions); returns @c false otherwise, leaving @p q untouched.
8152 */
8153static bool oj_wrap_outer_from(const constants_t *constants, Query *q,
8154 SubLink *sl, bool in_where) {
8155 int rtlen = list_length(q->rtable);
8156 int **pos = (int **)palloc0((rtlen + 1) * sizeof(int *));
8157 Query *Rp = makeNode(Query);
8158 RangeTblEntry *rp_rte;
8159 RangeTblRef *rtr = makeNode(RangeTblRef);
8160 FromExpr *outer_fe = makeNode(FromExpr);
8161 List *rp_tl = NIL;
8162 Node *subquery_conj = NULL; /* the WHERE conjunct holding the SubLink */
8163 List *kept_conj = NIL;
8164 oj_wrap_ctx wc;
8165 ListCell *lc;
8166 int idx, posn = 0;
8167 bool any_tracked = false;
8168
8169 /* Only base relations and join RTEs are supported in the wrapped FROM. */
8170 idx = 0;
8171 foreach (lc, q->rtable) {
8172 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
8173 ++idx;
8174 if (r->rtekind == RTE_RELATION) {
8175 if (oj_rte_has_provsql(constants, r))
8176 any_tracked = true;
8177 } else if (r->rtekind != RTE_JOIN) {
8178 return false;
8179 }
8180 }
8181
8182 /* R' exposes every base-relation user column; record (rtindex,attno)->pos. */
8183 idx = 0;
8184 foreach (lc, q->rtable) {
8185 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
8186 oj_cols rc;
8187 int j;
8188 ++idx;
8189 if (r->rtekind != RTE_RELATION)
8190 continue;
8191 oj_collect_cols(constants, r, &rc);
8192 pos[idx] = (int *)palloc0((list_length(r->eref->colnames) + 1) * sizeof(int));
8193 for (j = 0; j < rc.n; ++j) {
8194 Var *v = makeVar(idx, rc.attno[j], rc.type[j], rc.typmod[j], rc.coll[j], 0);
8195 rp_tl = lappend(rp_tl, makeTargetEntry((Expr *)v, ++posn,
8196 pstrdup(rc.name[j]), false));
8197 pos[idx][rc.attno[j]] = posn;
8198 }
8199 }
8200
8201 /* When no FROM relation is provenance-tracked, the outer tuples are certain
8202 * (exactly like joining an untracked table): give R' a synthetic gate_one()
8203 * provsql column so the decorrelation / outer-join lowering treat it as a
8204 * certain-provenance arm. No warning -- no provenance is lost, the outer
8205 * simply contributes the identity and the subquery's provenance flows. */
8206 if (!any_tracked) {
8207 FuncExpr *one = makeNode(FuncExpr);
8208 one->funcid = constants->OID_FUNCTION_GATE_ONE;
8209 one->funcresulttype = constants->OID_TYPE_UUID;
8210 one->args = NIL;
8211 one->location = -1;
8212 rp_tl = lappend(rp_tl, makeTargetEntry((Expr *)one, ++posn,
8213 pstrdup(PROVSQL_COLUMN_NAME), false));
8214 }
8215
8216 /* Split the WHERE: the conjunct holding the SubLink (for a WHERE SubLink)
8217 * stays in the outer query (it becomes HAVING); the rest move into R'. */
8218 if (q->jointree->quals) {
8219 Node *quals = q->jointree->quals;
8220 List *conjs = (IsA(quals, BoolExpr) &&
8221 ((BoolExpr *)quals)->boolop == AND_EXPR)
8222 ? ((BoolExpr *)quals)->args
8223 : list_make1(quals);
8224 foreach (lc, conjs) {
8225 Node *cnode = (Node *)lfirst(lc);
8226 if (in_where && oj_contains_sublink_walker(cnode, sl))
8227 subquery_conj = cnode;
8228 else
8229 kept_conj = lappend(kept_conj, cnode);
8230 }
8231 }
8232
8233 /* Build R'. */
8234 Rp->commandType = CMD_SELECT;
8235 Rp->canSetTag = true;
8236 Rp->rtable = q->rtable;
8237 Rp->jointree = makeNode(FromExpr);
8238 Rp->jointree->fromlist = q->jointree->fromlist;
8239 Rp->jointree->quals =
8240 (kept_conj == NIL)
8241 ? NULL
8242 : (list_length(kept_conj) == 1 ? (Node *)linitial(kept_conj)
8243 : (Node *)makeBoolExpr(AND_EXPR, kept_conj,
8244 -1));
8245 Rp->targetList = rp_tl;
8246#if PG_VERSION_NUM >= 160000
8247 Rp->rteperminfos = q->rteperminfos;
8248#endif
8249
8250 /* Retarget references to R': the outer target list (skipping the SubLink),
8251 * the SubLink body's correlation (level 1), and the retained subquery
8252 * conjunct (level 0). */
8253 wc.newidx = 1;
8254 wc.rtlen = rtlen;
8255 wc.pos = pos;
8256
8257 wc.target_level = 0;
8258 wc.skip = sl;
8259 q->targetList = (List *)oj_wrap_remap_mut((Node *)q->targetList, &wc);
8260 if (subquery_conj)
8261 subquery_conj = oj_wrap_remap_mut(subquery_conj, &wc);
8262
8263 /* The SubLink body's correlated (level-1) references to the FROM relations
8264 * become level-1 references to R'. Walk its target list and quals directly
8265 * (the mutator does not descend into a Query node). */
8266 {
8267 Query *sub = (Query *)sl->subselect;
8268 wc.target_level = 1;
8269 wc.skip = NULL;
8270 sub->targetList = (List *)oj_wrap_remap_mut((Node *)sub->targetList, &wc);
8271 if (sub->jointree && sub->jointree->quals)
8272 sub->jointree->quals = oj_wrap_remap_mut(sub->jointree->quals, &wc);
8273 }
8274
8275 /* Rebuild the outer query: FROM R', WHERE = the retained subquery conjunct. */
8276 rp_rte = oj_make_subquery_rte(Rp);
8277 q->rtable = list_make1(rp_rte);
8278#if PG_VERSION_NUM >= 160000
8279 q->rteperminfos = NIL;
8280#endif
8281 rtr->rtindex = 1;
8282 outer_fe->fromlist = list_make1(rtr);
8283 outer_fe->quals = subquery_conj;
8284 q->jointree = outer_fe;
8285 return true;
8286}
8287
8288/**
8289 * @brief Is @p sub a subselect that the predicate-sublink rewrite can turn into
8290 * a correlated @c "SELECT count(*) FROM Q WHERE corr"?
8291 *
8292 * Requires a body FROM over tracked base relations -- a single relation Q, or
8293 * an all-tracked comma-join that @c oj_wrap_body_from collapses downstream into
8294 * one derived cross-product subquery -- and a (correlated) WHERE, with none
8295 * of the shapes @c decorrelate_scalar_sublinks rejects downstream (aggregates,
8296 * grouping, set ops, LIMIT, nested sublinks, CTEs). The targetList is replaced
8297 * wholesale by @c count(*), so its width is irrelevant here. @p corr_supplied
8298 * is set for @c IN / @c NOT @c IN, whose correlation comes from the testexpr and
8299 * is ANDed into the (possibly empty) subselect WHERE by the caller.
8300 */
8302 Query *sub,
8303 bool corr_supplied) {
8304 ListCell *lc;
8305 if (!IsA(sub, Query) || sub->commandType != CMD_SELECT)
8306 return false;
8307 if (sub->groupClause || sub->groupingSets || sub->hasAggs ||
8308 sub->distinctClause || sub->setOperations || sub->hasWindowFuncs ||
8309 sub->hasSubLinks || sub->limitCount || sub->limitOffset || sub->cteList ||
8310 sub->rtable == NIL)
8311 return false;
8312 if (!sub->jointree || (!corr_supplied && !sub->jointree->quals))
8313 return false; /* an uncorrelated predicate has no Q key for count() */
8314 /* Single tracked base relation, or an all-tracked comma-join (the
8315 * multi-relation body is collapsed downstream by oj_wrap_body_from into one
8316 * derived cross-product subquery D -- same preconditions checked here). */
8317 foreach (lc, sub->rtable) {
8318 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
8319 if (r->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, r))
8320 return false;
8321 }
8322 foreach (lc, sub->jointree->fromlist) {
8323 if (!IsA(lfirst(lc), RangeTblRef))
8324 return false;
8325 }
8326 return true;
8327}
8328
8329/**
8330 * @brief Turn a predicate subselect into the boolean @c "(SELECT count(*) FROM Q
8331 * WHERE corr) >= 1" (semijoin) or @c "... = 0" (antijoin).
8332 *
8333 * @c EXISTS / @c IN are existence tests (@c "⊕Q present"), so they are exactly
8334 * @c "count(*) >= 1"; @c NOT @c EXISTS / @c NOT @c IN are their antijoin duals,
8335 * @c "count(*) = 0". Lowering them to a correlated count() comparison lets the
8336 * aggregate-body arm of @c decorrelate_scalar_sublinks do the rest: it rewrites
8337 * @c count(*) to @c count(Q.key) over the @c "R ⟕ Q" group (so the null-padded
8338 * antijoin row is not counted) and lifts the comparison into @c HAVING -- i.e.
8339 * the semijoin @c R⊗⊕Q and the antijoin @c R⊗(1⊖⊕Q) fall out of the existing
8340 * outer-join lowering.
8341 *
8342 * @p extra_corr (for @c IN / @c NOT @c IN) is the @c "Q.col = x" correlation
8343 * lifted out of the testexpr; it is ANDed into the subselect's WHERE. @c EXISTS
8344 * passes @c NULL, its correlation already living in the subselect.
8345 */
8346static Node *build_count_predicate(Query *subselect, Node *extra_corr,
8347 bool antijoin) {
8348 Query *sq = (Query *)copyObject(subselect);
8349 Aggref *cnt = makeNode(Aggref);
8350 SubLink *sl = makeNode(SubLink);
8351 OpExpr *op = makeNode(OpExpr);
8352 Oid o;
8353
8354 if (extra_corr)
8355 sq->jointree->quals =
8356 sq->jointree->quals
8357 ? (Node *)makeBoolExpr(AND_EXPR,
8358 list_make2(sq->jointree->quals, extra_corr), -1)
8359 : extra_corr;
8360
8361 cnt->aggfnoid = F_COUNT_; /* count(*) */
8362 cnt->aggtype = INT8OID;
8363 cnt->aggtranstype = InvalidOid;
8364 cnt->aggargtypes = NIL;
8365 cnt->args = NIL;
8366 cnt->aggstar = true;
8367 cnt->aggkind = AGGKIND_NORMAL;
8368 cnt->aggsplit = AGGSPLIT_SIMPLE;
8369 cnt->location = -1;
8370#if PG_VERSION_NUM >= 140000
8371 cnt->aggno = cnt->aggtransno = -1;
8372#endif
8373 sq->targetList =
8374 list_make1(makeTargetEntry((Expr *)cnt, 1, pstrdup("count"), false));
8375 sq->hasAggs = true;
8376
8377 sl->subLinkType = EXPR_SUBLINK;
8378 sl->subselect = (Node *)sq;
8379 sl->testexpr = NULL;
8380 sl->operName = NIL;
8381 sl->location = -1;
8382
8383 o = OpernameGetOprid(list_make1(makeString(antijoin ? "=" : ">=")), INT8OID,
8384 INT8OID);
8385 op->opno = o;
8386 op->opfuncid = get_opcode(o);
8387 op->opresulttype = BOOLOID;
8388 op->opcollid = InvalidOid;
8389 op->inputcollid = InvalidOid;
8390 op->args = list_make2(sl, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
8391 Int64GetDatum(antijoin ? 0 : 1), false,
8392 FLOAT8PASSBYVAL));
8393 op->location = -1;
8394 return (Node *)op;
8395}
8396
8397/** @brief Context for @c oj_param_repl_mut. */
8398typedef struct {
8402
8403/** @brief Replace every @c PARAM_SUBLINK with @p paramid by @p replacement. */
8404static Node *oj_param_repl_mut(Node *node, void *cx) {
8406 if (node == NULL)
8407 return NULL;
8408 if (IsA(node, Param)) {
8409 Param *p = (Param *)node;
8410 if (p->paramkind == PARAM_SUBLINK && p->paramid == c->paramid)
8411 return copyObject(c->replacement);
8412 return node;
8413 }
8414 return expression_tree_mutator(node, oj_param_repl_mut, cx);
8415}
8416
8417/** @brief Does the body's range table reach at least one provenance-tracked
8418 * relation? Bodies over untracked relations only are left to PostgreSQL's
8419 * native sublink machinery. */
8420static bool oj_body_has_tracked_relation(const constants_t *constants,
8421 Query *body) {
8422 ListCell *lc;
8423 foreach (lc, body->rtable) {
8424 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
8425 if ((r->rtekind == RTE_RELATION || r->rtekind == RTE_SUBQUERY) &&
8426 oj_rte_has_provsql(constants, r))
8427 return true;
8428 }
8429 return false;
8430}
8431
8432/**
8433 * @brief Normalize quantified comparisons over a single bare-aggregate body
8434 * into plain scalar comparisons.
8435 *
8436 * An aggregate body without @c GROUP @c BY returns exactly one row, so
8437 * @c "x op ANY (SELECT agg(..) …)" and @c "x op ALL (…)" are the scalar
8438 * comparison @c "x op (SELECT agg(..) …)" (NULL semantics included), and a
8439 * @c NOT-wrapped form (@c NOT @c IN) is the negator-operator comparison. The
8440 * conjunct's @c PARAM_SUBLINK placeholder is substituted by the
8441 * @c EXPR_SUBLINK body, after which the scalar paths lower it: the
8442 * HAVING-gated cross-joined subquery for a constant comparand
8443 * (@c move_uncorrelated_where_predicates) or the @c "R ⟕ Q" decorrelation for
8444 * an outer-column comparand (@c decorrelate_scalar_sublinks). Runs before
8445 * @c rewrite_uncorrelated_antijoin so a normalized count() comparison that is
8446 * true on the empty body still gets the antijoin treatment there.
8447 */
8449 const constants_t *constants, Query *q) {
8450 Node *quals;
8451 List *conjs, *newconjs = NIL;
8452 ListCell *lc;
8453 bool changed = false;
8454
8455 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
8456 !q->jointree->quals)
8457 return false;
8458
8459 quals = q->jointree->quals;
8460 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
8461 ? ((BoolExpr *)quals)->args
8462 : list_make1(quals);
8463
8464 foreach (lc, conjs) {
8465 Node *c = (Node *)lfirst(lc);
8466 Node *inner = c, *rewritten = NULL;
8467 bool neg = false;
8468
8469 if (IsA(c, BoolExpr) && ((BoolExpr *)c)->boolop == NOT_EXPR &&
8470 list_length(((BoolExpr *)c)->args) == 1) {
8471 neg = true;
8472 inner = (Node *)linitial(((BoolExpr *)c)->args);
8473 }
8474 if (IsA(inner, SubLink) &&
8475 (((SubLink *)inner)->subLinkType == ANY_SUBLINK ||
8476 ((SubLink *)inner)->subLinkType == ALL_SUBLINK) &&
8477 IsA(((SubLink *)inner)->subselect, Query)) {
8478 SubLink *sl = (SubLink *)inner;
8479 Query *body = (Query *)sl->subselect;
8480 if (body->commandType == CMD_SELECT && body->hasAggs &&
8481 !body->groupClause && !body->groupingSets && !body->setOperations &&
8482 !body->hasWindowFuncs && !body->hasSubLinks && !body->limitCount &&
8483 !body->limitOffset && !body->cteList &&
8484 list_length(body->targetList) == 1 &&
8485 IsA(((TargetEntry *)linitial(body->targetList))->expr, Aggref) &&
8486 sl->testexpr && IsA(sl->testexpr, OpExpr) &&
8487 list_length(((OpExpr *)sl->testexpr)->args) == 2 &&
8488 oj_body_has_tracked_relation(constants, body)) {
8489 OpExpr *op = (OpExpr *)copyObject(sl->testexpr);
8490 Oid opno = neg ? get_negator(op->opno) : op->opno;
8491 if (OidIsValid(opno)) {
8492 SubLink *esl = makeNode(SubLink);
8494 esl->subLinkType = EXPR_SUBLINK;
8495 esl->testexpr = NULL;
8496 esl->operName = NIL;
8497 esl->subselect = (Node *)copyObject(body);
8498 esl->location = -1;
8499 op->opno = opno;
8500 op->opfuncid = get_opcode(opno);
8501 pc.paramid = 1;
8502 pc.replacement = (Node *)esl;
8503 rewritten = oj_param_repl_mut((Node *)op, &pc);
8504 }
8505 }
8506 }
8507 newconjs = lappend(newconjs, rewritten ? rewritten : c);
8508 if (rewritten)
8509 changed = true;
8510 }
8511
8512 if (changed)
8513 q->jointree->quals = (list_length(newconjs) == 1)
8514 ? (Node *)linitial(newconjs)
8515 : (Node *)makeBoolExpr(AND_EXPR, newconjs, -1);
8516 return changed;
8517}
8518
8519/**
8520 * @brief Build the per-row correlation for a quantified sublink (@c IN /
8521 * @c op @c ANY / @c op @c ALL), setting @p *antijoin.
8522 *
8523 * The testexpr is @c "x op Param(subselect output)" (single column), or -- for a
8524 * row @c IN -- a @c BoolExpr @c AND of per-column @c "xᵢ = Paramᵢ". For each we
8525 * copy the op, sink the outer operand one level, and substitute the subselect's
8526 * paramid-th output column for the @c PARAM_SUBLINK placeholder, keeping any
8527 * coercions (e.g. a @c varchar->text relabel) intact. @c ANY is a semijoin
8528 * (@c *antijoin = false, operator kept); @c ALL is the universal dual, the
8529 * antijoin (@c *antijoin = true, operator negated -- @c "∀q. x op q" =
8530 * @c "¬∃q. x ¬op q"). Returns @c NULL for unsupported shapes (a @c RowCompareExpr,
8531 * a multi-column @c ALL, a bad paramid…).
8532 */
8533static Node *extract_quantified_corr(SubLink *sl, bool *antijoin) {
8534 Query *sub = (Query *)sl->subselect;
8535 List *opexprs, *conjs = NIL;
8536 ListCell *lc;
8537 bool negate_op;
8538
8539 /* ANY (IN, op ANY) is a semijoin: ∃q. x op q. ALL (op ALL) is its universal
8540 * dual: ∀q. x op q = ¬∃q. x ¬op q -- the antijoin, with the operator negated
8541 * in the per-row correlation. */
8542 if (sl->subLinkType == ANY_SUBLINK) {
8543 *antijoin = false;
8544 negate_op = false;
8545 } else if (sl->subLinkType == ALL_SUBLINK) {
8546 *antijoin = true;
8547 negate_op = true;
8548 } else {
8549 return NULL;
8550 }
8551
8552 /* The testexpr is a single "x op Param" (single-column), or -- only for a row
8553 * IN -- a BoolExpr AND of per-column "xᵢ = Paramᵢ". */
8554 if (IsA(sl->testexpr, OpExpr))
8555 opexprs = list_make1(sl->testexpr);
8556 else if (sl->subLinkType == ANY_SUBLINK && IsA(sl->testexpr, BoolExpr) &&
8557 ((BoolExpr *)sl->testexpr)->boolop == AND_EXPR)
8558 opexprs = ((BoolExpr *)sl->testexpr)->args;
8559 else
8560 return NULL;
8561
8562 foreach (lc, opexprs) {
8563 OpExpr *oe = (OpExpr *)lfirst(lc);
8564 Node *rhs, *qcol, *ci;
8565 Param *p;
8567
8568 if (!IsA(oe, OpExpr) || list_length(oe->args) != 2)
8569 return NULL;
8570 rhs = (Node *)lsecond(oe->args);
8571 if (IsA(rhs, RelabelType))
8572 rhs = (Node *)((RelabelType *)rhs)->arg; /* varchar->text etc. */
8573 if (!IsA(rhs, Param))
8574 return NULL;
8575 p = (Param *)rhs;
8576 if (p->paramkind != PARAM_SUBLINK || p->paramid < 1 ||
8577 p->paramid > list_length(sub->targetList))
8578 return NULL;
8579
8580 /* Build "xᵢ <op'> Q.colᵢ": copy the testexpr op (negating it for ALL),
8581 * sink the outer operand a level, and substitute the subselect's
8582 * paramid-th output column for its PARAM_SUBLINK placeholder. */
8583 ci = copyObject((Node *)oe);
8584 if (negate_op) {
8585 Oid neg = get_negator(((OpExpr *)ci)->opno);
8586 if (!OidIsValid(neg))
8587 return NULL;
8588 ((OpExpr *)ci)->opno = neg;
8589 ((OpExpr *)ci)->opfuncid = get_opcode(neg);
8590 }
8591 IncrementVarSublevelsUp(ci, 1, 0);
8592 qcol = copyObject(
8593 (Node *)((TargetEntry *)list_nth(sub->targetList, p->paramid - 1))->expr);
8594 ctx.paramid = p->paramid;
8595 ctx.replacement = qcol;
8596 conjs = lappend(conjs, oj_param_repl_mut(ci, &ctx));
8597 }
8598
8599 if (conjs == NIL)
8600 return NULL;
8601 return (list_length(conjs) == 1)
8602 ? (Node *)linitial(conjs)
8603 : (Node *)makeBoolExpr(AND_EXPR, conjs, -1);
8604}
8605
8606/**
8607 * @brief Rewrite top-level @c EXISTS / @c IN WHERE conjuncts (optionally negated)
8608 * over a single tracked relation into correlated @c count(*) comparisons.
8609 *
8610 * A pre-pass for @c decorrelate_scalar_sublinks: each qualifying conjunct (a
8611 * bare @c EXISTS / @c IN sublink, or one wrapped in a single @c NOT -- i.e.
8612 * @c NOT @c EXISTS / @c NOT @c IN) is replaced by the @c build_count_predicate
8613 * form, after which the scalar-subquery decorrelation lowers the count()
8614 * comparison to the @c "R ⟕ Q" semijoin / antijoin. Conjuncts whose subselect is
8615 * not decorrelatable (untracked / uncorrelated / non-comma-join) are left
8616 * untouched, so they hit the usual unsupported-subquery error.
8617 */
8618static bool rewrite_predicate_sublinks(const constants_t *constants, Query *q) {
8619 Node *quals;
8620 List *conjs, *newconjs = NIL;
8621 ListCell *lc;
8622 bool changed = false;
8623
8624 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
8625 !q->jointree->quals)
8626 return false;
8627
8628 quals = q->jointree->quals;
8629 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
8630 ? ((BoolExpr *)quals)->args
8631 : list_make1(quals);
8632
8633 foreach (lc, conjs) {
8634 Node *c = (Node *)lfirst(lc);
8635 Node *inner = c, *rewritten = NULL;
8636 bool neg = false;
8637 SubLink *sl;
8638
8639 if (IsA(c, BoolExpr) && ((BoolExpr *)c)->boolop == NOT_EXPR &&
8640 list_length(((BoolExpr *)c)->args) == 1) {
8641 neg = true;
8642 inner = (Node *)linitial(((BoolExpr *)c)->args);
8643 }
8644 if (IsA(inner, SubLink) && IsA(((SubLink *)inner)->subselect, Query)) {
8645 sl = (SubLink *)inner;
8646 if (sl->subLinkType == EXISTS_SUBLINK &&
8648 (Query *)sl->subselect, false)) {
8649 /* EXISTS / NOT EXISTS: correlation already in the subselect WHERE. */
8650 rewritten =
8651 build_count_predicate((Query *)sl->subselect, NULL, neg);
8652 } else if (sl->subLinkType == ANY_SUBLINK ||
8653 sl->subLinkType == ALL_SUBLINK) {
8654 /* IN / NOT IN / op ANY / op ALL: correlation lifted from the testexpr.
8655 * ANY is a semijoin, ALL its antijoin dual; a wrapping NOT flips that. */
8656 bool base_antijoin;
8657 Node *corr = extract_quantified_corr(sl, &base_antijoin);
8658 if (corr &&
8660 (Query *)sl->subselect, true))
8661 rewritten = build_count_predicate((Query *)sl->subselect, corr,
8662 base_antijoin ^ neg);
8663 }
8664 }
8665 newconjs = lappend(newconjs, rewritten ? rewritten : c);
8666 if (rewritten)
8667 changed = true;
8668 }
8669
8670 if (changed)
8671 q->jointree->quals = (list_length(newconjs) == 1)
8672 ? (Node *)linitial(newconjs)
8673 : (Node *)makeBoolExpr(AND_EXPR, newconjs, -1);
8674 return changed;
8675}
8676
8677/**
8678 * @brief Rewrite a top-level @c ARRAY(SELECT Q.col FROM Q WHERE corr) target-list
8679 * entry into the aggregate body @c (SELECT array_agg(Q.col) FROM Q WHERE
8680 * corr).
8681 *
8682 * A pre-pass for @c decorrelate_scalar_sublinks: an @c ARRAY_SUBLINK collects the
8683 * correlated rows into an array, which is exactly @c array_agg over the group, so
8684 * mutating it into an @c EXPR_SUBLINK aggregate body lets the aggregate arm lower
8685 * it to @c array_agg(Q.col) over the @c "R ⟕ Q" group -- no @c count gate, since
8686 * an array may have zero, one, or many elements. Subselects that are not
8687 * decorrelatable (untracked / multi-relation / uncorrelated) are left untouched.
8688 */
8689static bool rewrite_array_sublinks(const constants_t *constants, Query *q) {
8690 ListCell *lc;
8691 bool changed = false;
8692
8693 if (q->commandType != CMD_SELECT || !q->hasSubLinks)
8694 return false;
8695
8696 foreach (lc, q->targetList) {
8697 TargetEntry *te = (TargetEntry *)lfirst(lc);
8698 SubLink *sl;
8699 Query *sub;
8700 TargetEntry *innerte;
8701 Oid elemtype, arrtype;
8702 oj_sublink_scan scan;
8703 Aggref *agg;
8704 NullTest *nt;
8705
8706 if (!IsA(te->expr, SubLink))
8707 continue;
8708 sl = (SubLink *)te->expr;
8709 if (sl->subLinkType != ARRAY_SUBLINK || !IsA(sl->subselect, Query))
8710 continue;
8711 sub = (Query *)sl->subselect;
8712 if (!predicate_subselect_decorrelatable(constants, sub, false))
8713 continue;
8714 /* Exactly one non-junk output column (the element value). A body ORDER BY
8715 * adds junk sort-key entries to the targetList; those become the ordered
8716 * array_agg's extra args (see below), so they are allowed here. */
8717 {
8718 int nreal = 0;
8719 ListCell *tlc;
8720 foreach (tlc, sub->targetList)
8721 if (!((TargetEntry *)lfirst(tlc))->resjunk)
8722 ++nreal;
8723 if (nreal != 1 || ((TargetEntry *)linitial(sub->targetList))->resjunk)
8724 continue;
8725 }
8726
8727 innerte = (TargetEntry *)linitial(sub->targetList);
8728 elemtype = exprType((Node *)innerte->expr);
8729 arrtype = get_array_type(elemtype);
8730 if (!OidIsValid(arrtype))
8731 continue; /* no array type for this element (e.g. a pseudo-type) */
8732
8733 /* A Q column from the correlation, to key the null-padded-row filter. */
8734 scan.n_sublinks = 0;
8735 scan.found_sublink = NULL;
8736 scan.target_varno = 1; /* Q is rtindex 1 in the subselect */
8737 scan.found_var = NULL;
8738 oj_sublink_scan_walker(sub->jointree->quals, &scan);
8739 if (scan.found_var == NULL)
8740 continue; /* uncorrelated: decorrelation would bail anyway */
8741
8742 if (sub->sortClause) {
8743 /* ARRAY(SELECT v FROM Q WHERE corr ORDER BY key) -> the ordered aggregate
8744 * array_agg(v ORDER BY key): the body's ORDER BY moves inside the
8745 * aggregate (where it survives the regroup into the R ⟕ Q group), exactly
8746 * as the LIMIT-1 argmax path does for choose(). The aggregate's args are
8747 * the value plus the junk sort-key entries, its aggorder the body's
8748 * sortClause; decorrelate's Var-remap pulls every arg's Q reference up to
8749 * the joined Q. */
8750 List *args = NIL, *argtypes = NIL;
8751 ListCell *alc;
8752 agg = makeNode(Aggref);
8753 foreach (alc, sub->targetList) {
8754 TargetEntry *ate = (TargetEntry *)copyObject(lfirst(alc));
8755 args = lappend(args, ate);
8756 argtypes = lappend_oid(argtypes, exprType((Node *)ate->expr));
8757 }
8758 agg->aggfnoid = F_ARRAY_AGG_ANYNONARRAY;
8759 agg->aggtype = arrtype;
8760 agg->aggtranstype = InvalidOid;
8761 agg->aggargtypes = argtypes;
8762 agg->args = args;
8763 agg->aggorder = (List *)copyObject((Node *)sub->sortClause);
8764 agg->aggkind = AGGKIND_NORMAL;
8765 agg->aggsplit = AGGSPLIT_SIMPLE;
8766 agg->location = -1;
8767#if PG_VERSION_NUM >= 140000
8768 agg->aggno = agg->aggtransno = -1;
8769#endif
8770 } else {
8771 agg = oj_make_aggref(F_ARRAY_AGG_ANYNONARRAY, arrtype, elemtype,
8772 innerte->expr);
8773 }
8774 /* array_agg keeps NULLs in its value, so the LEFT JOIN's null-padded
8775 * antijoin row (Q key IS NULL) would inject a spurious NULL element.
8776 * Filter it out; a genuinely-NULL matched element (Q key non-NULL) is still
8777 * collected. decorrelate's Var-remap retargets this Q key to the pulled-up
8778 * Q just like the aggregate argument. */
8779 nt = makeNode(NullTest);
8780 nt->arg = (Expr *)copyObject((Node *)scan.found_var);
8781 nt->nulltesttype = IS_NOT_NULL;
8782 nt->argisrow = false;
8783 nt->location = -1;
8784 agg->aggfilter = (Expr *)nt;
8785
8786 /* The single body output is now the array_agg; the ORDER BY (if any) lives
8787 * inside it, so the query-level sortClause and the junk sort-key targetList
8788 * entries are dropped. */
8789 sub->targetList = list_make1(makeTargetEntry(
8790 (Expr *)agg, 1, innerte->resname ? pstrdup(innerte->resname) : NULL,
8791 false));
8792 sub->sortClause = NIL;
8793 sub->hasAggs = true;
8794 sl->subLinkType = EXPR_SUBLINK;
8795 changed = true;
8796 }
8797 return changed;
8798}
8799
8800/**
8801 * @brief Collapse a multi-table scalar-subquery body FROM into one derived
8802 * cross-product subquery @c D, so the decorrelation can treat the body as
8803 * @c "SELECT val FROM D WHERE W" with @c D a single tracked subquery.
8804 *
8805 * Mirror of @c oj_wrap_outer_from, but for the SubLink body: every body relation
8806 * must be a tracked base relation and the FROM a comma-join. @c D exposes every
8807 * base user column (@c oj_collect_cols); the body's own (level-0) references are
8808 * retargeted to @c D, while the correlated level-1 references to the outer query
8809 * are left untouched. The body WHERE @c W (correlation + inter-table join) stays
8810 * in place: it becomes the @c "R LEFT JOIN D" ON clause, and @c get_provenance_attributes
8811 * later processes @c D recursively, giving it the @c Q1 ⊗ … ⊗ Qn provenance.
8812 */
8813static bool oj_wrap_body_from(const constants_t *constants, Query *sub) {
8814 int rtlen = list_length(sub->rtable);
8815 int **pos;
8816 Query *D = makeNode(Query);
8817 RangeTblEntry *d_rte;
8818 RangeTblRef *rtr = makeNode(RangeTblRef);
8819 List *d_tl = NIL;
8820 oj_wrap_ctx wc;
8821 ListCell *lc;
8822 int idx, posn = 0;
8823
8824 if (!sub->jointree || sub->jointree->fromlist == NIL)
8825 return false;
8826 foreach (lc, sub->rtable) {
8827 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
8828 if (r->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, r))
8829 return false;
8830 }
8831 foreach (lc, sub->jointree->fromlist) {
8832 if (!IsA(lfirst(lc), RangeTblRef))
8833 return false; /* only a plain comma-join, no explicit JoinExprs */
8834 }
8835
8836 /* D exposes every base user column; record (rtindex,attno) -> D column. */
8837 pos = (int **)palloc0((rtlen + 1) * sizeof(int *));
8838 idx = 0;
8839 foreach (lc, sub->rtable) {
8840 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
8841 oj_cols rc;
8842 int j;
8843 ++idx;
8844 oj_collect_cols(constants, r, &rc);
8845 pos[idx] =
8846 (int *)palloc0((list_length(r->eref->colnames) + 1) * sizeof(int));
8847 for (j = 0; j < rc.n; ++j) {
8848 Var *v =
8849 makeVar(idx, rc.attno[j], rc.type[j], rc.typmod[j], rc.coll[j], 0);
8850 d_tl = lappend(d_tl, makeTargetEntry((Expr *)v, ++posn,
8851 pstrdup(rc.name[j]), false));
8852 pos[idx][rc.attno[j]] = posn;
8853 }
8854 }
8855
8856 /* D = SELECT <body base user cols> FROM <body fromlist> (cross product). */
8857 D->commandType = CMD_SELECT;
8858 D->canSetTag = true;
8859 D->rtable = sub->rtable;
8860 D->jointree = makeNode(FromExpr);
8861 D->jointree->fromlist = sub->jointree->fromlist;
8862 D->jointree->quals = NULL;
8863 D->targetList = d_tl;
8864#if PG_VERSION_NUM >= 160000
8865 D->rteperminfos = sub->rteperminfos;
8866#endif
8867
8868 /* Retarget the body's own (level-0) Vars to D; level-1 (outer) Vars stay. */
8869 wc.newidx = 1;
8870 wc.rtlen = rtlen;
8871 wc.pos = pos;
8872 wc.target_level = 0;
8873 wc.skip = NULL;
8874 sub->targetList = (List *)oj_wrap_remap_mut((Node *)sub->targetList, &wc);
8875 if (sub->jointree->quals)
8876 sub->jointree->quals = oj_wrap_remap_mut(sub->jointree->quals, &wc);
8877
8878 /* Rebuild the body over D. */
8879 d_rte = oj_make_subquery_rte(D);
8880 sub->rtable = list_make1(d_rte);
8881#if PG_VERSION_NUM >= 160000
8882 sub->rteperminfos = NIL;
8883#endif
8884 rtr->rtindex = 1;
8885 sub->jointree->fromlist = list_make1(rtr);
8886 return true;
8887}
8888
8889/**
8890 * @brief Build the derived single-row aggregate @c D for an UNcorrelated scalar
8891 * subquery body, to be cross-joined into the outer FROM.
8892 *
8893 * Aggregate body @c "SELECT agg(..) FROM Q [WHERE]" -> @c D is the body itself
8894 * (always one row). Value body @c "SELECT val FROM Q [WHERE]" -> @c D is
8895 * @c "SELECT choose(val) FROM Q [WHERE] HAVING count(*) <= 1" -- one row, with
8896 * the scalar subquery's at-most-one-row rule baked into the moved subquery.
8897 * Returns @c NULL unless the body is an uncorrelated clean SELECT over tracked
8898 * base relations (a comma-join is fine; @c D is then an inner join).
8899 *
8900 * Faithful to ProvSQL aggregates: an empty @c Q yields an empty group, hence a
8901 * @c gate_zero row that drops out -- exactly what a hand-written derived
8902 * aggregate does; the correlated path's 0-match NULL row is not reconstructed.
8903 */
8905 Query *body) {
8906 Query *D;
8907 TargetEntry *vte;
8908 ListCell *lc;
8909
8910 if (!IsA(body, Query) || body->commandType != CMD_SELECT)
8911 return NULL;
8912 if (body->groupClause || body->groupingSets || body->distinctClause ||
8913 body->setOperations || body->hasWindowFuncs || body->hasSubLinks ||
8914 body->limitCount || body->limitOffset || body->cteList ||
8915 list_length(body->targetList) != 1)
8916 return NULL;
8917 if (!body->jointree || body->jointree->fromlist == NIL)
8918 return NULL;
8919 /* Correlated bodies are the LEFT-JOIN decorrelation's job, not this one. */
8920 if (contain_vars_of_level((Node *)body->targetList, 1) ||
8921 (body->jointree->quals && contain_vars_of_level(body->jointree->quals, 1)))
8922 return NULL;
8923 /* Every FROM relation must be a tracked base relation, so D is a processable
8924 * tracked subquery (a comma-join is fine -- D is then an inner join). */
8925 foreach (lc, body->rtable) {
8926 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
8927 if (r->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, r))
8928 return NULL;
8929 }
8930 foreach (lc, body->jointree->fromlist) {
8931 if (!IsA(lfirst(lc), RangeTblRef))
8932 return NULL;
8933 }
8934
8935 vte = (TargetEntry *)linitial(body->targetList);
8936
8937 if (body->hasAggs) {
8938 /* Aggregate body: a single bare aggregate (one row, no grouping). */
8939 if (!IsA(vte->expr, Aggref))
8940 return NULL;
8941 D = (Query *)copyObject(body);
8942 } else {
8943 /* Value body: pick the single value with choose(), gate the >1-row worlds
8944 * with HAVING count(*) <= 1. */
8945 Aggref *cnt;
8946 OpExpr *le;
8947 Oid le_op;
8948
8949 if (!OidIsValid(constants->OID_FUNCTION_CHOOSE))
8950 return NULL;
8951 D = (Query *)copyObject(body);
8952 vte = (TargetEntry *)linitial(D->targetList);
8953 vte->expr = (Expr *)oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
8954 exprType((Node *)vte->expr),
8955 exprType((Node *)vte->expr), vte->expr);
8956 D->hasAggs = true;
8957
8958 cnt = makeNode(Aggref);
8959 cnt->aggfnoid = F_COUNT_; /* count(*) */
8960 cnt->aggtype = INT8OID;
8961 cnt->aggtranstype = InvalidOid;
8962 cnt->aggargtypes = NIL;
8963 cnt->args = NIL;
8964 cnt->aggstar = true;
8965 cnt->aggkind = AGGKIND_NORMAL;
8966 cnt->aggsplit = AGGSPLIT_SIMPLE;
8967 cnt->location = -1;
8968#if PG_VERSION_NUM >= 140000
8969 cnt->aggno = cnt->aggtransno = -1;
8970#endif
8971 le = makeNode(OpExpr);
8972 le_op = OpernameGetOprid(list_make1(makeString("<=")), INT8OID, INT8OID);
8973 le->opno = le_op;
8974 le->opfuncid = get_opcode(le_op);
8975 le->opresulttype = BOOLOID;
8976 le->opcollid = InvalidOid;
8977 le->inputcollid = InvalidOid;
8978 le->args = list_make2(cnt, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
8979 Int64GetDatum(1), false,
8980 FLOAT8PASSBYVAL));
8981 le->location = -1;
8982 D->havingQual = (Node *)le;
8983 }
8984 return D;
8985}
8986
8987/** @brief Is @p sub an uncorrelated clean SELECT over tracked base relations (a
8988 * comma-join is fine)? The targetList is not inspected (callers replace it). */
8990 Query *sub) {
8991 ListCell *lc;
8992 if (!IsA(sub, Query) || sub->commandType != CMD_SELECT)
8993 return false;
8994 if (sub->groupClause || sub->groupingSets || sub->distinctClause ||
8995 sub->setOperations || sub->hasWindowFuncs || sub->hasSubLinks ||
8996 sub->limitCount || sub->limitOffset || sub->cteList)
8997 return false;
8998 if (!sub->jointree || sub->jointree->fromlist == NIL)
8999 return false;
9000 if (contain_vars_of_level((Node *)sub->targetList, 1) ||
9001 (sub->jointree->quals && contain_vars_of_level(sub->jointree->quals, 1)))
9002 return false; /* correlated */
9003 foreach (lc, sub->rtable) {
9004 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
9005 if (r->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, r))
9006 return false;
9007 }
9008 foreach (lc, sub->jointree->fromlist) {
9009 if (!IsA(lfirst(lc), RangeTblRef))
9010 return false;
9011 }
9012 return true;
9013}
9014
9015/** @brief A fresh @c count(*) @c Aggref (returns @c int8). */
9016static Aggref *oj_make_count_star(void) {
9017 Aggref *cnt = makeNode(Aggref);
9018 cnt->aggfnoid = F_COUNT_;
9019 cnt->aggtype = INT8OID;
9020 cnt->aggtranstype = InvalidOid;
9021 cnt->aggargtypes = NIL;
9022 cnt->args = NIL;
9023 cnt->aggstar = true;
9024 cnt->aggkind = AGGKIND_NORMAL;
9025 cnt->aggsplit = AGGSPLIT_SIMPLE;
9026 cnt->location = -1;
9027#if PG_VERSION_NUM >= 140000
9028 cnt->aggno = cnt->aggtransno = -1;
9029#endif
9030 return cnt;
9031}
9032
9033/** @brief Build the one-row @c "SELECT 1 FROM <body FROM> HAVING <pred>" gated
9034 * subquery: @p body supplies the FROM (and any uncorrelated WHERE), @p pred the
9035 * aggregate comparison that becomes its provenance. */
9036static Query *oj_having_gated_subquery(Query *body, Node *pred) {
9037 Query *D = (Query *)copyObject(body);
9038 D->targetList = list_make1(makeTargetEntry(
9039 (Expr *)makeConst(INT4OID, -1, InvalidOid, sizeof(int32), Int32GetDatum(1),
9040 false, true),
9041 1, pstrdup("exists"), false));
9042 D->havingQual = pred;
9043 D->hasAggs = true;
9044 return D;
9045}
9046
9047/**
9048 * @brief Handle UNcorrelated @c EXISTS and uncorrelated aggregate comparisons in
9049 * WHERE by cross-joining a HAVING-gated one-row subquery.
9050 *
9051 * @c EXISTS (SELECT … FROM Q) -> @c "SELECT 1 FROM Q HAVING count(*) >= 1";
9052 * @c "(SELECT agg(..) FROM Q) OP v" (v not referencing the outer) ->
9053 * @c "SELECT 1 FROM Q HAVING agg(..) OP v". The gated @c D is appended to the
9054 * FROM, so the conjunct's truth becomes @c "R ⊗ [predicate]" -- ProvSQL's HAVING
9055 * annotates (the one aggregate row is always materialised, gated), so no
9056 * actual-instance row is needed. Faithful to ProvSQL aggregates: the empty-Q
9057 * world drops (so @c NOT @c EXISTS, satisfied only by the empty group, is left
9058 * rejected). Correlated predicates are handled by @c rewrite_predicate_sublinks.
9059 */
9061 Query *q) {
9062 Node *quals;
9063 List *conjs, *newconjs = NIL;
9064 ListCell *lc;
9065 bool changed = false;
9066
9067 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
9068 !q->jointree->quals)
9069 return false;
9070
9071 quals = q->jointree->quals;
9072 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
9073 ? ((BoolExpr *)quals)->args
9074 : list_make1(quals);
9075
9076 foreach (lc, conjs) {
9077 Node *c = (Node *)lfirst(lc);
9078 Query *D = NULL;
9079
9080 if (IsA(c, SubLink) && ((SubLink *)c)->subLinkType == EXISTS_SUBLINK &&
9081 IsA(((SubLink *)c)->subselect, Query) &&
9083 (Query *)((SubLink *)c)->subselect)) {
9084 /* EXISTS -> HAVING count(*) >= 1. */
9085 OpExpr *ge = makeNode(OpExpr);
9086 Oid o = OpernameGetOprid(list_make1(makeString(">=")), INT8OID, INT8OID);
9087 ge->opno = o;
9088 ge->opfuncid = get_opcode(o);
9089 ge->opresulttype = BOOLOID;
9090 ge->opcollid = InvalidOid;
9091 ge->inputcollid = InvalidOid;
9092 ge->args = list_make2(oj_make_count_star(),
9093 makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
9094 Int64GetDatum(1), false, FLOAT8PASSBYVAL));
9095 ge->location = -1;
9096 D = oj_having_gated_subquery((Query *)((SubLink *)c)->subselect,
9097 (Node *)ge);
9098 } else if (IsA(c, OpExpr) && list_length(((OpExpr *)c)->args) == 2) {
9099 OpExpr *op = (OpExpr *)c;
9100 Node *l = (Node *)linitial(op->args), *r = (Node *)lsecond(op->args);
9101 SubLink *sl = NULL;
9102 Node *val = NULL;
9103 bool sublink_left = false;
9104
9105 if (IsA(l, SubLink)) {
9106 sl = (SubLink *)l;
9107 val = r;
9108 sublink_left = true;
9109 } else if (IsA(r, SubLink)) {
9110 sl = (SubLink *)r;
9111 val = l;
9112 }
9113 if (sl != NULL && sl->subLinkType == EXPR_SUBLINK &&
9114 IsA(sl->subselect, Query)) {
9115 Query *sub = (Query *)sl->subselect;
9116 if (sub->hasAggs && list_length(sub->targetList) == 1 &&
9117 IsA(((TargetEntry *)linitial(sub->targetList))->expr, Aggref) &&
9118 oj_uncorrelated_body_over_tracked(constants, sub) &&
9119 !contain_vars_of_level(val, 0)) {
9120 /* (agg) OP v -> HAVING agg OP v, the aggregate copied from the body. */
9121 Node *agg =
9122 copyObject((Node *)((TargetEntry *)linitial(sub->targetList))->expr);
9123 OpExpr *pred = (OpExpr *)copyObject((Node *)op);
9124 pred->args = sublink_left ? list_make2(agg, copyObject(val))
9125 : list_make2(copyObject(val), agg);
9126 D = oj_having_gated_subquery(sub, (Node *)pred);
9127 } else if (!sub->hasAggs && list_length(sub->targetList) == 1 &&
9128 !((TargetEntry *)linitial(sub->targetList))->resjunk &&
9129 OidIsValid(constants->OID_FUNCTION_CHOOSE) &&
9130 oj_uncorrelated_body_over_tracked(constants, sub) &&
9131 !contain_vars_of_level(val, 0)) {
9132 /* (value) OP v -> HAVING (choose(value) OP v) AND count(*) <= 1. The
9133 * scalar subquery's single value is picked by choose() and compared;
9134 * count(*) <= 1 enforces the at-most-one-row rule. Empty Q gives
9135 * choose() = NULL (the comparison is NULL, so the gated row drops --
9136 * matching SQL's NULL-valued scalar subquery); the >1-row world (a SQL
9137 * runtime error) is gated out by count(*) <= 1. */
9138 Expr *bodyval = ((TargetEntry *)linitial(sub->targetList))->expr;
9139 Aggref *ch = oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
9140 exprType((Node *)bodyval),
9141 exprType((Node *)bodyval),
9142 (Expr *)copyObject((Node *)bodyval));
9143 OpExpr *cmp = (OpExpr *)copyObject((Node *)op);
9144 Oid leo = OpernameGetOprid(list_make1(makeString("<=")), INT8OID,
9145 INT8OID);
9146 OpExpr *le1 = makeNode(OpExpr);
9147
9148 cmp->args = sublink_left ? list_make2(ch, copyObject(val))
9149 : list_make2(copyObject(val), ch);
9150 le1->opno = leo;
9151 le1->opfuncid = get_opcode(leo);
9152 le1->opresulttype = BOOLOID;
9153 le1->opcollid = InvalidOid;
9154 le1->inputcollid = InvalidOid;
9155 le1->args =
9156 list_make2(oj_make_count_star(),
9157 makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
9158 Int64GetDatum(1), false, FLOAT8PASSBYVAL));
9159 le1->location = -1;
9161 sub, (Node *)makeBoolExpr(AND_EXPR, list_make2(cmp, le1), -1));
9162 }
9163 }
9164 }
9165
9166 if (D != NULL) {
9167 RangeTblEntry *d_rte = oj_make_subquery_rte(D);
9168 RangeTblRef *rtr = makeNode(RangeTblRef);
9169 q->rtable = lappend(q->rtable, d_rte);
9170 rtr->rtindex = list_length(q->rtable);
9171 q->jointree->fromlist = lappend(q->jointree->fromlist, rtr);
9172 changed = true; /* the conjunct is now carried by D's HAVING gate */
9173 } else {
9174 newconjs = lappend(newconjs, c);
9175 }
9176 }
9177
9178 if (changed) {
9179 oj_sublink_scan scan;
9180 q->jointree->quals =
9181 (newconjs == NIL)
9182 ? NULL
9183 : (list_length(newconjs) == 1 ? (Node *)linitial(newconjs)
9184 : (Node *)makeBoolExpr(AND_EXPR, newconjs,
9185 -1));
9186 scan.n_sublinks = 0;
9187 scan.found_sublink = NULL;
9188 scan.target_varno = 0;
9189 scan.found_var = NULL;
9190 oj_sublink_scan_walker((Node *)q->targetList, &scan);
9191 if (q->jointree->quals)
9192 oj_sublink_scan_walker(q->jointree->quals, &scan);
9193 if (scan.n_sublinks == 0)
9194 q->hasSubLinks = false;
9195 }
9196 return changed;
9197}
9198
9199/** @brief Build the @c "<cnt> <op> const" OpExpr for an antijoin's HAVING, where
9200 * @p cnt is a @c count aggregate (@c count(*) or @c count(col)). */
9201static OpExpr *oj_count_const_cmp(Oid opno, Oid inputcollid, Aggref *cnt,
9202 Node *constarg) {
9203 OpExpr *op = makeNode(OpExpr);
9204 op->opno = opno;
9205 op->opfuncid = get_opcode(opno);
9206 op->opresulttype = BOOLOID;
9207 op->opcollid = InvalidOid;
9208 op->inputcollid = inputcollid;
9209 op->args = list_make2(cnt, copyObject(constarg));
9210 op->location = -1;
9211 return op;
9212}
9213
9214/** @brief Does @c 0 satisfy the @c int8 comparison @c "0 <opno> c"? Detects
9215 * @c count(*) predicates that hold on the empty group (so the HAVING-gate would
9216 * drop them and the antijoin construction is needed instead). */
9217static bool oj_zero_satisfies(Oid opno, Const *c) {
9218 return DatumGetBool(OidFunctionCall2Coll(get_opcode(opno), c->constcollid,
9219 Int64GetDatum(0), c->constvalue));
9220}
9221
9222/**
9223 * @brief Rewrite an uncorrelated WHERE predicate that is satisfied by the empty
9224 * group -- @c NOT @c EXISTS, or @c "(SELECT count(*) FROM Q) <op> const"
9225 * with @c "0 <op> const" true (e.g. @c "< k", @c "<= k", @c "= 0") -- into
9226 * the EXCEPT-ALL antijoin.
9227 *
9228 * Such a predicate is @c "NOT P" for a @c P that is FALSE on the empty group
9229 * (@c EXISTS, @c count(*) @c >= @c k…), so it is the m-semiring antijoin
9230 * @c "R ⊗ (1 ⊖ ⟦P⟧)". We materialise @c ⟦P⟧ as the one-row HAVING-gated subquery
9231 * @c D = @c "SELECT 1 FROM Q [WHERE w] HAVING count(*) <negated op> const"
9232 * (count(*) always yields a row, so @c ⟦P⟧ is correctly captured even when the
9233 * group is empty), then take the difference @c "R EXCEPT ALL π_R(R × D)" via
9234 * @c oj_build_diff -- ProvSQL's NOT-IN EXCEPT-ALL, giving each kept tuple
9235 * @c "R(r) ⊖ (R(r) ⊗ ⟦P⟧) = R(r) ⊗ (1 ⊖ ⟦P⟧)", multiplicity preserved and correct
9236 * in every semiring.
9237 *
9238 * Runs before @c rewrite_predicate_sublinks / @c move_uncorrelated_where_predicates:
9239 * those would instead push the raw predicate into a HAVING-gate, whose empty
9240 * group is @c gate_zero -- dropping exactly the world this predicate selects (a
9241 * silent under-count: @c count(*)=0 → p=0, @c count(*)<2 → @c P(=1) not @c P(≤1)).
9242 */
9243static bool rewrite_uncorrelated_antijoin(const constants_t *constants,
9244 Query *q) {
9245 Node *quals;
9246 List *conjs, *newconjs = NIL;
9247 ListCell *lc;
9248 RangeTblRef *r_ref;
9249 RangeTblEntry *R_rte;
9250 Index R_idx;
9251 Query *q_body = NULL; /* the uncorrelated Q body of the matched predicate */
9252 Node *neg_having = NULL; /* the false-on-empty count(*) predicate for D */
9253 Query *Diff;
9254 RangeTblEntry *d_rte;
9255 oj_cols Rc, Dc;
9256
9257 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
9258 !q->jointree->quals || q->setOperations)
9259 return false;
9260 if (list_length(q->jointree->fromlist) != 1 ||
9261 !IsA(linitial(q->jointree->fromlist), RangeTblRef))
9262 return false;
9263 r_ref = (RangeTblRef *)linitial(q->jointree->fromlist);
9264 R_idx = r_ref->rtindex;
9265 R_rte = list_nth_node(RangeTblEntry, q->rtable, R_idx - 1);
9266 if (R_rte->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, R_rte))
9267 return false;
9268
9269 quals = q->jointree->quals;
9270 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
9271 ? ((BoolExpr *)quals)->args
9272 : list_make1(quals);
9273 foreach (lc, conjs) {
9274 Node *c = (Node *)lfirst(lc);
9275
9276 if (q_body == NULL && IsA(c, BoolExpr) &&
9277 ((BoolExpr *)c)->boolop == NOT_EXPR &&
9278 list_length(((BoolExpr *)c)->args) == 1) {
9279 /* NOT EXISTS(Q) == NOT (count(*) >= 1). */
9280 Node *inner = (Node *)linitial(((BoolExpr *)c)->args);
9281 if (IsA(inner, SubLink) &&
9282 ((SubLink *)inner)->subLinkType == EXISTS_SUBLINK &&
9283 IsA(((SubLink *)inner)->subselect, Query) &&
9285 constants, (Query *)((SubLink *)inner)->subselect)) {
9286 Oid ge = OpernameGetOprid(list_make1(makeString(">=")), INT8OID,
9287 INT8OID);
9288 q_body = (Query *)((SubLink *)inner)->subselect;
9289 neg_having = (Node *)oj_count_const_cmp(
9290 ge, InvalidOid, oj_make_count_star(),
9291 (Node *)makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
9292 Int64GetDatum(1), false, FLOAT8PASSBYVAL));
9293 continue; /* drop this conjunct -- carried by the antijoin */
9294 }
9295 } else if (q_body == NULL && IsA(c, OpExpr) &&
9296 list_length(((OpExpr *)c)->args) == 2) {
9297 /* (SELECT count(*) FROM Q) <op> const, when 0 <op> const is true. */
9298 OpExpr *op = (OpExpr *)c;
9299 Node *l = (Node *)linitial(op->args), *r = (Node *)lsecond(op->args);
9300 /* count(*) is int8 on the left (so 0::int8 is the right empty value for
9301 * oj_zero_satisfies); the literal may be int4 or int8 (PG has cross-type
9302 * int8/int4 comparison operators, so it is not coerced). */
9303 if (IsA(l, SubLink) && IsA(r, Const) && !((Const *)r)->constisnull &&
9304 exprType(l) == INT8OID) {
9305 SubLink *sl = (SubLink *)l;
9306 Oid neg;
9307 if (sl->subLinkType == EXPR_SUBLINK && IsA(sl->subselect, Query)) {
9308 Query *s = (Query *)sl->subselect;
9309 TargetEntry *te = (list_length(s->targetList) == 1)
9310 ? (TargetEntry *)linitial(s->targetList)
9311 : NULL;
9312 Aggref *cnt = (te && IsA(te->expr, Aggref)) ? (Aggref *)te->expr
9313 : NULL;
9314 /* count(*) (F_COUNT_) or count(col) (F_COUNT_ANY): both return 0 on
9315 * the empty group, so a predicate true at 0 needs the antijoin. D's
9316 * HAVING reuses the original count aggregate (so count(col)'s NULL
9317 * semantics are preserved). */
9318 if (cnt &&
9319 (cnt->aggfnoid == F_COUNT_ || cnt->aggfnoid == F_COUNT_ANY) &&
9320 oj_uncorrelated_body_over_tracked(constants, s) &&
9321 oj_zero_satisfies(op->opno, (Const *)r) &&
9322 OidIsValid((neg = get_negator(op->opno)))) {
9323 q_body = s;
9324 neg_having = (Node *)oj_count_const_cmp(
9325 neg, op->inputcollid, (Aggref *)copyObject(cnt), r);
9326 continue;
9327 }
9328 }
9329 }
9330 }
9331 newconjs = lappend(newconjs, c);
9332 }
9333 if (q_body == NULL)
9334 return false;
9335
9336 /* D = SELECT 1 FROM <Q body> HAVING <false-on-empty count(*) predicate>. */
9337 d_rte = oj_make_subquery_rte(oj_having_gated_subquery(q_body, neg_having));
9338 oj_collect_cols(constants, R_rte, &Rc);
9339 oj_collect_cols(constants, d_rte, &Dc);
9340
9341 /* Diff = R EXCEPT ALL π_R(R × D) = R(r) ⊗ (1 ⊖ ⟦P⟧). D is a self-contained
9342 * subquery, so oj_build_diff copies it into the matched arm (no outer perms). */
9343 Diff = oj_build_diff(constants, q, R_rte, d_rte, R_idx,
9344 R_idx /* S_idx unused: theta is NULL */, &Rc, &Dc, NULL,
9345 true /* keep_left */);
9346 lfirst(list_nth_cell(q->rtable, R_idx - 1)) =
9347 (void *)oj_make_subquery_rte(Diff);
9348
9349 q->jointree->quals =
9350 (newconjs == NIL)
9351 ? NULL
9352 : (list_length(newconjs) == 1
9353 ? (Node *)linitial(newconjs)
9354 : (Node *)makeBoolExpr(AND_EXPR, newconjs, -1));
9355 {
9356 oj_sublink_scan scan;
9357 scan.n_sublinks = 0;
9358 scan.found_sublink = NULL;
9359 scan.target_varno = 0;
9360 scan.found_var = NULL;
9361 oj_sublink_scan_walker((Node *)q->targetList, &scan);
9362 if (q->jointree->quals)
9363 oj_sublink_scan_walker(q->jointree->quals, &scan);
9364 if (scan.n_sublinks == 0)
9365 q->hasSubLinks = false;
9366 }
9367 return true;
9368}
9369
9370/**
9371 * @brief Move uncorrelated scalar subqueries that are direct target-list entries
9372 * into a cross-joined derived aggregate in the outer FROM.
9373 *
9374 * An uncorrelated @c (SELECT agg/val FROM Q …) is a single constant value: it
9375 * becomes a one-row derived table @c D (see @c oj_build_uncorrelated_from_subquery)
9376 * appended to the FROM as a cross-join, and the target entry is replaced by a Var
9377 * to @c D's column. Restricted to a direct target-list entry so the (aggregate)
9378 * @c agg_token flows straight to the output column: nesting it inside arithmetic
9379 * would coerce the @c agg_token to a scalar and silently drop its provenance.
9380 * Runs before @c decorrelate_scalar_sublinks; correlated sublinks (and ones in
9381 * other positions) are left untouched for the remaining paths.
9382 */
9384 Query *q) {
9385 ListCell *lc;
9386 bool changed = false;
9387
9388 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree)
9389 return false;
9390
9391 foreach (lc, q->targetList) {
9392 TargetEntry *te = (TargetEntry *)lfirst(lc);
9393 SubLink *sl;
9394 Query *D;
9395 RangeTblEntry *d_rte;
9396 RangeTblRef *rtr;
9397 TargetEntry *dte;
9398 Index d_idx;
9399
9400 if (!IsA(te->expr, SubLink))
9401 continue;
9402 sl = (SubLink *)te->expr;
9403 if (sl->subLinkType != EXPR_SUBLINK || !IsA(sl->subselect, Query))
9404 continue;
9405 if (sublink_is_inert(sl))
9406 continue; /* an inert fetch stays an untracked scalar subquery */
9407 D = oj_build_uncorrelated_from_subquery(constants, (Query *)sl->subselect);
9408 if (D == NULL)
9409 continue;
9410
9411 d_rte = oj_make_subquery_rte(D);
9412 rtr = makeNode(RangeTblRef);
9413 dte = (TargetEntry *)linitial(D->targetList);
9414 q->rtable = lappend(q->rtable, d_rte);
9415 d_idx = list_length(q->rtable);
9416 rtr->rtindex = d_idx;
9417 q->jointree->fromlist = lappend(q->jointree->fromlist, rtr);
9418 te->expr = (Expr *)makeVar(d_idx, 1, exprType((Node *)dte->expr),
9419 exprTypmod((Node *)dte->expr),
9420 exprCollation((Node *)dte->expr), 0);
9421 changed = true;
9422 }
9423
9424 if (changed) {
9425 oj_sublink_scan scan;
9426 /* Some correlated sublinks may remain; clear hasSubLinks only if none do. */
9427 scan.n_sublinks = 0;
9428 scan.found_sublink = NULL;
9429 scan.target_varno = 0;
9430 scan.found_var = NULL;
9431 oj_sublink_scan_walker((Node *)q->targetList, &scan);
9432 if (q->jointree->quals)
9433 oj_sublink_scan_walker(q->jointree->quals, &scan);
9434 if (scan.n_sublinks == 0)
9435 q->hasSubLinks = false;
9436 }
9437 return changed;
9438}
9439
9440/** @brief Is @p limitCount the literal 1? Unwraps the @c int4->int8 coercion
9441 * PostgreSQL wraps a @c "LIMIT 1" literal in. */
9442static bool oj_limit_count_is_one(Node *limitCount) {
9443 Node *n = limitCount;
9444 Const *c;
9445 if (n == NULL)
9446 return false;
9447 if (IsA(n, FuncExpr) && list_length(((FuncExpr *)n)->args) == 1)
9448 n = (Node *)linitial(((FuncExpr *)n)->args);
9449 if (IsA(n, RelabelType))
9450 n = (Node *)((RelabelType *)n)->arg;
9451 if (!IsA(n, Const) || ((Const *)n)->constisnull)
9452 return false;
9453 c = (Const *)n;
9454 if (c->consttype == INT8OID)
9455 return DatumGetInt64(c->constvalue) == 1;
9456 if (c->consttype == INT4OID)
9457 return DatumGetInt32(c->constvalue) == 1;
9458 if (c->consttype == INT2OID)
9459 return DatumGetInt16(c->constvalue) == 1;
9460 return false;
9461}
9462
9463/**
9464 * @brief Can two scalar-subquery bodies share a single decorrelating LEFT JOIN?
9465 *
9466 * True when both are plain value bodies (no aggregate / DISTINCT / ORDER BY /
9467 * LIMIT) over the same single relation @c Q with the same correlation @c WHERE,
9468 * differing only in the one selected value. Then a single @c "R ⟕ Q ON corr"
9469 * group serves both: one @c count(Q.key) @c <= @c 1 gate, a @c choose() per
9470 * sublink. Used to decorrelate several correlated target-list sublinks that
9471 * share a @c (Q, @c corr) -- e.g. @c "(SELECT Q.x WHERE Q.k=R.k),
9472 * (SELECT Q.y WHERE Q.k=R.k)" -- in one pass.
9473 */
9474/** @brief @c equal() on two single-RTE rtables, ignoring per-RTE ACL fields.
9475 *
9476 * Before PostgreSQL 16 the permission bookkeeping (@c requiredPerms,
9477 * @c selectedCols…) lived inside @c RangeTblEntry, so two sublink bodies
9478 * over the same @c Q differing only in the selected value column would
9479 * spuriously compare unequal (their @c selectedCols differ). PG16 moved
9480 * those fields out into @c Query.rteperminfos and a plain @c equal()
9481 * suffices. */
9482static bool oj_rtables_coalescible(List *rta, List *rtb) {
9483#if PG_VERSION_NUM >= 160000
9484 return equal(rta, rtb);
9485#else
9486 RangeTblEntry *a = (RangeTblEntry *)copyObject(linitial(rta));
9487 RangeTblEntry *b = (RangeTblEntry *)copyObject(linitial(rtb));
9488 a->requiredPerms = b->requiredPerms = 0;
9489 a->checkAsUser = b->checkAsUser = InvalidOid;
9490 a->selectedCols = b->selectedCols = NULL;
9491 a->insertedCols = b->insertedCols = NULL;
9492 a->updatedCols = b->updatedCols = NULL;
9493#if PG_VERSION_NUM >= 120000
9494 a->extraUpdatedCols = b->extraUpdatedCols = NULL;
9495#endif
9496 return equal(a, b);
9497#endif
9498}
9499
9500static bool oj_sub_bodies_coalescible(Query *a, Query *b) {
9501 if (a->hasAggs || b->hasAggs || a->distinctClause || b->distinctClause ||
9502 a->sortClause || b->sortClause || a->limitCount || b->limitCount ||
9503 a->limitOffset || b->limitOffset)
9504 return false;
9505 if (list_length(a->targetList) != 1 || list_length(b->targetList) != 1 ||
9506 list_length(a->rtable) != 1 || list_length(b->rtable) != 1)
9507 return false;
9508 return oj_rtables_coalescible(a->rtable, b->rtable) &&
9509 equal(a->jointree, b->jointree);
9510}
9511
9512/**
9513 * @brief Decorrelate a single top-level scalar subquery into a LEFT JOIN.
9514 *
9515 * Restricted (v1) to: a @c CMD_SELECT whose FROM is a single tracked base
9516 * relation R, with exactly one SubLink in the whole query, that SubLink being
9517 * an @c EXPR_SUBLINK that is the direct expression of a target-list entry,
9518 * whose body is @c "SELECT val FROM Q [WHERE corr]" over a single base relation
9519 * Q referencing only Q (level 0) and R (level 1). Returns @c true if the
9520 * query was rewritten in place.
9521 */
9522static bool decorrelate_scalar_sublinks(const constants_t *constants,
9523 Query *q) {
9524 RangeTblRef *r_ref;
9525 Index R_idx, Q_idx, join_idx;
9526 RangeTblEntry *R_rte, *Q_rte_orig, *Q_copy, *jrte;
9527 Query *sub;
9528 SubLink *sl = NULL;
9529 TargetEntry *sl_te = NULL;
9530 Expr *valexpr;
9531 Node *theta;
9532 oj_cols Rc, Qc;
9533 oj_decorr_ctx dctx;
9534 oj_sublink_scan scan;
9535 ListCell *lc;
9536 int i, n_tl_sublinks = 0;
9537 bool in_where = false;
9538 bool is_agg_body = false;
9539 bool is_limit1 = false; /* ORDER BY … LIMIT 1 value body: argmax via choose */
9540 bool is_distinct = false; /* SELECT DISTINCT body: count(DISTINCT v) <= 1 gate */
9541 bool coalesce = false; /* >1 target-list sublinks sharing one (Q, corr) */
9542 List *co_sls = NIL, *co_tes = NIL; /* parallel: each sublink + its target entry */
9543 Expr *repl_expr; /* what replaces the SubLink: choose(val) or the aggregate */
9544
9545 if (!OidIsValid(constants->OID_FUNCTION_CHOOSE))
9546 return false;
9547 if (q->commandType != CMD_SELECT || !q->hasSubLinks)
9548 return false;
9549 if (q->groupClause || q->groupingSets || q->hasAggs || q->distinctClause ||
9550 q->setOperations || q->havingQual || q->hasWindowFuncs)
9551 return false;
9552 if (!q->jointree || q->jointree->fromlist == NIL)
9553 return false;
9554
9555 /* Exactly one SubLink in the whole query. It is either the direct expr of a
9556 * target-list entry (its value flows to choose()), or it sits inside a WHERE
9557 * conjunct (a comparison that will be lifted to HAVING on choose()). */
9558 scan.n_sublinks = 0;
9559 scan.found_sublink = NULL;
9560 scan.target_varno = 0;
9561 scan.found_var = NULL;
9562 oj_sublink_scan_walker((Node *)q->targetList, &scan);
9563 n_tl_sublinks = scan.n_sublinks;
9564 if (q->jointree->quals)
9565 oj_sublink_scan_walker(q->jointree->quals, &scan);
9566
9567 /* Several correlated target-list sublinks sharing one (Q, corr) coalesce onto
9568 * a single LEFT JOIN: every one a direct EXPR_SUBLINK target entry, all bodies
9569 * coalescible (same Q + correlation, differing only in the value). Then below
9570 * builds one R ⟕ Q group with a choose() per sublink and a single count gate. */
9571 if (scan.n_sublinks > 1) {
9572 Query *rep = NULL;
9573 if (n_tl_sublinks != scan.n_sublinks)
9574 return false; /* a WHERE sublink in the mix: not coalescible */
9575 foreach (lc, q->targetList) {
9576 TargetEntry *te = (TargetEntry *)lfirst(lc);
9577 SubLink *e;
9578 if (te->expr == NULL || !IsA(te->expr, SubLink))
9579 continue; /* plain column / expression: kept as a GROUP BY key below */
9580 e = (SubLink *)te->expr;
9581 if (e->subLinkType != EXPR_SUBLINK || !IsA(e->subselect, Query))
9582 return false;
9583 if (rep == NULL)
9584 rep = (Query *)e->subselect;
9585 else if (!oj_sub_bodies_coalescible(rep, (Query *)e->subselect))
9586 return false;
9587 co_sls = lappend(co_sls, e);
9588 co_tes = lappend(co_tes, te);
9589 }
9590 /* All sublinks must be direct target entries (none nested in an expression). */
9591 if (list_length(co_sls) != scan.n_sublinks)
9592 return false;
9593 coalesce = true;
9594 sl = (SubLink *)linitial(co_sls);
9595 sl_te = (TargetEntry *)linitial(co_tes);
9596 } else {
9597 if (scan.n_sublinks != 1)
9598 return false;
9599 sl = scan.found_sublink;
9600 if (sl == NULL || sl->subLinkType != EXPR_SUBLINK ||
9601 !IsA(sl->subselect, Query))
9602 return false;
9603
9604 /* Is it a direct target-list entry? Otherwise it must be in WHERE; a
9605 * SubLink nested inside a target-list expression (arithmetic, comparison)
9606 * is not supported. */
9607 foreach (lc, q->targetList) {
9608 TargetEntry *te = (TargetEntry *)lfirst(lc);
9609 if (te->expr == (Expr *)sl) {
9610 sl_te = te;
9611 break;
9612 }
9613 }
9614 if (sl_te == NULL) {
9615 if (n_tl_sublinks > 0)
9616 return false; /* nested in a target-list expression */
9617 in_where = true;
9618 /* A WHERE SubLink must be a direct operand of a comparison (its value is
9619 * lifted to a HAVING cmp gate on choose()). If it is nested inside
9620 * arithmetic -- (SELECT …) + 1 > k -- the comparison cannot be lifted; bail
9621 * so the caller passes the sublink through with a warning instead. */
9622 {
9623 List *direct = NIL;
9624 collect_direct_qual_sublinks(q->jointree->quals, &direct);
9625 if (!list_member_ptr(direct, sl))
9626 return false;
9627 }
9628 }
9629 } /* end single-sublink branch */
9630
9631 /* The body must be SELECT val FROM Q [WHERE corr], Q a single base rel, where
9632 * val is either a plain value (decorrelated with choose() + count(...)<=1) or
9633 * a single bare aggregate (decorrelated to that aggregate over the LEFT-JOIN
9634 * group, no count gate). LIMIT / OFFSET would pick a bounded, order-dependent
9635 * subset and a CTE would be dropped; reject those. */
9636 sub = (Query *)sl->subselect;
9637 if (sub->commandType != CMD_SELECT || sub->groupClause ||
9638 sub->groupingSets || sub->setOperations || sub->hasWindowFuncs ||
9639 sub->hasSubLinks || sub->limitOffset || sub->cteList)
9640 return false;
9641 /* SELECT DISTINCT v: the at-most-one-row rule counts distinct VALUES, so the
9642 * gate becomes count(DISTINCT v) <= 1 (admitting many rows of one value). Not
9643 * combined with an aggregate body or LIMIT. */
9644 if (sub->distinctClause != NIL) {
9645 if (sub->hasDistinctOn || sub->hasAggs || sub->limitCount)
9646 return false;
9647 is_distinct = true;
9648 }
9649 /* LIMIT: a bare LIMIT picks an arbitrary row (rejected), but an ORDER BY …
9650 * LIMIT 1 value body is the argmax -- decorrelated to choose(val ORDER BY key)
9651 * with no count gate (LIMIT 1 never errors on >1 rows). */
9652 if (sub->limitCount) {
9653 if (!sub->sortClause || sub->hasAggs ||
9654 !oj_limit_count_is_one(sub->limitCount))
9655 return false;
9656 is_limit1 = true;
9657 }
9658 /* Exactly one non-junk output column (the scalar value); ORDER BY adds junk
9659 * sort-key entries, which become the choose aggregate's order arguments. */
9660 {
9661 int nreal = 0;
9662 ListCell *tlc;
9663 foreach (tlc, sub->targetList)
9664 if (!((TargetEntry *)lfirst(tlc))->resjunk)
9665 ++nreal;
9666 if (nreal != 1 || ((TargetEntry *)linitial(sub->targetList))->resjunk)
9667 return false;
9668 }
9669 if (sub->hasAggs &&
9670 !IsA(((TargetEntry *)linitial(sub->targetList))->expr, Aggref))
9671 return false; /* aggregate body must be a single bare aggregate */
9672 is_agg_body = sub->hasAggs;
9673 if (!sub->jointree || sub->jointree->fromlist == NIL)
9674 return false;
9675 /* A multi-table body (Q1, Q2, … in FROM) is collapsed into a single derived
9676 * cross-product subquery D, after which the body is "SELECT val FROM D WHERE
9677 * W" -- the single-Q path below handles D exactly as it handles a subquery R. */
9678 if (list_length(sub->rtable) != 1 && !oj_wrap_body_from(constants, sub))
9679 return false;
9680 if (list_length(sub->jointree->fromlist) != 1 ||
9681 !IsA(linitial(sub->jointree->fromlist), RangeTblRef))
9682 return false;
9683 Q_rte_orig = list_nth_node(RangeTblEntry, sub->rtable, 0);
9684 if ((Q_rte_orig->rtekind != RTE_RELATION &&
9685 !(Q_rte_orig->rtekind == RTE_SUBQUERY && !Q_rte_orig->lateral)) ||
9686 !oj_rte_has_provsql(constants, Q_rte_orig))
9687 return false;
9688
9689 /* Determine R. If the outer FROM is already a single tracked relation or
9690 * (non-lateral) subquery, use it directly; otherwise wrap the whole FROM into
9691 * a derived subquery R' (the subquery-arm lowering then handles R' LEFT JOIN
9692 * Q either way). */
9693 R_rte = NULL;
9694 if (list_length(q->jointree->fromlist) == 1 &&
9695 IsA(linitial(q->jointree->fromlist), RangeTblRef)) {
9696 r_ref = (RangeTblRef *)linitial(q->jointree->fromlist);
9697 R_rte = list_nth_node(RangeTblEntry, q->rtable, r_ref->rtindex - 1);
9698 if ((R_rte->rtekind == RTE_RELATION ||
9699 (R_rte->rtekind == RTE_SUBQUERY && !R_rte->lateral)) &&
9700 oj_rte_has_provsql(constants, R_rte))
9701 R_idx = r_ref->rtindex;
9702 else
9703 R_rte = NULL;
9704 }
9705 if (R_rte == NULL) {
9706 /* The coalesce path re-finds many sublink target entries; the outer-FROM
9707 * wrap only re-finds one. Restrict coalesce to a single base R (the common
9708 * case); a wrap-needing R with several sublinks stays for a later pass. */
9709 if (coalesce)
9710 return false;
9711 if (!oj_wrap_outer_from(constants, q, sl, in_where))
9712 return false;
9713 R_idx = 1;
9714 R_rte = list_nth_node(RangeTblEntry, q->rtable, 0);
9715 sub = (Query *)sl->subselect; /* remapped in place by the wrap */
9716 /* The wrap rebuilt the target list (the SubLink node itself is preserved),
9717 * so re-find the target entry that still carries the SubLink. */
9718 sl_te = NULL;
9719 foreach (lc, q->targetList) {
9720 TargetEntry *te = (TargetEntry *)lfirst(lc);
9721 if (te->expr == (Expr *)sl) {
9722 sl_te = te;
9723 break;
9724 }
9725 }
9726 }
9727
9728 /* The correlation normally references some Q column (so count() has a key
9729 * that is NULL on the null-padded antijoin rows). A bare body with no
9730 * Q-referencing WHERE at all is fine when it is a non-star aggregate --
9731 * e.g. "(SELECT max(x) FROM Q) > R.col", whose comparison lifts to HAVING
9732 * over the R ⟕ Q ON TRUE group below: such a body needs no key (no
9733 * at-most-one-row gate, no count(*) -> count(Q.key) rewrite, and the
9734 * aggregate ignores the null-padded row by itself). Value bodies (count
9735 * gates) and count(*) (key rewrite) do need a genuine Q column: decline. */
9736 scan.n_sublinks = 0;
9737 scan.target_varno = 1; /* Q is at index 1 inside the body */
9738 scan.found_var = NULL;
9739 if (sub->jointree->quals)
9740 oj_sublink_scan_walker(sub->jointree->quals, &scan);
9741 if (scan.found_var == NULL &&
9742 (!is_agg_body ||
9743 ((Aggref *)((TargetEntry *)linitial(sub->targetList))->expr)->aggstar))
9744 return false;
9745
9746 /* ---- Commit: pull Q up, build the LEFT JOIN, choose() + GROUP BY + count.
9747 * R stays at R_idx, Q is appended (Q_idx), join RTE appended (join_idx). ---*/
9748 oj_collect_cols(constants, R_rte, &Rc);
9749 oj_collect_cols(constants, Q_rte_orig, &Qc);
9750
9751 Q_copy = copyObject(Q_rte_orig);
9752#if PG_VERSION_NUM >= 160000
9753 if (Q_rte_orig->perminfoindex != 0) {
9754 RTEPermissionInfo *pi =
9755 getRTEPermissionInfo(sub->rteperminfos, Q_rte_orig);
9756 q->rteperminfos = lappend(q->rteperminfos, copyObject(pi));
9757 Q_copy->perminfoindex = list_length(q->rteperminfos);
9758 }
9759#endif
9760 q->rtable = lappend(q->rtable, Q_copy);
9761 Q_idx = list_length(q->rtable);
9762
9763 /* Move the body's Vars to the outer level: Q(level0,1) -> (level0, Q_idx);
9764 * correlated R(level1) -> (level0). */
9765 dctx.q_old = 1;
9766 dctx.q_new = Q_idx;
9767 theta = oj_decorr_var_mut(copyObject(sub->jointree->quals), &dctx);
9768 valexpr = (Expr *)oj_decorr_var_mut(
9769 copyObject((Node *)((TargetEntry *)linitial(sub->targetList))->expr),
9770 &dctx);
9771
9772 /* Build the synthetic join RTE (eref / joinaliasvars / left/right cols), the
9773 * same bookkeeping the deparser needs as in oj_build_join_query. */
9774 {
9775 List *av = NIL, *lcols = NIL, *rcols = NIL, *cn = NIL;
9776 jrte = makeNode(RangeTblEntry);
9777 for (i = 0; i < Rc.n; ++i) {
9778 av = lappend(av, makeVar(R_idx, Rc.attno[i], Rc.type[i], Rc.typmod[i],
9779 Rc.coll[i], 0));
9780 lcols = lappend_int(lcols, Rc.attno[i]);
9781 rcols = lappend_int(rcols, 0);
9782 cn = lappend(cn, makeString(pstrdup(Rc.name[i])));
9783 }
9784 for (i = 0; i < Qc.n; ++i) {
9785 av = lappend(av, makeVar(Q_idx, Qc.attno[i], Qc.type[i], Qc.typmod[i],
9786 Qc.coll[i], 0));
9787 lcols = lappend_int(lcols, 0);
9788 rcols = lappend_int(rcols, Qc.attno[i]);
9789 cn = lappend(cn, makeString(pstrdup(Qc.name[i])));
9790 }
9791 jrte->rtekind = RTE_JOIN;
9792 jrte->jointype = JOIN_LEFT;
9793 jrte->alias = NULL;
9794 jrte->eref = makeAlias("unnamed_join", cn);
9795 jrte->joinaliasvars = av;
9796#if PG_VERSION_NUM >= 130000
9797 jrte->joinleftcols = lcols;
9798 jrte->joinrightcols = rcols;
9799 jrte->joinmergedcols = 0;
9800#endif
9801 jrte->inFromCl = true;
9802 q->rtable = lappend(q->rtable, jrte);
9803 join_idx = list_length(q->rtable);
9804 }
9805
9806 {
9807 JoinExpr *je = makeNode(JoinExpr);
9808 RangeTblRef *lr = makeNode(RangeTblRef), *rr = makeNode(RangeTblRef);
9809 lr->rtindex = R_idx;
9810 rr->rtindex = Q_idx;
9811 je->jointype = JOIN_LEFT;
9812 je->larg = (Node *)lr;
9813 je->rarg = (Node *)rr;
9814 je->quals = theta;
9815 je->isNatural = false;
9816 je->usingClause = NIL;
9817 je->rtindex = join_idx;
9818 q->jointree->fromlist = list_make1(je);
9819 /* Any pre-existing outer WHERE (over R, level 0) stays in jointree->quals.*/
9820 }
9821
9822 /* What replaces the SubLink, over the LEFT-JOIN group:
9823 * - value body -> choose(val) picks the single matched value;
9824 * - aggregate body -> the aggregate itself. count(*) is rewritten to
9825 * count(Q.key) so the null-padded antijoin row (Q.key IS NULL) is not
9826 * counted -- an empty correlated group must give 0, not 1.
9827 * For a target-list SubLink it replaces the entry directly; for a WHERE
9828 * SubLink it is substituted into the conjunct, which moves to HAVING. */
9829 if (is_agg_body) {
9830 Aggref *agg = (Aggref *)valexpr; /* the remapped body aggregate */
9831 if (agg->aggstar) {
9832 Var *qkey = (Var *)copyObject(scan.found_var);
9833 qkey->varno = Q_idx;
9834#if PG_VERSION_NUM >= 130000
9835 qkey->varnosyn = 0;
9836 qkey->varattnosyn = 0;
9837#endif
9838 repl_expr = (Expr *)oj_make_aggref(F_COUNT_ANY, INT8OID, qkey->vartype,
9839 (Expr *)qkey);
9840 } else {
9841 repl_expr = valexpr;
9842 }
9843 } else if (is_limit1) {
9844 /* ORDER BY … LIMIT 1 = argmax: choose(val ORDER BY key). The subselect's
9845 * targetList (the value plus junk sort-key entries) and its sortClause map
9846 * directly onto the ordered Aggref's args / aggorder; remap each arg's Q
9847 * vars to the pulled-up Q. No count gate -- LIMIT 1 always takes one row. */
9848 Aggref *agg = makeNode(Aggref);
9849 List *new_args = NIL;
9850 ListCell *alc;
9851 foreach (alc, sub->targetList) {
9852 TargetEntry *te = (TargetEntry *)copyObject(lfirst(alc));
9853 te->expr = (Expr *)oj_decorr_var_mut((Node *)te->expr, &dctx);
9854 new_args = lappend(new_args, te);
9855 }
9856 agg->aggfnoid = constants->OID_FUNCTION_CHOOSE;
9857 agg->aggtype = exprType((Node *)valexpr);
9858 agg->aggtranstype = InvalidOid;
9859 agg->aggargtypes = list_make1_oid(exprType((Node *)valexpr));
9860 agg->args = new_args;
9861 agg->aggorder = (List *)copyObject((Node *)sub->sortClause);
9862 agg->aggkind = AGGKIND_NORMAL;
9863 agg->aggsplit = AGGSPLIT_SIMPLE;
9864 agg->location = -1;
9865#if PG_VERSION_NUM >= 140000
9866 agg->aggno = agg->aggtransno = -1;
9867#endif
9868 repl_expr = (Expr *)agg;
9869 } else {
9870 repl_expr = (Expr *)oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
9871 exprType((Node *)valexpr),
9872 exprType((Node *)valexpr), valexpr);
9873 }
9874 if (coalesce) {
9875 /* Each coalesced sublink gets its own choose() over the shared group: remap
9876 * its body's value to the pulled-up Q and replace its target entry. (The
9877 * representative's repl_expr above is recomputed here as the first item, so
9878 * the bodies all use the identical Q/correlation remap.) */
9879 ListCell *la, *lb;
9880 forboth(la, co_sls, lb, co_tes) {
9881 SubLink *sli = (SubLink *)lfirst(la);
9882 TargetEntry *tei = (TargetEntry *)lfirst(lb);
9883 Expr *vi = (Expr *)oj_decorr_var_mut(
9884 copyObject(
9885 (Node *)((TargetEntry *)linitial(((Query *)sli->subselect)->targetList))
9886 ->expr),
9887 &dctx);
9888 tei->expr = (Expr *)oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
9889 exprType((Node *)vi),
9890 exprType((Node *)vi), vi);
9891 }
9892 } else if (!in_where)
9893 sl_te->expr = repl_expr;
9894
9895 /* GROUP BY every R user column: each needs a target-list entry carrying a
9896 * ressortgroupref plus a SortGroupClause. */
9897 {
9898 int sgref = 0;
9899 /* Highest existing ressortgroupref, so new ones do not collide. */
9900 foreach (lc, q->targetList) {
9901 TargetEntry *te = (TargetEntry *)lfirst(lc);
9902 if (te->ressortgroupref > sgref)
9903 sgref = te->ressortgroupref;
9904 }
9905 for (i = 0; i < Rc.n; ++i) {
9906 TargetEntry *gte = NULL;
9907 SortGroupClause *sgc;
9908 ListCell *lc2;
9909
9910 /* Reuse an existing target entry that already projects this R column. */
9911 foreach (lc2, q->targetList) {
9912 TargetEntry *te = (TargetEntry *)lfirst(lc2);
9913 if (IsA(te->expr, Var)) {
9914 Var *v = (Var *)te->expr;
9915 if (v->varlevelsup == 0 && v->varno == R_idx &&
9916 v->varattno == Rc.attno[i]) {
9917 gte = te;
9918 break;
9919 }
9920 }
9921 }
9922 if (gte == NULL) {
9923 Var *v = makeVar(R_idx, Rc.attno[i], Rc.type[i], Rc.typmod[i],
9924 Rc.coll[i], 0);
9925 gte = makeTargetEntry((Expr *)v, list_length(q->targetList) + 1,
9926 pstrdup(Rc.name[i]), true /* resjunk */);
9927 q->targetList = lappend(q->targetList, gte);
9928 }
9929 if (gte->ressortgroupref == 0)
9930 gte->ressortgroupref = ++sgref;
9931 sgc = makeNode(SortGroupClause);
9932 sgc->tleSortGroupRef = gte->ressortgroupref;
9933 get_sort_group_operators(Rc.type[i], false, true, false, &sgc->sortop,
9934 &sgc->eqop, NULL, &sgc->hashable);
9935 q->groupClause = lappend(q->groupClause, sgc);
9936 }
9937 }
9938
9939 /* HAVING. A value body adds count(Q.key) <= 1 (Q.key NULL on the null-padded
9940 * antijoin rows), enforcing the scalar subquery's at-most-one-row rule; an
9941 * aggregate body -- and an ORDER BY … LIMIT 1 (argmax) body, which legally
9942 * takes the top of many rows -- needs no such gate. When the SubLink came from
9943 * a WHERE comparison, that conjunct (with the SubLink replaced) is ANDed in --
9944 * a comparison on the aggregated value belongs in HAVING. */
9945 {
9946 List *having_conjuncts = NIL;
9947
9948 if (!is_agg_body && !is_limit1) {
9949 /* SELECT DISTINCT v counts distinct VALUES (count(DISTINCT v) <= 1); a
9950 * plain value body counts matching rows (count(Q.key) <= 1). */
9951 having_conjuncts = list_make1(
9952 is_distinct ? oj_count_distinct_cmp(valexpr, "<=", 1)
9953 : oj_count_cmp((Var *)scan.found_var, Q_idx, "<=", 1));
9954 /* A WHERE comparison must test an actual subquery value, so the correlated
9955 * group has to be non-empty: count(…) = 1, not merely <= 1. An empty
9956 * group would give a NULL comparison (the row is excluded), but the value
9957 * gate over the all-NULL aggregate does not encode that, so the >= 1 gate
9958 * supplies it. (A target-list subquery keeps <= 1 only: zero matches is a
9959 * legal NULL value, and the row still exists.) */
9960 if (in_where)
9961 having_conjuncts = lappend(
9962 having_conjuncts,
9963 is_distinct ? oj_count_distinct_cmp(valexpr, ">=", 1)
9964 : oj_count_cmp((Var *)scan.found_var, Q_idx, ">=", 1));
9965 }
9966
9967 if (in_where) {
9968 /* Split the WHERE AND-list: the conjunct holding the SubLink (with the
9969 * SubLink -> repl_expr substitution) moves to HAVING; the rest stay. */
9971 Node *quals = q->jointree->quals;
9972 List *conjs =
9973 (quals && IsA(quals, BoolExpr) &&
9974 ((BoolExpr *)quals)->boolop == AND_EXPR)
9975 ? ((BoolExpr *)quals)->args
9976 : (quals ? list_make1(quals) : NIL);
9977 List *kept = NIL;
9978 ListCell *lc2;
9979
9980 rc.target = sl;
9981 rc.replacement = (Node *)repl_expr;
9982 foreach (lc2, conjs) {
9983 Node *c = (Node *)lfirst(lc2);
9984 if (oj_contains_sublink_walker(c, sl))
9985 having_conjuncts =
9986 lappend(having_conjuncts, oj_sl_replace_mut(c, &rc));
9987 else
9988 kept = lappend(kept, c);
9989 }
9990 q->jointree->quals =
9991 (kept == NIL) ? NULL
9992 : (list_length(kept) == 1 ? (Node *)linitial(kept)
9993 : (Node *)makeBoolExpr(
9994 AND_EXPR, kept, -1));
9995 }
9996
9997 q->havingQual =
9998 (having_conjuncts == NIL)
9999 ? NULL
10000 : (list_length(having_conjuncts) == 1
10001 ? (Node *)linitial(having_conjuncts)
10002 : (Node *)makeBoolExpr(AND_EXPR, having_conjuncts, -1));
10003 }
10004
10005 q->hasAggs = true;
10006 q->hasSubLinks = false;
10007 return true;
10008}
10009
10010/**
10011 * @brief Group the right-hand arm of a set difference by all its columns so
10012 * the per-tuple right provenances ⊕-combine before the monus.
10013 *
10014 * ProvSQL's multiset difference implements the NOT-IN semantics of the ICDE
10015 * 2026 paper (§IV-B):
10016 * @code
10017 * ⟪q₁ − q₂⟫ = {{ (u, α ⊖ ⊕_{β : (u,β)∈q₂} β) | (u,α) ∈ q₁ }}
10018 * @endcode
10019 * The sum @c ⊕β ranges over ALL right tuples equal to @c u, so the right arm
10020 * must be grouped by its columns first. Without that,
10021 * @c transform_except_into_join's bare @c LEFT @c JOIN emits one monus per
10022 * matching right tuple (yielding @c ⊕(α⊖βᵢ) instead of @c α⊖⊕β) and inflates
10023 * the result multiplicity -- the long-standing "add group by in the right-side
10024 * table" gap. Wrapping the still-raw right arm in
10025 * @code
10026 * SELECT cols FROM (rarg) GROUP BY cols
10027 * @endcode
10028 * makes the later @c get_provenance_attributes / group-by pass build @c ⊕β per
10029 * group and gives the right arm exactly one row per distinct @c u.
10030 *
10031 * Runs before provenance discovery, on the @c SETOP_EXCEPT query (for the
10032 * non-ALL case, on the @c all=true inner set operation that
10033 * @c rewrite_non_all_into_external_group_by leaves behind). It applies equally
10034 * to @c EXCEPT (@c ε(q₁−q₂)) and @c EXCEPT @c ALL (@c q₁−q₂): the only
10035 * difference between them, duplicate elimination of the left arm, is handled
10036 * separately by the non-ALL outer GROUP BY.
10037 */
10038static void group_set_difference_right_arm(const constants_t *constants,
10039 Query *q) {
10040 SetOperationStmt *so;
10041 RangeTblRef *rarg_ref;
10042 RangeTblEntry *rarg_rte;
10043 Query *origB, *G;
10044 RangeTblEntry *w_rte;
10045 RangeTblRef *rtr;
10046 FromExpr *fe;
10047 List *tl = NIL;
10048 ListCell *lc;
10049 int colno = 0, sgref = 0;
10050 bool any_group = false;
10051
10052 (void)constants;
10053
10054 if (q->setOperations == NULL || !IsA(q->setOperations, SetOperationStmt))
10055 return;
10056 so = (SetOperationStmt *)q->setOperations;
10057 if (so->op != SETOP_EXCEPT)
10058 return;
10059 /* Chained difference (rarg is itself a SetOperationStmt) is rejected later
10060 * by transform_except_into_join; leave it untouched here. */
10061 if (!IsA(so->rarg, RangeTblRef))
10062 return;
10063 rarg_ref = (RangeTblRef *)so->rarg;
10064 rarg_rte = list_nth_node(RangeTblEntry, q->rtable, rarg_ref->rtindex - 1);
10065 if (rarg_rte->rtekind != RTE_SUBQUERY || rarg_rte->subquery == NULL)
10066 return;
10067 origB = rarg_rte->subquery;
10068
10069 /* Already a single-row-per-group shape? (Our own outer-join antijoin builds
10070 * the right arm pre-grouped.) Re-grouping is harmless but pointless, so skip
10071 * when the arm already carries a groupClause. */
10072 if (origB->groupClause != NIL || origB->groupingSets != NIL)
10073 return;
10074
10075 G = makeNode(Query);
10076 G->commandType = CMD_SELECT;
10077 G->canSetTag = true;
10078 w_rte = oj_make_subquery_rte(origB);
10079 G->rtable = list_make1(w_rte);
10080 rtr = makeNode(RangeTblRef);
10081 rtr->rtindex = 1;
10082 fe = makeNode(FromExpr);
10083 fe->fromlist = list_make1(rtr);
10084 G->jointree = fe;
10085
10086 colno = 0;
10087 foreach (lc, origB->targetList) {
10088 TargetEntry *te = (TargetEntry *)lfirst(lc);
10089 Var *v;
10090 TargetEntry *nte;
10091 SortGroupClause *sgc;
10092 Oid coltype;
10093
10094 ++colno;
10095 if (te->resjunk)
10096 continue;
10097
10098 coltype = exprType((Node *)te->expr);
10099 v = makeVar(1, colno, coltype, exprTypmod((Node *)te->expr),
10100 exprCollation((Node *)te->expr), 0);
10101 nte = makeTargetEntry((Expr *)v, list_length(tl) + 1,
10102 te->resname ? pstrdup(te->resname) : NULL, false);
10103
10104 /* Group by every column (a UUID provsql column would already have been
10105 * rejected upstream; raw arms expose only value columns here). */
10106 sgc = makeNode(SortGroupClause);
10107 sgc->tleSortGroupRef = nte->ressortgroupref = ++sgref;
10108 get_sort_group_operators(coltype, false, true, false, &sgc->sortop,
10109 &sgc->eqop, NULL, &sgc->hashable);
10110 G->groupClause = lappend(G->groupClause, sgc);
10111 any_group = true;
10112
10113 tl = lappend(tl, nte);
10114 }
10115 G->targetList = tl;
10116
10117 if (!any_group)
10118 return; /* nothing to group on; leave the arm unchanged */
10119
10120 rarg_rte->subquery = G;
10121}
10122
10123/**
10124 * @brief Recursively annotate a UNION tree with the provenance UUID type.
10125 *
10126 * Walks the @c SetOperationStmt tree of a UNION and appends the UUID type
10127 * to @c colTypes / @c colTypmods / @c colCollations on every node, and sets
10128 * @c all = true so that PostgreSQL does not deduplicate the combined stream.
10129 * The non-ALL deduplication has already been moved to an outer GROUP BY by
10130 * @c rewrite_non_all_into_external_group_by before this is called.
10131 *
10132 * @param constants Extension OID cache.
10133 * @param stmt Root (or subtree) of the UNION @c SetOperationStmt.
10134 * @param q Outer query (to look up subquery RTEs for agg_token type updates).
10135 */
10136static void process_set_operation_union(const constants_t *constants,
10137 SetOperationStmt *stmt,
10138 Query *q) {
10139 if (stmt->op != SETOP_UNION) {
10140 provsql_error("Unsupported mixed set operations");
10141 }
10142 if (IsA(stmt->larg, SetOperationStmt)) {
10143 process_set_operation_union(constants, (SetOperationStmt *)(stmt->larg), q);
10144 }
10145 if (IsA(stmt->rarg, SetOperationStmt)) {
10146 process_set_operation_union(constants, (SetOperationStmt *)(stmt->rarg), q);
10147 }
10148
10149 /* Update colTypes for columns that became agg_token after rewriting.
10150 * Use the left branch's subquery to detect agg_token columns. */
10151 if (IsA(stmt->larg, RangeTblRef)) {
10152 Index rtindex = ((RangeTblRef *)stmt->larg)->rtindex;
10153 RangeTblEntry *rte = list_nth_node(RangeTblEntry, q->rtable, rtindex - 1);
10154 if (rte->rtekind == RTE_SUBQUERY && rte->subquery != NULL) {
10155 ListCell *lc_type = list_head(stmt->colTypes);
10156 ListCell *lc_te = list_head(rte->subquery->targetList);
10157 while (lc_type != NULL && lc_te != NULL) {
10158 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
10159 if (exprType((Node *)te->expr) == constants->OID_TYPE_AGG_TOKEN) {
10160 lfirst_oid(lc_type) = constants->OID_TYPE_AGG_TOKEN;
10161 }
10162 lc_type = my_lnext(stmt->colTypes, lc_type);
10163 lc_te = my_lnext(rte->subquery->targetList, lc_te);
10164 }
10165 }
10166 }
10167
10168 stmt->colTypes = lappend_oid(stmt->colTypes, constants->OID_TYPE_UUID);
10169 stmt->colTypmods = lappend_int(stmt->colTypmods, -1);
10170 stmt->colCollations = lappend_int(stmt->colCollations, 0);
10171 stmt->all = true;
10172}
10173
10174/**
10175 * @brief Add a WHERE condition filtering out zero-provenance tuples.
10176 *
10177 * For EXCEPT queries, tuples whose provenance evaluates to zero (i.e., the
10178 * right-hand side fully subsumes the left-hand side) must be excluded from
10179 * the result. This function appends @c provsql <> gate_zero() to
10180 * @p q->jointree->quals, ANDing with any existing WHERE condition.
10181 *
10182 * @param constants Extension OID cache.
10183 * @param q Query to modify in place.
10184 * @param provsql Provenance expression that was added to the SELECT list.
10185 */
10186static void add_select_non_zero(const constants_t *constants, Query *q,
10187 Expr *provsql) {
10188 FuncExpr *gate_zero = makeNode(FuncExpr);
10189 OpExpr *oe = makeNode(OpExpr);
10190
10191 gate_zero->funcid = constants->OID_FUNCTION_GATE_ZERO;
10192 gate_zero->funcresulttype = constants->OID_TYPE_UUID;
10193
10194 oe->opno = constants->OID_OPERATOR_NOT_EQUAL_UUID;
10195 oe->opfuncid = constants->OID_FUNCTION_NOT_EQUAL_UUID;
10196 oe->opresulttype = BOOLOID;
10197 oe->args = list_make2(provsql, gate_zero);
10198 oe->location = -1;
10199
10200 if (q->jointree->quals != NULL) {
10201 BoolExpr *be = makeNode(BoolExpr);
10202
10203 be->boolop = AND_EXPR;
10204 be->args = list_make2(oe, q->jointree->quals);
10205 be->location = -1;
10206
10207 q->jointree->quals = (Node *)be;
10208 } else
10209 q->jointree->quals = (Node *)oe;
10210}
10211
10212/**
10213 * @brief Append @p expr to @p havingQual with an AND, creating one if needed.
10214 *
10215 * If @p havingQual is NULL, returns @p expr directly. If it is already an
10216 * AND @c BoolExpr, appends to its argument list. Otherwise wraps both in a
10217 * new AND node.
10218 *
10219 * @param havingQual Existing HAVING qualifier, or NULL.
10220 * @param expr Expression to conjoin.
10221 * @return The updated HAVING qualifier.
10222 */
10223static Node *add_to_havingQual(Node *havingQual, Expr *expr)
10224{
10225 if(!havingQual) {
10226 havingQual = (Node*) expr;
10227 } else if(IsA(havingQual, BoolExpr) && ((BoolExpr*)havingQual)->boolop==AND_EXPR) {
10228 BoolExpr *be = (BoolExpr*)havingQual;
10229 be->args = lappend(be->args, expr);
10230 } else if(IsA(havingQual, OpExpr) || IsA(havingQual, BoolExpr)) {
10231 /* BoolExpr that is not an AND (OR/NOT): wrap with a new AND node. */
10232 BoolExpr *be = makeNode(BoolExpr);
10233 be->boolop=AND_EXPR;
10234 be->location=-1;
10235 be->args = list_make2(havingQual, expr);
10236 havingQual = (Node*) be;
10237 } else
10238 provsql_error("Unknown structure within Boolean expression");
10239
10240 return havingQual;
10241}
10242
10243/**
10244 * @brief Check whether @p op is a supported comparison on an aggregate result.
10245 *
10246 * Returns true iff @p op is a two-argument operator where at least one
10247 * argument is a @c Var of type @c agg_token (or an implicit-cast wrapper
10248 * thereof) and the other is a @c Const (possibly cast). This is the set
10249 * of WHERE-on-aggregate patterns that ProvSQL can safely move to a HAVING
10250 * clause.
10251 *
10252 * @param op The @c OpExpr to inspect.
10253 * @param constants Extension OID cache.
10254 * @return True if the pattern is supported, false otherwise.
10255 */
10256static bool check_selection_on_aggregate(OpExpr *op, const constants_t *constants)
10257{
10258 int agg_sides = 0;
10259
10260 if(op->args->length != 2)
10261 return false;
10262
10263 for(unsigned i=0; i<2; ++i) {
10264 Node *arg = lfirst(list_nth_cell(op->args, i));
10265 if(expr_contains_agg(arg, constants))
10266 agg_sides++;
10267 /* The other side may be any aggregate-free expression: the threshold. */
10268 }
10269
10270 /* At least one aggregate side. Constant arithmetic over a single aggregate
10271 * is folded into the threshold (normalize_agg_comparison); anything else
10272 * (agg-vs-agg, products of aggregates, c/agg, ...) is resolved by the
10273 * possible-worlds enumeration in having_semantics, which leaves the gate
10274 * unresolved -- a clean error -- if it cannot handle the shape. */
10275 return agg_sides >= 1;
10276}
10277
10278/**
10279 * @brief Check whether every leaf of a Boolean expression is a supported
10280 * comparison on an aggregate result.
10281 *
10282 * Recursively validates @c OpExpr leaves via @c check_selection_on_aggregate
10283 * and descends into nested @c BoolExpr nodes.
10284 *
10285 * @param be The Boolean expression to validate.
10286 * @param constants Extension OID cache.
10287 * @return True if all leaves are supported, false if any is not.
10288 */
10289static bool check_boolexpr_on_aggregate(BoolExpr *be, const constants_t *constants)
10290{
10291 ListCell *lc;
10292
10293 foreach (lc, be->args) {
10294 Node *n=lfirst(lc);
10295 /* An agg-free child is an ordinary (regular) condition mixed into the
10296 * HAVING predicate -- supported as a deterministic indicator (the χ
10297 * case). Only children that actually involve an aggregate must match a
10298 * supported aggregate-comparison shape. */
10299 if(!expr_contains_agg(n, constants))
10300 continue;
10301 if(IsA(n, OpExpr)) {
10302 if(!check_selection_on_aggregate((OpExpr*) n, constants))
10303 return false;
10304 } else if(IsA(n, BoolExpr)) {
10305 if(!check_boolexpr_on_aggregate((BoolExpr*) n, constants))
10306 return false;
10307 } else
10308 return false;
10309 }
10310
10311 return true;
10312}
10313
10314/**
10315 * @brief Top-level dispatcher for supported WHERE-on-aggregate patterns.
10316 *
10317 * @param expr Expression to validate (@c OpExpr or @c BoolExpr).
10318 * @param constants Extension OID cache.
10319 * @return True if ProvSQL can handle this expression.
10320 */
10321static bool check_expr_on_aggregate(Expr *expr, const constants_t *constants) {
10322 switch(expr->type) {
10323 case T_BoolExpr:
10324 return check_boolexpr_on_aggregate((BoolExpr*) expr, constants);
10325 case T_OpExpr:
10326 return check_selection_on_aggregate((OpExpr*) expr, constants);
10327 default:
10328 provsql_error("Unknown structure within Boolean expression");
10329 }
10330}
10331
10332/* -------------------------------------------------------------------------
10333 * Main query transformation
10334 * ------------------------------------------------------------------------- */
10335
10336/**
10337 * @brief Build the per-RTE column-numbering map used by where-provenance.
10338 *
10339 * Assigns a sequential position (1, 2, 3, …) to every non-provenance,
10340 * non-join, non-empty column across all RTEs in @p q->rtable. The
10341 * @c provsql column is assigned -1 so callers can detect provenance-tracked
10342 * RTEs. Join-RTE columns and empty-named columns (used for anonymous GROUP
10343 * BY keys) are assigned 0.
10344 *
10345 * @note For @c RTE_RELATION entries that are provenance-tracked, the
10346 * sequential numbers produced here must @b not be used as PROJECT gate
10347 * positions. Because numbering is query-order-dependent, the sequential
10348 * number for a column of a provenance table that is not the first RTE
10349 * will exceed @c nb_columns of that table's IN gate, causing
10350 * @c WhereCircuit::evaluate() to return an empty locator set. Instead,
10351 * callers should use @c varattno directly (see
10352 * @c make_provenance_expression()). The -1 sentinel is the reliable
10353 * way to identify a provenance-tracked RTE.
10354 *
10355 * @param q Query whose range table is mapped.
10356 * @param columns Pre-allocated array of length @p q->rtable->length.
10357 * Each element is allocated and filled by this function.
10358 * @param nbcols Out-param: total number of non-provenance output columns.
10359 */
10360static void build_column_map(Query *q, int **columns, int *nbcols) {
10361 unsigned i = 0;
10362 ListCell *l;
10363
10364 *nbcols = 0;
10365
10366 foreach (l, q->rtable) {
10367 RangeTblEntry *r = (RangeTblEntry *)lfirst(l);
10368 ListCell *lc;
10369
10370 columns[i] = 0;
10371 if (r->eref && r->eref->colnames != NIL) {
10372 unsigned j = 0;
10373
10374 columns[i] = (int *)palloc(list_length(r->eref->colnames) * sizeof(int));
10375
10376 foreach (lc, r->eref->colnames) {
10377 if (!lfirst(lc)) {
10378 /* Column without name – used e.g. when grouping by a discarded column */
10379 columns[i][j] = ++(*nbcols);
10380 } else {
10381 const char *v = strVal(lfirst(lc));
10382
10383 if (strcmp(v, "") && r->rtekind != RTE_JOIN) { /* join RTE columns ignored */
10384 if (!strcmp(v, PROVSQL_COLUMN_NAME))
10385 columns[i][j] = -1;
10386 else
10387 columns[i][j] = ++(*nbcols);
10388 } else {
10389 columns[i][j] = 0;
10390 }
10391 }
10392
10393 ++j;
10394 }
10395 }
10396
10397 ++i;
10398 }
10399}
10400
10401/**
10402 * @brief Categorisation of a top-level WHERE conjunct.
10403 *
10404 * Drives the unified WHERE classifier.
10405 * Both probabilistic flavours (agg_token's "moved to HAVING" world and
10406 * random_variable's "lifted to provenance" world) are special cases of
10407 * "this conjunct involves a probabilistic value the executor cannot
10408 * evaluate as a Boolean directly, so the planner has to route it to a
10409 * different evaluation site". The classifier reports which site, or
10410 * (for unsupported mixes) errors.
10411 */
10412typedef enum {
10413 QUAL_DETERMINISTIC, /**< no probabilistic value; stays in WHERE */
10414 QUAL_PURE_AGG, /**< pure agg_token expression; route to HAVING */
10415 QUAL_PURE_RV, /**< pure random_variable expression; lift to provenance */
10416 QUAL_MIXED_AGG_DET, /**< agg_token mixed with non-agg leaves; error */
10417 QUAL_MIXED_RV_DET, /**< random_variable mixed with non-RV leaves; error */
10418 QUAL_MIXED_AGG_RV /**< agg_token and random_variable in the same expr; error */
10419} qual_class;
10420
10421/**
10422 * @brief Classify @p expr along the @c qual_class axis.
10423 *
10424 * Decision table (the predicates @c has_aggtoken,
10425 * @c expr_contains_rv_cmp, @c check_expr_on_aggregate, and
10426 * @c check_expr_on_rv each return whether the expression "contains" or
10427 * "is purely" the corresponding flavour):
10428 *
10429 * | aggtoken | rv_cmp | check_agg | check_rv | classification |
10430 * |----------|--------|-----------|----------|-----------------------|
10431 * | yes | yes | - | - | QUAL_MIXED_AGG_RV |
10432 * | yes | no | true | - | QUAL_PURE_AGG |
10433 * | yes | no | false | - | QUAL_MIXED_AGG_DET |
10434 * | no | yes | - | true | QUAL_PURE_RV |
10435 * | no | yes | - | false | QUAL_MIXED_RV_DET |
10436 * | no | no | - | - | QUAL_DETERMINISTIC |
10437 */
10438static qual_class classify_qual(Expr *expr, const constants_t *constants)
10439{
10440 bool has_agg = has_aggtoken((Node *)expr, constants);
10441 bool has_rv = expr_contains_rv_cmp((Node *)expr, constants);
10442
10443 if (has_agg && has_rv)
10444 return QUAL_MIXED_AGG_RV;
10445 if (has_agg) {
10446 if (check_expr_on_aggregate(expr, constants))
10447 return QUAL_PURE_AGG;
10448 return QUAL_MIXED_AGG_DET;
10449 }
10450 if (has_rv) {
10451 if (check_expr_on_rv(expr, constants))
10452 return QUAL_PURE_RV;
10453 return QUAL_MIXED_RV_DET;
10454 }
10455 return QUAL_DETERMINISTIC;
10456}
10457
10458/** @brief Raise the user-facing error appropriate to a mixed @p c.
10459 *
10460 * Each @c provsql_error call is @c ereport(ERROR), which does not
10461 * return; the explicit @c break statements below are present only to
10462 * keep @c -Wimplicit-fallthrough happy (PostgreSQL's @c elog macro is
10463 * not marked @c noreturn for the compiler's flow analysis). */
10465{
10466 switch (c) {
10467 case QUAL_MIXED_AGG_DET:
10468 /* An ordinary comparison mixed with an aggregate one is now supported
10469 * (the regular leaf becomes a deterministic indicator); this fires only
10470 * when an aggregate comparison itself has an unsupported shape. */
10471 provsql_error("Unsupported aggregate comparison shape in the selection "
10472 "predicate");
10473 break;
10474 case QUAL_MIXED_RV_DET:
10475 /* Likewise: a random_variable comparison mixed with ordinary ones is
10476 * supported; this fires only on an unsupported random_variable
10477 * comparison shape. */
10478 provsql_error("Unsupported random_variable comparison shape in the "
10479 "WHERE clause");
10480 break;
10481 case QUAL_MIXED_AGG_RV:
10482 provsql_error("WHERE clause mixes agg_token (HAVING-style) and "
10483 "random_variable (per-tuple) comparisons inside the "
10484 "same Boolean expression; this combination is not "
10485 "supported");
10486 break;
10487 default:
10488 /* QUAL_DETERMINISTIC / QUAL_PURE_AGG / QUAL_PURE_RV: not a mixed case. */
10489 break;
10490 }
10491}
10492
10493/**
10494 * @brief Unified WHERE classifier &ndash; routes each top-level conjunct
10495 * to the right evaluation site in a single pass.
10496 *
10497 * Walks the WHERE clause, classifies each top-level conjunct, and
10498 * routes pure-agg_token conjuncts to HAVING and pure-random_variable
10499 * conjuncts to the returned rv_cmps list, leaving the deterministic
10500 * conjuncts in WHERE. Doing it in one pass means the rare conjunct
10501 * that mixes agg_token and random_variable gets a deterministic, useful
10502 * error message.
10503 *
10504 * Supported shapes:
10505 * - Whole WHERE is a single conjunct: classify and route or error.
10506 * - Top-level AND of conjuncts: classify each, route, and (after
10507 * walking) collapse the AND if it has zero or one remaining children
10508 * so downstream code does not see a degenerate Boolean node.
10509 * - Top-level OR / NOT containing both deterministic and probabilistic
10510 * leaves: error.
10511 *
10512 * @param constants Extension OID cache.
10513 * @param q Query whose @c jointree->quals and @c havingQual
10514 * may both be mutated in place.
10515 * @return List of @c FuncExpr nodes (one per lifted RV conjunct), each
10516 * producing a @c UUID. The caller conjoins these into
10517 * @c prov_atts before @c make_provenance_expression.
10518 */
10519static List *
10520migrate_probabilistic_quals(const constants_t *constants, Query *q)
10521{
10522 List *rv_cmps = NIL;
10523 Node *quals;
10524
10525 if (!q->jointree || !q->jointree->quals)
10526 return NIL;
10527
10528 quals = q->jointree->quals;
10529
10530 /* Whole WHERE is one conjunct (single OpExpr, or non-AND BoolExpr
10531 * which we treat opaquely &ndash; the per-flavour pure checks
10532 * @c check_expr_on_aggregate / @c check_expr_on_rv recurse through
10533 * the BoolExpr structure themselves). */
10534 if (!IsA(quals, BoolExpr) || ((BoolExpr *)quals)->boolop != AND_EXPR) {
10535 qual_class c = classify_qual((Expr *)quals, constants);
10537
10538 switch (c) {
10539 case QUAL_PURE_AGG:
10540 q->havingQual = add_to_havingQual(q->havingQual, (Expr *)quals);
10541 q->jointree->quals = NULL;
10542 break;
10543 case QUAL_PURE_RV:
10544 rv_cmps = lappend(rv_cmps,
10545 rv_Expr_to_provenance((Expr *)quals,
10546 constants, false));
10547 q->jointree->quals = NULL;
10548 break;
10549 case QUAL_DETERMINISTIC:
10550 /* Leave WHERE alone. */
10551 break;
10552 default:
10553 /* Errors handled by error_for_mixed_qual. */
10554 break;
10555 }
10556 return rv_cmps;
10557 }
10558
10559 /* Top-level AND: walk conjuncts. */
10560 {
10561 BoolExpr *be = (BoolExpr *)quals;
10562 ListCell *cell, *prev;
10563
10564 for (cell = list_head(be->args), prev = NULL; cell != NULL;) {
10565 Expr *conjunct = (Expr *)lfirst(cell);
10566 qual_class c = classify_qual(conjunct, constants);
10567
10569
10570 switch (c) {
10571 case QUAL_PURE_AGG:
10572 q->havingQual = add_to_havingQual(q->havingQual, conjunct);
10573 be->args = my_list_delete_cell(be->args, cell, prev);
10574 if (prev)
10575 cell = my_lnext(be->args, prev);
10576 else
10577 cell = list_head(be->args);
10578 break;
10579 case QUAL_PURE_RV:
10580 rv_cmps = lappend(rv_cmps,
10581 rv_Expr_to_provenance(conjunct,
10582 constants, false));
10583 be->args = my_list_delete_cell(be->args, cell, prev);
10584 if (prev)
10585 cell = my_lnext(be->args, prev);
10586 else
10587 cell = list_head(be->args);
10588 break;
10589 case QUAL_DETERMINISTIC:
10590 prev = cell;
10591 cell = my_lnext(be->args, cell);
10592 break;
10593 default:
10594 /* Errors handled by error_for_mixed_qual. */
10595 break;
10596 }
10597 }
10598
10599 /* Collapse degenerate ANDs so downstream code sees a tidy WHERE. */
10600 if (be->args == NIL)
10601 q->jointree->quals = NULL;
10602 else if (list_length(be->args) == 1)
10603 q->jointree->quals = (Node *)linitial(be->args);
10604 }
10605
10606 return rv_cmps;
10607}
10608
10609/** @brief Context for the @c insert_agg_token_casts_mutator. */
10611 Query *query; ///< Outer query (to look up subquery RTEs)
10612 const constants_t *constants; ///< Extension OID cache
10614
10615/**
10616 * @brief Look up the original aggregate return type for an agg_token Var.
10617 *
10618 * Navigates from the Var's varno/varattno to the subquery's target list,
10619 * finds the provenance_aggregate() FuncExpr, and extracts the type OID
10620 * from its second argument (aggtype).
10621 */
10623 RangeTblEntry *rte;
10624 TargetEntry *te;
10625
10626 if (v->varno < 1 || v->varno > list_length(ctx->query->rtable))
10627 return InvalidOid;
10628
10629 rte = list_nth_node(RangeTblEntry, ctx->query->rtable, v->varno - 1);
10630 if (rte->rtekind != RTE_SUBQUERY || rte->subquery == NULL)
10631 return InvalidOid;
10632
10633 if (v->varattno < 1 || v->varattno > list_length(rte->subquery->targetList))
10634 return InvalidOid;
10635
10636 te = list_nth_node(TargetEntry, rte->subquery->targetList, v->varattno - 1);
10637 if (IsA(te->expr, FuncExpr)) {
10638 FuncExpr *f = (FuncExpr *)te->expr;
10639 if (f->funcid == ctx->constants->OID_FUNCTION_PROVENANCE_AGGREGATE) {
10640 Const *aggtype_const = (Const *)lsecond(f->args);
10641 return DatumGetObjectId(aggtype_const->constvalue);
10642 }
10643 }
10644 return InvalidOid;
10645}
10646
10647/**
10648 * @brief Wrap an agg_token Var in a cast to its original type, in place.
10649 */
10650static void cast_agg_token_in_list(ListCell *lc,
10652 Var *v = (Var *)lfirst(lc);
10653 Oid target = get_agg_token_orig_type(v, ctx);
10654 HeapTuple castTuple;
10655
10656 if (!OidIsValid(target))
10657 return;
10658
10659 castTuple = SearchSysCache2(CASTSOURCETARGET,
10660 ObjectIdGetDatum(ctx->constants->OID_TYPE_AGG_TOKEN),
10661 ObjectIdGetDatum(target));
10662 if (HeapTupleIsValid(castTuple)) {
10663 Form_pg_cast castForm = (Form_pg_cast)GETSTRUCT(castTuple);
10664 if (OidIsValid(castForm->castfunc)) {
10665 FuncExpr *fc = makeNode(FuncExpr);
10666 fc->funcid = castForm->castfunc;
10667 fc->funcresulttype = target;
10668 fc->funcretset = false;
10669 fc->funcvariadic = false;
10670 fc->funcformat = COERCE_IMPLICIT_CAST;
10671 fc->funccollid = InvalidOid;
10672 fc->inputcollid = InvalidOid;
10673 fc->args = list_make1(v);
10674 fc->location = -1;
10675 lfirst(lc) = fc;
10676 }
10677 ReleaseSysCache(castTuple);
10678 }
10679}
10680
10681/**
10682 * @brief Wrap any agg_token Vars in an argument list.
10683 */
10684static void cast_agg_token_args(List *args,
10686 ListCell *lc;
10687 foreach (lc, args) {
10688 if (IsA(lfirst(lc), Var) &&
10689 ((Var *)lfirst(lc))->vartype == ctx->constants->OID_TYPE_AGG_TOKEN)
10690 cast_agg_token_in_list(lc, ctx);
10691 }
10692}
10693
10694/**
10695 * @brief Insert agg_token casts for Vars used in expressions.
10696 *
10697 * After the WHERE-to-HAVING migration, agg_token Vars remaining in
10698 * expression nodes (OpExpr, WindowFunc, CoalesceExpr, MinMaxExpr, etc.)
10699 * need explicit casts to their original type so that operators and
10700 * functions receive correct values. The original type is looked up
10701 * from the provenance_aggregate() call in the subquery.
10702 */
10703static Node *
10704insert_agg_token_casts_mutator(Node *node, void *data) {
10706
10707 if (node == NULL)
10708 return NULL;
10709
10710 if (IsA(node, OpExpr)) {
10711 /* Arithmetic over an agg_token Var (e.g. cnt+1 where cnt comes from a
10712 * subquery aggregate) is kept as an agg_token (gate_arith) rather than
10713 * cast to scalar, preserving provenance. */
10714 Node *swapped = try_swap_agg_arith((OpExpr *)node, ctx->constants);
10715 if (swapped != NULL)
10716 return swapped;
10717 cast_agg_token_args(((OpExpr *)node)->args, ctx);
10718 return (Node *)node;
10719 }
10720 if (IsA(node, WindowFunc)) {
10721 cast_agg_token_args(((WindowFunc *)node)->args, ctx);
10722 return (Node *)node;
10723 }
10724 if (IsA(node, CoalesceExpr)) {
10725 cast_agg_token_args(((CoalesceExpr *)node)->args, ctx);
10726 return (Node *)node;
10727 }
10728 if (IsA(node, MinMaxExpr)) {
10729 cast_agg_token_args(((MinMaxExpr *)node)->args, ctx);
10730 return (Node *)node;
10731 }
10732 if (IsA(node, NullIfExpr)) {
10733 cast_agg_token_args(((NullIfExpr *)node)->args, ctx);
10734 return (Node *)node;
10735 }
10736
10737 return expression_tree_mutator(node, insert_agg_token_casts_mutator, data);
10738}
10739
10740/**
10741 * @brief Walk query and insert agg_token casts where needed.
10742 */
10743static void insert_agg_token_casts(const constants_t *constants, Query *q) {
10744 insert_agg_token_casts_context ctx = {q, constants};
10745 query_tree_mutator(q, insert_agg_token_casts_mutator, &ctx,
10746 QTW_DONT_COPY_QUERY | QTW_IGNORE_RC_SUBQUERIES);
10747}
10748
10749/** @brief Context for @c join_qual_has_agg_token_walker. */
10751 const constants_t *constants; ///< Extension OID cache
10752 Index *rteid; ///< Out: varno of the agg_token Var
10753 AttrNumber *join_attno; ///< Out: attno of the agg_token Var
10755
10756static bool join_qual_has_agg_token_walker(Node *node,
10758{
10759 if (node == NULL)
10760 return false;
10761 if (IsA(node, OpExpr)) {
10762 OpExpr *oe = (OpExpr *) node;
10763 Node *left = (Node *) linitial(oe->args);
10764 Node *right = (Node *) lsecond(oe->args);
10765
10766 /* Unwrap casts */
10767 if (IsA(left, FuncExpr) &&
10768 (((FuncExpr *)left)->funcformat == COERCE_IMPLICIT_CAST ||
10769 ((FuncExpr *)left)->funcformat == COERCE_EXPLICIT_CAST) &&
10770 list_length(((FuncExpr *)left)->args) == 1)
10771 left = linitial(((FuncExpr *)left)->args);
10772 if (IsA(right, FuncExpr) &&
10773 (((FuncExpr *)right)->funcformat == COERCE_IMPLICIT_CAST ||
10774 ((FuncExpr *)right)->funcformat == COERCE_EXPLICIT_CAST) &&
10775 list_length(((FuncExpr *)right)->args) == 1)
10776 right = linitial(((FuncExpr *)right)->args);
10777
10778 if (IsA(left, Var) && IsA(right, Var)) {
10779 Var *left_var = (Var *)left;
10780 Var *right_var = (Var *)right;
10781 if (left_var->vartype == ctx->constants->OID_TYPE_AGG_TOKEN &&
10782 right_var->vartype != ctx->constants->OID_TYPE_AGG_TOKEN) {
10783 *ctx->rteid = left_var->varno;
10784 *ctx->join_attno = left_var->varattno;
10785 return true;
10786 }
10787 if (right_var->vartype == ctx->constants->OID_TYPE_AGG_TOKEN &&
10788 left_var->vartype != ctx->constants->OID_TYPE_AGG_TOKEN) {
10789 *ctx->rteid = right_var->varno;
10790 *ctx->join_attno = right_var->varattno;
10791 return true;
10792 }
10793 }
10794 }
10795 return expression_tree_walker(node, join_qual_has_agg_token_walker,
10796 (void *) ctx);
10797}
10798
10799/**
10800 * @brief Return true if @p node contains an @c OpExpr that equates an
10801 * @c agg_token @c Var with a non-@c agg_token @c Var.
10802 *
10803 * On a match, writes the agg_token Var's @c varno and @c varattno to
10804 * @p *rteid and @p *join_attno. Used to detect JOIN conditions that
10805 * require the @c rewrite_join_agg_token rewrite.
10806 *
10807 * @param node Expression tree to inspect.
10808 * @param constants Extension OID cache.
10809 * @param rteid Out: varno of the agg_token Var (unchanged on miss).
10810 * @param join_attno Out: attno of the agg_token Var (unchanged on miss).
10811 * @return True iff such an @c OpExpr was found.
10812 */
10813static bool join_qual_has_agg_token(Node *node, const constants_t *constants,
10814 Index *rteid, AttrNumber *join_attno)
10815{
10817 ctx.constants = constants;
10818 ctx.rteid = rteid;
10819 ctx.join_attno = join_attno;
10820 return join_qual_has_agg_token_walker(node, &ctx);
10821}
10822
10823/**
10824 * @brief Build an AST node for <tt>arr[idx]</tt> on a uuid[] expression.
10825 *
10826 * Wraps the version rename between @c ArrayRef (PG < 12) and
10827 * @c SubscriptingRef (PG 12+), and the addition of @c refrestype (PG 14+).
10828 *
10829 * @param arr_expr Expression evaluating to @c uuid[].
10830 * @param index 1-based element position.
10831 * @param constants Extension OID cache.
10832 * @return Subscripting node with result type @c uuid.
10833 */
10834static Node *make_uuid_array_subscript(Node *arr_expr, int index,
10835 const constants_t *constants)
10836{
10837 Const *idx = makeConst(INT4OID, -1, InvalidOid, sizeof(int32),
10838 Int32GetDatum(index), false, true);
10839#if PG_VERSION_NUM >= 120000
10840 SubscriptingRef *sub = makeNode(SubscriptingRef);
10841 sub->refcontainertype = constants->OID_TYPE_UUID_ARRAY;
10842 sub->refelemtype = constants->OID_TYPE_UUID;
10843#if PG_VERSION_NUM >= 140000
10844 sub->refrestype = constants->OID_TYPE_UUID;
10845#endif
10846 sub->reftypmod = -1;
10847 sub->refcollid = InvalidOid;
10848 sub->refupperindexpr = list_make1(idx);
10849 sub->reflowerindexpr = NIL;
10850 sub->refexpr = (Expr *)arr_expr;
10851 sub->refassgnexpr = NULL;
10852 return (Node *)sub;
10853#else
10854 ArrayRef *sub = makeNode(ArrayRef);
10855 sub->refarraytype = constants->OID_TYPE_UUID_ARRAY;
10856 sub->refelemtype = constants->OID_TYPE_UUID;
10857 sub->reftypmod = -1;
10858 sub->refcollid = InvalidOid;
10859 sub->refupperindexpr = list_make1(idx);
10860 sub->reflowerindexpr = NIL;
10861 sub->refexpr = (Expr *)arr_expr;
10862 sub->refassgnexpr = NULL;
10863 return (Node *)sub;
10864#endif
10865}
10866
10867/**
10868 * @brief Context for @c retype_agg_var_walker.
10869 *
10870 * Identifies the Var location whose type must flip from @c agg_token
10871 * to @c text after the source relation has been replaced by an
10872 * explode-style subquery.
10873 */
10874typedef struct retype_agg_var_ctx {
10875 Index rteid; ///< Varno of the replaced RTE
10876 AttrNumber join_attno; ///< Attno of the former agg_token column
10877 const constants_t *constants;///< Extension OID cache
10879
10880/**
10881 * @brief Walker that retypes agg_token Vars to text and rewrites the
10882 * equality OpExpr to @c text = text with the non-agg side cast via I/O.
10883 *
10884 * Only affects Vars with @c varlevelsup == 0 matching @c (rteid, join_attno).
10885 * Sibling-query subqueries are left untouched via @c QTW_IGNORE_RT_SUBQUERIES
10886 * at the top-level call.
10887 */
10888static bool retype_agg_var_walker(Node *node, retype_agg_var_ctx *ctx)
10889{
10890 if (node == NULL)
10891 return false;
10892
10893 if (IsA(node, OpExpr)) {
10894 OpExpr *oe = (OpExpr *)node;
10895 if (list_length(oe->args) == 2) {
10896 Node *left = (Node *)linitial(oe->args);
10897 Node *right = (Node *)lsecond(oe->args);
10898 Var *agg_v = NULL;
10899 bool agg_on_left = false;
10900
10901 if (IsA(left, Var)) {
10902 Var *v = (Var *)left;
10903 if (v->varlevelsup == 0 && v->varno == ctx->rteid &&
10904 v->varattno == ctx->join_attno &&
10905 v->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
10906 agg_v = v;
10907 agg_on_left = true;
10908 }
10909 }
10910 if (agg_v == NULL && IsA(right, Var)) {
10911 Var *v = (Var *)right;
10912 if (v->varlevelsup == 0 && v->varno == ctx->rteid &&
10913 v->varattno == ctx->join_attno &&
10914 v->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
10915 agg_v = v;
10916 }
10917 }
10918
10919 if (agg_v != NULL) {
10920 Node *other = agg_on_left ? right : left;
10921 Oid text_eq;
10922 Operator opInfo;
10923 Form_pg_operator opform;
10924
10925 agg_v->vartype = TEXTOID;
10926 agg_v->varcollid = DEFAULT_COLLATION_OID;
10927
10928 if (exprType(other) != TEXTOID) {
10929 CoerceViaIO *c = makeNode(CoerceViaIO);
10930 c->arg = (Expr *)other;
10931 c->resulttype = TEXTOID;
10932 c->resultcollid = DEFAULT_COLLATION_OID;
10933 c->coerceformat = COERCE_EXPLICIT_CAST;
10934 c->location = -1;
10935 other = (Node *)c;
10936 }
10937
10938 if (agg_on_left)
10939 oe->args = list_make2(agg_v, other);
10940 else
10941 oe->args = list_make2(other, agg_v);
10942
10943 text_eq = find_equality_operator(TEXTOID, TEXTOID);
10944 if (!OidIsValid(text_eq))
10945 provsql_error("rewrite_join_agg_token: text = text operator "
10946 "not found");
10947 opInfo = SearchSysCache1(OPEROID, ObjectIdGetDatum(text_eq));
10948 if (!HeapTupleIsValid(opInfo))
10949 provsql_error("rewrite_join_agg_token: could not look up "
10950 "text equality operator");
10951 opform = (Form_pg_operator)GETSTRUCT(opInfo);
10952 oe->opno = text_eq;
10953 oe->opfuncid = opform->oprcode;
10954 oe->opresulttype = opform->oprresult;
10955 oe->inputcollid = DEFAULT_COLLATION_OID;
10956 ReleaseSysCache(opInfo);
10957
10958 /* Args handled; skip their subtree walk */
10959 return false;
10960 }
10961 }
10962 }
10963
10964 if (IsA(node, Var)) {
10965 Var *v = (Var *)node;
10966 if (v->varlevelsup == 0 && v->varno == ctx->rteid &&
10967 v->varattno == ctx->join_attno &&
10968 v->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
10969 v->vartype = TEXTOID;
10970 v->varcollid = DEFAULT_COLLATION_OID;
10971 }
10972 return false;
10973 }
10974
10975 if (IsA(node, Query)) {
10976 /* Nested queries address a different rtable; do not descend. */
10977 return false;
10978 }
10979
10980 return expression_tree_walker(node, retype_agg_var_walker, (void *)ctx);
10981}
10982
10983/**
10984 * @brief Replace the source relation of an agg_token JOIN with an
10985 * explode-style subquery.
10986 *
10987 * Given a JOIN qual of the form @c rteid.join_attno = other where
10988 * @c rteid.join_attno is of type @c agg_token, replaces the RTE at @p rteid
10989 * in place with a subquery:
10990 *
10991 * @code{.sql}
10992 * SELECT t.col_1, ..., t.col_{join_attno-1},
10993 * get_extra(get_children(sm)[2]) AS <agg_col>,
10994 * ...,
10995 * provenance_times(get_children(sm)[1], t.provsql) AS provsql
10996 * FROM <t>, LATERAL unnest(get_children(t.<agg_col>)) AS sm
10997 * @endcode
10998 *
10999 * The subquery preserves the original column order, so outer Vars still
11000 * address the same attnos. The outer query is then walked to retype Vars
11001 * at (@p rteid, @p join_attno) from @c agg_token to @c text and rewrite the
11002 * equality @c OpExpr to @c text = text (casting the other side via I/O).
11003 *
11004 * The copy of the source RTE inside the subquery has its @c provsql column
11005 * renamed so the recursive @c process_query pass does not re-detect it as a
11006 * provenance source – the combined provenance is already captured by the
11007 * subquery's exposed @c provsql target entry.
11008 *
11009 * @param q Query to rewrite (modified in place).
11010 * @param constants Extension OID cache.
11011 * @param rteid 1-based varno of the RTE owning the agg_token column.
11012 * @param join_attno 1-based attno of the agg_token column in that RTE.
11013 * @return The modified query.
11014 */
11015static Query *rewrite_join_agg_token(Query *q, const constants_t *constants,
11016 Index rteid, AttrNumber join_attno)
11017{
11018 RangeTblEntry *src_rte = (RangeTblEntry *)list_nth(q->rtable, rteid - 1);
11019 AttrNumber provsql_attno = 0;
11020 AttrNumber attno;
11021 ListCell *lc;
11022 Query *inner;
11023 RangeTblEntry *inner_src, *sm_rte;
11024 RangeTblFunction *rtfunc;
11025 FuncExpr *unnest_call, *get_children_of_agg, *agg_to_uuid;
11026 Var *agg_var_in_inner;
11027 Alias *sm_alias, *sm_eref;
11028 RangeTblRef *inner_rtr1, *inner_rtr2;
11029 FromExpr *inner_jt;
11030 List *inner_tl = NIL;
11031
11032 if (src_rte->rtekind != RTE_RELATION && src_rte->rtekind != RTE_SUBQUERY)
11033 provsql_error("rewrite_join_agg_token: source RTE kind %d not supported",
11034 (int)src_rte->rtekind);
11035
11036 /* Locate the provsql column of the source RTE. */
11037 attno = 1;
11038 foreach (lc, src_rte->eref->colnames) {
11039 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME)) {
11040 provsql_attno = attno;
11041 break;
11042 }
11043 ++attno;
11044 }
11045 if (provsql_attno == 0)
11046 provsql_error("rewrite_join_agg_token: source relation has no "
11047 "provsql column");
11048
11049 /* --- Build the lateral RTE: unnest(get_children(agg_token_uuid(agg_var))) --- */
11050
11051 agg_var_in_inner = makeNode(Var);
11052 agg_var_in_inner->varno = 1;
11053 agg_var_in_inner->varattno = join_attno;
11054 agg_var_in_inner->vartype = constants->OID_TYPE_AGG_TOKEN;
11055 agg_var_in_inner->varcollid = InvalidOid;
11056 agg_var_in_inner->vartypmod = -1;
11057 agg_var_in_inner->location = -1;
11058
11059 agg_to_uuid = makeNode(FuncExpr);
11060 agg_to_uuid->funcid = constants->OID_FUNCTION_AGG_TOKEN_UUID;
11061 agg_to_uuid->funcresulttype = constants->OID_TYPE_UUID;
11062 agg_to_uuid->funcretset = false;
11063 agg_to_uuid->funcvariadic = false;
11064 agg_to_uuid->funcformat = COERCE_IMPLICIT_CAST;
11065 agg_to_uuid->funccollid = InvalidOid;
11066 agg_to_uuid->inputcollid = InvalidOid;
11067 agg_to_uuid->args = list_make1(agg_var_in_inner);
11068 agg_to_uuid->location = -1;
11069
11070 get_children_of_agg = makeNode(FuncExpr);
11071 get_children_of_agg->funcid = constants->OID_FUNCTION_GET_CHILDREN;
11072 get_children_of_agg->funcresulttype = constants->OID_TYPE_UUID_ARRAY;
11073 get_children_of_agg->funcretset = false;
11074 get_children_of_agg->funcvariadic = false;
11075 get_children_of_agg->funcformat = COERCE_EXPLICIT_CALL;
11076 get_children_of_agg->funccollid = InvalidOid;
11077 get_children_of_agg->inputcollid = InvalidOid;
11078 get_children_of_agg->args = list_make1(agg_to_uuid);
11079 get_children_of_agg->location = -1;
11080
11081 unnest_call = makeNode(FuncExpr);
11082 unnest_call->funcid = constants->OID_UNNEST;
11083 unnest_call->funcresulttype = constants->OID_TYPE_UUID;
11084 unnest_call->funcretset = true;
11085 unnest_call->funcvariadic = false;
11086 unnest_call->funcformat = COERCE_EXPLICIT_CALL;
11087 unnest_call->funccollid = InvalidOid;
11088 unnest_call->inputcollid = InvalidOid;
11089 unnest_call->args = list_make1(get_children_of_agg);
11090 unnest_call->location = -1;
11091
11092 rtfunc = makeNode(RangeTblFunction);
11093 rtfunc->funcexpr = (Node *)unnest_call;
11094 rtfunc->funccolcount = 1;
11095 rtfunc->funccolnames = NIL;
11096 rtfunc->funccoltypes = NIL;
11097 rtfunc->funccoltypmods = NIL;
11098 rtfunc->funccolcollations = NIL;
11099 rtfunc->funcparams = NULL;
11100
11101 sm_alias = makeNode(Alias);
11102 sm_eref = makeNode(Alias);
11103 sm_alias->aliasname = "sm";
11104 sm_eref->aliasname = "sm";
11105 sm_eref->colnames = list_make1(makeString("sm"));
11106
11107 sm_rte = makeNode(RangeTblEntry);
11108 sm_rte->rtekind = RTE_FUNCTION;
11109 sm_rte->functions = list_make1(rtfunc);
11110 sm_rte->funcordinality = false;
11111 sm_rte->alias = sm_alias;
11112 sm_rte->eref = sm_eref;
11113 sm_rte->lateral = true;
11114 sm_rte->inFromCl = true;
11115#if PG_VERSION_NUM < 160000
11116 sm_rte->requiredPerms = 0;
11117#endif
11118
11119 /* --- Inner rtable RTE 1: the source relation (deep copy). --- */
11120
11121 inner_src = copyObject(src_rte);
11122
11123 /* Rename the provsql column in the inner RTE's eref so the recursive
11124 * process_query pass does not re-detect it as a provenance source. The
11125 * combined provenance is already captured by the subquery's exposed
11126 * provsql TargetEntry below. */
11127 hide_provsql_colname(inner_src);
11128
11129 inner_rtr1 = makeNode(RangeTblRef);
11130 inner_rtr1->rtindex = 1;
11131 inner_rtr2 = makeNode(RangeTblRef);
11132 inner_rtr2->rtindex = 2;
11133 inner_jt = makeNode(FromExpr);
11134 inner_jt->fromlist = list_make2(inner_rtr1, inner_rtr2);
11135 inner_jt->quals = NULL;
11136
11137 /* --- Target list of the inner subquery, preserving original column order. --- */
11138
11139 attno = 1;
11140 foreach (lc, src_rte->eref->colnames) {
11141 const char *colname = strVal(lfirst(lc));
11142 TargetEntry *te = makeNode(TargetEntry);
11143 te->resno = attno;
11144 te->resname = pstrdup(colname);
11145 te->resjunk = false;
11146
11147 if (attno == join_attno) {
11148 /* get_extra(get_children(sm)[2]) */
11149 Var *sm_var = makeNode(Var);
11150 FuncExpr *gch, *ge;
11151 Node *subscript;
11152
11153 sm_var->varno = 2;
11154 sm_var->varattno = 1;
11155 sm_var->vartype = constants->OID_TYPE_UUID;
11156 sm_var->varcollid = InvalidOid;
11157 sm_var->vartypmod = -1;
11158 sm_var->location = -1;
11159
11160 gch = makeNode(FuncExpr);
11161 gch->funcid = constants->OID_FUNCTION_GET_CHILDREN;
11162 gch->funcresulttype = constants->OID_TYPE_UUID_ARRAY;
11163 gch->funcretset = false;
11164 gch->funcvariadic = false;
11165 gch->funcformat = COERCE_EXPLICIT_CALL;
11166 gch->funccollid = InvalidOid;
11167 gch->inputcollid = InvalidOid;
11168 gch->args = list_make1(sm_var);
11169 gch->location = -1;
11170
11171 subscript = make_uuid_array_subscript((Node *)gch, 2, constants);
11172
11173 ge = makeNode(FuncExpr);
11174 ge->funcid = constants->OID_FUNCTION_GET_EXTRA;
11175 ge->funcresulttype = TEXTOID;
11176 ge->funcretset = false;
11177 ge->funcvariadic = false;
11178 ge->funcformat = COERCE_EXPLICIT_CALL;
11179 ge->funccollid = DEFAULT_COLLATION_OID;
11180 ge->inputcollid = InvalidOid;
11181 ge->args = list_make1(subscript);
11182 ge->location = -1;
11183
11184 te->expr = (Expr *)ge;
11185 } else if (attno == provsql_attno) {
11186 /* provenance_times(get_children(sm)[1], t.provsql) – VARIADIC uuid[] */
11187 Var *sm_var = makeNode(Var);
11188 Var *prov_var = makeNode(Var);
11189 FuncExpr *gch, *pt;
11190 ArrayExpr *arr;
11191 Node *subscript;
11192
11193 sm_var->varno = 2;
11194 sm_var->varattno = 1;
11195 sm_var->vartype = constants->OID_TYPE_UUID;
11196 sm_var->varcollid = InvalidOid;
11197 sm_var->vartypmod = -1;
11198 sm_var->location = -1;
11199
11200 gch = makeNode(FuncExpr);
11201 gch->funcid = constants->OID_FUNCTION_GET_CHILDREN;
11202 gch->funcresulttype = constants->OID_TYPE_UUID_ARRAY;
11203 gch->funcretset = false;
11204 gch->funcvariadic = false;
11205 gch->funcformat = COERCE_EXPLICIT_CALL;
11206 gch->funccollid = InvalidOid;
11207 gch->inputcollid = InvalidOid;
11208 gch->args = list_make1(sm_var);
11209 gch->location = -1;
11210
11211 subscript = make_uuid_array_subscript((Node *)gch, 1, constants);
11212
11213 prov_var->varno = 1;
11214 prov_var->varattno = provsql_attno;
11215 prov_var->vartype = constants->OID_TYPE_UUID;
11216 prov_var->varcollid = InvalidOid;
11217 prov_var->vartypmod = -1;
11218 prov_var->location = -1;
11219
11220 arr = makeNode(ArrayExpr);
11221 arr->array_typeid = constants->OID_TYPE_UUID_ARRAY;
11222 arr->element_typeid = constants->OID_TYPE_UUID;
11223 arr->elements = list_make2(subscript, prov_var);
11224 arr->location = -1;
11225
11226 pt = makeNode(FuncExpr);
11227 pt->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
11228 pt->funcresulttype = constants->OID_TYPE_UUID;
11229 pt->funcretset = false;
11230 pt->funcvariadic = true;
11231 pt->funcformat = COERCE_EXPLICIT_CALL;
11232 pt->funccollid = InvalidOid;
11233 pt->inputcollid = InvalidOid;
11234 pt->args = list_make1(arr);
11235 pt->location = -1;
11236
11237 te->expr = (Expr *)pt;
11238 } else {
11239 /* Passthrough Var(1, attno). */
11240 Var *v = makeNode(Var);
11241 Oid vtype = InvalidOid;
11242 int32 vtypmod = -1;
11243 Oid vcoll = InvalidOid;
11244
11245 if (src_rte->rtekind == RTE_RELATION) {
11246 get_atttypetypmodcoll(src_rte->relid, attno, &vtype, &vtypmod, &vcoll);
11247 } else { /* RTE_SUBQUERY */
11248 TargetEntry *sub_te =
11249 (TargetEntry *)list_nth(src_rte->subquery->targetList, attno - 1);
11250 vtype = exprType((Node *)sub_te->expr);
11251 vtypmod = exprTypmod((Node *)sub_te->expr);
11252 vcoll = exprCollation((Node *)sub_te->expr);
11253 }
11254
11255 v->varno = 1;
11256 v->varattno = attno;
11257 v->vartype = vtype;
11258 v->varcollid = vcoll;
11259 v->vartypmod = vtypmod;
11260 v->location = -1;
11261 te->expr = (Expr *)v;
11262 }
11263
11264 inner_tl = lappend(inner_tl, te);
11265 ++attno;
11266 }
11267
11268 inner = makeNode(Query);
11269 inner->commandType = CMD_SELECT;
11270 inner->canSetTag = true;
11271 inner->rtable = list_make2(inner_src, sm_rte);
11272 inner->jointree = inner_jt;
11273 inner->targetList = inner_tl;
11274 inner->hasAggs = false;
11275 inner->hasSubLinks = false;
11276
11277#if PG_VERSION_NUM >= 160000
11278 /* PG 16+ moved permission info from RangeTblEntry into a separate
11279 * Query.rteperminfos list, indexed by RangeTblEntry.perminfoindex.
11280 * Our copy of src_rte kept its original perminfoindex, so the inner
11281 * query needs a matching rteperminfos entry – without it, perminfoindex
11282 * dangles and the planner short-circuits the subquery. */
11283 if (inner_src->perminfoindex != 0) {
11284 RTEPermissionInfo *perminfo =
11285 getRTEPermissionInfo(q->rteperminfos, src_rte);
11286 inner->rteperminfos = list_make1(copyObject(perminfo));
11287 inner_src->perminfoindex = 1;
11288 }
11289#endif
11290
11291 /* --- Replace src_rte in place with the subquery; outer varnos unchanged. --- */
11292
11293 src_rte->rtekind = RTE_SUBQUERY;
11294 src_rte->subquery = inner;
11295 src_rte->relid = InvalidOid;
11296 src_rte->relkind = 0;
11297#if PG_VERSION_NUM >= 120000
11298 src_rte->rellockmode = 0; /* field added in PG 12 */
11299#endif
11300 src_rte->inh = false;
11301 src_rte->lateral = false;
11302#if PG_VERSION_NUM >= 160000
11303 src_rte->perminfoindex = 0;
11304#else
11305 src_rte->selectedCols = NULL;
11306 src_rte->insertedCols = NULL;
11307 src_rte->updatedCols = NULL;
11308 src_rte->requiredPerms = ACL_SELECT;
11309#endif
11310
11311 /* Drop the "provsql" entry from the outer RTE's eref->colnames.
11312 * get_provenance_attributes will scan the subquery's target list for
11313 * a "provsql" TE and reinsert the colname at the matching position,
11314 * keeping eref->colnames length in sync with the subquery's target
11315 * list. Without this, the pre-existing "provsql" entry (inherited
11316 * from the original relation) plus the reinsertion would produce a
11317 * 5-colname list for a 4-column subquery, which PostgreSQL rejects. */
11318 {
11319 ListCell *cell, *prev;
11320 AttrNumber i;
11321
11322 prev = NULL;
11323 i = 1;
11324 for (cell = list_head(src_rte->eref->colnames); cell != NULL; ) {
11325 if (i == provsql_attno) {
11326 src_rte->eref->colnames =
11327 my_list_delete_cell(src_rte->eref->colnames, cell, prev);
11328 break;
11329 }
11330 prev = cell;
11331 cell = my_lnext(src_rte->eref->colnames, cell);
11332 ++i;
11333 }
11334 }
11335
11336 /* --- Retype outer Vars (rteid, join_attno) from agg_token to text and
11337 * rewrite the equality OpExpr to text = text. --- */
11338 {
11340 ctx.rteid = rteid;
11341 ctx.join_attno = join_attno;
11342 ctx.constants = constants;
11343 query_tree_walker(q, retype_agg_var_walker, (void *)&ctx,
11344 QTW_IGNORE_RT_SUBQUERIES);
11345 }
11346
11347 return q;
11348}
11349
11350/**
11351 * @brief Wrap @p expr in a @c provsql.assume_boolean FuncExpr.
11352 *
11353 * Used by @c make_provenance_expression when its caller (the
11354 * safe-query rewrite path in @c process_query) flagged the result
11355 * as needing the @c gate_assumed structural marker.
11356 * Wrapping at expression-build time rather than at splice time
11357 * means @c add_to_select and
11358 * @c replace_provenance_function_by_expression both consume the
11359 * already-wrapped expression, so every per-row root occurrence in
11360 * the final target list -- the auto-added @c provsql column and
11361 * every substituted user-side @c provenance() call -- carries the
11362 * wrapper uniformly.
11363 *
11364 * @param constants Extension OID cache.
11365 * @param expr Provenance expression to wrap.
11366 * @return A @c FuncExpr applying @c provsql.assume_boolean to @p expr.
11367 */
11368static Expr *wrap_in_assume_boolean(const constants_t *constants,
11369 Expr *expr) {
11370 FuncExpr *wrap = makeNode(FuncExpr);
11371 wrap->funcid = constants->OID_FUNCTION_ASSUME_BOOLEAN;
11372 wrap->funcresulttype = constants->OID_TYPE_UUID;
11373 wrap->funcretset = false;
11374 wrap->funcvariadic = false;
11375 wrap->funcformat = COERCE_EXPLICIT_CALL;
11376 wrap->funccollid = InvalidOid;
11377 wrap->inputcollid = InvalidOid;
11378 wrap->args = list_make1(expr);
11379 wrap->location = -1;
11380 return (Expr *) wrap;
11381}
11382
11383/**
11384 * @brief Wrap @p expr in a @c provsql.annotate(uuid, text) FuncExpr carrying
11385 * @p cert.
11386 *
11387 * Used by @c make_provenance_expression to attach the inversion-free
11388 * tractability certificate to the per-row provenance root: the resulting
11389 * annotation gate is transparent for every evaluator and carries @p cert in
11390 * its @c extra (and folded into its UUID). @p cert is copied into a text
11391 * @c Const.
11392 */
11393static Expr *wrap_in_annotate(const constants_t *constants, Expr *expr,
11394 const char *cert) {
11395 FuncExpr *wrap = makeNode(FuncExpr);
11396 Const *ce = makeConst(TEXTOID, -1, DEFAULT_COLLATION_OID, -1,
11397 CStringGetTextDatum(cert), false, false);
11398 wrap->funcid = constants->OID_FUNCTION_ANNOTATE;
11399 wrap->funcresulttype = constants->OID_TYPE_UUID;
11400 wrap->funcretset = false;
11401 wrap->funcvariadic = false;
11402 wrap->funcformat = COERCE_EXPLICIT_CALL;
11403 wrap->funccollid = InvalidOid;
11404 wrap->inputcollid = DEFAULT_COLLATION_OID;
11405 wrap->args = list_make2(expr, (Expr *) ce);
11406 wrap->location = -1;
11407 return (Expr *) wrap;
11408}
11409
11410/**
11411 * @brief Wrap @p target in a @c provsql.cond(uuid, uuid) FuncExpr conditioning
11412 * it on @p evidence.
11413 *
11414 * Used by @c process_query when the query carries a @c given(...) marker: the
11415 * per-row output provenance @p target is conditioned on the marker's evidence
11416 * expression, so each output row's provenance becomes
11417 * @c "cond(row_provenance, evidence)". @p evidence is the (per-row, possibly
11418 * correlated) argument captured from the stripped @c given() term.
11419 */
11420static Expr *wrap_in_cond(const constants_t *constants, Expr *target,
11421 Expr *evidence) {
11422 FuncExpr *wrap = makeNode(FuncExpr);
11423 wrap->funcid = constants->OID_FUNCTION_COND;
11424 wrap->funcresulttype = constants->OID_TYPE_UUID;
11425 wrap->funcretset = false;
11426 wrap->funcvariadic = false;
11427 wrap->funcformat = COERCE_EXPLICIT_CALL;
11428 wrap->funccollid = InvalidOid;
11429 wrap->inputcollid = InvalidOid;
11430 wrap->args = list_make2(target, evidence);
11431 wrap->location = -1;
11432 return (Expr *) wrap;
11433}
11434
11435/** @brief Mark column @p attno of RTE @p r as selected (read permission). */
11436static void mark_col_selected(Query *q, RangeTblEntry *r, AttrNumber attno) {
11437#if PG_VERSION_NUM >= 160000
11438 if (r->perminfoindex != 0) {
11439 RTEPermissionInfo *rpi =
11440 list_nth_node(RTEPermissionInfo, q->rteperminfos, r->perminfoindex - 1);
11441 rpi->selectedCols = bms_add_member(
11442 rpi->selectedCols, attno - FirstLowInvalidHeapAttributeNumber);
11443 }
11444#else
11445 r->selectedCols = bms_add_member(r->selectedCols,
11446 attno - FirstLowInvalidHeapAttributeNumber);
11447#endif
11448}
11449
11450/** @brief A @c Var for column @p attno of RTE @p relid, with the column's
11451 * actual type/typmod/collation, marking the column selected. */
11452static Var *make_column_var(Query *q, RangeTblEntry *r, Index relid,
11453 AttrNumber attno) {
11454 Oid typid; int32 typmod; Oid coll;
11455 Var *v;
11456 get_atttypetypmodcoll(r->relid, attno, &typid, &typmod, &coll);
11457 v = makeVar(relid, attno, typid, typmod, coll, 0);
11458 v->location = -1;
11459 mark_col_selected(q, r, attno);
11460 return v;
11461}
11462
11463/** @brief Coerce @p arg to @c text via its output function (any type -> text). */
11464static Expr *coerce_via_io_to_text(Expr *arg) {
11465 CoerceViaIO *c = makeNode(CoerceViaIO);
11466 c->arg = arg;
11467 c->resulttype = TEXTOID;
11468 c->resultcollid = DEFAULT_COLLATION_OID;
11469 c->coerceformat = COERCE_IMPLICIT_CAST;
11470 c->location = -1;
11471 return (Expr *) c;
11472}
11473
11474/**
11475 * @brief Wrap an atom's provenance @c Var in the inversion-free per-input
11476 * order marker: @c annotate(prov, inversion_free_key(root, sec, factor)).
11477 *
11478 * @p prov_var is a @c Var on the atom's provsql column (its @c varno is the
11479 * range-table index of the atom); @p m gives the root- and secondary-class
11480 * columns and the factor for that atom.
11481 */
11482static Expr *build_inversion_free_marker(const constants_t *constants, Query *q,
11483 Var *prov_var, const InvFreeMarker *m) {
11484 Index relid = prov_var->varno;
11485 RangeTblEntry *r = list_nth_node(RangeTblEntry, q->rtable, relid - 1);
11486 Var *rootv = make_column_var(q, r, relid, m->root_col);
11487 Const *factorc = makeConst(INT4OID, -1, InvalidOid, sizeof(int32),
11488 Int32GetDatum(m->factor), false, true);
11489 FuncExpr *keyf = makeNode(FuncExpr);
11490 FuncExpr *ann = makeNode(FuncExpr);
11491 Expr *secarg;
11492
11493 /* A root-only atom (no secondary class, e.g. a self-join-free hierarchical
11494 * query's atoms all binding only the head variable) carries a constant
11495 * secondary key: every such input shares the single tile of its block. */
11496 if (m->sec_col == 0)
11497 secarg = (Expr *) makeConst(TEXTOID, -1, DEFAULT_COLLATION_OID, -1,
11498 CStringGetTextDatum("0"), false, false);
11499 else
11500 secarg = coerce_via_io_to_text(
11501 (Expr *) make_column_var(q, r, relid, m->sec_col));
11502
11503 keyf->funcid = constants->OID_FUNCTION_INVERSION_FREE_KEY;
11504 keyf->funcresulttype = TEXTOID;
11505 keyf->funcretset = false;
11506 keyf->funcvariadic = false;
11507 keyf->funcformat = COERCE_EXPLICIT_CALL;
11508 keyf->funccollid = DEFAULT_COLLATION_OID;
11509 keyf->inputcollid = DEFAULT_COLLATION_OID;
11510 keyf->args = list_make3(coerce_via_io_to_text((Expr *) rootv),
11511 secarg,
11512 (Expr *) factorc);
11513 keyf->location = -1;
11514
11515 ann->funcid = constants->OID_FUNCTION_ANNOTATE;
11516 ann->funcresulttype = constants->OID_TYPE_UUID;
11517 ann->funcretset = false;
11518 ann->funcvariadic = false;
11519 ann->funcformat = COERCE_EXPLICIT_CALL;
11520 ann->funccollid = InvalidOid;
11521 ann->inputcollid = DEFAULT_COLLATION_OID;
11522 ann->args = list_make2((Expr *) prov_var, (Expr *) keyf);
11523 ann->location = -1;
11524 return (Expr *) ann;
11525}
11526
11527/**
11528 * @brief Replace each certified atom's provenance @c Var in @p prov_atts with
11529 * its per-input-marker-wrapped form (in place).
11530 */
11531static void wrap_inversion_free_markers(const constants_t *constants, Query *q,
11532 List *prov_atts,
11533 const InvFreeMarker *markers,
11534 int natoms) {
11535 ListCell *lc;
11536 foreach (lc, prov_atts) {
11537 Node *n = (Node *) lfirst(lc);
11538 if (IsA(n, Var)) {
11539 Var *pv = (Var *) n;
11540 if (pv->varno >= 1 && (int) pv->varno <= natoms
11541 && markers[pv->varno - 1].valid)
11542 lfirst(lc) = build_inversion_free_marker(constants, q, pv,
11543 &markers[pv->varno - 1]);
11544 }
11545 }
11546}
11547
11548/* -------------------------------------------------------------------------
11549 * Inversion-free: conjunctive flattening of SPJ subqueries/views
11550 * ------------------------------------------------------------------------- */
11551
11552/**
11553 * @brief Where a flattened base atom came from, for mapping markers back.
11554 *
11555 * A slot @em path from the top lineage query down to the base relation: each
11556 * element is a 1-based range-table slot, and the last element is the base's
11557 * position within the innermost subquery. @c depth @c == @c 1 (@c path @c ==
11558 * @c [s]) is a base/kept relation directly at top slot @c s; deeper paths step
11559 * through one nested SPJ subquery per element, so views-over-views map back to
11560 * the right input through the recursive subquery rewrite.
11561 */
11562typedef struct FlatAtomOrigin {
11564 int *path; /* palloc'd, length depth (1-based slot indices) */
11566
11567/** @brief Context for @c flatten_mut (a multi-relation conjunctive inliner). */
11568typedef struct flatten_ctx {
11569 int N; /* original parent range-table length */
11570 bool *slot_flat; /* [1..N]: parent slot is an inlined subquery */
11571 int *parent_newpos; /* [1..N]: new varno of a kept (non-inlined) slot */
11572 int **sub_newpos; /* [1..N] -> [1..sub_rtlen]: new varno of a subquery base */
11573 int *sub_rtlen; /* [1..N]: that subquery's range-table length */
11574 Var ***sub_tl; /* [1..N] -> [1..sub_tl_n]: subquery TL base Var by resno */
11575 int *sub_tl_n; /* [1..N] */
11576 bool quals_mode; /* true while remapping a subquery's pulled-up WHERE */
11577 int quals_slot; /* the inlined parent slot whose WHERE is being remapped */
11578} flatten_ctx;
11579
11580/**
11581 * @brief Tree mutator implementing the conjunctive inlining of SPJ subqueries.
11582 *
11583 * Parent mode (@c quals_mode false): a @c Var on an inlined subquery slot is
11584 * replaced by the base @c Var its target list maps the column to, renumbered to
11585 * that base's new flat position; a @c Var on a kept slot is renumbered to the
11586 * slot's new position. Subquery-WHERE mode (@c quals_mode true): a base @c Var
11587 * inside subquery @c quals_slot is renumbered to its new flat position. Outer
11588 * references (@c varlevelsup > 0) are never touched.
11589 */
11590static Node *flatten_mut(Node *node, void *cp) {
11591 flatten_ctx *c = (flatten_ctx *) cp;
11592 if (node == NULL)
11593 return NULL;
11594 if (IsA(node, Var)) {
11595 Var *v = (Var *) node;
11596 if (v->varlevelsup == 0) {
11597 if (c->quals_mode) {
11598 int i = c->quals_slot;
11599 if ((int) v->varno >= 1 && (int) v->varno <= c->sub_rtlen[i]
11600 && c->sub_newpos[i][v->varno] > 0) {
11601 Var *nv = (Var *) copyObject(v);
11602 nv->varno = c->sub_newpos[i][v->varno];
11603 return (Node *) nv;
11604 }
11605 } else if ((int) v->varno >= 1 && (int) v->varno <= c->N) {
11606 int i = (int) v->varno;
11607 if (c->slot_flat[i]) {
11608 if (v->varattno >= 1 && v->varattno <= c->sub_tl_n[i]
11609 && c->sub_tl[i][v->varattno] != NULL) {
11610 Var *base = c->sub_tl[i][v->varattno];
11611 Var *nv = (Var *) copyObject(base);
11612 nv->varno = c->sub_newpos[i][base->varno];
11613 nv->varlevelsup = 0;
11614 return (Node *) nv;
11615 }
11616 } else {
11617 Var *nv = (Var *) copyObject(v);
11618 nv->varno = c->parent_newpos[i];
11619 return (Node *) nv;
11620 }
11621 }
11622 }
11623 return (Node *) copyObject(v);
11624 }
11625 return expression_tree_mutator(node, flatten_mut, cp);
11626}
11627
11628/** @brief A depth-1 origin path @c [slot]. */
11629static FlatAtomOrigin *flat_origin1(int slot) {
11630 FlatAtomOrigin *o = (FlatAtomOrigin *) palloc(sizeof(FlatAtomOrigin));
11631 o->depth = 1;
11632 o->path = (int *) palloc(sizeof(int));
11633 o->path[0] = slot;
11634 return o;
11635}
11636
11637/** @brief Prepend @p slot to @p sub's path, for an atom inlined one level up. */
11639 FlatAtomOrigin *o = (FlatAtomOrigin *) palloc(sizeof(FlatAtomOrigin));
11640 int d;
11641 o->depth = sub->depth + 1;
11642 o->path = (int *) palloc(o->depth * sizeof(int));
11643 o->path[0] = slot;
11644 for (d = 0; d < sub->depth; d++)
11645 o->path[d + 1] = sub->path[d];
11646 return o;
11647}
11648
11649/* Forward declaration: the flattener recurses into nested subqueries. */
11650static FlatAtomOrigin *flatten_spj_subqueries(Query *probe, int *nflat_out);
11651
11652/**
11653 * @brief In place, inline every SPJ subquery/view of @p probe into its base
11654 * relations, flattening to one conjunction of base atoms.
11655 *
11656 * A range-table slot is inlined when it is a non-lateral @c RTE_SUBQUERY whose
11657 * subquery is a plain SELECT (no aggregation, grouping, DISTINCT, set
11658 * operation, sublink, CTE or LIMIT), whose @c FROM is flat @c RangeTblRefs over
11659 * base @c RTE_RELATIONs (PG 14/15 view OLD/NEW placeholders ignored; one or
11660 * more bases -- a view with a join inside is fine), and whose non-junk target
11661 * list entries are all plain @c Vars on those bases. Such a subquery is a pure
11662 * SPJ over base relations: its bases are appended in place of the slot, the
11663 * parent's column references are substituted by the corresponding base columns,
11664 * and the subquery's WHERE is pulled up, yielding an equivalent flat
11665 * conjunction. The parent's own @c FROM must already be flat @c RangeTblRefs
11666 * (the detector requires this too); an explicit @c JoinExpr there carries
11667 * ON-conditions a fromlist rebuild would drop, so flattening is declined.
11668 *
11669 * @param probe the (throwaway) query copy to flatten in place.
11670 * @param nflat_out set to the flattened range-table length.
11671 * @return a palloc'd @c FlatAtomOrigin per flattened position, mapping it back
11672 * to the parent slot (and, for an inlined subquery, the base position within
11673 * it) so the detector's per-atom markers can be threaded to the right input.
11674 */
11675static FlatAtomOrigin *flatten_spj_subqueries(Query *probe, int *nflat_out) {
11676 int N = list_length(probe->rtable);
11677 flatten_ctx c;
11678 List *new_rtable = NIL;
11679 List *origins_l = NIL;
11680 List *merged_quals = NIL;
11681 bool any_flat = false, parent_flat = (probe->jointree != NULL);
11682 int i, newpos = 0;
11683 ListCell *lc;
11684 FlatAtomOrigin *origins;
11685
11686 c.N = N;
11687 c.slot_flat = (bool *) palloc0((N + 1) * sizeof(bool));
11688 c.parent_newpos = (int *) palloc0((N + 1) * sizeof(int));
11689 c.sub_newpos = (int **) palloc0((N + 1) * sizeof(int *));
11690 c.sub_rtlen = (int *) palloc0((N + 1) * sizeof(int));
11691 c.sub_tl = (Var ***) palloc0((N + 1) * sizeof(Var **));
11692 c.sub_tl_n = (int *) palloc0((N + 1) * sizeof(int));
11693 c.quals_mode = false;
11694 c.quals_slot = 0;
11695
11696 if (parent_flat)
11697 foreach (lc, probe->jointree->fromlist)
11698 if (!IsA((Node *) lfirst(lc), RangeTblRef)) { parent_flat = false; break; }
11699
11700 /* Layout pass: decide which slots inline, append base atoms / kept slots to
11701 * new_rtable, and record each new position's origin. */
11702 for (i = 1; parent_flat && i <= N; i++) {
11703 RangeTblEntry *rte = list_nth_node(RangeTblEntry, probe->rtable, i - 1);
11704 Query *sq;
11705 bool ok;
11706 int maxres = 0, b;
11707 ListCell *lc2;
11708 Var **tl;
11709 FlatAtomOrigin *sub_origins = NULL;
11710 int sub_n = 0;
11711
11712 if (!(rte->rtekind == RTE_SUBQUERY && rte->subquery != NULL && !rte->lateral)) {
11713 newpos++;
11714 new_rtable = lappend(new_rtable, rte);
11715 c.parent_newpos[i] = newpos;
11716 origins_l = lappend(origins_l, flat_origin1(i));
11717 continue;
11718 }
11719 sq = rte->subquery;
11720 ok = !(sq->commandType != CMD_SELECT
11721 || sq->setOperations || sq->hasAggs || sq->hasWindowFuncs
11722 || sq->groupingSets || sq->groupClause || sq->havingQual
11723 || sq->distinctClause || sq->hasDistinctOn || sq->hasSubLinks
11724 || sq->limitCount || sq->limitOffset || sq->cteList
11725 || sq->jointree == NULL);
11726 if (ok)
11727 foreach (lc2, sq->jointree->fromlist)
11728 if (!IsA((Node *) lfirst(lc2), RangeTblRef)) { ok = false; break; }
11729 /* Recursively flatten this subquery's own SPJ subqueries first, so a
11730 * view-over-views collapses to base atoms before we inline it. Mutates
11731 * sq (a node in the throwaway probe) in place; sub_origins maps sq's
11732 * flattened positions back to paths within sq, which we prepend our slot to
11733 * so the marker reaches the right base input through the nested rewrite. */
11734 if (ok)
11735 sub_origins = flatten_spj_subqueries(sq, &sub_n);
11736 /* every range-table entry a real base relation or a view artifact */
11737 if (ok) {
11738 int realbase = 0;
11739 foreach (lc2, sq->rtable) {
11740 RangeTblEntry *br = (RangeTblEntry *) lfirst(lc2);
11741 if (br->rtekind == RTE_RELATION && br->relkind == RELKIND_VIEW)
11742 continue; /* OLD/NEW placeholder */
11743 else if (br->rtekind == RTE_RELATION) realbase++;
11744 else { ok = false; break; } /* join / nested subquery inside */
11745 }
11746 if (realbase < 1) ok = false;
11747 }
11748 /* non-junk target list entries all plain Vars on a base relation */
11749 if (ok)
11750 foreach (lc2, sq->targetList) {
11751 TargetEntry *te = (TargetEntry *) lfirst(lc2);
11752 if (!te->resjunk && te->resno > maxres) maxres = te->resno;
11753 }
11754 tl = ok ? (Var **) palloc0((maxres + 1) * sizeof(Var *)) : NULL;
11755 if (ok) {
11756 foreach (lc2, sq->targetList) {
11757 TargetEntry *te = (TargetEntry *) lfirst(lc2);
11758 Var *v;
11759 RangeTblEntry *br;
11760 if (te->resjunk) continue;
11761 if (!IsA(te->expr, Var)) { ok = false; break; }
11762 v = (Var *) te->expr;
11763 if (v->varlevelsup != 0
11764 || (int) v->varno < 1 || (int) v->varno > list_length(sq->rtable)) {
11765 ok = false; break;
11766 }
11767 br = list_nth_node(RangeTblEntry, sq->rtable, v->varno - 1);
11768 if (!(br->rtekind == RTE_RELATION && br->relkind != RELKIND_VIEW)) {
11769 ok = false; break;
11770 }
11771 tl[te->resno] = v;
11772 }
11773 }
11774
11775 if (!ok) {
11776 /* not flattenable: keep the slot as-is (detector will reject it) */
11777 if (tl) pfree(tl);
11778 newpos++;
11779 new_rtable = lappend(new_rtable, rte);
11780 c.parent_newpos[i] = newpos;
11781 origins_l = lappend(origins_l, flat_origin1(i));
11782 continue;
11783 }
11784
11785 /* inline: append each real base, assigning it a new flat position */
11786 c.slot_flat[i] = true;
11787 c.sub_rtlen[i] = list_length(sq->rtable);
11788 c.sub_newpos[i] = (int *) palloc0((c.sub_rtlen[i] + 1) * sizeof(int));
11789 c.sub_tl[i] = tl;
11790 c.sub_tl_n[i] = maxres;
11791 b = 0;
11792 foreach (lc2, sq->rtable) {
11793 RangeTblEntry *br = (RangeTblEntry *) lfirst(lc2);
11794 ++b;
11795 if (br->rtekind == RTE_RELATION && br->relkind != RELKIND_VIEW) {
11796 newpos++;
11797 new_rtable = lappend(new_rtable, copyObject(br));
11798 c.sub_newpos[i][b] = newpos;
11799 /* compose: our slot, then the base's path within the (already
11800 * recursively flattened) subquery -- so nested views map all the way
11801 * down to the base input. */
11802 origins_l = lappend(origins_l,
11803 (sub_origins != NULL && b - 1 < sub_n)
11804 ? flat_origin_prepend(i, &sub_origins[b - 1])
11805 : flat_origin1(i));
11806 }
11807 }
11808 any_flat = true;
11809 }
11810
11811 if (parent_flat && any_flat) {
11812 /* (1) remap the parent's target list and WHERE */
11813 probe->targetList = (List *) flatten_mut((Node *) probe->targetList, &c);
11814 if (probe->jointree->quals)
11815 merged_quals = lappend(merged_quals, flatten_mut(probe->jointree->quals, &c));
11816 /* (2) pull every inlined subquery's WHERE up, remapping base varnos */
11817 for (i = 1; i <= N; i++) {
11818 RangeTblEntry *rte;
11819 if (!c.slot_flat[i]) continue;
11820 rte = list_nth_node(RangeTblEntry, probe->rtable, i - 1);
11821 if (rte->subquery->jointree && rte->subquery->jointree->quals) {
11822 c.quals_mode = true; c.quals_slot = i;
11823 merged_quals =
11824 lappend(merged_quals,
11825 flatten_mut((Node *) copyObject(rte->subquery->jointree->quals),
11826 &c));
11827 c.quals_mode = false;
11828 }
11829 }
11830 /* (3) commit the flattened range table, a flat fromlist and combined WHERE */
11831 probe->rtable = new_rtable;
11832 {
11833 List *fl = NIL;
11834 for (i = 1; i <= newpos; i++) {
11835 RangeTblRef *r = makeNode(RangeTblRef);
11836 r->rtindex = i;
11837 fl = lappend(fl, r);
11838 }
11839 probe->jointree->fromlist = fl;
11840 }
11841 probe->jointree->quals =
11842 (merged_quals == NIL) ? NULL
11843 : (list_length(merged_quals) == 1) ? (Node *) linitial(merged_quals)
11844 : (Node *) makeBoolExpr(AND_EXPR, merged_quals, -1);
11845
11846 *nflat_out = newpos;
11847 origins = (FlatAtomOrigin *) palloc(newpos * sizeof(FlatAtomOrigin));
11848 i = 0;
11849 foreach (lc, origins_l)
11850 origins[i++] = *(FlatAtomOrigin *) lfirst(lc);
11851 return origins;
11852 }
11853
11854 /* Nothing flattened (no flattenable subquery, or a non-flat parent FROM):
11855 * leave probe untouched and return an identity map (one depth-1 path per slot). */
11856 *nflat_out = N;
11857 origins = (FlatAtomOrigin *) palloc(N * sizeof(FlatAtomOrigin));
11858 for (i = 0; i < N; i++) {
11859 origins[i].depth = 1;
11860 origins[i].path = (int *) palloc(sizeof(int));
11861 origins[i].path[0] = i + 1;
11862 }
11863 return origins;
11864}
11865
11866/**
11867 * @brief Build the inversion-free marker context for top-level query @p q.
11868 *
11869 * Runs the detector on a flattened, group-RTE-stripped copy of @p q so that
11870 * single-base SPJ subqueries/views are recognised as base atoms. On success
11871 * sets @p *cert_out to the serialised root certificate and returns a context
11872 * tree mirroring @p q's range table: a direct base atom's marker at its slot,
11873 * a flattened subquery's marker in a one-entry child context at its slot.
11874 * Returns NULL (declining) when @p q is not certified or carries no markers;
11875 * @p *cert_out may still be set (the cert attaches even without markers, and
11876 * the path then declines at evaluation and falls back).
11877 */
11879 Query *q, char **cert_out) {
11880 bool has_subq = false, has_group = false;
11881 Query *probe;
11882 FlatAtomOrigin *origins = NULL;
11883 InvFreeMarker *flat = NULL;
11884 int nflat = 0, norigins = 0, N, p;
11885 char *cert = NULL;
11886 InvFreeMarkerCtx *ctx;
11887 ListCell *lc;
11888
11889 foreach (lc, q->rtable)
11890 if (((RangeTblEntry *) lfirst(lc))->rtekind == RTE_SUBQUERY) has_subq = true;
11891#if PG_VERSION_NUM >= 180000
11892 has_group = q->hasGroupRTE;
11893#endif
11894
11895 /* No subqueries and no synthetic group RTE: analyse q in place (read-only),
11896 * positions equal q's slots. Otherwise work on a copy: strip the PG 18 group
11897 * RTE, then flatten SPJ subqueries (origins map flattened positions back). */
11898 if (!has_subq && !has_group) {
11899 probe = q;
11900 } else {
11901 probe = (Query *) copyObject(q);
11902#if PG_VERSION_NUM >= 180000
11903 if (has_group)
11904 strip_group_rte_pg18(probe);
11905#endif
11906 if (has_subq)
11907 origins = flatten_spj_subqueries(probe, &norigins);
11908 }
11909
11910 if (!inversion_free_analyze(constants, probe, &cert, &flat, &nflat))
11911 return NULL;
11912 if (cert_out)
11913 *cert_out = cert;
11914 if (flat == NULL) /* certified but no marker model: decline */
11915 return NULL;
11916
11917 N = list_length(q->rtable);
11918 ctx = (InvFreeMarkerCtx *) palloc0(sizeof(InvFreeMarkerCtx));
11919 ctx->natoms = N;
11920 ctx->markers = (InvFreeMarker *) palloc0((size_t) N * sizeof(InvFreeMarker));
11921 ctx->sub = (InvFreeMarkerCtx **) palloc0((size_t) N * sizeof(InvFreeMarkerCtx *));
11922 /* Map each flattened atom's marker back to q by walking its origin slot path
11923 * down the *original* (un-flattened) query tree, creating/sizing a nested
11924 * child context at each subquery hop, so the recursive subquery rewrite later
11925 * threads the marker to the right base input. With no flattening, position
11926 * == q slot (the synthetic group RTE, if any, sits after the base atoms, so
11927 * the prefix aligns), i.e. an implicit depth-1 path. */
11928 for (p = 0; p < nflat; p++) {
11929 int tmp_path[1];
11930 int *path;
11931 int depth, d, base;
11932 InvFreeMarkerCtx *cur = ctx;
11933 Query *qcur = q;
11934 if (!flat[p].valid)
11935 continue;
11936 if (origins != NULL) {
11937 if (p >= norigins) continue;
11938 path = origins[p].path;
11939 depth = origins[p].depth;
11940 } else {
11941 tmp_path[0] = p + 1;
11942 path = tmp_path;
11943 depth = 1;
11944 }
11945 /* descend all but the last path element (the nested subquery slots) */
11946 for (d = 0; d + 1 < depth && cur != NULL; d++) {
11947 int slot = path[d];
11948 RangeTblEntry *rte;
11949 InvFreeMarkerCtx *child;
11950 int sublen;
11951 if (slot < 1 || slot > qcur->rtable->length) { cur = NULL; break; }
11952 rte = list_nth_node(RangeTblEntry, qcur->rtable, slot - 1);
11953 if (rte->rtekind != RTE_SUBQUERY || rte->subquery == NULL) { cur = NULL; break; }
11954 sublen = list_length(rte->subquery->rtable);
11955 child = cur->sub[slot - 1];
11956 if (child == NULL) {
11957 child = (InvFreeMarkerCtx *) palloc0(sizeof(InvFreeMarkerCtx));
11958 child->natoms = sublen;
11959 child->markers = (InvFreeMarker *) palloc0((size_t) sublen * sizeof(InvFreeMarker));
11960 child->sub = (InvFreeMarkerCtx **) palloc0((size_t) sublen * sizeof(InvFreeMarkerCtx *));
11961 cur->sub[slot - 1] = child;
11962 }
11963 cur = child;
11964 qcur = rte->subquery;
11965 }
11966 if (cur == NULL)
11967 continue;
11968 base = path[depth - 1]; /* base slot at the leaf */
11969 if (base >= 1 && base - 1 < cur->natoms)
11970 cur->markers[base - 1] = flat[p];
11971 }
11972 return ctx;
11973}
11974
11975/**
11976 * @brief Process the inert provenance() fetches in one query's own clauses.
11977 *
11978 * Walks @p q's target list, jointree and HAVING for scalar SubLinks whose
11979 * subselect's sole output is provenance() (@c subselect_is_pure_provenance_fetch),
11980 * runs @c process_query on each such subselect (resolving its provenance()
11981 * to that scope's token, with no provsql column appended), and records the
11982 * processed subselect so later coupling-time detectors treat it as
11983 * untracked. Does not descend into nested queries: a FROM subquery, or a
11984 * non-inert sublink's subselect, runs its own pass when @c process_query
11985 * reaches it.
11986 */
11987typedef struct { const constants_t *constants; } inert_walk_ctx;
11988
11989/**
11990 * @brief Make a processed inert subselect return exactly its provenance
11991 * token as a single column.
11992 *
11993 * After @c process_query (with @c wrap_root false, so the token is the
11994 * plain provenance expression, not @c assume_boolean-wrapped) the subselect
11995 * carries the resolved provenance() value, an auto-appended @c provsql
11996 * column, and possibly resjunk grouping / ordering keys. An inert scalar
11997 * fetch must return exactly one non-junk column: keep the resolved
11998 * provenance() value (the first non-junk entry that is not the appended
11999 * @c provsql), drop the @c provsql duplicate, and retain the resjunk
12000 * entries that GROUP BY / ORDER BY still reference.
12001 */
12002static void keep_only_provenance_output(Query *sub) {
12003 ListCell *lc;
12004 TargetEntry *value = NULL;
12005 List *kept;
12006 int n = 0;
12007 foreach (lc, sub->targetList) {
12008 TargetEntry *te = (TargetEntry *)lfirst(lc);
12009 if (te->resjunk)
12010 continue;
12011 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME))
12012 continue; /* the appended duplicate */
12013 value = te; /* the resolved provenance() value */
12014 break;
12015 }
12016 if (value == NULL)
12017 return; /* defensive */
12018 kept = list_make1(value);
12019 foreach (lc, sub->targetList) {
12020 TargetEntry *te = (TargetEntry *)lfirst(lc);
12021 if (te != value && te->resjunk)
12022 kept = lappend(kept, te); /* grouping / ordering keys */
12023 }
12024 foreach (lc, kept)
12025 ((TargetEntry *)lfirst(lc))->resno = ++n;
12026 sub->targetList = kept;
12027}
12028
12029static bool process_inert_fetches_walker(Node *node, void *cx) {
12030 inert_walk_ctx *ctx = (inert_walk_ctx *)cx;
12031 if (node == NULL)
12032 return false;
12033 if (IsA(node, SubLink)) {
12034 SubLink *sl = (SubLink *)node;
12035 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
12036 IsA(sl->subselect, Query) &&
12038 (Query *)sl->subselect)) {
12039 bool *removed = NULL;
12040 Query *processed = process_query(ctx->constants, (Query *)sl->subselect,
12041 &removed, false, false, false, NULL);
12042 keep_only_provenance_output(processed);
12043 sl->subselect = (Node *)processed;
12045 return false; /* handled; do not descend into it */
12046 }
12047 /* A non-inert sublink: descend into its testexpr only, not its
12048 * subselect (a different scope, handled on its own). */
12049 return expression_tree_walker((Node *) sl->testexpr,
12051 }
12052 if (IsA(node, Query))
12053 return false; /* a nested query scope: not this pass's job */
12054 return expression_tree_walker(node, process_inert_fetches_walker, cx);
12055}
12056
12057static void process_inert_fetches(const constants_t *constants, Query *q) {
12058 inert_walk_ctx ctx = { constants };
12059 process_inert_fetches_walker((Node *)q->targetList, &ctx);
12060 if (q->jointree)
12061 process_inert_fetches_walker((Node *)q->jointree, &ctx);
12062 if (q->havingQual)
12063 process_inert_fetches_walker(q->havingQual, &ctx);
12064}
12065
12066/**
12067 * @brief Rewrite a single SELECT query to carry provenance.
12068 *
12069 * This is the recursive entry point for the provenance rewriter. It is
12070 * called from @c provsql_planner for top-level queries and re-entered from
12071 * @c get_provenance_attributes for subqueries in FROM.
12072 *
12073 * High-level steps:
12074 * 1. Strip any @c provsql column propagated into this query's target list.
12075 * 2. Detect and rewrite structural forms requiring pre-processing:
12076 * non-ALL set operations (wrap in outer GROUP BY), AGG DISTINCT (push
12077 * into a subquery), DISTINCT (convert to GROUP BY).
12078 * 3. Collect provenance attributes via @c get_provenance_attributes.
12079 * 4. Build a column-numbering map for where-provenance (@c build_column_map).
12080 * 5. Handle aggregates, migrate WHERE-on-aggregate to HAVING, and set ops.
12081 * 6. Build and splice the combined provenance expression.
12082 *
12083 * @param constants Extension OID cache.
12084 * @param q Query to rewrite (modified in place).
12085 * @param removed Out-param: boolean array indicating which original target
12086 * list entries were provenance columns and were removed.
12087 * May be @c NULL if the caller does not need this info.
12088 * @param wrap_root If true, mark this query's provenance expression as a
12089 * safe-query root that must be wrapped in
12090 * @c provsql.assume_boolean before splicing.
12091 * @param top_level True for the outermost query the user evaluates; gates the
12092 * inversion-free analysis (run only at the top).
12093 * @param in_boolean_rewrite True once a safe-query (boolean) rewrite has fired
12094 * above; propagated through every recursion (including into
12095 * subqueries, where @c wrap_root is otherwise lost) so the
12096 * joint-width recogniser defers to the safe rewrite everywhere
12097 * in its subtree.
12098 * @param inv_ctx Inversion-free marker context supplied by a parent that
12099 * flattened this query as a subquery, or @c NULL; when set,
12100 * this query applies the supplied per-input markers instead of
12101 * running its own analysis or read-once rewrite.
12102 * @return The (possibly restructured) rewritten query, or @c NULL if the
12103 * query has no FROM clause and can be skipped.
12104 */
12105static Query *process_query(const constants_t *constants, Query *q,
12106 bool **removed, bool wrap_root, bool top_level,
12107 bool in_boolean_rewrite,
12108 const InvFreeMarkerCtx *inv_ctx) {
12109 List *prov_atts;
12110 bool has_union = false;
12111 bool has_difference = false;
12112 bool supported = true;
12113 bool group_by_rewrite = false;
12114 int nbcols = 0;
12115 int **columns = NULL;
12116 int columns_len = 0;
12117 unsigned i = 0;
12118 char *inv_cert = NULL; /* serialised inversion-free certificate (root) */
12119 const InvFreeMarkerCtx *local_inv_ctx = NULL; /* this query's marker context */
12120 List *given_evidence = NIL; /* captured given(...) whole-tuple evidence */
12121 if (provsql_verbose >= 50)
12122 elog_node_display(NOTICE, "ProvSQL: Before query rewriting", q, true);
12123
12124 /* Inert provenance() fetches: resolve a scalar `(SELECT provenance()
12125 * FROM R ...)` to that subquery's token in place and record it as
12126 * untracked. Runs *before* the FROM-less early return below (a
12127 * `SELECT (SELECT provenance() ...)` has no outer rtable but still
12128 * carries the fetch) and before the decorrelation passes that would
12129 * otherwise couple it into the outer lineage. */
12130 if (provsql_active)
12131 process_inert_fetches(constants, q);
12132
12133 /* Natural Boolean-predicate conditioning: rewrite "X | (predicate)" into
12134 * the carrier's conditioning constructor over the converted condition gate
12135 * (cond / random_variable_cond / agg_token_cond, and given(...) for the
12136 * prefix whole-tuple form). Runs before the FROM-less early return (an
12137 * rv-conditioning query commonly has no FROM), before the given()-marker
12138 * strip (which then sees the emitted given() call), and before
12139 * migrate_probabilistic_quals (so a comparison inside the predicate is
12140 * consumed here, not lifted as a WHERE qual). */
12141 if (provsql_active)
12142 rewrite_cond_predicates(constants, q);
12143
12144 /* Normalise a bare boolean aggregate used as a HAVING condition (HAVING
12145 * bool_or(x), HAVING NOT(every(x))) to "agg = true", while the aggregate is
12146 * still a raw Aggref -- before it is replaced by a provenance_aggregate.
12147 * The existing aggregate-comparison recognition and the boolean-domain
12148 * HAVING evaluator then handle it. */
12149 if (provsql_active && q->havingQual != NULL)
12150 q->havingQual = normalize_bool_agg_having((Node *) q->havingQual);
12151
12152 if (q->rtable == NULL) {
12153 /* FROM-less SELECT: the rest of the rewriter indexes into
12154 * q->rtable, so it can't process anything tied to a base relation.
12155 * But a WHERE-on-RV is still meaningful in this shape (e.g.
12156 * SELECT 1 WHERE normal(0,1) > 2)
12157 * since the comparison produces a pure-rv gate that's lifted into
12158 * a synthesised provsql column on the single result row. Run only
12159 * the qual migration + targetList splice and return; everything
12160 * else this function does (column mapping, set-ops, aggregation
12161 * rewriting, ...) assumes a non-empty rtable. */
12162 List *rv_cmps = migrate_probabilistic_quals(constants, q);
12163 if (rv_cmps != NIL) {
12164 Expr *provenance;
12165 RangeTblEntry *values_rte;
12166 RangeTblRef *rtr;
12167 Var *v;
12168
12169 if (list_length(rv_cmps) == 1) {
12170 provenance = (Expr *)linitial(rv_cmps);
12171 } else {
12172 /* Multiple rv conjuncts: combine via provenance_times. */
12173 FuncExpr *times = makeNode(FuncExpr);
12174 ArrayExpr *array = makeNode(ArrayExpr);
12175 times->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
12176 times->funcresulttype = constants->OID_TYPE_UUID;
12177 times->funcvariadic = true;
12178 times->location = -1;
12179 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
12180 array->element_typeid = constants->OID_TYPE_UUID;
12181 array->elements = rv_cmps;
12182 array->location = -1;
12183 times->args = list_make1(array);
12184 provenance = (Expr *)times;
12185 }
12186
12187 /* Bind the lifted expression to a single evaluation by wrapping
12188 * it in a synthesized FROM (VALUES (<expr>)) AS _prov_(provsql).
12189 * Without this, multiple references to the same provenance
12190 * expression in the outer targetList (the user's provenance()
12191 * call, plus the auto-added provsql column) each re-invoke any
12192 * rv constructor inside, producing distinct UUIDs per call
12193 * because uniform / normal / ... mint a fresh leaf gate each
12194 * time. Wrapping in VALUES gives one evaluation site that all
12195 * outer references read from. */
12196 values_rte = makeNode(RangeTblEntry);
12197 values_rte->rtekind = RTE_VALUES;
12198 values_rte->values_lists = list_make1(list_make1(provenance));
12199 values_rte->coltypes = list_make1_oid(constants->OID_TYPE_UUID);
12200 values_rte->coltypmods = list_make1_int(-1);
12201 values_rte->colcollations = list_make1_oid(InvalidOid);
12202 values_rte->eref = makeAlias(
12203 "_prov_",
12204 list_make1(makeString(pstrdup(PROVSQL_COLUMN_NAME))));
12205 values_rte->inh = false;
12206 values_rte->inFromCl = true;
12207#if PG_VERSION_NUM < 160000
12208 values_rte->requiredPerms = 0;
12209#endif
12210 q->rtable = list_make1(values_rte);
12211
12212 rtr = makeNode(RangeTblRef);
12213 rtr->rtindex = 1;
12214 if (q->jointree == NULL) {
12215 q->jointree = makeNode(FromExpr);
12216 }
12217 q->jointree->fromlist = list_make1(rtr);
12218
12219 v = makeVar(1, 1, constants->OID_TYPE_UUID, -1, InvalidOid, 0);
12220
12221 /* Substitute any provenance() FuncExpr in the targetList with
12222 * a reference to the bound expression. */
12223 replace_provenance_function_by_expression(constants, q, (Expr *)v);
12224
12225 /* Append a provsql column reading the same Var so callers that
12226 * expect the auto-added column find it. */
12227 {
12228 TargetEntry *te = makeTargetEntry(
12229 (Expr *)copyObject(v),
12230 list_length(q->targetList) + 1,
12231 pstrdup(PROVSQL_COLUMN_NAME),
12232 false);
12233 q->targetList = lappend(q->targetList, te);
12234 }
12235 }
12236 return q;
12237 }
12238
12239 /* Normalise SELECT DISTINCT into the equivalent GROUP BY *before*
12240 * inlining: the recursive-reachability aggregation detectors
12241 * (detect_reach_aggregations / detect_reach_conjunctions, run inside
12242 * inline_ctes) key on groupClause, and a DISTINCT aggregation is
12243 * provenance-identical to its GROUP BY twin -- normalising here lets
12244 * them recognise it with no DISTINCT-specific arm. Idempotent with
12245 * the late site below. */
12246 if (provsql_active)
12248
12249 /* Inline non-recursive CTE references as subqueries so we can track
12250 * provenance through them. Must happen before set operation handling
12251 * since UNION/EXCEPT branches may reference CTEs. Gated on
12252 * provsql.active: when provenance tracking is off the hook must stand
12253 * back and let the query plan as ordinary SQL -- it must not, in
12254 * particular, drive the recursive-CTE fixpoint (eval_recursive), which
12255 * runs SPI and creates temp tables at plan time. */
12256 if (provsql_active)
12257 inline_ctes(q);
12258
12259 /* Decorrelate a top-level scalar subquery into a LEFT JOIN + choose() +
12260 * GROUP BY + count<=1 HAVING. Runs before lower_outer_joins so the LEFT JOIN
12261 * it produces is lowered with correct outer-join provenance, and before the
12262 * "Subqueries not supported" guard further down. */
12263 if (provsql_active) {
12264 rewrite_array_sublinks(constants, q);
12266 rewrite_uncorrelated_antijoin(constants, q);
12267 rewrite_predicate_sublinks(constants, q);
12270 decorrelate_scalar_sublinks(constants, q);
12271 }
12272
12273 /* Lower a top-level outer JOIN (LEFT / RIGHT / FULL) of two base relations
12274 * into the UNION-ALL of its matched and null-padded antijoin arms, so the
12275 * non-monotone outer-join provenance (the 0-match world) is captured. No-op
12276 * on every other shape. Runs before provenance discovery / set-op handling
12277 * so the constructed UNION / EXCEPT subqueries are processed by the recursive
12278 * passes. */
12279 if (provsql_active)
12280 lower_outer_joins(constants, q);
12281
12282 {
12283 Bitmapset *removed_sortgrouprefs = NULL;
12284
12285 if (q->targetList) {
12286 removed_sortgrouprefs =
12287 remove_provenance_attributes_select(constants, q, removed);
12288 if (removed_sortgrouprefs != NULL)
12289 remove_provenance_attribute_groupref(q, removed_sortgrouprefs);
12290 if (q->setOperations)
12292 }
12293 }
12294
12295 /* Whole-tuple output conditioning: strip any given(...) marker now (before
12296 * the aggregation / set-operation restructuring below, which would
12297 * renumber the Vars in the captured per-row evidence), and condition this
12298 * query's output provenance on each captured evidence at splice time. The
12299 * marker is meaningful only for a per-row projection: an aggregated /
12300 * grouped / set-operation / DISTINCT query has no single output row to
12301 * condition, so reject it with a clear message rather than silently
12302 * conditioning an aggregate. */
12303 if (provsql_active && q->targetList) {
12304 given_evidence = strip_given_markers(constants, q);
12305 if (given_evidence != NIL &&
12306 (q->hasAggs || q->groupClause || q->groupingSets || q->havingQual ||
12307 q->distinctClause || q->setOperations || q->hasWindowFuncs))
12309 "provsql.given (whole-tuple output conditioning) is supported only in "
12310 "a plain per-row SELECT, not in an aggregated / grouped / DISTINCT / "
12311 "set-operation query; condition the individual tokens with the binary "
12312 "| operator instead");
12313 }
12314
12315 if(provsql_active) {
12316 if (q->setOperations) {
12317 // TODO: Nest set operations as subqueries in FROM,
12318 // so that we only do set operations on base tables
12319
12320 SetOperationStmt *stmt = (SetOperationStmt *)q->setOperations;
12321 if (!stmt->all) {
12322 /* Check if any branch has aggregates – non-ALL set operations
12323 * on aggregate results are not supported because agg_token
12324 * lacks comparison operators for deduplication */
12325 ListCell *lc_rte;
12326 foreach (lc_rte, q->rtable) {
12327 RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc_rte);
12328 if (rte->rtekind == RTE_SUBQUERY && rte->subquery &&
12329 rte->subquery->hasAggs)
12330 provsql_error("Non-ALL set operations (UNION, EXCEPT) on "
12331 "aggregate results not supported");
12332 }
12334 return process_query(constants, q, removed, wrap_root, top_level,
12335 in_boolean_rewrite, inv_ctx);
12336 }
12337 }
12338
12339 if (q->hasAggs) {
12340 Query *rewritten = rewrite_agg_distinct(q, constants);
12341 if (rewritten)
12342 return process_query(constants, rewritten, removed, wrap_root, top_level,
12343 in_boolean_rewrite, inv_ctx);
12344 }
12345
12346 /* Rewrite any JOIN on an agg_token column before provenance
12347 * discovery, so get_provenance_attributes sees the already-correct
12348 * subquery with a proper provsql column. */
12349 {
12350 Index rteid;
12351 AttrNumber join_attno;
12352
12353 if (join_qual_has_agg_token((Node *)q->jointree, constants, &rteid,
12354 &join_attno))
12355 {
12356 Query *rewritten = rewrite_join_agg_token(q, constants, rteid, join_attno);
12357 if (rewritten)
12358 return process_query(constants, rewritten, removed, wrap_root,
12359 top_level, in_boolean_rewrite, inv_ctx);
12360 }
12361 }
12362
12363 /* Opt-in safe-query optimisation slot: when on, try to rewrite
12364 * hierarchical conjunctive queries to a read-once form whose
12365 * probability is computable in linear time via independent
12366 * evaluation. See try_safe_query_rewrite().
12367 *
12368 * The rewriter is gated on the presence of the assume_boolean()
12369 * helper (installed by the 1.6.0 upgrade script). Without it we
12370 * cannot wrap the per-row root in a gate_assumed, which is
12371 * what downstream evaluators inspect to refuse unsound evaluation,
12372 * so we refuse to rewrite on schemas that still predate the
12373 * helper. */
12374 if (provsql_boolean_provenance && inv_ctx == NULL &&
12375 OidIsValid(constants->OID_FUNCTION_ASSUME_BOOLEAN)) {
12376 /* Read-once rewrite (an operation-mode change: it rewrites the query and
12377 * changes the produced circuit), so it is gated on boolean_provenance.
12378 * Skipped when @c inv_ctx is supplied: this query is an inlined subquery
12379 * whose base inputs must receive the parent's transparent order markers
12380 * (a no-op rewrite for the single-base projection it then is), not a
12381 * circuit-changing read-once rewrite that would bypass them. */
12382 Query *rewritten = try_safe_query_rewrite(constants, q);
12383 if (rewritten)
12384 /* The whole rewritten subtree is a boolean safe-query rewrite: flag it
12385 * so the joint-width recogniser defers to it everywhere below (the
12386 * signal would otherwise be lost at the subquery boundary). */
12387 return process_query(constants, rewritten, removed, true, top_level,
12388 true, inv_ctx);
12389
12390 }
12391
12392 /* Inversion-free analysis is *not* an operation-mode change: it leaves the
12393 * lineage intact and only attaches a transparent certificate + per-input
12394 * order markers, read back at probability evaluation. So it is decoupled
12395 * from boolean_provenance and gated on its own knob (provsql.inversion_free,
12396 * default on), run on THIS query – the one whose lineage we build – so the
12397 * certificate and markers align with the lineage by construction. Only at
12398 * the outermost (top-level) root the user evaluates; never when the
12399 * read-once rewrite above already fired (that path returns early). */
12400 if (inv_ctx != NULL) {
12401 /* This query is an inlined subquery: the parent's flattened analysis
12402 * already produced our base-atom markers. Apply them as-is; attach no
12403 * certificate here (the cert lives on the parent's per-row root). */
12404 local_inv_ctx = inv_ctx;
12405 } else if (top_level && provsql_inversion_free
12406 && OidIsValid(constants->OID_FUNCTION_ANNOTATE)) {
12407 /* Build the inversion-free marker context tree. The detector runs on a
12408 * flattened copy (single-base SPJ subqueries / views inlined to their
12409 * base relation in place; on PG 18 the synthetic RTE_GROUP is stripped),
12410 * so the certificate and per-input order markers align with the lineage
12411 * by construction; the original q is left intact (only transparent
12412 * markers + a root certificate are added, read back at probability
12413 * evaluation). The evaluator's size-bounded mismatch backstop declines
12414 * if any marker fails to land on its input. */
12415 local_inv_ctx = build_inversion_free_ctx(constants, q, &inv_cert);
12416 }
12417
12418 /* Set difference (EXCEPT / EXCEPT ALL): group the right arm so the per-row
12419 * right provenances ⊕-combine before the monus, giving the paper's NOT-IN
12420 * semantics α ⊖ ⊕β. Must run before get_provenance_attributes processes
12421 * the arms. */
12422 group_set_difference_right_arm(constants, q);
12423
12424 // get_provenance_attributes will also recursively process subqueries
12425 // by calling process_query (threading each subquery's marker sub-context)
12426 prov_atts = get_provenance_attributes(constants, q, in_boolean_rewrite,
12427 local_inv_ctx);
12428
12429 /* Inversion-free path: wrap each certified atom's provenance token in its
12430 * per-input order marker. prov_atts are base-relation Vars (the certified
12431 * class has only RTE_RELATION atoms and no agg/distinct/set-op restructuring,
12432 * so each Var's varno is still the atom's range-table index). */
12433 if (local_inv_ctx != NULL && local_inv_ctx->markers != NULL)
12434 wrap_inversion_free_markers(constants, q, prov_atts,
12435 local_inv_ctx->markers, local_inv_ctx->natoms);
12436
12437 if (prov_atts == NIL) {
12438 /* If the WHERE clause contains a random_variable comparison, we
12439 * still need to take the rewriting path so the result tuple
12440 * carries the comparator's gate_cmp UUID as its provenance.
12441 * Synthesize a single gate_one() prov_att; the combination
12442 * provenance_times(one, rv_cmp) collapses to rv_cmp downstream
12443 * because gate_one is the multiplicative identity. */
12444 if (q->jointree && q->jointree->quals &&
12445 expr_contains_rv_cmp(q->jointree->quals, constants)) {
12446 FuncExpr *one_expr = makeNode(FuncExpr);
12447 one_expr->funcid = constants->OID_FUNCTION_GATE_ONE;
12448 one_expr->funcresulttype = constants->OID_TYPE_UUID;
12449 one_expr->args = NIL;
12450 one_expr->location = -1;
12451 prov_atts = list_make1(one_expr);
12452 } else {
12453 return q;
12454 }
12455 }
12456
12457 if (q->hasSubLinks && query_has_tracked_sublink(constants, q)) {
12458 /* Only sublinks over a provenance-tracked relation are unsupported; one
12459 * whose body touches no tracked relation is a deterministic filter/value
12460 * and is left for Postgres to evaluate (the row keeps R's provenance).
12461 *
12462 * Of the tracked ones, a scalar subquery nested inside a larger expression
12463 * (arithmetic, a function argument) is not decorrelatable by the current
12464 * rewrites, but rather than rejecting it we let it through with a warning:
12465 * Postgres evaluates the sublink (the value is correct), the row keeps the
12466 * outer relation's provenance, and the subquery's data is treated as
12467 * certain. A tracked sublink still in a direct position (a GROUP BY body, a
12468 * multi-relation EXISTS…) is a genuinely unsupported form and still errors. */
12469 bool has_direct = false;
12470 List *nested = classify_remaining_sublinks(constants, q, &has_direct);
12471 if (has_direct || nested == NIL) {
12472 provsql_error("Subqueries (EXISTS, IN, scalar subquery) not supported");
12473 supported = false;
12474 } else {
12476 "scalar subquery nested in an expression is not tracked; its data is "
12477 "treated as certain and the result keeps only the outer provenance");
12478 }
12479 }
12480
12481 /* Normally already normalised before inline_ctes; this late call
12482 * catches any DISTINCT introduced by the intervening rewrites (set
12483 * operations, sublink decorrelation) and is a no-op otherwise. */
12484 if (supported && q->distinctClause)
12486
12487 if (supported && q->setOperations) {
12488 SetOperationStmt *stmt = (SetOperationStmt *)q->setOperations;
12489
12490 if (stmt->op == SETOP_UNION) {
12491 process_set_operation_union(constants, stmt, q);
12492 has_union = true;
12493 } else if (stmt->op == SETOP_EXCEPT) {
12494 if (!transform_except_into_join(constants, q))
12495 supported = false;
12496 has_difference = true;
12497 } else {
12498 provsql_error("Set operations other than UNION and EXCEPT not "
12499 "supported");
12500 supported = false;
12501 }
12502 }
12503
12504 if (supported && q->groupClause &&
12505 !provenance_function_in_group_by(constants, q)) {
12506 group_by_rewrite = true;
12507 }
12508
12509 if (supported && q->groupingSets) {
12510 if (q->groupClause || list_length(q->groupingSets) > 1 ||
12511 ((GroupingSet *)linitial(q->groupingSets))->kind !=
12512 GROUPING_SET_EMPTY) {
12513 provsql_error("GROUPING SETS, CUBE, and ROLLUP not supported");
12514 supported = false;
12515 } else {
12516 // Simple GROUP BY ()
12517 group_by_rewrite = true;
12518 }
12519 }
12520
12521 if (supported) {
12522 /* Sized here, after every rewrite that can grow q->rtable (scalar-
12523 * subquery decorrelation, ARRAY() lowering, outer-join lowering,
12524 * EXCEPT / set-operation transforms…). Sizing it at the top of the
12525 * provsql_active block under-allocated once those rewrites added RTEs,
12526 * and build_column_map then wrote past the end of the array. */
12527 columns_len = q->rtable->length;
12528 columns = (int **)palloc0(columns_len * sizeof(int *));
12529 build_column_map(q, columns, &nbcols);
12530 }
12531
12532 if (supported) {
12533 Expr *provenance;
12534 List *rv_cmps;
12535
12536 /* Window functions are not supported: their per-row result has no
12537 * aggregate-provenance semantics. The query still executes and each
12538 * output row carries its input row's tuple provenance, but the
12539 * windowed computation itself (e.g. SUM() OVER ...) is an opaque
12540 * scalar, not an agg_token. Warn once per rewritten query level that
12541 * actually involves provenance-tracked relations. */
12542 if (q->hasWindowFuncs)
12543 provsql_warning("window functions are not supported; provenance is "
12544 "tracked per input row only, and the windowed "
12545 "computation is treated as an opaque scalar");
12546
12547 /* Single unified pass over WHERE: each top-level conjunct is
12548 * routed to the right evaluation site (HAVING for agg_token,
12549 * the returned rv_cmps list for random_variable, left in WHERE
12550 * otherwise). Mixed shapes raise a clear error. See the
12551 * qual_class doc above for the routing matrix.
12552 *
12553 * Must run before replace_aggregations_by_provenance_aggregate
12554 * so the lifted RV cmps factor into each row's contribution to
12555 * any surrounding agg_token: otherwise the cmp lands at group
12556 * level with row-typed Vars the executor cannot resolve, or
12557 * gets discarded by the HAVING-replaces-result branch of
12558 * make_provenance_expression.
12559 *
12560 * Skipped for SR_PLUS / SR_MONUS (UNION / EXCEPT outer level):
12561 * each branch is rewritten by its own recursive process_query
12562 * call, so an outer-level WHERE on RV here is exotic; the
12563 * fallback after make_provenance_expression handles it. */
12564 rv_cmps = migrate_probabilistic_quals(constants, q);
12565 if (rv_cmps != NIL && !has_union && !has_difference) {
12566 prov_atts = list_concat(prov_atts, rv_cmps);
12567 rv_cmps = NIL;
12568 }
12569
12570 if (q->hasAggs) {
12571 ListCell *lc_sort;
12572
12573 // Compute aggregation expressions
12575 constants, q, prov_atts,
12576 has_union ? SR_PLUS : (has_difference ? SR_MONUS : SR_TIMES));
12577
12578 // If there are any sort clauses on something whose type is now
12579 // aggregate token, we throw an error: sorting aggregation values
12580 // when provenance is captured is ill-defined
12581 foreach (lc_sort, q->sortClause) {
12582 SortGroupClause *sort = (SortGroupClause *)lfirst(lc_sort);
12583 ListCell *lc_te;
12584 foreach (lc_te, q->targetList) {
12585 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
12586 if (sort->tleSortGroupRef == te->ressortgroupref) {
12587 if (exprType((Node *)te->expr) == constants->OID_TYPE_AGG_TOKEN)
12588 provsql_error("ORDER BY on the result of an aggregate function is "
12589 "not supported");
12590 break;
12591 }
12592 }
12593 }
12594 }
12595
12596 /* Insert casts for agg_token Vars used in arithmetic or window
12597 * functions, now that WHERE-to-HAVING migration is done */
12598 insert_agg_token_casts(constants, q);
12599
12601 constants, q, prov_atts, q->hasAggs, group_by_rewrite,
12602 has_union ? SR_PLUS : (has_difference ? SR_MONUS : SR_TIMES), columns,
12603 nbcols, wrap_root, in_boolean_rewrite, inv_cert);
12604
12605 /* Fallback for the rare set-op outer WHERE case: conjoin via
12606 * provenance_times after the aggregation wrappers. Correct only
12607 * when no aggregation collapses rows above this point. */
12608 if (rv_cmps != NIL) {
12609 FuncExpr *times = makeNode(FuncExpr);
12610 ArrayExpr *array = makeNode(ArrayExpr);
12611 times->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
12612 times->funcresulttype = constants->OID_TYPE_UUID;
12613 times->funcvariadic = true;
12614 times->location = -1;
12615 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
12616 array->element_typeid = constants->OID_TYPE_UUID;
12617 array->elements = lcons(provenance, rv_cmps);
12618 array->location = -1;
12619 times->args = list_make1(array);
12620 provenance = (Expr *)times;
12621 }
12622
12623 /* Whole-tuple output conditioning: wrap the per-row provenance in
12624 * cond(row_provenance, evidence) for each captured given(...) marker.
12625 * Done before add_to_select / replace_provenance_function_by_expression
12626 * so the auto-added provsql column AND any user-side provenance() call
12627 * uniformly carry the conditioning (mirrors the assume_boolean wrap).
12628 * Multiple markers accumulate as a conjunction of evidence (cond folds
12629 * (X|A)|B = X|(A∧B)). */
12630 if (given_evidence != NIL && OidIsValid(constants->OID_FUNCTION_COND)) {
12631 ListCell *lc_ev;
12632 foreach (lc_ev, given_evidence)
12633 provenance = wrap_in_cond(constants, provenance, (Expr *)lfirst(lc_ev));
12634 }
12635
12638
12639 if (has_difference)
12640 add_select_non_zero(constants, q, provenance);
12641 }
12642
12643 /* columns is NULL when the query was not supported (build_column_map
12644 * never ran); columns_len is its allocation-time length, in case a later
12645 * step grew q->rtable again. */
12646 for (i = 0; columns != NULL && i < (unsigned)columns_len; ++i) {
12647 if (columns[i])
12648 pfree(columns[i]);
12649 }
12650 }
12651
12652 if (provsql_verbose >= 50)
12653 elog_node_display(NOTICE, "ProvSQL: After query rewriting", q, true);
12654
12655 return q;
12656}
12657
12658/* -------------------------------------------------------------------------
12659 * INSERT ... SELECT provenance propagation
12660 * ------------------------------------------------------------------------- */
12661
12662/**
12663 * @brief Propagate provenance through INSERT ... SELECT.
12664 *
12665 * If the source SELECT involves provenance-tracked tables and the target
12666 * table has a provsql column, rewrites the source SELECT to carry
12667 * provenance and maps its provsql output to the target's provsql column,
12668 * replacing the default uuid_generate_v4().
12669 *
12670 * If the target has no provsql column, emits a warning instead.
12671 */
12672static void process_insert_select(const constants_t *constants, Query *q) {
12673 ListCell *lc;
12674 Index src_rteid = 0;
12675 RangeTblEntry *src_rte = NULL;
12676 RangeTblEntry *tgt_rte;
12677 AttrNumber provsql_attno = 0;
12678 TargetEntry *provsql_te = NULL;
12679 bool provsql_te_is_new = false;
12680
12681 /* Find the source SELECT subquery with provenance */
12682 foreach (lc, q->rtable) {
12683 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
12684 ++src_rteid;
12685 if (r->rtekind == RTE_SUBQUERY && r->subquery &&
12686 has_provenance(constants, r->subquery)) {
12687 src_rte = r;
12688 break;
12689 }
12690 }
12691
12692 if (src_rte == NULL)
12693 return;
12694
12695 /* Rewrite the source SELECT so its own provenance semantics -- HAVING
12696 * lifting, provenance() resolution -- take effect. This must run whether or
12697 * not the target table is provenance-tracked: the old code returned early
12698 * (warning, below) when the target had no provsql column, which left the
12699 * SELECT's HAVING on the physical rows and provenance() unresolved, so the
12700 * INSERT saw zero rows. */
12701 {
12702 bool *removed = NULL;
12703 Query *new_subquery =
12704 process_query(constants, src_rte->subquery, &removed, false, false, false,
12705 NULL);
12706 if (new_subquery == NULL)
12707 return;
12708 src_rte->subquery = new_subquery;
12709 }
12710
12711 /* Check if the target table has a provsql column */
12712 tgt_rte = list_nth_node(RangeTblEntry, q->rtable, q->resultRelation - 1);
12713 if (tgt_rte->rtekind == RTE_RELATION) {
12714 AttrNumber attid = 1;
12715 foreach (lc, tgt_rte->eref->colnames) {
12716 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME) &&
12717 get_atttype(tgt_rte->relid, attid) == constants->OID_TYPE_UUID)
12718 provsql_attno = attid;
12719 ++attid;
12720 }
12721 }
12722
12723 if (provsql_attno == 0) {
12724 /* The target cannot store provenance. The source SELECT was rewritten
12725 * above (so it returns the right rows), but its auto-added provsql column
12726 * has no target column to land in -- drop it so the INSERT's column
12727 * mapping stays consistent, and warn that provenance is not propagated. */
12728 remove_provsql_from_select(src_rte->subquery);
12729 provsql_warning("INSERT ... SELECT on provenance-tracked "
12730 "tables: source provenance is not propagated "
12731 "to inserted rows");
12732 return;
12733 }
12734
12735 /* Find the provsql target entry and verify it's a UUID default */
12736 foreach (lc, q->targetList) {
12737 TargetEntry *te = (TargetEntry *)lfirst(lc);
12738 if (te->resno == provsql_attno &&
12739 exprType((Node *)te->expr) == constants->OID_TYPE_UUID) {
12740 provsql_te = te;
12741 break;
12742 }
12743 }
12744
12745 if (provsql_te == NULL) {
12746 /* The target's provsql column is not in the INSERT's targetList
12747 * (no DEFAULT on the column since 1.6.0; the user did not name
12748 * the column either). Synthesise a TE so we have something to
12749 * substitute the source provsql Var into below. */
12750 provsql_te = makeNode(TargetEntry);
12751 provsql_te->resno = provsql_attno;
12752 provsql_te->resname = pstrdup(PROVSQL_COLUMN_NAME);
12753 provsql_te_is_new = true;
12754 }
12755
12756 /* Map the source's provsql column into the target's provsql column. */
12757 {
12758 AttrNumber src_provsql_attno = 0;
12759
12760 foreach (lc, src_rte->subquery->targetList) {
12761 TargetEntry *te = (TargetEntry *)lfirst(lc);
12762 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME) &&
12763 exprType((Node *)te->expr) == constants->OID_TYPE_UUID) {
12764 src_provsql_attno = te->resno;
12765 break;
12766 }
12767 }
12768
12769 if (src_provsql_attno == 0)
12770 return;
12771
12772 /* Replace the target's provsql default with a Var from the source */
12773 {
12774 Var *v = makeNode(Var);
12775 v->varno = src_rteid;
12776 v->varattno = src_provsql_attno;
12777 v->vartype = constants->OID_TYPE_UUID;
12778 v->vartypmod = -1;
12779 v->varcollid = InvalidOid;
12780 v->location = -1;
12781 provsql_te->expr = (Expr *)v;
12782 }
12783
12784 /* Now that its expr is set, splice a freshly synthesised provsql
12785 * target entry into the INSERT's targetList. */
12786 if (provsql_te_is_new)
12787 q->targetList = lappend(q->targetList, provsql_te);
12788
12789 /* Update the subquery RTE's column names to include provsql */
12790 src_rte->eref->colnames = lappend(src_rte->eref->colnames,
12791 makeString(pstrdup(PROVSQL_COLUMN_NAME)));
12792 }
12793}
12794
12795/* -------------------------------------------------------------------------
12796 * Planner hook & extension lifecycle
12797 * ------------------------------------------------------------------------- */
12798
12799/**
12800 * @brief Walker: true if any @c Query in the tree defines a @c provsql column
12801 * by hand.
12802 *
12803 * A non-junk target entry resnamed @c provsql whose expression is not a
12804 * legitimate uuid-typed @c Var (the passthrough of a tracked relation's
12805 * provsql column, which @c remove_provenance_attributes_select strips) is a
12806 * hand-made provenance column -- e.g. @c "provenance() AS provsql" or
12807 * @c "expr AS provsql". It collides with the provenance column ProvSQL adds
12808 * itself: the output column count desyncs and a later @c Var mis-binds to a
12809 * non-uuid column, crashing @c get_gate_type when it dereferences the value as
12810 * a pointer.
12811 *
12812 * Run once on the user's ORIGINAL query in the planner hook, before any
12813 * rewriting, so the intermediate queries ProvSQL builds (which legitimately
12814 * carry a provsql column) are never visited.
12815 */
12816static bool query_defines_handmade_provsql(Node *node, void *cx) {
12817 const constants_t *constants = (const constants_t *)cx;
12818 if (node == NULL)
12819 return false;
12820 if (IsA(node, Query)) {
12821 Query *q = (Query *)node;
12822 ListCell *lc;
12823 foreach (lc, q->targetList) {
12824 TargetEntry *te = (TargetEntry *)lfirst(lc);
12825 if (te->resjunk || te->resname == NULL ||
12826 strcmp(te->resname, PROVSQL_COLUMN_NAME))
12827 continue;
12828 if (IsA(te->expr, Var) &&
12829 ((Var *)te->expr)->vartype == constants->OID_TYPE_UUID)
12830 continue; /* legitimate passthrough of a real provsql column */
12831 return true;
12832 }
12833 return query_tree_walker(q, query_defines_handmade_provsql, cx, 0);
12834 }
12835 return expression_tree_walker(node, query_defines_handmade_provsql, cx);
12836}
12837
12838/** @brief Executor nesting depth.
12839 *
12840 * Tracks how deep we are inside @c Executor invocations. Incremented
12841 * in @c provsql_executor_start, decremented in @c provsql_executor_end.
12842 * The classifier @c NOTICE only fires when this is zero, which
12843 * corresponds to the user's outermost statement being planned (before
12844 * any executor entry). Plans built for PL/pgSQL function bodies that
12845 * the rewriter inserts -- @c provenance_times, @c provenance_plus,
12846 * @c provenance_aggregate, ... -- happen during execution of the
12847 * user's plan, so they see depth >= 1 and skip the NOTICE. */
12849
12850/**
12851 * @brief PostgreSQL planner hook – entry point for provenance rewriting.
12852 *
12853 * Replaces (or chains after) the standard planner. For every CMD_SELECT
12854 * that involves at least one provenance-bearing relation or an explicit
12855 * @c provenance() call, rewrites the query via @c process_query before
12856 * handing the result to the standard planner. Non-SELECT commands and
12857 * queries without provenance are passed through unchanged.
12858 * @param q The query to plan.
12859 * @param cursorOptions Cursor options bitmask.
12860 * @param boundParams Pre-bound parameter values.
12861 * @return The planned statement.
12862 */
12863static PlannedStmt *provsql_planner(Query *q,
12864#if PG_VERSION_NUM >= 130000
12865 const char *query_string,
12866#endif
12867 int cursorOptions,
12868 ParamListInfo boundParams) {
12869 /* Scope the inert-fetch record to this rewrite (re-entrant: nested
12870 * planner invocations save and restore their own). */
12871 List *saved_inert_subselects = provsql_inert_subselects;
12873
12874 if (q->commandType == CMD_INSERT && q->rtable && provsql_active) {
12875 const constants_t constants = get_constants(false);
12876 if (constants.ok) {
12877 if (provenance_in_sublink_walker((Node *)q, (void *)&constants))
12878 provsql_error("a subquery over a provenance-tracked relation cannot be "
12879 "used as a scalar subquery / IN / EXISTS expression; put "
12880 "it in the FROM clause instead");
12881 process_insert_select(&constants, q);
12882 }
12883 } else if (q->commandType == CMD_SELECT) {
12884 /* No rtable check here: a FROM-less SELECT (e.g.
12885 * SELECT 1 WHERE normal(0,1) > 2)
12886 * still needs the hook to engage when the WHERE contains an
12887 * rv_cmp. has_provenance walks the tree and returns false fast
12888 * on FROM-less queries that have neither rv_cmp nor provenance(),
12889 * so widening the gate costs nothing in the common case. */
12890 const constants_t constants = get_constants(false);
12891
12892 /* A subquery over a provenance-tracked relation used in an expression
12893 * context (scalar subquery / IN / EXISTS) is not supported -- and would
12894 * otherwise slip past has_provenance() (which does not descend into
12895 * SubLinks) and leave provenance() to fail at runtime. Flag it clearly. */
12896 if (provsql_active && constants.ok &&
12897 provenance_in_sublink_walker((Node *)q, (void *)&constants))
12898 provsql_error("a subquery over a provenance-tracked relation cannot be "
12899 "used as a scalar subquery / IN / EXISTS expression; put "
12900 "it in the FROM clause instead");
12901
12902 /* Query-time TID / BID / OPAQUE classifier. Emits a NOTICE for
12903 * the user's outermost SELECT when the GUC is on. Runs on the
12904 * user's original Query before any provsql rewriting so the
12905 * reported kind reflects the SQL the user wrote. Gating on
12906 * @c provsql_executor_depth @c == @c 0 skips the spurious extra
12907 * planning calls triggered by PL/pgSQL function bodies the
12908 * rewriter inserts (@c provenance_times, ...), whose internal
12909 * SELECTs go through the planner hook during execution of the
12910 * user's plan. */
12911 if (provsql_executor_depth == 0
12913 && q->rtable != NIL) {
12915 provsql_classify_query(q, &cls);
12917 list_free(cls.source_relids);
12918 }
12919
12920 if (constants.ok && has_provenance(&constants, q)) {
12921 bool *removed = NULL;
12922 Query *new_query;
12923 clock_t begin = 0;
12924
12925 /* A user query may not define its own provsql column by hand; ProvSQL
12926 * manages the provenance column itself. Checked here, once, on the
12927 * original query before any rewriting -- so the intermediate queries the
12928 * rewriter builds (which legitimately carry a provsql column) are not
12929 * flagged. */
12930 if (provsql_active &&
12931 query_defines_handmade_provsql((Node *)q, (void *)&constants))
12932 provsql_error("a query may not define a column named \"%s\" by hand; "
12933 "ProvSQL manages the provenance column itself",
12935
12936#if PG_VERSION_NUM >= 150000
12937 if (provsql_verbose >= 20)
12938 provsql_notice("Main query before query rewriting:\n%s\n",
12939 pg_get_querydef(q, true));
12940#endif
12941
12942 if (provsql_verbose >= 40)
12943 begin = clock();
12944
12945 new_query = process_query(&constants, q, &removed, false, true, false,
12946 NULL);
12947
12948 if (provsql_verbose >= 40)
12949 provsql_notice("planner time spent=%f",
12950 (double)(clock() - begin) / CLOCKS_PER_SEC);
12951
12952 if (new_query != NULL)
12953 q = new_query;
12954
12955#if PG_VERSION_NUM >= 150000
12956 if (provsql_verbose >= 20)
12957 provsql_notice("Main query after query rewriting:\n%s\n",
12958 pg_get_querydef(q, true));
12959#endif
12960 }
12961 }
12962
12963 provsql_inert_subselects = saved_inert_subselects;
12964
12965 if (prev_planner)
12966 return prev_planner(q,
12967#if PG_VERSION_NUM >= 130000
12968 query_string,
12969#endif
12970 cursorOptions, boundParams);
12971 else
12972 return standard_planner(q,
12973#if PG_VERSION_NUM >= 130000
12974 query_string,
12975#endif
12976 cursorOptions, boundParams);
12977}
12978
12979/* -------------------------------------------------------------------------
12980 * Executor hooks (depth tracking only)
12981 *
12982 * We install ExecutorStart / ExecutorEnd hooks solely to maintain
12983 * @c provsql_executor_depth, which the classifier in @c provsql_planner
12984 * consults to distinguish the user's outermost statement from nested
12985 * PL/pgSQL bodies the rewriter calls into. No other behaviour changes.
12986 * ------------------------------------------------------------------------- */
12987static ExecutorStart_hook_type prev_ExecutorStart = NULL;
12988static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
12989
12990static void provsql_executor_start(QueryDesc *queryDesc, int eflags) {
12992 PG_TRY();
12993 {
12995 prev_ExecutorStart(queryDesc, eflags);
12996 else
12997 standard_ExecutorStart(queryDesc, eflags);
12998 }
12999 PG_CATCH();
13000 {
13002 PG_RE_THROW();
13003 }
13004 PG_END_TRY();
13005}
13006
13007static void provsql_executor_end(QueryDesc *queryDesc) {
13008#if PG_VERSION_NUM >= 130000
13009 PG_TRY();
13010 {
13011 if (prev_ExecutorEnd)
13012 prev_ExecutorEnd(queryDesc);
13013 else
13014 standard_ExecutorEnd(queryDesc);
13015 }
13016 PG_FINALLY();
13017 {
13019 }
13020 PG_END_TRY();
13021#else
13022 /* PG < 13 lacks PG_FINALLY: emulate by running the cleanup on the
13023 * error path (via PG_CATCH + PG_RE_THROW) and on the success path
13024 * (after PG_END_TRY). Functionally equivalent. */
13025 PG_TRY();
13026 {
13027 if (prev_ExecutorEnd)
13028 prev_ExecutorEnd(queryDesc);
13029 else
13030 standard_ExecutorEnd(queryDesc);
13031 }
13032 PG_CATCH();
13033 {
13035 PG_RE_THROW();
13036 }
13037 PG_END_TRY();
13039#endif
13040}
13041
13042/* -------------------------------------------------------------------------
13043 * ProcessUtility hook: CTAS lineage inheritance.
13044 *
13045 * When a @c CREATE @c TABLE @c AS (or @c CREATE @c MATERIALIZED @c VIEW,
13046 * or @c SELECT @c INTO -- PG's parser transforms all three into
13047 * @c CreateTableAsStmt) projects a @c provsql column lifted verbatim
13048 * from a tracked source, the resulting relation's atoms are not freshly
13049 * minted UUIDs but lineage tokens of one or more base @c
13050 * add_provenance / @c repair_key relations. The hook intercepts the
13051 * utility statement, classifies the inner @c SELECT via
13052 * @c provsql_classify_query, lets PG run the CTAS, then populates
13053 * @c provsql_table_info (with the inherited @c kind / BID @c block_key)
13054 * and the ancestor registry (with the transitive union of source
13055 * ancestor sets) on the just-created relation. A @c provenance_guard
13056 * trigger is installed on the new table so any subsequent INSERT /
13057 * UPDATE that supplies a non-NULL @c provsql still flips the table to
13058 * OPAQUE the standard way.
13059 *
13060 * The hook deliberately fires only when the inner @c SELECT projects
13061 * a @c provsql column from a tracked source -- otherwise the new
13062 * relation has no @c provsql column and the lineage metadata would be
13063 * operationally pointless. Users who want a tracked CTAS-derived
13064 * table without inherited lineage still call @c add_provenance on it
13065 * afterwards (that path seeds @c {self} and overrides whatever this
13066 * hook may have recorded).
13067 * ------------------------------------------------------------------------- */
13068
13069static ProcessUtility_hook_type prev_ProcessUtility = NULL;
13070
13071/** @brief State captured by the pre-execution pass for the post-execution one. */
13072typedef struct ProvSQLCtasCapture {
13073 bool fire; ///< true when the post-pass should run
13074 Query *inner_query; ///< cloned for safety; freed by pfree on completion
13078 Oid source_relid; ///< Single source whose block_key we want to align (BID only)
13082
13083/**
13084 * @brief Decide whether @p parsetree is a CTAS that should trigger
13085 * the ancestry hook, and if so populate @p cap with the inner
13086 * classification, the (single) source's block-key columns, and
13087 * the transitive ancestor union.
13088 *
13089 * Fires only when the inner @c SELECT's target list projects a base-
13090 * level @c Var (possibly through @c RelabelType wrappers) that
13091 * resolves to the @c provsql column of an @c RTE_RELATION whose
13092 * metadata is non-OPAQUE. Anything else (no @c provsql in the
13093 * projection, classifier says OPAQUE, the projected source is itself
13094 * OPAQUE) leaves @c cap->fire false and the post-pass becomes a
13095 * no-op.
13096 */
13097static void provsql_ProcessUtility_capture(Node *parsetree,
13098 ProvSQLCtasCapture *cap) {
13099 CreateTableAsStmt *stmt;
13100 Query *qry;
13102 ListCell *lc;
13103 AttrNumber prov_resno = InvalidAttrNumber;
13104 Oid source_relid = InvalidOid;
13105 ProvenanceTableInfo source_info;
13106 Bitmapset *ancestor_bms = NULL;
13107 int bms_member;
13108 uint16 ancestor_n;
13109
13110 cap->fire = false;
13111 if (!provsql_active)
13112 return;
13113 if (parsetree == NULL || !IsA(parsetree, CreateTableAsStmt))
13114 return;
13115 stmt = (CreateTableAsStmt *) parsetree;
13116 if (stmt->query == NULL || !IsA(stmt->query, Query))
13117 return;
13118 qry = (Query *) stmt->query;
13119 if (qry->commandType != CMD_SELECT)
13120 return;
13121
13122 provsql_classify_query(qry, &cls);
13123 if (cls.kind == PROVSQL_TABLE_OPAQUE) {
13124 list_free(cls.source_relids);
13125 return;
13126 }
13127
13128 /* Walk the inner target list for a TLE whose Var resolves to the
13129 * provsql column of a tracked, non-OPAQUE source. First match wins
13130 * (CTAS preserves the TLE's column name verbatim in the new
13131 * table, so a single provsql TLE is the normal case). */
13132 foreach (lc, qry->targetList) {
13133 TargetEntry *te = (TargetEntry *) lfirst(lc);
13134 Node *e = (Node *) te->expr;
13135 Var *v;
13136 RangeTblEntry *rte;
13137 AttrNumber prov_attno;
13138
13139 if (te->resjunk)
13140 continue;
13141 while (e != NULL && IsA(e, RelabelType))
13142 e = (Node *) ((RelabelType *) e)->arg;
13143 if (e == NULL || !IsA(e, Var))
13144 continue;
13145 v = (Var *) e;
13146 if (v->varlevelsup != 0)
13147 continue;
13148 if (v->varno < 1 || (int) v->varno > list_length(qry->rtable))
13149 continue;
13150 rte = (RangeTblEntry *) list_nth(qry->rtable, v->varno - 1);
13151 if (rte->rtekind != RTE_RELATION)
13152 continue;
13153 prov_attno = get_attnum(rte->relid, PROVSQL_COLUMN_NAME);
13154 if (prov_attno == InvalidAttrNumber || v->varattno != prov_attno)
13155 continue;
13156 if (!provsql_lookup_table_info(rte->relid, &source_info))
13157 continue;
13158 if (source_info.kind == PROVSQL_TABLE_OPAQUE)
13159 continue;
13160 prov_resno = te->resno;
13161 source_relid = rte->relid;
13162 break;
13163 }
13164 if (prov_resno == InvalidAttrNumber) {
13165 list_free(cls.source_relids);
13166 return;
13167 }
13168
13169 /* Transitive ancestor union: lookup each classifier-reported source's
13170 * registered ancestry, fall back to {source} when none recorded
13171 * (defensive: the SQL add_provenance / repair_key seed should always
13172 * give us a non-empty set). Bitmapset dedupes; we then walk it in
13173 * ascending order to get the sorted Oid array the registry stores. */
13174 foreach (lc, cls.source_relids) {
13175 Oid src_relid = lfirst_oid(lc);
13176 uint16 src_n;
13177 Oid src_ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS];
13178 if (provsql_lookup_ancestry(src_relid, &src_n, src_ancestors)) {
13179 for (uint16 i = 0; i < src_n; ++i)
13180 ancestor_bms = bms_add_member(ancestor_bms, (int) src_ancestors[i]);
13181 } else {
13182 ancestor_bms = bms_add_member(ancestor_bms, (int) src_relid);
13183 }
13184 }
13185 list_free(cls.source_relids);
13186
13187 ancestor_n = 0;
13188 bms_member = -1;
13189 while ((bms_member = bms_next_member(ancestor_bms, bms_member)) >= 0) {
13190 if (ancestor_n >= PROVSQL_TABLE_INFO_MAX_ANCESTORS) {
13191 /* Cap exceeded: refuse to fire rather than truncate the ancestor
13192 * set silently (a partial set would let the safe-query
13193 * disjointness check accept a join that shouldn't be safe). */
13194 bms_free(ancestor_bms);
13195 return;
13196 }
13197 cap->ancestors[ancestor_n++] = (Oid) bms_member;
13198 }
13199 bms_free(ancestor_bms);
13200
13201 cap->fire = true;
13202 cap->inner_query = qry;
13204 cap->ancestor_n = ancestor_n;
13205 cap->source_relid = source_relid;
13206 cap->source_block_key_n = source_info.block_key_n;
13207 memcpy(cap->source_block_key, source_info.block_key,
13208 source_info.block_key_n * sizeof(AttrNumber));
13209}
13210
13211/** @brief Map @c provsql_table_kind to its textual label
13212 * (@c set_table_info accepts text). */
13214 switch (k) {
13215 case PROVSQL_TABLE_TID: return "tid";
13216 case PROVSQL_TABLE_BID: return "bid";
13217 case PROVSQL_TABLE_OPAQUE: return "opaque";
13218 }
13219 return "opaque";
13220}
13221
13222/** @brief Forward declaration of the C SQL entry points. */
13223extern Datum set_table_info(PG_FUNCTION_ARGS);
13224extern Datum set_ancestors(PG_FUNCTION_ARGS);
13225
13226/**
13227 * @brief Apply @p cap to the freshly-created relation @c stmt->into->rel.
13228 *
13229 * For BID sources: walks the inner query's target list to align each
13230 * source block-key column to its output @c resno. If any block-key
13231 * column is missing from the projection (the CTAS dropped it), the
13232 * new relation cannot honour the BID invariant under that column --
13233 * the hook demotes to TID rather than asserting a now-stale block
13234 * key.
13235 *
13236 * Installs @c provenance_guard via SPI so subsequent INSERT /
13237 * UPDATE OF provsql on the new relation flip its kind to OPAQUE
13238 * through the standard guard path.
13239 */
13240static void provsql_ProcessUtility_apply(Node *parsetree,
13241 ProvSQLCtasCapture *cap) {
13242 CreateTableAsStmt *stmt;
13243 Oid new_relid;
13244 AttrNumber prov_attno;
13245 provsql_table_kind eff_kind;
13246 uint16 eff_block_key_n = 0;
13247 AttrNumber eff_block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY];
13248 Datum kind_datum;
13249 Datum block_key_datum;
13250 Datum ancestors_datum;
13251 Datum *block_key_elems;
13252 Datum *ancestor_elems;
13253 ArrayType *block_key_arr;
13254 ArrayType *ancestors_arr;
13255 const char *nspname;
13256 const char *relname;
13257 StringInfoData trigger_sql;
13258
13259 if (!cap->fire)
13260 return;
13261 stmt = (CreateTableAsStmt *) parsetree;
13262
13263 new_relid = RangeVarGetRelid(stmt->into->rel, NoLock, true);
13264 if (new_relid == InvalidOid)
13265 return;
13266
13267 /* Confirm the new relation actually has a @c provsql @c uuid column.
13268 * CTAS preserves TLE column names, so this is essentially the
13269 * post-execution verification of what the pre-pass already
13270 * required. */
13271 prov_attno = get_attnum(new_relid, PROVSQL_COLUMN_NAME);
13272 if (prov_attno == InvalidAttrNumber)
13273 return;
13274 if (get_atttype(new_relid, prov_attno) != UUIDOID)
13275 return;
13276
13277 /* BID block-key alignment: each source block-key column must
13278 * survive in the inner-query target list. When all do, the new
13279 * relation's effective block key is the corresponding output
13280 * resno. When any is missing, demote to TID (a partial block
13281 * key would falsely advertise mutual exclusion the rows no
13282 * longer have). */
13283 eff_kind = cap->inherited_kind;
13284 if (eff_kind == PROVSQL_TABLE_BID) {
13285 bool ok = true;
13286 for (uint16 i = 0; i < cap->source_block_key_n; ++i) {
13287 AttrNumber src_attno = cap->source_block_key[i];
13288 ListCell *lc;
13289 bool found = false;
13290 foreach (lc, cap->inner_query->targetList) {
13291 TargetEntry *te = (TargetEntry *) lfirst(lc);
13292 Node *e = (Node *) te->expr;
13293 Var *v;
13294 RangeTblEntry *rte;
13295 if (te->resjunk)
13296 continue;
13297 while (e != NULL && IsA(e, RelabelType))
13298 e = (Node *) ((RelabelType *) e)->arg;
13299 if (e == NULL || !IsA(e, Var))
13300 continue;
13301 v = (Var *) e;
13302 if (v->varlevelsup != 0)
13303 continue;
13304 if (v->varno < 1
13305 || (int) v->varno > list_length(cap->inner_query->rtable))
13306 continue;
13307 rte = (RangeTblEntry *)
13308 list_nth(cap->inner_query->rtable, v->varno - 1);
13309 if (rte->rtekind != RTE_RELATION)
13310 continue;
13311 if (rte->relid == cap->source_relid
13312 && v->varattno == src_attno) {
13313 if (eff_block_key_n >= PROVSQL_TABLE_INFO_MAX_BLOCK_KEY) {
13314 ok = false;
13315 break;
13316 }
13317 eff_block_key[eff_block_key_n++] = te->resno;
13318 found = true;
13319 break;
13320 }
13321 }
13322 if (!found) {
13323 ok = false;
13324 break;
13325 }
13326 }
13327 if (!ok) {
13328 eff_kind = PROVSQL_TABLE_TID;
13329 eff_block_key_n = 0;
13330 }
13331 }
13332
13333 /* Marshal arguments and invoke the SQL-level helpers via
13334 * DirectFunctionCall: this reaches the worker through the same IPC
13335 * path the user-facing SQL functions use, including the relcache
13336 * invalidation broadcast on the way out. */
13337 kind_datum = CStringGetTextDatum(provsql_ctas_kind_label(eff_kind));
13338 if (eff_block_key_n == 0) {
13339 block_key_arr = construct_empty_array(INT2OID);
13340 } else {
13341 block_key_elems = palloc(eff_block_key_n * sizeof(Datum));
13342 for (uint16 i = 0; i < eff_block_key_n; ++i)
13343 block_key_elems[i] = Int16GetDatum(eff_block_key[i]);
13344 block_key_arr = construct_array(block_key_elems, eff_block_key_n,
13345 INT2OID, 2, true, 's');
13346 pfree(block_key_elems);
13347 }
13348 block_key_datum = PointerGetDatum(block_key_arr);
13349 DirectFunctionCall3(set_table_info,
13350 ObjectIdGetDatum(new_relid),
13351 kind_datum,
13352 block_key_datum);
13353
13354 if (cap->ancestor_n == 0) {
13355 ancestors_arr = construct_empty_array(OIDOID);
13356 } else {
13357 ancestor_elems = palloc(cap->ancestor_n * sizeof(Datum));
13358 for (uint16 i = 0; i < cap->ancestor_n; ++i)
13359 ancestor_elems[i] = ObjectIdGetDatum(cap->ancestors[i]);
13360 ancestors_arr = construct_array(ancestor_elems, cap->ancestor_n,
13361 OIDOID, sizeof(Oid), true, 'i');
13362 pfree(ancestor_elems);
13363 }
13364 ancestors_datum = PointerGetDatum(ancestors_arr);
13365 DirectFunctionCall2(set_ancestors,
13366 ObjectIdGetDatum(new_relid),
13367 ancestors_datum);
13368
13369 /* Install the provenance_guard trigger via SPI. Users who later
13370 * INSERT / UPDATE OF provsql with a non-NULL value will then
13371 * trigger the standard kind flip to OPAQUE; users who omit the
13372 * column on INSERT get a fresh @c uuid_generate_v4 leaf (which
13373 * already disconnects the row from the inherited lineage, but the
13374 * guard prevents the more dangerous shared-UUID aliasing path).
13375 *
13376 * Materialized views are exempt: PG forbids triggers on them, and
13377 * they cannot be modified through DML anyway (only @c REFRESH @c
13378 * MATERIALIZED @c VIEW changes the contents -- which re-runs the
13379 * inner SELECT and the freshly-projected rows continue to carry
13380 * lineage from the same sources). */
13381 /* PG 14 renamed CreateTableAsStmt.relkind -> objtype (same ObjectType,
13382 * same OBJECT_* values; pure field rename). */
13383#if PG_VERSION_NUM >= 140000
13384 if (stmt->objtype == OBJECT_MATVIEW)
13385#else
13386 if (stmt->relkind == OBJECT_MATVIEW)
13387#endif
13388 return;
13389
13390 nspname = get_namespace_name(get_rel_namespace(new_relid));
13391 relname = get_rel_name(new_relid);
13392 if (nspname == NULL || relname == NULL)
13393 return;
13394 initStringInfo(&trigger_sql);
13395 appendStringInfo(&trigger_sql,
13396 "CREATE TRIGGER provenance_guard "
13397 "BEFORE INSERT OR UPDATE OF provsql ON %s.%s "
13398 /* "EXECUTE PROCEDURE" is the legacy form, kept as a valid synonym
13399 * of "EXECUTE FUNCTION" through PG 18 -- matches the rest of the
13400 * codebase and stays PG 10-compatible. Promote when PG 10 drops
13401 * out of the support floor. */
13402 "FOR EACH ROW EXECUTE PROCEDURE provsql.provenance_guard()",
13403 quote_identifier(nspname), quote_identifier(relname));
13404 if (SPI_connect() != SPI_OK_CONNECT)
13405 provsql_error("CTAS lineage hook: SPI_connect failed");
13406 if (SPI_exec(trigger_sql.data, 0) != SPI_OK_UTILITY)
13407 provsql_error("CTAS lineage hook: failed to install provenance_guard "
13408 "on %s.%s", nspname, relname);
13409 SPI_finish();
13410 pfree(trigger_sql.data);
13411}
13412
13414 PlannedStmt *pstmt,
13415 const char *queryString,
13416#if PG_VERSION_NUM >= 140000
13417 bool readOnlyTree,
13418#endif
13419 ProcessUtilityContext context,
13420 ParamListInfo params,
13421 QueryEnvironment *queryEnv,
13422 DestReceiver *dest,
13423#if PG_VERSION_NUM >= 130000
13424 QueryCompletion *qc
13425#else
13426 char *completionTag
13427#endif
13428 ) {
13429 Node *parsetree = pstmt ? pstmt->utilityStmt : NULL;
13430 ProvSQLCtasCapture cap = {0};
13431
13432 provsql_ProcessUtility_capture(parsetree, &cap);
13433
13435 prev_ProcessUtility(pstmt, queryString,
13436#if PG_VERSION_NUM >= 140000
13437 readOnlyTree,
13438#endif
13439 context, params, queryEnv, dest,
13440#if PG_VERSION_NUM >= 130000
13441 qc
13442#else
13443 completionTag
13444#endif
13445 );
13446 else
13447 standard_ProcessUtility(pstmt, queryString,
13448#if PG_VERSION_NUM >= 140000
13449 readOnlyTree,
13450#endif
13451 context, params, queryEnv, dest,
13452#if PG_VERSION_NUM >= 130000
13453 qc
13454#else
13455 completionTag
13456#endif
13457 );
13458
13459 provsql_ProcessUtility_apply(parsetree, &cap);
13460}
13461
13462/**
13463 * @brief Extension initialization – called once when the shared library is loaded.
13464 *
13465 * Registers the GUC variables (@c provsql.active, @c where_provenance,
13466 * @c update_provenance, @c verbose_level, @c aggtoken_text_as_uuid,
13467 * @c tool_search_path), installs the planner hook and shared-memory hooks,
13468 * and launches the background MMap worker.
13469 *
13470 * Must be loaded via @c shared_preload_libraries; raises an error otherwise.
13471 */
13472void _PG_init(void) {
13473#ifndef PROVSQL_INPROCESS_STORE
13474 /* The multi-process build registers background workers and a shared
13475 memory segment, which only works when loaded at postmaster start via
13476 shared_preload_libraries. The single-process build has neither: the
13477 planner hook is installed here at CREATE EXTENSION / dlopen time and
13478 covers every subsequent query in the one backend, so no preload (and
13479 no shared_preload_libraries, which the WASM host does not support) is
13480 required. */
13481 if (!process_shared_preload_libraries_in_progress)
13482 provsql_error("provsql needs to be added to the shared_preload_libraries "
13483 "configuration variable");
13484#endif
13485
13486 DefineCustomBoolVariable("provsql.active",
13487 "Should ProvSQL track provenance?",
13488 "1 is standard ProvSQL behavior, 0 means provsql attributes will be dropped.",
13490 true,
13491 PGC_USERSET,
13492 0,
13493 NULL,
13494 NULL,
13495 NULL);
13496 DefineCustomEnumVariable("provsql.provenance",
13497 "Provenance class tracked and assumed by the rewriter.",
13498 "Declares, for the session, the most specific "
13499 "class of provenance semantics the circuits "
13500 "must remain faithful for; constructions are "
13501 "licensed accordingly, from the most general "
13502 "to the most specialised: 'where' adds "
13503 "where-provenance tracking (equality and "
13504 "projection gates) on top of universal "
13505 "semiring provenance; 'semiring' (the "
13506 "default) tracks universal semiring "
13507 "provenance; 'absorptive' additionally lets "
13508 "recursive queries on cyclic data stop at "
13509 "the absorptive value fixpoint, tagging "
13510 "their tokens so non-absorptive semirings "
13511 "refuse them; 'boolean' (which implies "
13512 "'absorptive') additionally enables the "
13513 "Boolean-only machinery -- the safe-query "
13514 "read-once rewrite, the bounded-treewidth "
13515 "reachability route, Boolean circuit "
13516 "simplifications -- whose outputs only "
13517 "preserve the Boolean function of the "
13518 "lineage and are tagged as such.",
13522 PGC_USERSET,
13523 0,
13524 NULL,
13526 NULL);
13527 DefineCustomBoolVariable("provsql.update_provenance",
13528 "Should ProvSQL track update provenance?",
13529 "1 turns update provenance on, 0 off.",
13531 false,
13532 PGC_USERSET,
13533 0,
13534 NULL,
13535 NULL,
13536 NULL);
13537 DefineCustomBoolVariable("provsql.aggtoken_text_as_uuid",
13538 "Output agg_token cells as the underlying UUID "
13539 "instead of \"value (*)\".",
13540 "Off by default for psql-friendly output. UI "
13541 "layers (notably ProvSQL Studio) flip this on "
13542 "per session so aggregate cells expose the "
13543 "circuit root UUID for click-through; the "
13544 "display value is recovered via "
13545 "provsql.agg_token_value_text(uuid).",
13547 false,
13548 PGC_USERSET,
13549 0,
13550 NULL,
13551 NULL,
13552 NULL);
13553 DefineCustomIntVariable("provsql.verbose_level",
13554 "Level of verbosity for ProvSQL informational and debug messages",
13555 "0 for quiet (default), 1-9 for informational messages, 10-100 for debug information.",
13557 0,
13558 0,
13559 100,
13560 PGC_USERSET,
13561 1,
13562 NULL,
13563 NULL,
13564 NULL);
13565 DefineCustomStringVariable("provsql.tool_search_path",
13566 "Directories prepended to PATH when ProvSQL spawns external tools (superuser-only).",
13567 "Colon-separated list of directories searched before the server's PATH "
13568 "when locating d4, c2d, minic2d, dsharp, weightmc, or graph-easy. "
13569 "Empty (default) means rely on the server's PATH alone. "
13570 "Restricted to superusers (PGC_SUSET): it controls which directories the "
13571 "postgres OS user searches for executables, so a non-privileged role must "
13572 "not be able to redirect it to an attacker-controlled binary.",
13574 "",
13575 PGC_SUSET,
13576 0,
13577 NULL,
13578 NULL,
13579 NULL);
13580 DefineCustomStringVariable("provsql.fallback_compiler",
13581 "Compiler used by makeDD's final fallback when both "
13582 "interpretAsDD and tree-decomposition fail.",
13583 "Name of the external compiler invoked by "
13584 "BooleanCircuit::makeDD after interpretAsDD raises "
13585 "(non-independent or non-NNF circuit) and the "
13586 "tree-decomposition builder raises (treewidth above "
13587 "the supported bound). Accepts any value supported "
13588 "by BooleanCircuit::compilation: d4, d4v2, c2d, "
13589 "minic2d, dsharp, panini-obdd, panini-obdd-and, "
13590 "panini-decdnnf. Default: d4.",
13592 "d4",
13593 PGC_USERSET,
13594 0,
13595 NULL,
13596 NULL,
13597 NULL);
13598 DefineCustomStringVariable("provsql.kcmcp_server",
13599 "Launch command for the managed KCMCP knowledge-compiler server.",
13600 "Shell command the supervisor background worker runs to start a "
13601 "warm KCMCP server (see the KC server protocol). The literal "
13602 "{endpoint} is replaced by a Unix-socket path the worker picks "
13603 "and publishes for the in-extension client to reach (a registry "
13604 "record of kind 'kcmcp' with endpoint 'managed' uses it). {endpoint} "
13605 "already carries the scheme (e.g. unix:/path). Empty (default) "
13606 "launches no server. Example: 'tdkc --kcmcp {endpoint}'. "
13607 "PGC_SIGHUP (config file / ALTER SYSTEM + reload): it runs an "
13608 "arbitrary command as the postgres OS user, so like "
13609 "provsql.tool_search_path it is not settable per session.",
13611 "",
13612 PGC_SIGHUP,
13613 0,
13614 NULL,
13615 NULL,
13616 NULL);
13617 DefineCustomStringVariable("provsql.last_eval_method",
13618 "Probability evaluation method(s) used by the most "
13619 "recent probability_evaluate call.",
13620 "Set automatically after each probability_evaluate "
13621 "call to the method that produced the result "
13622 "(comma-separated and deduplicated across calls in "
13623 "the session). Useful to see which strategy the "
13624 "default auto-selection settled on.",
13626 "",
13627 PGC_USERSET,
13628 0,
13629 NULL,
13630 NULL,
13631 NULL);
13632 DefineCustomBoolVariable("provsql.simplify_on_load",
13633 "Apply universal cmp-resolution passes when "
13634 "loading a provenance circuit.",
13635 "When on (default), every GenericCircuit returned "
13636 "by getGenericCircuit goes through RangeCheck "
13637 "(and any future universal pass): comparators "
13638 "decidable to certain Boolean values become "
13639 "Bernoulli gate_input gates with probability 0 "
13640 "or 1, transparent to every downstream consumer "
13641 "(semiring evaluators, MC, view_circuit, PROV "
13642 "export). Set off to inspect raw circuit "
13643 "structure (e.g. when debugging gate-creation "
13644 "paths).",
13646 true,
13647 PGC_USERSET,
13648 0,
13649 NULL,
13650 NULL,
13651 NULL);
13652 /* Debug-only: hidden from SHOW ALL and postgresql.conf.sample.
13653 * On is strictly better for end users (analytic answers where
13654 * possible, lower MC variance, more methods usable on continuous
13655 * circuits); off only serves developer A/B against pure MC and as
13656 * a bisection escape valve if a closure rule misbehaves. */
13657 DefineCustomBoolVariable("provsql.hybrid_evaluation",
13658 "Run the hybrid-evaluator simplifier and "
13659 "island decomposer inside probability_evaluate. "
13660 "Debug only.",
13661 "When on (default), probability_evaluate runs "
13662 "the HybridEvaluator peephole simplifier "
13663 "between RangeCheck and AnalyticEvaluator and "
13664 "the per-cmp MC island decomposer after "
13665 "AnalyticEvaluator. Off bypasses both and lets "
13666 "unresolved comparators fall through to "
13667 "whole-circuit MC. End users have no reason "
13668 "to flip this; it exists for developer A/B "
13669 "testing against the unfolded path and as a "
13670 "bisection knob if a closure rule turns out "
13671 "to be unsound on some workload.",
13673 true,
13674 PGC_USERSET,
13675 GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
13676 NULL,
13677 NULL,
13678 NULL);
13679 /* Debug-only: hidden from SHOW ALL and postgresql.conf.sample.
13680 * Umbrella for closed-form / analytic resolution of gate_cmp
13681 * probabilities in probability_evaluate (currently the
13682 * Poisson-binomial HAVING-COUNT pre-pass; future MIN / MAX / SUM
13683 * pre-passes gate on the same flag). On is strictly better for
13684 * end users (each resolver replaces an exponential DNF
13685 * construction with O(N) or O(N x C) arithmetic); off only serves
13686 * developer A/B against the unoptimised enumerate_valid_worlds
13687 * path and as a bisection escape valve. */
13688 DefineCustomBoolVariable("provsql.cmp_probability_evaluation",
13689 "Run closed-form / analytic probability "
13690 "evaluators for gate_cmps inside "
13691 "probability_evaluate. Debug only.",
13692 "When on (default), probability_evaluate "
13693 "runs pre-passes that recognise specific "
13694 "gate_cmp shapes (currently HAVING COUNT(*) "
13695 "op C over distinct gate_input leaves) and "
13696 "replace each cmp with a Bernoulli "
13697 "gate_input carrying the closed-form "
13698 "probability, bypassing the DNF that "
13699 "provsql_having's enumerate_valid_worlds "
13700 "would otherwise emit. Off forces the cmp "
13701 "to fall through to that enumeration path. "
13702 "Future MIN / MAX / SUM probability "
13703 "evaluators will gate on the same flag. "
13704 "End users have no reason to flip this; it "
13705 "exists for developer A/B testing and as a "
13706 "bisection escape valve.",
13708 true,
13709 PGC_USERSET,
13710 GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
13711 NULL,
13712 NULL,
13713 NULL);
13714 /* Kill-switch for the automatic inversion-free path in the default
13715 * probability chain. The path only fires when the query carries an
13716 * inversion-free certificate (attached by the planner only to certified
13717 * queries), so it is self-gating and safe on by default; off is for A/B
13718 * testing against the tree-decomposition / d4 fallback. The explicit
13719 * 'inversion-free' method bypasses this flag. */
13720 DefineCustomBoolVariable("provsql.inversion_free",
13721 "Use the inversion-free structured-d-DNNF "
13722 "probability path when available.",
13723 "When on (default), probability_evaluate, on a "
13724 "query whose provenance root carries an "
13725 "inversion-free tractability certificate, tries the "
13726 "structured-d-DNNF builder after the read-once "
13727 "independent evaluator and before the "
13728 "tree-decomposition / external-compiler fallback. "
13729 "Off disables only this automatic insertion; the "
13730 "explicit probability_evaluate(token, "
13731 "'inversion-free') method always runs and errors "
13732 "without a certificate. The path is gated on the "
13733 "certificate, attached only to certified queries, "
13734 "so on is safe; off serves developer A/B testing.",
13736 true,
13737 PGC_USERSET,
13738 GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
13739 NULL,
13740 NULL,
13741 NULL);
13742 DefineCustomBoolVariable("provsql.classify_top_level",
13743 "Emit a NOTICE classifying each top-level SELECT.",
13744 "When on, every top-level SELECT that "
13745 "touches a relation triggers a NOTICE of "
13746 "the form `ProvSQL: query result is "
13747 "<KIND> (sources: ...)` where <KIND> is "
13748 "TID, BID, or OPAQUE under the existing "
13749 "provsql_table_kind taxonomy and the "
13750 "sources list names the provenance-"
13751 "tracked base relations the query touches. "
13752 "Read-only : the classifier does not "
13753 "rewrite the query. Studio reads the "
13754 "NOTICE to label query results with their "
13755 "certified kind.",
13757 false,
13758 PGC_USERSET,
13759 0,
13760 NULL,
13761 NULL,
13762 NULL);
13763 DefineCustomIntVariable("provsql.monte_carlo_seed",
13764 "Seed for the Monte Carlo sampler.",
13765 "-1 (default) seeds from std::random_device for "
13766 "non-deterministic sampling. Any other value "
13767 "(including 0) is used as a literal seed for "
13768 "std::mt19937_64, making "
13769 "probability_evaluate(..., 'monte-carlo', n) "
13770 "reproducible across runs and across the Bernoulli "
13771 "and continuous (gate_rv) sampling paths.",
13773 -1,
13774 -1,
13775 INT_MAX,
13776 PGC_USERSET,
13777 0,
13778 NULL,
13779 NULL,
13780 NULL);
13781 DefineCustomIntVariable("provsql.rv_mc_samples",
13782 "Default sample count for analytical-evaluator MC fallbacks.",
13783 "Used when an analytical evaluator (Expectation, "
13784 "future hybrid evaluator, etc.) cannot decompose a "
13785 "sub-circuit and needs to fall back to Monte Carlo. "
13786 "Default 10000. Set to 0 to disable the fallback "
13787 "entirely: callers raise an exception rather than "
13788 "sampling, which is useful when only analytical "
13789 "answers are acceptable. Unrelated to "
13790 "probability_evaluate(..., 'monte-carlo', n) where "
13791 "the sample count is an explicit argument.",
13793 10000,
13794 0,
13795 INT_MAX,
13796 PGC_USERSET,
13797 0,
13798 NULL,
13799 NULL,
13800 NULL);
13801
13802 DefineCustomIntVariable("provsql.dtree_max_subproblems",
13803 "Hard cap on d-tree subproblems before it bails (0 = off).",
13804 "Debug / safety knob for the d-tree speculative-execution "
13805 "budget. The cost chooser already budgets the d-tree at the "
13806 "next-best method's estimated cost; when this is > 0 it adds "
13807 "a fixed hard cap on the number of d-tree subproblems, after "
13808 "which the method throws and the chooser escalates to the "
13809 "next method. 0 leaves only the automatic budget.",
13811 0,
13812 0,
13813 INT_MAX,
13814 PGC_USERSET,
13815 GUC_NO_SHOW_ALL,
13816 NULL,
13817 NULL,
13818 NULL);
13819
13820 DefineCustomIntVariable("provsql.joint_max_treewidth",
13821 "Maximum joint treewidth the joint-width UCQ "
13822 "compiler attempts.",
13823 "The joint-width UCQ compiler "
13824 "(ucq_joint_evaluate) evaluates an arbitrary UCQ -- "
13825 "including queries that are #P-hard under the "
13826 "Dalvi-Suciu dichotomy -- exactly, tractably when "
13827 "the joint treewidth of the data and its "
13828 "correlation structure is bounded. Above this "
13829 "bound the path declines (the degeneracy screen or "
13830 "the min-fill build raises), and the caller falls "
13831 "back to the standard probability ladder. Default "
13832 "10 (the tree-decomposition compiler's own cap).",
13834 10,
13835 0,
13836 10,
13837 PGC_USERSET,
13838 0,
13839 NULL,
13840 NULL,
13841 NULL);
13842 DefineCustomIntVariable("provsql.joint_max_states",
13843 "Per-bag DP state-count cap of the joint-width UCQ "
13844 "compiler.",
13845 "The joint-width UCQ compiler caps the number of "
13846 "dynamic-programming states at any decomposition "
13847 "node; exceeding it raises and the caller falls "
13848 "back to the ladder. This cap, not the static "
13849 "enumerating-variable count, is the true safety "
13850 "net: the realised state count is governed by data "
13851 "sparsity and the absorbing satisfied-state "
13852 "collapse, typically far below the a-priori bound. "
13853 "Default 65536.",
13855 65536,
13856 1,
13857 INT_MAX,
13858 PGC_USERSET,
13859 0,
13860 NULL,
13861 NULL,
13862 NULL);
13863
13864 DefineCustomBoolVariable("provsql.joint_width",
13865 "Recognise unsafe UCQs at planner time and route "
13866 "their existence provenance through the "
13867 "joint-width compiler (debug-only switch).",
13868 "On by default. When provsql.provenance = "
13869 "'boolean', a conjunctive query the safe-query "
13870 "rewriter declined -- an unsafe / #P-hard UCQ -- "
13871 "whose existence is being formed (DISTINCT / GROUP "
13872 "BY) is recognised and its provenance replaced by "
13873 "the joint-width compiler's certified d-D (exact "
13874 "and tractable when the joint treewidth of the data "
13875 "and its correlation structure is bounded). Turn "
13876 "off only to compare against the general lineage "
13877 "for debugging.",
13879 true,
13880 PGC_USERSET,
13881 GUC_NO_SHOW_ALL,
13882 NULL,
13883 NULL,
13884 NULL);
13885
13886 DefineCustomBoolVariable("provsql.mobius",
13887 "Try the safe-UCQ Möbius-inversion route before the "
13888 "joint-width compiler (debug-only switch).",
13889 "On by default. The last missing exact route of the "
13890 "Dalvi-Suciu dichotomy: a UCQ safe only because the "
13891 "#P-hard terms of its inclusion-exclusion expansion "
13892 "carry a zero Möbius value on the CNF lattice and "
13893 "cancel (canonical witness QW / q9). Shares the "
13894 "joint-width descriptor but has PRECEDENCE over it: a "
13895 "guaranteed-PTIME exact route for its class (TID, "
13896 "self-join-free, safe), it is tried first and "
13897 "short-circuits past the joint-width compiler on "
13898 "success. The joint-width compiler runs only on a "
13899 "Möbius decline (correlated inputs, self-joins, "
13900 "unsafe shape); on its decline too the normal "
13901 "provenance is the fallback, so the query never "
13902 "fails. Turn off only to compare against the "
13903 "joint-width / general lineage for debugging.",
13905 true,
13906 PGC_USERSET,
13907 GUC_NO_SHOW_ALL,
13908 NULL,
13909 NULL,
13910 NULL);
13911
13912 DefineCustomIntVariable("provsql.mobius_max_gates",
13913 "Data-cost cap of the safe-UCQ Möbius-inversion "
13914 "route.",
13915 "The Möbius route is PTIME in its class but its "
13916 "lifted-inference recursion is O(|D|^k) in the data, "
13917 "k the safe query's level (number of nested "
13918 "independent-projections) -- supra-linear, and the "
13919 "degree grows with the query. To keep it safe to "
13920 "leave on by default with precedence, it declines "
13921 "(falling through to the joint-width compiler / the "
13922 "ladder) once its compile has built more than this "
13923 "many gates, so a high-level safe query on large data "
13924 "never out-costs the general pipeline. Default "
13925 "4000000.",
13927 4000000,
13928 0,
13929 INT_MAX,
13930 PGC_USERSET,
13931 0,
13932 NULL,
13933 NULL,
13934 NULL);
13935
13936 // Emit warnings for undeclared provsql.* configuration parameters
13937 EmitWarningsOnPlaceholders("provsql");
13938
13939 prev_planner = planner_hook;
13940 prev_shmem_startup = shmem_startup_hook;
13941 prev_ExecutorStart = ExecutorStart_hook;
13942 prev_ExecutorEnd = ExecutorEnd_hook;
13943 prev_ProcessUtility = ProcessUtility_hook;
13944#ifdef PROVSQL_INPROCESS_STORE
13945 /* Single-process store: no shared memory to request and no background
13946 worker; the circuit lives in this process behind an in-memory
13947 dispatch. */
13948 provsql_inproc_init();
13949#elif (PG_VERSION_NUM >= 150000)
13950 prev_shmem_request = shmem_request_hook;
13951 shmem_request_hook = provsql_shmem_request;
13952#else
13954#endif
13955
13956 planner_hook = provsql_planner;
13957#ifndef PROVSQL_INPROCESS_STORE
13958 shmem_startup_hook = provsql_shmem_startup;
13959#endif
13960 ExecutorStart_hook = provsql_executor_start;
13961 ExecutorEnd_hook = provsql_executor_end;
13962 ProcessUtility_hook = provsql_ProcessUtility;
13963
13964#ifndef PROVSQL_INPROCESS_STORE
13967#endif
13968}
13969
13970/**
13971 * @brief Extension teardown – restores the planner and shmem hooks.
13972 */
13973void _PG_fini(void) {
13974 planner_hook = prev_planner;
13975 shmem_startup_hook = prev_shmem_startup;
13976 ExecutorStart_hook = prev_ExecutorStart;
13977 ExecutorEnd_hook = prev_ExecutorEnd;
13978 ProcessUtility_hook = prev_ProcessUtility;
13979}
#define PROVSQL_TABLE_INFO_MAX_BLOCK_KEY
Cap on the number of block-key columns recorded per relation.
provsql_table_kind
How the provenance leaves of a tracked relation are correlated.
@ 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.
bool provsql_classify_top_level
Backing storage for the provsql.classify_top_level GUC.
void provsql_classify_emit_notice(const ProvSQLClassification *c)
Render the result of provsql_classify_query as a NOTICE.
void provsql_classify_query(Query *q, ProvSQLClassification *out)
Classify the result relation of a parsed top-level Query.
Public surface of the query-time TID / BID / OPAQUE classifier.
List * list_insert_nth(List *list, int pos, void *datum)
Insert datum at position pos in list (PG < 13 backport).
PostgreSQL cross-version compatibility shims for ProvSQL.
#define F_SUM_INT4
OID of sum(int4) aggregate function (pre-PG 14).
#define F_ARRAY_AGG_ANYNONARRAY
OID of the array_agg(anynonarray) aggregate (pre-PG 14).
static List * my_list_delete_cell(List *list, ListCell *cell, ListCell *prev)
Version-agnostic wrapper around list_delete_cell().
static ListCell * my_lnext(const List *l, const ListCell *c)
Version-agnostic wrapper around lnext().
#define TYPALIGN_INT
Alignment codes for the array routines (construct_array / deconstruct_array).
#define F_COUNT_
OID of count() aggregate function (pre-PG 14).
#define F_COUNT_ANY
OID of count(*) / count(any) aggregate function (pre-PG 14).
char * provsql_joint_width_descriptor(const constants_t *constants, Query *q, bool *all_existential, List **head_var_idx, List **head_exprs)
Build the joint-width descriptor for a recognised UCQ.
Planner-time recognition of unsafe UCQs for the joint-width compiler.
void RegisterProvSQLKCMCPWorker(void)
Register the supervisor background worker that launches and supervises the managed KCMCP server; call...
int provsql_mobius_max_gates
Data-cost cap of the Möbius route: it declines (falling through to joint-width / the ladder) once its...
Definition provsql.c:102
Datum provenance(PG_FUNCTION_ARGS)
Error stub for provsql.provenance() on untracked tables.
Definition provenance.c:31
static void transform_distinct_into_group_by(Query *q)
Convert a SELECT DISTINCT into an equivalent GROUP BY.
Definition provsql.c:5895
static Expr * make_aggregation_expression(const constants_t *constants, Aggref *agg_ref, List *prov_atts, semiring_operation op, bool is_scalar)
Build the provenance expression for a single aggregate function.
Definition provsql.c:2578
static bool sublink_classify_walker(Node *node, void *cx)
Walker classifying each tracked SubLink of a query as either a still-unsupported direct form or an ar...
Definition provsql.c:6614
static void collect_direct_qual_sublinks(Node *node, List **out)
Collect SubLink nodes sitting in a "direct", decorrelatable position: a target-list entry that is the...
Definition provsql.c:6569
static bool contains_agg_walker(Node *node, contains_agg_ctx *ctx)
Definition provsql.c:2796
static const char * provsql_ctas_kind_label(provsql_table_kind k)
Map provsql_table_kind to its textual label (set_table_info accepts text).
Definition provsql.c:13213
static Node * cast_agg_token_to_type(Node *arg, Oid target_type, const constants_t *constants)
Wrap an agg_token expression in a cast to target_type.
Definition provsql.c:5348
static void remove_provenance_attribute_groupref(Query *q, const Bitmapset *removed_sortgrouprefs)
Remove sort/group references that belonged to removed provenance columns.
Definition provsql.c:5961
static qual_class classify_qual(Expr *expr, const constants_t *constants)
Classify expr along the qual_class axis.
Definition provsql.c:10438
static FuncExpr * having_Expr_to_provenance_cmp(Expr *expr, const constants_t *constants, bool negated)
Dispatch a HAVING sub-expression to the appropriate converter.
Definition provsql.c:3396
static void maybe_cast_agg_token_args(List *args, Oid parent_funcid, const constants_t *constants)
Cast provenance_aggregate arguments of an operator or function when the formal parameter type require...
Definition provsql.c:5386
Datum set_ancestors(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for setTableAncestry() over the IPC pipe.
static bool transform_except_into_join(const constants_t *constants, Query *q)
Rewrite an EXCEPT query into a LEFT JOIN with monus provenance.
Definition provsql.c:6916
bool provsql_where_provenance
Global variable that indicates if where-provenance support has been activated through the provsql....
Definition provsql.c:87
bool provsql_absorptive_provenance
Derived flag: the session's provenance class is 'absorptive' or 'boolean' – licenses constructions so...
Definition provsql.c:108
static void oj_neutralize_orphan_arm(RangeTblEntry *rel)
Neutralise an outer-join arm RTE left orphaned after the lowering so get_provenance_attributes does n...
Definition provsql.c:7298
static bool normalize_quantified_aggregate_sublinks(const constants_t *constants, Query *q)
Normalize quantified comparisons over a single bare-aggregate body into plain scalar comparisons.
Definition provsql.c:8448
static bool has_provenance_walker(Node *node, void *data)
Definition provsql.c:6377
static Expr * wrap_in_cond(const constants_t *constants, Expr *target, Expr *evidence)
Wrap target in a provsql.cond(uuid, uuid) FuncExpr conditioning it on evidence.
Definition provsql.c:11420
static bool has_rv_or_provenance_call(Node *node, void *data)
Tree walker that detects any provenance-bearing relation or provenance() call.
Definition provsql.c:6193
int provsql_verbose
Verbosity level; controlled by the provsql.verbose_level GUC.
Definition provsql.c:89
static bool oj_refs_join_index(Query *q, Index join_idx)
True if any outer Var references the join RTE directly (USING / whole-row / alias....
Definition provsql.c:7703
static Expr * build_mobius_answer_expr(const constants_t *constants, const char *desc, List *head_var_idx, List *head_exprs, Expr *fallback)
Build the per-answer ucq_mobius_provenance_answer(...) call, identical in shape to build_joint_width_...
Definition provsql.c:4074
static bool sublink_over_tracked_walker(Node *node, void *cx)
Walker: set found if a SubLink whose subselect (transitively) involves a provenance-tracked relation ...
Definition provsql.c:6512
static bool rewrite_array_sublinks(const constants_t *constants, Query *q)
Rewrite a top-level ARRAY(SELECT Q.col FROM Q WHERE corr) target-list entry into the aggregate body (...
Definition provsql.c:8689
static Aggref * oj_make_count_star(void)
A fresh count(*) Aggref (returns int8).
Definition provsql.c:9016
static bool expr_has_probabilistic_cmp(Node *node, void *data)
Walker: does node contain a probabilistic (random_variable or aggregate) comparison?
Definition provsql.c:3615
static Node * normalize_bool_agg_having(Node *n)
Definition provsql.c:6869
static bool aggtoken_walker(Node *node, void *data)
Tree walker that detects any Var of type agg_token.
Definition provsql.c:6768
static Query * oj_build_diff(const constants_t *constants, Query *outer, RangeTblEntry *R, RangeTblEntry *S, Index R_idx, Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta, bool keep_left)
Build the difference subquery for the kept side of an outer join: "SELECT X.cols FROM X EXCEPT ALL SE...
Definition provsql.c:7484
static Query * lookup_lowered_cte(List *lowered, const char *name)
Definition provsql.c:432
static FuncExpr * make_regular_indicator(const constants_t *constants, Expr *expr, bool negated)
Build the deterministic indicator gate for an ordinary (regular) comparison: regular_indicator(cond) ...
Definition provsql.c:3313
static Node * rewrite_cond_predicate_mutator(Node *node, void *data)
Mutator: rewrite "X | (predicate)" into the carrier's cond.
Definition provsql.c:3762
static List * provsql_inert_subselects
Walker (this query level only): true if an EXPR_SUBLINK whose body is a decorrelatable value subquery...
Definition provsql.c:6255
static bool check_selection_on_aggregate(OpExpr *op, const constants_t *constants)
Check whether op is a supported comparison on an aggregate result.
Definition provsql.c:10256
static Expr * build_joint_width_provenance_expr(const constants_t *constants, const char *desc, Expr *fallback)
Build the ucq_joint_provenance(descriptor) call substituted for a recognised unsafe UCQ's existence p...
Definition provsql.c:3913
void _PG_init(void)
Extension initialization – called once when the shared library is loaded.
Definition provsql.c:13472
bool provsql_simplify_on_load
Run universal cmp-resolution passes when getGenericCircuit returns; controlled by the provsql....
Definition provsql.c:103
static bool oj_rte_has_provsql(const constants_t *constants, RangeTblEntry *rel)
True if rel contributes provenance: a base relation with a provsql UUID column, or a subquery over tr...
Definition provsql.c:7213
static FuncExpr * having_BoolExpr_to_provenance(BoolExpr *be, const constants_t *constants, bool negated)
Convert a Boolean combination of HAVING comparisons into a provenance_times / provenance_plus gate ex...
Definition provsql.c:3346
static Node * oj_renum_mut(Node *node, void *cx)
Definition provsql.c:7329
static bool is_target_agg_var(Node *node, aggregation_type_mutator_context *context)
Check if a Var matches the target aggregate column.
Definition provsql.c:270
static RangeTblEntry * oj_make_subquery_rte(Query *sub)
Wrap a constructed Query as an RTE_SUBQUERY, building its eref->colnames from the (non-junk) target l...
Definition provsql.c:7247
static bool has_aggtoken(Node *node, const constants_t *constants)
Return true if node contains a Var of type agg_token.
Definition provsql.c:6792
static List * classify_remaining_sublinks(const constants_t *constants, Query *q, bool *has_direct)
Partition q's remaining tracked sublinks into unsupported-direct vs arithmetic-nested.
Definition provsql.c:6640
static int rv_cmp_index(const constants_t *constants, Oid funcoid)
Test whether funcoid is one of the random_variable_* comparison procedures, and if so return its Comp...
Definition provsql.c:3442
static void process_inert_fetches(const constants_t *constants, Query *q)
Definition provsql.c:12057
static FuncExpr * having_NullTest_to_provenance(NullTest *nt, const constants_t *constants, bool negated)
Convert a NullTest on an aggregate (agg IS [NOT] NULL) into a provenance expression.
Definition provsql.c:3192
static bool oj_limit_count_is_one(Node *limitCount)
Is limitCount the literal 1?
Definition provsql.c:9442
static void replace_provenance_function_by_expression(const constants_t *constants, Query *q, Expr *provsql)
Replace every explicit provenance() call in q with provsql.
Definition provsql.c:5871
static FuncExpr * having_null_filtered_plus(const constants_t *constants, Aggref *base_arr, Node *V, Node *K, NullTestType filter)
Build "⊕(array_agg(K) FILTER (WHERE V IS [NOT] NULL))" – the per-row provenance ⊕ over just the value...
Definition provsql.c:3125
static Node * reduce_varattno_mutator(Node *node, void *ctx)
Tree-mutator callback that adjusts Var attribute numbers.
Definition provsql.c:221
static Node * oj_outer_remap(Node *node, void *cx)
Definition provsql.c:7724
bool provsql_inversion_free
Insert the inversion-free structured-d-DNNF path into the default probability chain (after independen...
Definition provsql.c:106
static void provsql_ProcessUtility_capture(Node *parsetree, ProvSQLCtasCapture *cap)
Decide whether parsetree is a CTAS that should trigger the ancestry hook, and if so populate cap with...
Definition provsql.c:13097
static bool inert_fetch_sublink_walker(Node *node, void *data)
Walker: set found if an inert provenance()-fetch SubLink is present in this query's own clauses (not ...
Definition provsql.c:6304
static Query * oj_build_uncorrelated_from_subquery(const constants_t *constants, Query *body)
Build the derived single-row aggregate D for an UNcorrelated scalar subquery body,...
Definition provsql.c:8904
static Expr * build_joint_width_answer_expr(const constants_t *constants, const char *desc, List *head_var_idx, List *head_exprs, Expr *fallback)
Build the per-answer ucq_joint_provenance_answer(...) call for a recognised non-Boolean UCQ (head var...
Definition provsql.c:4001
static Node * oj_param_repl_mut(Node *node, void *cx)
Replace every PARAM_SUBLINK with paramid by replacement.
Definition provsql.c:8404
char * provsql_last_eval_method
Last probability evaluation method(s) used; exposed via provsql.last_eval_method.
Definition provsql.c:90
static bool check_expr_on_rv(Expr *expr, const constants_t *constants)
Test whether expr is a Boolean combination of only random_variable comparisons (no other leaves allow...
Definition provsql.c:3873
PG_MODULE_MAGIC
Required PostgreSQL extension magic block.
Definition provsql.c:79
static void wrap_inversion_free_markers(const constants_t *constants, Query *q, List *prov_atts, const InvFreeMarker *markers, int natoms)
Replace each certified atom's provenance Var in prov_atts with its per-input-marker-wrapped form (in ...
Definition provsql.c:11531
static void insert_agg_token_casts(const constants_t *constants, Query *q)
Walk query and insert agg_token casts where needed.
Definition provsql.c:10743
Datum set_table_info(PG_FUNCTION_ARGS)
Forward declaration of the C SQL entry points.
static Query * build_inner_for_distinct_key(Query *q, Expr *key_expr, List *groupby_tes)
Build the inner GROUP-BY subquery for one AGG(DISTINCT key).
Definition provsql.c:4785
static bool oj_rtables_coalescible(List *rta, List *rtb)
Can two scalar-subquery bodies share a single decorrelating LEFT JOIN?
Definition provsql.c:9482
static Expr * wrap_random_variable_uuid(Node *operand, const constants_t *constants)
Wrap an expression returning random_variable in a binary-coercible cast to uuid.
Definition provsql.c:3463
bool provsql_mobius
Try the safe-UCQ Möbius-inversion route (a guaranteed-PTIME exact route for its class) BEFORE the joi...
Definition provsql.c:101
int provsql_rv_mc_samples
Default sample count for analytical-evaluator MC fallbacks; 0 disables fallback (callers raise instea...
Definition provsql.c:96
int provsql_dtree_max_subproblems
Debug/safety hard cap on d-tree subproblems before it bails (0 = off; the chooser auto-budgets at the...
Definition provsql.c:97
static bool oj_sublink_scan_walker(Node *node, void *cx)
Definition provsql.c:7964
static Node * add_to_havingQual(Node *havingQual, Expr *expr)
Append expr to havingQual with an AND, creating one if needed.
Definition provsql.c:10223
static void fix_type_of_aggregation_result(const constants_t *constants, Query *q, Index rteid, List *targetList)
Retypes aggregation-result Vars in q from UUID to agg_token.
Definition provsql.c:350
static Expr * wrap_in_assume_boolean(const constants_t *constants, Expr *expr)
Wrap expr in a provsql.assume_boolean FuncExpr.
Definition provsql.c:11368
static void hide_provsql_colname(RangeTblEntry *rel)
Rename the provsql column in rel's eref so a later get_provenance_attributes pass does not re-detect ...
Definition provsql.c:7129
static void provsql_executor_end(QueryDesc *queryDesc)
Definition provsql.c:13007
char * provsql_kcmcp_server
Launch command for the managed KCMCP server (with a {endpoint} placeholder); controlled by the provsq...
Definition provsql.c:94
static FlatAtomOrigin * flatten_spj_subqueries(Query *probe, int *nflat_out)
In place, inline every SPJ subquery/view of probe into its base relations, flattening to one conjunct...
Definition provsql.c:11675
static bool is_inert_subselect(Query *q)
Is q a recorded inert provenance()-fetch subselect?
Definition provsql.c:6258
static void process_set_operation_union(const constants_t *constants, SetOperationStmt *stmt, Query *q)
Recursively annotate a UNION tree with the provenance UUID type.
Definition provsql.c:10136
static Query * rewrite_join_agg_token(Query *q, const constants_t *constants, Index rteid, AttrNumber join_attno)
Replace the source relation of an agg_token JOIN with an explode-style subquery.
Definition provsql.c:11015
static bool oj_joinref_walker(Node *node, void *cx)
Definition provsql.c:7689
static bool having_lift_walker(Node *node, void *data)
Walker for needs_having_lift: detect any operand shape that the HAVING-lift rewriter (having_OpExpr_t...
Definition provsql.c:6812
static Node * wrap_agg_token_with_cast(FuncExpr *prov_agg, const constants_t *constants)
Wrap a provenance_aggregate FuncExpr with a cast to the original aggregate return type.
Definition provsql.c:5313
static void oj_build_coltype_lists(oj_cols *Rc, oj_cols *Sc, List **types, List **typmods, List **collations)
Build the column-type lists (R-then-S, user columns only) shared by every set-operation node of the r...
Definition provsql.c:7591
static InvFreeMarkerCtx * build_inversion_free_ctx(const constants_t *constants, Query *q, char **cert_out)
Build the inversion-free marker context for top-level query q.
Definition provsql.c:11878
static void replace_aggregations_by_provenance_aggregate(const constants_t *constants, Query *q, List *prov_atts, semiring_operation op)
Replace every Aggref in q with a provenance-aware aggregate.
Definition provsql.c:5690
static Var * make_provenance_attribute(const constants_t *constants, Query *q, RangeTblEntry *r, Index relid, AttrNumber attid)
Build a Var node that references the provenance column of a relation.
Definition provsql.c:169
static bool query_defines_handmade_provsql(Node *node, void *cx)
Walker: true if any Query in the tree defines a provsql column by hand.
Definition provsql.c:12816
static bool predicate_subselect_decorrelatable(const constants_t *constants, Query *sub, bool corr_supplied)
Is sub a subselect that the predicate-sublink rewrite can turn into a correlated "SELECT count(*) FRO...
Definition provsql.c:8301
bool provsql_joint_width
Recognise unsafe UCQs at planner time and route their existence provenance through the joint-width co...
Definition provsql.c:100
static void provsql_executor_start(QueryDesc *queryDesc, int eflags)
Definition provsql.c:12990
static void remove_provenance_attribute_setoperations(Query *q, bool *removed)
Strip the provenance column's type info from a set-operation node.
Definition provsql.c:5998
static void add_to_select(Query *q, Expr *provenance)
Append the provenance expression to q's target list.
Definition provsql.c:5738
static void inline_ctes(Query *q)
Inline all CTE references in q as subqueries.
Definition provsql.c:1933
void _PG_fini(void)
Extension teardown – restores the planner and shmem hooks.
Definition provsql.c:13973
static Node * provenance_mutator(Node *node, void *ctx)
Tree-mutator that replaces provenance() calls with the actual provenance expression.
Definition provsql.c:5816
provsql_provenance_class_t
Values of the provsql.provenance enum GUC, from most general to most specialised.
Definition provsql.c:111
@ PROVSQL_PROVENANCE_WHERE
Universal semiring provenance plus where-provenance gates.
Definition provsql.c:112
@ PROVSQL_PROVENANCE_BOOLEAN
Boolean-only machinery licensed (tagged); implies absorptive.
Definition provsql.c:115
@ PROVSQL_PROVENANCE_SEMIRING
Universal semiring provenance (default).
Definition provsql.c:113
@ PROVSQL_PROVENANCE_ABSORPTIVE
Absorptive-semiring constructions licensed (tagged).
Definition provsql.c:114
static Node * oj_sl_replace_mut(Node *node, void *cx)
Definition provsql.c:7989
static Node * aggregation_type_mutator(Node *node, void *ctx)
Tree-mutator that retypes a specific Var to agg_token.
Definition provsql.c:295
int provsql_monte_carlo_seed
Seed for the Monte Carlo sampler; -1 means non-deterministic (std::random_device); controlled by the ...
Definition provsql.c:95
static bool oj_sub_bodies_coalescible(Query *a, Query *b)
Definition provsql.c:9500
static void provsql_ProcessUtility_apply(Node *parsetree, ProvSQLCtasCapture *cap)
Apply cap to the freshly-created relation stmt->into->rel.
Definition provsql.c:13240
static Node * replace_having_distinct_mutator(Node *node, void *ctx)
Mutator that replaces each AGG(DISTINCT) Aggref in a HAVING clause with Var(next_rtindex++,...
Definition provsql.c:4986
static List * migrate_probabilistic_quals(const constants_t *constants, Query *q)
Unified WHERE classifier – routes each top-level conjunct to the right evaluation site in a single pa...
Definition provsql.c:10520
static bool collect_having_distinct_walker(Node *node, void *ctx)
Walker that collects AGG(DISTINCT) Aggrefs from an expression.
Definition provsql.c:4960
static bool decorr_value_sublink_walker(Node *node, void *data)
Definition provsql.c:6337
static void cast_agg_token_in_list(ListCell *lc, insert_agg_token_casts_context *ctx)
Wrap an agg_token Var in a cast to its original type, in place.
Definition provsql.c:10650
bool provsql_cmp_probability_evaluation
Run closed-form / analytic probability evaluators for gate_cmps inside probability_evaluate (currentl...
Definition provsql.c:105
static Query * oj_build_antijoin(const constants_t *constants, Query *outer, RangeTblEntry *R, RangeTblEntry *S, Index R_idx, Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta, bool keep_left)
Build a null-padded antijoin arm in R-then-S column order.
Definition provsql.c:7543
static Expr * build_inversion_free_marker(const constants_t *constants, Query *q, Var *prov_var, const InvFreeMarker *m)
Wrap an atom's provenance Var in the inversion-free per-input order marker: annotate(prov,...
Definition provsql.c:11482
static bool query_has_inert_fetch(const constants_t *constants, Query *q)
Does q's own target list / jointree / HAVING contain an inert provenance()-fetch SubLink?
Definition provsql.c:6325
static Expr * wrap_in_annotate(const constants_t *constants, Expr *expr, const char *cert)
Wrap expr in a provsql.annotate(uuid, text) FuncExpr carrying cert.
Definition provsql.c:11393
static const struct config_enum_entry provsql_provenance_options[]
Option table of the provsql.provenance GUC.
Definition provsql.c:121
static void rewrite_cond_predicates(const constants_t *constants, Query *q)
Rewrite every "X | (predicate)" in q's own clauses.
Definition provsql.c:3814
static Node * cast_agg_token_mutator(Node *node, void *ctx)
Tree-mutator that casts provenance_aggregate results back to the original aggregate return type where...
Definition provsql.c:5536
char * provsql_tool_search_path
Colon-separated directory list prepended to PATH when invoking external tools (d4,...
Definition provsql.c:92
static bool move_uncorrelated_sublinks_to_from(const constants_t *constants, Query *q)
Move uncorrelated scalar subqueries that are direct target-list entries into a cross-joined derived a...
Definition provsql.c:9383
static OpExpr * normalize_agg_comparison(OpExpr *cmp, const constants_t *constants)
Fold constant arithmetic over an aggregate into the comparison threshold.
Definition provsql.c:2860
static Node * push_arith_into_agg_mutator(Node *node, void *ctx)
Tree-mutator applying try_push_into_aggref bottom-up.
Definition provsql.c:5664
static void mark_col_selected(Query *q, RangeTblEntry *r, AttrNumber attno)
Mark column attno of RTE r as selected (read permission).
Definition provsql.c:11436
static Query * process_query(const constants_t *constants, Query *q, bool **removed, bool wrap_root, bool top_level, bool in_boolean_rewrite, const InvFreeMarkerCtx *inv_ctx)
Rewrite a single SELECT query to carry provenance.
Definition provsql.c:12105
static Query * rewrite_agg_distinct(Query *q, const constants_t *constants)
Rewrite every AGG(DISTINCT key) in q using independent subqueries.
Definition provsql.c:5028
static bool rewrite_uncorrelated_antijoin(const constants_t *constants, Query *q)
Rewrite an uncorrelated WHERE predicate that is satisfied by the empty group – NOT EXISTS,...
Definition provsql.c:9243
bool provsql_interrupted
Global variable that becomes true if this particular backend received an interrupt signal.
Definition provsql.c:85
static void keep_only_provenance_output(Query *sub)
Make a processed inert subselect return exactly its provenance token as a single column.
Definition provsql.c:12002
static bool calls_provenance_walker(Node *node, void *data)
Walker: true if node (descending through nested queries) contains an explicit provenance() call.
Definition provsql.c:6677
static Expr * combine_prov_atts(const constants_t *constants, List *prov_atts, semiring_operation op)
Build the per-row provenance token for an aggregate rewrite.
Definition provsql.c:2460
static Node * try_push_into_aggref(OpExpr *op, const constants_t *constants)
Push distributive constant arithmetic into an aggregate's argument.
Definition provsql.c:5578
bool provsql_boolean_provenance
Derived flag: the session's provenance class is 'boolean' – enables the Boolean-only machinery (safe-...
Definition provsql.c:107
int provsql_joint_max_states
Per-bag DP state-count cap of the joint-width UCQ compiler (the true safety net); provsql....
Definition provsql.c:99
static FuncExpr * rv_BoolExpr_to_provenance(BoolExpr *be, const constants_t *constants, bool negated)
Convert a Boolean combination of RV comparisons into a provenance_times / provenance_plus expression.
Definition provsql.c:3536
static FuncExpr * predicate_to_condition_gate(Expr *expr, const constants_t *constants, bool negated)
Convert a Boolean predicate into a provenance condition gate.
Definition provsql.c:3645
static bool provenance_in_sublink_walker(Node *node, void *data)
Walker: true if a SubLink subselect calls provenance().
Definition provsql.c:6705
static bool decorrelate_scalar_sublinks(const constants_t *constants, Query *q)
Decorrelate a single top-level scalar subquery into a LEFT JOIN.
Definition provsql.c:9522
static RangeTblEntry * oj_copy_rel(Query *outer, Query *sub, RangeTblEntry *orig)
Copy an outer-join arm RTE into the range table of subquery sub.
Definition provsql.c:7276
static List * get_provenance_attributes(const constants_t *constants, Query *q, bool in_boolean_rewrite, const InvFreeMarkerCtx *inv_ctx)
Collect all provenance Var nodes reachable from q's range table.
Definition provsql.c:1985
static bool oj_zero_satisfies(Oid opno, Const *c)
Does 0 satisfy the int8 comparison "0 <opno> c"?
Definition provsql.c:9217
static Expr * add_eq_from_Quals_to_Expr(const constants_t *constants, Node *quals, Expr *result, int **columns)
Walk a join-condition or WHERE quals node and add eq gates for every equality it contains.
Definition provsql.c:2417
static Query * rewrite_non_all_into_external_group_by(Query *q)
Wrap a non-ALL set operation in an outer GROUP BY query.
Definition provsql.c:6039
static Query * oj_build_union(const constants_t *constants, Query *outer, RangeTblEntry *R, RangeTblEntry *S, Index R_idx, Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta, JoinType jointype)
Build the UNION-ALL of the matched arm and the outer join's antijoin arm(s): the full outer-join rela...
Definition provsql.c:7614
char * provsql_fallback_compiler
Compiler used by BooleanCircuit::makeDD as the final fallback after interpretAsDD and tree-decomposit...
Definition provsql.c:93
static PlannedStmt * provsql_planner(Query *q, int cursorOptions, ParamListInfo boundParams)
PostgreSQL planner hook – entry point for provenance rewriting.
Definition provsql.c:12863
static bool oj_wrap_body_from(const constants_t *constants, Query *sub)
Collapse a multi-table scalar-subquery body FROM into one derived cross-product subquery D,...
Definition provsql.c:8813
static ExecutorStart_hook_type prev_ExecutorStart
Definition provsql.c:12987
static bool provsql_active
true while ProvSQL query rewriting is enabled
Definition provsql.c:86
static bool oj_contains_sublink_walker(Node *node, void *cx)
Walker: true if the subtree contains the specific SubLink cx.
Definition provsql.c:7999
static bool provenance_function_in_group_by(const constants_t *constants, Query *q)
Check whether a provenance() call appears in the GROUP BY list.
Definition provsql.c:6136
static Expr * combine_safe_routes(const constants_t *constants, Expr *mobius_call, Expr *joint_call, Expr *lineage)
Combine the Möbius and joint-width routes under Möbius precedence.
Definition provsql.c:4183
static void provsql_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, char *completionTag)
Definition provsql.c:13413
static bool move_uncorrelated_where_predicates(const constants_t *constants, Query *q)
Handle UNcorrelated EXISTS and uncorrelated aggregate comparisons in WHERE by cross-joining a HAVING-...
Definition provsql.c:9060
static Bitmapset * remove_provenance_attributes_select(const constants_t *constants, Query *q, bool **removed)
Strip provenance UUID columns from q's SELECT list.
Definition provsql.c:2178
static Node * oj_decorr_var_mut(Node *node, void *cx)
Definition provsql.c:7929
static void provsql_provenance_assign_hook(int newval, void *extra)
Assign hook of provsql.provenance: refresh the derived per-class flags.
Definition provsql.c:130
static bool is_supported_bool_agg(Oid aggfnoid)
Definition provsql.c:6852
static bool oj_uncorrelated_body_over_tracked(const constants_t *constants, Query *sub)
Is sub an uncorrelated clean SELECT over tracked base relations (a comma-join is fine)?
Definition provsql.c:8989
static Expr * wrap_mobius_or_null(const constants_t *constants, Expr *mobius_call)
Wrap a Möbius call in mobius_or_null(...): the token if it roots a gate_mobius (a Möbius success),...
Definition provsql.c:4140
static bool lower_outer_joins(const constants_t *constants, Query *q)
Lower a top-level outer JOIN of two base relations into the UNION-ALL of its matched and null-padded ...
Definition provsql.c:7762
static bool oj_body_has_tracked_relation(const constants_t *constants, Query *body)
Does the body's range table reach at least one provenance-tracked relation?
Definition provsql.c:8420
static Expr * add_eq_from_OpExpr_to_Expr(const constants_t *constants, OpExpr *fromOpExpr, Expr *toExpr, int **columns)
Wrap toExpr in a provenance_eq gate if fromOpExpr is an equality between two tracked columns.
Definition provsql.c:2341
static Node * build_count_predicate(Query *subselect, Node *extra_corr, bool antijoin)
Turn a predicate subselect into the boolean "(SELECT count(*) FROM Q WHERE corr) >= 1" (semijoi...
Definition provsql.c:8346
static bool oj_wrap_outer_from(const constants_t *constants, Query *q, SubLink *sl, bool in_where)
Wrap a non-single-relation outer FROM into a derived subquery R' so a scalar subquery can be decorrel...
Definition provsql.c:8153
static OpExpr * oj_count_distinct_cmp(Expr *valexpr, const char *opstr, int64 n)
Build "count(DISTINCT v) <op> n" -- the at-most-one-DISTINCT-value gate of a "SELECT DISTINCT v" body...
Definition provsql.c:8065
static FuncExpr * rv_Expr_to_provenance(Expr *expr, const constants_t *constants, bool negated)
Dispatch a WHERE sub-expression to the appropriate RV converter.
Definition provsql.c:3586
static Query * oj_build_join_query(const constants_t *constants, Query *outer, RangeTblEntry *R, RangeTblEntry *S, Index R_idx, Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta, bool select_r, bool select_s)
Build the inner-join scan subquery "SELECT [R.cols][, S.cols] FROM R JOIN S ON θ".
Definition provsql.c:7363
static planner_hook_type prev_planner
Previous planner hook (chained).
Definition provsql.c:141
static void normalize_distinct_into_group_by(Query *q)
Normalise a supported SELECT DISTINCT into a GROUP BY.
Definition provsql.c:5936
static bool check_boolexpr_on_aggregate(BoolExpr *be, const constants_t *constants)
Check whether every leaf of a Boolean expression is a supported comparison on an aggregate result.
Definition provsql.c:10289
static void process_insert_select(const constants_t *constants, Query *q)
Propagate provenance through INSERT ... SELECT.
Definition provsql.c:12672
static FlatAtomOrigin * flat_origin1(int slot)
A depth-1 origin path [slot].
Definition provsql.c:11629
static bool subselect_is_pure_provenance_fetch(const constants_t *constants, Query *sub)
Whether sub's sole non-junk output is a bare provenance() call.
Definition provsql.c:6284
static bool join_qual_has_agg_token_walker(Node *node, join_qual_agg_token_ctx *ctx)
Definition provsql.c:10756
static bool sublink_is_inert(SubLink *sl)
Does sl wrap a recorded inert provenance()-fetch subselect?
Definition provsql.c:6267
static bool expr_contains_rv_cmp(Node *node, const constants_t *constants)
Test whether an Expr (sub-)tree contains any RV comparison.
Definition provsql.c:3836
static Oid get_agg_token_orig_type(Var *v, insert_agg_token_casts_context *ctx)
Look up the original aggregate return type for an agg_token Var.
Definition provsql.c:10622
static Aggref * oj_make_aggref(Oid aggfnoid, Oid aggtype, Oid argtype, Expr *arg)
Build an Aggref for a single-argument aggregate.
Definition provsql.c:8008
static void cast_agg_token_args(List *args, insert_agg_token_casts_context *ctx)
Wrap any agg_token Vars in an argument list.
Definition provsql.c:10684
static void oj_collect_cols(const constants_t *constants, RangeTblEntry *rel, oj_cols *out)
Collect the user columns (skipping provsql and dropped columns) of an outer-join arm: a base relation...
Definition provsql.c:7153
static bool const_as_double(Node *n, double *out)
Numeric value of a (possibly cast-wrapped) Const; false if the node is not a non-NULL Const.
Definition provsql.c:2823
static bool retype_agg_var_walker(Node *node, retype_agg_var_ctx *ctx)
Walker that retypes agg_token Vars to text and rewrites the equality OpExpr to text = text with the n...
Definition provsql.c:10888
static Query * build_outer_for_distinct_key(TargetEntry *orig_agg_te, Query *inner, int n_gb, const constants_t *constants)
Wrap inner in an outer query that applies the original aggregate.
Definition provsql.c:4855
static int provsql_executor_depth
Executor nesting depth.
Definition provsql.c:12848
static bool cond_predicate_target(const constants_t *constants, Oid opfuncid, Oid *cond_fn, Oid *result_type, bool *is_prefix)
Carrier-routing for an "X | (predicate)" placeholder OpExpr.
Definition provsql.c:3727
static Query * oj_having_gated_subquery(Query *body, Node *pred)
Build the one-row "SELECT 1 FROM <body FROM> HAVING <pred>" gated subquery: body supplies the FROM (a...
Definition provsql.c:9036
static ProcessUtility_hook_type prev_ProcessUtility
Definition provsql.c:13069
static Expr * make_provenance_expression(const constants_t *constants, Query *q, List *prov_atts, bool aggregation, bool group_by_rewrite, semiring_operation op, int **columns, int nbcols, bool wrap_assumed, bool in_boolean_rewrite, const char *inv_cert)
Build the combined provenance expression to be added to the SELECT list.
Definition provsql.c:4253
static void build_column_map(Query *q, int **columns, int *nbcols)
Build the per-RTE column-numbering map used by where-provenance.
Definition provsql.c:10360
static List * strip_given_markers(const constants_t *constants, Query *q)
Strip given(evidence) whole-tuple conditioning markers from the visible projection,...
Definition provsql.c:2253
static Node * insert_agg_token_casts_mutator(Node *node, void *data)
Insert agg_token casts for Vars used in expressions.
Definition provsql.c:10704
bool provsql_hybrid_evaluation
Run the hybrid-evaluator simplifier inside probability_evaluate; controlled by the provsql....
Definition provsql.c:104
static bool provsql_update_provenance
true when provenance tracking for DML is enabled
Definition provsql.c:88
static int provsql_provenance_class
Backing variable of the provsql.provenance GUC.
Definition provsql.c:118
static bool check_expr_on_aggregate(Expr *expr, const constants_t *constants)
Top-level dispatcher for supported WHERE-on-aggregate patterns.
Definition provsql.c:10321
static bool provenance_function_walker(Node *node, void *data)
Tree walker that returns true if any provenance() call is found.
Definition provsql.c:6111
semiring_operation
Semiring operation used to combine provenance tokens.
Definition provsql.c:2308
@ SR_PLUS
Semiring addition (UNION, SELECT DISTINCT).
Definition provsql.c:2309
@ SR_TIMES
Semiring multiplication (JOIN, Cartesian product).
Definition provsql.c:2311
@ SR_MONUS
Semiring monus / set difference (EXCEPT).
Definition provsql.c:2310
static void group_set_difference_right_arm(const constants_t *constants, Query *q)
Group the right-hand arm of a set difference by all its columns so the per-tuple right provenances ⊕-...
Definition provsql.c:10038
static bool needs_having_lift(Node *havingQual, const constants_t *constants)
Return true if havingQual contains anything the HAVING-lift path needs to handle (an agg_token Var or...
Definition provsql.c:6845
static bool join_qual_has_agg_token(Node *node, const constants_t *constants, Index *rteid, AttrNumber *join_attno)
Return true if node contains an OpExpr that equates an agg_token Var with a non-agg_token Var.
Definition provsql.c:10813
static Node * aggregation_mutator(Node *node, void *ctx)
Tree-mutator that replaces Aggrefs with provenance-aware aggregates.
Definition provsql.c:5290
int provsql_joint_max_treewidth
Maximum joint treewidth the joint-width UCQ compiler attempts before declining (caller falls back to ...
Definition provsql.c:98
static bool rewrite_predicate_sublinks(const constants_t *constants, Query *q)
Rewrite top-level EXISTS / IN WHERE conjuncts (optionally negated) over a single tracked relation int...
Definition provsql.c:8618
static bool expr_contains_agg(Node *node, const constants_t *constants)
Whether an expression subtree references an aggregate (a bare provenance_aggregate call or an agg_tok...
Definition provsql.c:2815
static Node * oj_wrap_remap_mut(Node *node, void *cx)
Definition provsql.c:8116
static OpExpr * oj_count_const_cmp(Oid opno, Oid inputcollid, Aggref *cnt, Node *constarg)
Build the "<cnt> <op> const" OpExpr for an antijoin's HAVING, where cnt is a count aggregate (count(*...
Definition provsql.c:9201
static bool expr_contains_aggref_walker(Node *node, void *context)
expression_tree_walker predicate: returns true on the first Aggref it encounters.
Definition provsql.c:5801
static void remove_provsql_from_select(Query *q)
Remove the auto-added provsql output column from a rewritten query.
Definition provsql.c:6737
static OpExpr * oj_count_cmp(Var *found_var, Index q_idx, const char *opstr, int64 n)
Build "count(Q.key) <op> n" over the decorrelated LEFT-JOIN group.
Definition provsql.c:8036
static Expr * coerce_via_io_to_text(Expr *arg)
Coerce arg to text via its output function (any type -> text).
Definition provsql.c:11464
static Expr * build_mobius_provenance_expr(const constants_t *constants, const char *desc, Expr *fallback)
Build the ucq_mobius_provenance(descriptor, fallback) call.
Definition provsql.c:3959
static Var * make_column_var(Query *q, RangeTblEntry *r, Index relid, AttrNumber attno)
A Var for column attno of RTE relid, with the column's actual type/typmod/collation,...
Definition provsql.c:11452
static Node * build_binop(const char *op, Node *l, Node *r)
Build l <op> r, resolving the operator by name.
Definition provsql.c:2841
static Node * flatten_mut(Node *node, void *cp)
Tree mutator implementing the conjunctive inlining of SPJ subqueries.
Definition provsql.c:11590
static ExecutorEnd_hook_type prev_ExecutorEnd
Definition provsql.c:12988
static bool query_has_tracked_sublink(const constants_t *constants, Query *q)
Does any SubLink in q's own clauses have a subselect that (transitively) involves a provenance-tracke...
Definition provsql.c:6543
static bool process_inert_fetches_walker(Node *node, void *cx)
Definition provsql.c:12029
bool provsql_aggtoken_text_as_uuid
When true, agg_token::text emits the underlying provenance UUID instead of "value (*)".
Definition provsql.c:91
static FuncExpr * having_OpExpr_to_provenance_cmp(OpExpr *opExpr, const constants_t *constants, bool negated)
Convert a comparison OpExpr on aggregate results into a provenance_cmp gate expression.
Definition provsql.c:3009
static void add_select_non_zero(const constants_t *constants, Query *q, Expr *provsql)
Add a WHERE condition filtering out zero-provenance tuples.
Definition provsql.c:10186
static void reduce_varattno_by_offset(List *targetList, Index varno, int *offset)
Adjust Var attribute numbers in targetList after columns are removed.
Definition provsql.c:249
qual_class
Categorisation of a top-level WHERE conjunct.
Definition provsql.c:10412
@ QUAL_MIXED_RV_DET
random_variable mixed with non-RV leaves; error
Definition provsql.c:10417
@ QUAL_PURE_AGG
pure agg_token expression; route to HAVING
Definition provsql.c:10414
@ QUAL_DETERMINISTIC
no probabilistic value; stays in WHERE
Definition provsql.c:10413
@ QUAL_MIXED_AGG_DET
agg_token mixed with non-agg leaves; error
Definition provsql.c:10416
@ QUAL_MIXED_AGG_RV
agg_token and random_variable in the same expr; error
Definition provsql.c:10418
@ QUAL_PURE_RV
pure random_variable expression; lift to provenance
Definition provsql.c:10415
static void inline_ctes_in_rtable(List *rtable, List *cteList, List **lowered)
Inline CTE references as subqueries within a query.
Definition provsql.c:1347
static Node * make_uuid_array_subscript(Node *arr_expr, int index, const constants_t *constants)
Build an AST node for arr[idx] on a uuid[] expression.
Definition provsql.c:10834
static FuncExpr * rv_OpExpr_to_provenance_cmp(OpExpr *opExpr, const constants_t *constants, bool negated)
Convert a single RV-comparison OpExpr into a provenance_cmp() FuncExpr returning UUID.
Definition provsql.c:3497
static Node * try_swap_agg_arith(OpExpr *op, const constants_t *constants)
Rebuild an arithmetic operator over an aggregate so the result stays an agg_token (provenance preserv...
Definition provsql.c:5468
static Expr * make_rv_aggregate_expression(const constants_t *constants, Aggref *agg_ref, List *prov_atts, semiring_operation op)
Inline rewrite of an RV-returning aggregate into the same aggregate over provenance-wrapped per-row a...
Definition provsql.c:2516
static void error_for_mixed_qual(qual_class c)
Raise the user-facing error appropriate to a mixed c.
Definition provsql.c:10464
static Node * extract_quantified_corr(SubLink *sl, bool *antijoin)
Build the per-row correlation for a quantified sublink (IN / op ANY / op ALL), setting *antijoin.
Definition provsql.c:8533
static bool has_provenance(const constants_t *constants, Query *q)
Return true if q involves any provenance-bearing relation or contains an explicit provenance() call.
Definition provsql.c:6494
static FlatAtomOrigin * flat_origin_prepend(int slot, const FlatAtomOrigin *sub)
Prepend slot to sub's path, for an atom inlined one level up.
Definition provsql.c:11638
static Node * peel_agg_casts(Node *n)
Peel implicit/explicit cast FuncExprs and RelabelTypes that wrap a single argument,...
Definition provsql.c:5436
static Query * oj_build_rel_query(const constants_t *constants, Query *outer, RangeTblEntry *R, oj_cols *Rc)
Build the plain-scan subquery "SELECT R.cols FROM R".
Definition provsql.c:7450
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
#define provsql_warning(fmt,...)
Emit a ProvSQL warning message (execution continues).
#define provsql_notice(fmt,...)
Emit a ProvSQL informational notice (execution continues).
void RegisterProvSQLMMapWorker(void)
Register the ProvSQL mmap background worker with PostgreSQL.
Background worker and IPC primitives for mmap-backed circuit storage.
void provsql_shmem_request(void)
Request shared memory from PostgreSQL (PG ≥ 15).
shmem_startup_hook_type prev_shmem_startup
Saved pointer to the previous shmem_startup_hook, for chaining.
void provsql_shmem_startup(void)
Initialise the ProvSQL shared-memory segment.
Shared-memory segment and inter-process pipe management.
shmem_request_hook_type prev_shmem_request
Saved pointer to the previous shmem_request_hook (PG ≥ 15), for chaining.
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.
constants_t get_constants(bool failure_if_not_possible)
Retrieve the cached OID constants for the current database.
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.
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.
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...
Where a flattened base atom came from, for mapping markers back.
Definition provsql.c:11562
Per-query marker context for the inversion-free path, threaded through the recursive query rewrite to...
Definition safe_query.h:73
InvFreeMarkerCtx ** sub
Definition safe_query.h:76
InvFreeMarker * markers
Definition safe_query.h:75
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
Memo entry mapping a recursive-CTE name to its lowered scan subquery.
Definition provsql.c:411
const char * name
Definition provsql.c:412
Query * subquery
Definition provsql.c:413
Result of provsql_classify_query.
provsql_table_kind kind
State captured by the pre-execution pass for the post-execution one.
Definition provsql.c:13072
provsql_table_kind inherited_kind
Definition provsql.c:13075
bool fire
true when the post-pass should run
Definition provsql.c:13073
uint16 source_block_key_n
Definition provsql.c:13079
Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS]
Definition provsql.c:13077
AttrNumber source_block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]
Definition provsql.c:13080
Query * inner_query
cloned for safety; freed by pfree on completion
Definition provsql.c:13074
Oid source_relid
Single source whose block_key we want to align (BID only).
Definition provsql.c:13078
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.
Context for the aggregation_mutator tree walker.
Definition provsql.c:5276
semiring_operation op
Semiring operation for combining tokens.
Definition provsql.c:5278
const constants_t * constants
Extension OID cache.
Definition provsql.c:5279
bool is_scalar
Aggregation has no GROUP BY (single always-present row).
Definition provsql.c:5280
List * prov_atts
List of provenance Var nodes.
Definition provsql.c:5277
Context for the aggregation_type_mutator tree walker.
Definition provsql.c:261
const constants_t * constants
Extension OID cache.
Definition provsql.c:264
Index varattno
Attribute number of the aggregate column.
Definition provsql.c:263
Index varno
Range-table entry index of the aggregate var.
Definition provsql.c:262
Structure to store the value of various constants.
Oid OID_FUNCTION_REGULAR_INDICATOR
OID of provsql.regular_indicator(boolean): the deterministic gate_one/gate_zero indicator the planner...
Oid OID_FUNCTION_PROVENANCE_EQ
OID of the provenance_eq FUNCTION.
Oid OID_FUNCTION_PROVENANCE_AGGREGATE
OID of the provenance_aggregate FUNCTION.
Oid OID_FUNCTION_PROVENANCE_SEMIMOD
OID of the provenance_semimod FUNCTION.
Oid OID_FUNCTION_CHOOSE
OID of the choose(anyelement) aggregate (keeps the first non-NULL value); used to decorrelate scalar ...
Oid OID_FUNCTION_ANNOTATE
OID of provsql.annotate(uuid,text)->uuid.
Oid OID_FUNCTION_PROVENANCE
OID of the provenance FUNCTION.
Oid OID_FUNCTION_INVERSION_FREE_KEY
OID of provsql.inversion_free_key(text,text,int)->text.
Oid OID_FUNCTION_AGG_TOKEN_UUID
OID of the agg_token_uuid FUNCTION.
Oid OID_FUNCTION_RV_AGGREGATE_SEMIMOD
OID of rv_aggregate_semimod helper (uuid, rv -> rv) used to wrap each per-row argument of an RV-retur...
Oid OID_FUNCTION_GATE_ZERO
OID of the provenance_zero FUNCTION.
Oid OID_FUNCTION_COND
OID of provsql.cond(uuid,uuid)->uuid.
Oid OID_FUNCTION_PROVENANCE_PROJECT
OID of the provenance_project FUNCTION.
Oid OID_FUNCTION_GET_CHILDREN
OID of the get_children FUNCTION.
Oid OID_UNNEST
OID of the unnest(anyarray) FUNCTION.
Oid OID_FUNCTION_COND_PREDICATE
cond_predicate(uuid,boolean)
Oid OID_TYPE_AGG_TOKEN
OID of the agg_token TYPE.
Oid OID_FUNCTION_ARRAY_AGG
OID of the array_agg FUNCTION.
Oid OID_TYPE_INT
OID of the INT TYPE.
Oid OID_FUNCTION_PROVENANCE_PLUS
OID of the provenance_plus FUNCTION.
Oid OID_OPERATOR_NOT_EQUAL_UUID
OID of the <> operator on UUIDs FUNCTION.
Oid OID_TYPE_UUID
OID of the uuid TYPE.
bool ok
true if constants were loaded
Oid OID_TYPE_INT_ARRAY
OID of the INT[] TYPE.
Oid OID_FUNCTION_PROVENANCE_DELTA
OID of the provenance_delta FUNCTION.
Oid OID_FUNCTION_ASSUME_BOOLEAN
OID of provsql.assume_boolean(uuid)->uuid.
Oid OID_FUNCTION_PROVENANCE_TIMES
OID of the provenance_times FUNCTION.
Oid OID_FUNCTION_PROVENANCE_MONUS
OID of the provenance_monus FUNCTION.
Oid OID_FUNCTION_AGG_COND_PREDICATE
agg_token_cond_predicate(agg_token,boolean)
Oid OID_FUNCTION_GIVEN_PREDICATE
given_predicate(boolean) – prefix whole-tuple
Oid OID_FUNCTION_NOT_EQUAL_UUID
OID of the = operator on UUIDs FUNCTION.
Oid OID_FUNCTION_GIVEN
OID of provsql.given(uuid)->uuid.
Oid OID_FUNCTION_GATE_ONE
OID of the provenance_one FUNCTION.
Oid OID_FUNCTION_RV_COND
OID of provsql.random_variable_cond(random_variable,uuid).
Oid OID_FUNCTION_RV_COND_PREDICATE
random_variable_cond_predicate(random_variable,boolean)
Oid OID_TYPE_UUID_ARRAY
OID of the uuid[] TYPE.
Oid OID_FUNCTION_AGG_COND
OID of provsql.agg_token_cond(agg_token,uuid): the conditioning constructor for the agg_token carrier...
Oid OID_TYPE_RANDOM_VARIABLE
OID of the random_variable TYPE.
Oid OID_FUNCTION_PROVENANCE_CMP
OID of the provenance_cmp FUNCTION.
Oid OID_FUNCTION_GET_EXTRA
OID of the get_extra FUNCTION.
Oid OID_FUNCTION_RV_CMP[6]
OIDs of the random_variable_{eq,ne,le,lt,ge,gt} comparison procedure functions, indexed by the Compar...
Context for contains_agg_walker.
Definition provsql.c:2791
const constants_t * constants
Definition provsql.c:2792
Context for flatten_mut (a multi-relation conjunctive inliner).
Definition provsql.c:11568
Var *** sub_tl
Definition provsql.c:11574
bool * slot_flat
Definition provsql.c:11570
int ** sub_newpos
Definition provsql.c:11572
int * sub_tl_n
Definition provsql.c:11575
bool quals_mode
Definition provsql.c:11576
int * parent_newpos
Definition provsql.c:11571
int * sub_rtlen
Definition provsql.c:11573
Collector for AGG(DISTINCT) Aggrefs inside a HAVING clause.
Definition provsql.c:4949
List * aggs
Aggref* nodes carrying aggdistinct, in traversal order.
Definition provsql.c:4950
Context for replace_having_distinct_mutator: next outer RT index.
Definition provsql.c:4974
Process the inert provenance() fetches in one query's own clauses.
Definition provsql.c:11987
const constants_t * constants
Definition provsql.c:11987
Context for the insert_agg_token_casts_mutator.
Definition provsql.c:10610
const constants_t * constants
Extension OID cache.
Definition provsql.c:10612
Query * query
Outer query (to look up subquery RTEs).
Definition provsql.c:10611
Context for join_qual_has_agg_token_walker.
Definition provsql.c:10750
const constants_t * constants
Extension OID cache.
Definition provsql.c:10751
Index * rteid
Out: varno of the agg_token Var.
Definition provsql.c:10752
AttrNumber * join_attno
Out: attno of the agg_token Var.
Definition provsql.c:10753
Per-relation user-column descriptor for the outer-join lowering.
Definition provsql.c:7140
Oid * coll
column collation OID
Definition provsql.c:7145
int n
number of user (non-provsql, non-dropped) columns
Definition provsql.c:7141
int32 * typmod
column typmod
Definition provsql.c:7144
Oid * type
column type OID
Definition provsql.c:7143
AttrNumber * attno
original attribute number in the base relation
Definition provsql.c:7142
char ** name
column name
Definition provsql.c:7146
Mutator: lift a scalar subquery's body into the outer query level.
Definition provsql.c:7924
Walker context: detect a Var referencing the join RTE index.
Definition provsql.c:7685
Outer Var remap context for the LEFT-join lowering: base-relation Vars (R_idx / S_idx) are retargeted...
Definition provsql.c:7719
AttrNumber * S_map
Definition provsql.c:7721
Index new_idx
Definition provsql.c:7720
AttrNumber * R_map
Definition provsql.c:7721
Index S_idx
Definition provsql.c:7720
Index R_idx
Definition provsql.c:7720
Context for oj_param_repl_mut.
Definition provsql.c:8398
Var-renumber context: map varno from[i] → to[i].
Definition provsql.c:7323
Index from[2]
Definition provsql.c:7325
Index to[2]
Definition provsql.c:7326
Mutator: replace the specific SubLink node target (by pointer) with replacement.
Definition provsql.c:7984
SubLink * target
Definition provsql.c:7985
Var-remap context for the FROM-wrapping pre-step: a Var at target_level on relation varno / attribute...
Definition provsql.c:8108
int ** pos
Definition provsql.c:8112
int target_level
Definition provsql.c:8109
Index newidx
Definition provsql.c:8110
SubLink * skip
Definition provsql.c:8113
Context for the provenance_mutator tree walker.
Definition provsql.c:5785
bool provsql_has_aggref
true when provsql contains an Aggref (set once by replace_provenance_function_by_expression)....
Definition provsql.c:5788
bool inside_aggref
true while descending the argument tree of an Aggref node.
Definition provsql.c:5789
const constants_t * constants
Extension OID cache.
Definition provsql.c:5787
Expr * provsql
Provenance expression to substitute for provenance() calls.
Definition provsql.c:5786
Context for the reduce_varattno_mutator tree walker.
Definition provsql.c:210
Index varno
Range-table entry whose attribute numbers are being adjusted.
Definition provsql.c:211
int * offset
Per-attribute cumulative shift to apply.
Definition provsql.c:212
Context for retype_agg_var_walker.
Definition provsql.c:10874
Index rteid
Varno of the replaced RTE.
Definition provsql.c:10875
const constants_t * constants
Extension OID cache.
Definition provsql.c:10877
AttrNumber join_attno
Attno of the former agg_token column.
Definition provsql.c:10876