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#include "optimizer/clauses.h" /* contain_volatile_functions */
43#endif
44#include "optimizer/planner.h"
45#include "parser/parse_coerce.h"
46#include "parser/parse_node.h"
47#include "parser/parse_oper.h"
48#include "rewrite/rewriteManip.h"
49#include "parser/parse_relation.h"
50#include "utils/builtins.h"
51#if PG_VERSION_NUM >= 120000
52#include "utils/float.h" /* get_float8_infinity (moved out of builtins.h in PG12) */
53#endif
54#include "parser/parsetree.h"
55#include "storage/lwlock.h"
56#include "storage/shmem.h"
57#include "utils/fmgroids.h"
58#include "utils/guc.h"
59#include "utils/lsyscache.h"
60#include "utils/ruleutils.h"
61#include "utils/syscache.h"
62#include "catalog/namespace.h"
63#include "catalog/pg_cast.h"
64#include "commands/createas.h"
65#include "executor/spi.h"
66#include "tcop/utility.h"
67#include "tcop/tcopprot.h" /* pg_parse_query, pg_analyze_and_rewrite_fixedparams */
68#include <time.h>
69
70#include "classify_query.h"
71#include "joint_width_query.h"
72#include "provsql_mmap.h"
73#include "provsql_shmem.h"
74#include "provsql_utils.h"
75#include "safe_query.h"
76
77#if PG_VERSION_NUM < 100000
78#error "ProvSQL requires PostgreSQL version 10 or later"
79#endif
80
81#include "compatibility.h"
82
83PG_MODULE_MAGIC; ///< Required PostgreSQL extension magic block
84
85/* -------------------------------------------------------------------------
86 * Global state & forward declarations
87 * ------------------------------------------------------------------------- */
88
90static bool provsql_active = true; ///< @c true while ProvSQL query rewriting is enabled
92static bool provsql_update_provenance = false; ///< @c true when provenance tracking for DML is enabled
93int provsql_verbose = 100; ///< Verbosity level; controlled by the @c provsql.verbose_level GUC
94char *provsql_last_eval_method = NULL; ///< Last probability evaluation method(s) used; exposed via @c provsql.last_eval_method
95bool provsql_aggtoken_text_as_uuid = false; ///< When @c true, @c agg_token::text emits the underlying provenance UUID instead of @c "value (*)"
96char *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.
97char *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")
98char *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.
99int 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
100int 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
101double provsql_ess_warn_fraction = 0.1; ///< Effective-sample-size warning threshold for likelihood weighting: warn when the posterior ESS falls below this fraction of the accepted draws; controlled by the @c provsql.ess_warn_fraction GUC
102int 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
103int 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
104int 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
105bool 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
106bool 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
107int 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
108int provsql_mobius_max_cnf = 8; ///< Query-cost cap of the Möbius route: it declines when a sentence's CNF has more than this many conjuncts, since the inclusion-exclusion lattice it walks has \f$2^M\f$ elements; ranking / shattering can inflate the conjunct count, which is what raising it buys; @c provsql.mobius_max_cnf GUC
109bool provsql_simplify_on_load = true; ///< Run universal cmp-resolution passes when @c getGenericCircuit returns; controlled by the @c provsql.simplify_on_load GUC
110bool provsql_hybrid_evaluation = true; ///< Run the hybrid-evaluator simplifier inside @c probability_evaluate; controlled by the @c provsql.hybrid_evaluation GUC
111bool 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
112bool 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
113bool 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.
114bool 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.
115
116/** @brief Values of the @c provsql.provenance enum GUC, from most general to most specialised. */
118 PROVSQL_PROVENANCE_WHERE, ///< Universal semiring provenance plus where-provenance gates.
119 PROVSQL_PROVENANCE_SEMIRING, ///< Universal semiring provenance (default).
120 PROVSQL_PROVENANCE_ABSORPTIVE, ///< Absorptive-semiring constructions licensed (tagged).
121 PROVSQL_PROVENANCE_BOOLEAN ///< Boolean-only machinery licensed (tagged); implies absorptive.
123
124static int provsql_provenance_class = PROVSQL_PROVENANCE_SEMIRING; ///< Backing variable of the @c provsql.provenance GUC.
125
126/** @brief Option table of the @c provsql.provenance GUC. */
127static const struct config_enum_entry provsql_provenance_options[] = {
128 {"where", PROVSQL_PROVENANCE_WHERE, false},
129 {"semiring", PROVSQL_PROVENANCE_SEMIRING, false},
130 {"absorptive", PROVSQL_PROVENANCE_ABSORPTIVE, false},
131 {"boolean", PROVSQL_PROVENANCE_BOOLEAN, false},
132 {NULL, 0, false}
133};
134
135/** @brief Assign hook of @c provsql.provenance: refresh the derived per-class flags. */
143
144extern void _PG_init(void);
145extern void _PG_fini(void);
146
147static planner_hook_type prev_planner = NULL; ///< Previous planner hook (chained)
148
149static Query *process_query(const constants_t *constants, Query *q,
150 bool **removed, bool wrap_root, bool top_level,
151 bool in_boolean_rewrite,
152 const InvFreeMarkerCtx *inv_ctx);
153static bool has_provenance(const constants_t *constants, Query *q);
154static bool has_rv_or_provenance_call(Node *node, void *data);
155static Expr *wrap_in_assume_boolean(const constants_t *constants, Expr *expr);
156static Expr *wrap_in_annotate(const constants_t *constants, Expr *expr,
157 const char *cert);
158
159/* -------------------------------------------------------------------------
160 * Provenance attribute construction
161 * ------------------------------------------------------------------------- */
162
163/**
164 * @brief Build a Var node that references the provenance column of a relation.
165 *
166 * Creates a @c Var pointing to attribute @p attid of range-table entry
167 * @p relid, typed as UUID, and marks the column as selected in the
168 * permission bitmap so PostgreSQL grants access correctly.
169 *
170 * @param constants Extension OID cache.
171 * @param q Owning query (needed to update permission info on PG 16+).
172 * @param r Range-table entry that owns the provenance column.
173 * @param relid 1-based index of @p r in @p q->rtable.
174 * @param attid 1-based attribute number of the provenance column in @p r.
175 * @return A freshly allocated @c Var node.
176 */
177static Var *make_provenance_attribute(const constants_t *constants, Query *q,
178 RangeTblEntry *r, Index relid,
179 AttrNumber attid) {
180 Var *v = makeNode(Var);
181
182 v->varno = relid;
183 v->varattno = attid;
184
185#if PG_VERSION_NUM >= 130000
186 v->varnosyn = relid;
187 v->varattnosyn = attid;
188#else
189 v->varnoold = relid;
190 v->varoattno = attid;
191#endif
192
193 v->vartype = constants->OID_TYPE_UUID;
194 v->varcollid = InvalidOid;
195 v->vartypmod = -1;
196 v->location = -1;
197
198#if PG_VERSION_NUM >= 160000
199 if (r->perminfoindex != 0) {
200 RTEPermissionInfo *rpi =
201 list_nth_node(RTEPermissionInfo, q->rteperminfos, r->perminfoindex - 1);
202 rpi->selectedCols = bms_add_member(
203 rpi->selectedCols, attid - FirstLowInvalidHeapAttributeNumber);
204 }
205#else
206 r->selectedCols = bms_add_member(r->selectedCols,
207 attid - FirstLowInvalidHeapAttributeNumber);
208#endif
209
210 return v;
211}
212
213/* -------------------------------------------------------------------------
214 * Helper mutators: attribute-number fixup and type patching
215 * ------------------------------------------------------------------------- */
216
217/** @brief Context for the @c reduce_varattno_mutator tree walker. */
219 Index varno; ///< Range-table entry whose attribute numbers are being adjusted
220 int *offset; ///< Per-attribute cumulative shift to apply
222
223/**
224 * @brief Tree-mutator callback that adjusts Var attribute numbers.
225 * @param node Current expression tree node.
226 * @param ctx Pointer to a @c reduce_varattno_mutator_context.
227 * @return Possibly modified node.
228 */
229static Node *reduce_varattno_mutator(Node *node, void *ctx) {
231 if (node == NULL)
232 return NULL;
233
234 if (IsA(node, Var)) {
235 Var *v = (Var *)node;
236
237 if (v->varno == context->varno) {
238 v->varattno += context->offset[v->varattno - 1];
239 }
240 }
241
242 return expression_tree_mutator(node, reduce_varattno_mutator, ctx);
243}
244
245/**
246 * @brief Adjust Var attribute numbers in @p targetList after columns are removed.
247 *
248 * When provenance columns are stripped from a subquery's target list, the
249 * remaining columns shift left. This function applies a pre-computed
250 * @p offset array (one entry per original column) to correct all @c Var
251 * nodes that reference range-table entry @p varno.
252 *
253 * @param targetList Target list of the outer query to patch.
254 * @param varno Range-table entry whose attribute numbers need fixing.
255 * @param offset Cumulative shift per original attribute (negative or zero).
256 */
257static void reduce_varattno_by_offset(List *targetList, Index varno,
258 int *offset) {
259 ListCell *lc;
260 reduce_varattno_mutator_context context = {varno, offset};
261
262 foreach (lc, targetList) {
263 Node *te = lfirst(lc);
264 expression_tree_mutator(te, reduce_varattno_mutator, &context);
265 }
266}
267
268/** @brief Context for the @c aggregation_type_mutator tree walker. */
270 Index varno; ///< Range-table entry index of the aggregate var
271 Index varattno; ///< Attribute number of the aggregate column
272 const constants_t *constants; ///< Extension OID cache
274
275/**
276 * @brief Check if a Var matches the target aggregate column.
277 */
278static bool is_target_agg_var(Node *node,
280 if (IsA(node, Var)) {
281 Var *v = (Var *)node;
282 return v->varno == context->varno && v->varattno == context->varattno;
283 }
284 return false;
285}
286
287/**
288 * @brief Tree-mutator that retypes a specific Var to @c agg_token.
289 *
290 * When the target Var is inside a cast FuncExpr, replaces the cast
291 * function with the equivalent agg_token→target cast from pg_cast.
292 * When the Var appears bare (e.g. in a TargetEntry for display), it is
293 * retyped to agg_token directly. In all other contexts (arithmetic,
294 * window functions, etc.), wraps the Var in an explicit agg_token→original
295 * cast so that parent nodes receive the expected type.
296 *
297 * @param node Current expression tree node.
298 * @param ctx Pointer to an @c aggregation_type_mutator_context (varno,
299 * varattno, and constants).
300 * @return Possibly modified node.
301 */
302static Node *
303aggregation_type_mutator(Node *node, void *ctx) {
305 if (node == NULL)
306 return NULL;
307
308 if (IsA(node, FuncExpr)) {
309 FuncExpr *f = (FuncExpr *)node;
310
311 /* Check if this is a cast wrapping our target Var */
312 if (list_length(f->args) == 1 &&
313 is_target_agg_var(linitial(f->args), context)) {
314 /* Look up the cast from agg_token to the target type */
315 HeapTuple castTuple = SearchSysCache2(CASTSOURCETARGET,
316 ObjectIdGetDatum(context->constants->OID_TYPE_AGG_TOKEN),
317 ObjectIdGetDatum(f->funcresulttype));
318
319 if (HeapTupleIsValid(castTuple)) {
320 Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(castTuple);
321 if (OidIsValid(castForm->castfunc)) {
322 f->funcid = castForm->castfunc;
323 }
324 ReleaseSysCache(castTuple);
325 }
326
327 /* Retype the Var inside */
328 ((Var *)linitial(f->args))->vartype =
330
331 return (Node *)f;
332 }
333 }
334
335 if (IsA(node, Var)) {
336 Var *v = (Var *)node;
337
338 if (v->varno == context->varno && v->varattno == context->varattno) {
339 v->vartype = context->constants->OID_TYPE_AGG_TOKEN;
340 }
341 }
342 return expression_tree_mutator(node, aggregation_type_mutator, ctx);
343}
344
345/**
346 * @brief Retypes aggregation-result Vars in @p q from UUID to @c agg_token.
347 *
348 * After a subquery that contains @c provenance_aggregate is processed, its
349 * result type is @c agg_token rather than plain UUID. This mutator walks
350 * the outer query and updates the type of every @c Var referencing that
351 * result column so that subsequent type-checking passes correctly.
352 *
353 * An aggregate result reaches an enclosing query either directly, as the
354 * subquery's own @c provenance_aggregate call, or forwarded by an
355 * intermediate subquery that merely selects it -- in which case the deeper
356 * level's own pass (@c process_query recurses before this runs) has already
357 * retyped that intermediate @c Var. Both shapes are recognised by the
358 * column's *type* being @c agg_token, which is what carries the retyping
359 * through arbitrarily many levels of nesting: keying on the producing
360 * @c FuncExpr instead stops at the first level, leaving the column declared
361 * as its pre-rewrite scalar type, and a comparison against it is then
362 * executed natively on the raw composite datum.
363 *
364 * @param constants Extension OID cache.
365 * @param q Outer query to patch.
366 * @param rteid Range-table index of the subquery in @p q.
367 * @param targetList Target list of the subquery (to locate the aggregate
368 * result columns).
369 */
370static void fix_type_of_aggregation_result(const constants_t *constants,
371 Query *q, Index rteid,
372 List *targetList) {
373 ListCell *lc;
374 aggregation_type_mutator_context context = {0, 0, constants};
375 Index attno = 1;
376
377 foreach (lc, targetList) {
378 TargetEntry *te = (TargetEntry *)lfirst(lc);
379
380 if (exprType((Node *)te->expr) == constants->OID_TYPE_AGG_TOKEN) {
381 context.varno = rteid;
382 context.varattno = attno;
383 query_tree_mutator(q, aggregation_type_mutator, &context,
384 QTW_DONT_COPY_QUERY | QTW_IGNORE_RC_SUBQUERIES);
385
386 /* Check if the retyped column is used in ORDER BY or GROUP BY */
387 {
388 ListCell *lc2;
389 foreach (lc2, q->targetList) {
390 TargetEntry *outer_te = (TargetEntry *)lfirst(lc2);
391 if (IsA(outer_te->expr, Var)) {
392 Var *v = (Var *)outer_te->expr;
393 if (v->varno == rteid && v->varattno == attno &&
394 outer_te->ressortgroupref > 0) {
395 ListCell *lc3;
396 foreach (lc3, q->sortClause) {
397 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc3);
398 if (sgc->tleSortGroupRef == outer_te->ressortgroupref)
399 provsql_error("ORDER BY on aggregate results from "
400 "a subquery not supported");
401 }
402 foreach (lc3, q->groupClause) {
403 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc3);
404 if (sgc->tleSortGroupRef == outer_te->ressortgroupref)
405 provsql_error("GROUP BY on aggregate results from "
406 "a subquery not supported");
407 }
408 }
409 }
410 }
411 }
412 }
413 ++attno;
414 }
415}
416
417/**
418 * @brief Memo entry mapping a recursive-CTE name to its lowered scan subquery.
419 *
420 * A recursive CTE referenced more than once (e.g. in two arms of a top-level
421 * UNION) must be lowered -- and its backing temp table created by
422 * @c eval_recursive -- exactly once: re-running the fixpoint would
423 * @c DROP @c TABLE the temp table that an earlier reference's analyzed scan
424 * already bound to by OID, yielding "could not open relation with OID ...".
425 * @c inline_ctes_in_rtable records each lowering here and reuses it for
426 * subsequent references to the same CTE.
427 */
428typedef struct LoweredCte {
429 const char *name;
430 Query *subquery;
431#if PG_VERSION_NUM >= 150000
432 /* When the CTE was recognised as the (plain, single-column)
433 * reachability shape, the pieces the post-lowering aggregation
434 * planting needs to rebuild the gathering arguments; see
435 * plant_reach_aggregations(). */
436 bool reach_routed;
437 Oid edge_relid; /* InvalidOid for a subquery edge */
438 const char *src_name;
439 const char *dst_name;
440 const char *source_text; /* NULL for the multi-source form */
441 Oid source_relid;
442 const char *source_attname;
443 bool directed;
444 const char *edge_quals;
445 const char *edge_sql;
446#endif
447} LoweredCte;
448
449#if PG_VERSION_NUM >= 150000
450/* Only the >= 15 recursive-CTE lowering memoises into the list. */
451static Query *lookup_lowered_cte(List *lowered, const char *name) {
452 ListCell *lc;
453 foreach (lc, lowered) {
454 LoweredCte *e = (LoweredCte *)lfirst(lc);
455 if (strcmp(e->name, name) == 0)
456 return e->subquery;
457 }
458 return NULL;
459}
460#endif
461
462#if PG_VERSION_NUM >= 150000
463/** @brief Output of @c detect_reachability_cte(): the recognised shape's pieces. */
464typedef struct ReachabilityShape {
465 Oid relid; /**< Edge relation. */
466 AttrNumber src_attno; /**< Source-vertex column of the edge relation. */
467 AttrNumber dst_attno; /**< Destination-vertex column. */
468 char *source_text; /**< Base arm's constant, rendered as text (constant form). */
469 Oid source_relid; /**< Base arm's source relation (multi-source form), or InvalidOid. */
470 AttrNumber source_attno; /**< Vertex column of the source relation. */
471 bool directed; /**< false for the undirected (CASE/IN) shape. */
472 char *edge_quals; /**< Deparsed extra quals over edge columns, or NULL. */
473 char *edge_sql; /**< Deparsed edge subquery (join-defined edges), or NULL. */
474 List *edge_rte_colnames; /**< Output column names of the edge subquery. */
475 int hop_bound; /**< Maximum walk length (hop-counting shape), or -1. */
476 int hop_seed; /**< Base arm's hop constant (hop-counting shape). */
477 int hops_position; /**< 1-based CTE position of the hop column, or 0. */
478 int node_position; /**< 1-based CTE position of the vertex column. */
479} ReachabilityShape;
480
481/** @brief Context for @c reach_varnos_walker(): set of varnos seen. */
482typedef struct ReachVarnosCtx {
483 Bitmapset *varnos; /**< Accumulated varnos (level-0 Vars). */
484 bool other; /**< Found an upper-level Var or other disqualifier. */
485} ReachVarnosCtx;
486
487/** @brief expression_tree_walker collecting level-0 varnos. */
488static bool reach_varnos_walker(Node *node, ReachVarnosCtx *ctx) {
489 if (node == NULL)
490 return false;
491 if (IsA(node, Var)) {
492 Var *v = (Var *) node;
493 if (v->varlevelsup != 0)
494 ctx->other = true;
495 else
496 ctx->varnos = bms_add_member(ctx->varnos, v->varno);
497 return false;
498 }
499 return expression_tree_walker(node, reach_varnos_walker, (void *) ctx);
500}
501
502/** @brief Strip @c RelabelType decorations off an expression. */
503static Node *reach_strip(Node *n) {
504 while (n != NULL && IsA(n, RelabelType))
505 n = (Node *) ((RelabelType *) n)->arg;
506 return n;
507}
508
509/**
510 * @brief Collect every qual of a join tree into @p quals, flattening AND.
511 *
512 * Only inner joins are in scope; any outer join flips @p ok to false.
513 */
514static void reach_collect_quals(Node *jtnode, List **quals, bool *ok) {
515 if (jtnode == NULL || !*ok)
516 return;
517 if (IsA(jtnode, FromExpr)) {
518 FromExpr *f = (FromExpr *) jtnode;
519 ListCell *lc;
520 foreach(lc, f->fromlist)
521 reach_collect_quals((Node *) lfirst(lc), quals, ok);
522 if (f->quals)
523 *quals = list_concat(*quals, make_ands_implicit((Expr *) f->quals));
524 } else if (IsA(jtnode, JoinExpr)) {
525 JoinExpr *j = (JoinExpr *) jtnode;
526 if (j->jointype != JOIN_INNER) {
527 *ok = false;
528 return;
529 }
530 reach_collect_quals(j->larg, quals, ok);
531 reach_collect_quals(j->rarg, quals, ok);
532 if (j->quals)
533 *quals = list_concat(*quals, make_ands_implicit((Expr *) j->quals));
534 }
535 /* RangeTblRef: nothing to collect */
536}
537
538/** @brief Return the single non-junk target entry of @p q, or NULL. */
539static TargetEntry *reach_single_tle(Query *q) {
540 TargetEntry *res = NULL;
541 ListCell *lc;
542 foreach(lc, q->targetList) {
543 TargetEntry *te = (TargetEntry *) lfirst(lc);
544 if (te->resjunk)
545 continue;
546 if (res != NULL)
547 return NULL;
548 res = te;
549 }
550 return res;
551}
552
553/** @brief Collect the two non-junk target entries of @p q by resno (1, 2). */
554static bool reach_two_tles(Query *q, TargetEntry *out[2]) {
555 ListCell *lc;
556 out[0] = out[1] = NULL;
557 foreach(lc, q->targetList) {
558 TargetEntry *te = (TargetEntry *) lfirst(lc);
559 if (te->resjunk)
560 continue;
561 if (te->resno < 1 || te->resno > 2 || out[te->resno - 1] != NULL)
562 return false;
563 out[te->resno - 1] = te;
564 }
565 return out[0] != NULL && out[1] != NULL;
566}
567
568/** @brief Read an integer Const of int2/int4/int8 type into @p value. */
569static bool reach_int_const(Node *n, int64 *value) {
570 Const *c = (Const *) reach_strip(n);
571 if (c == NULL || !IsA(c, Const) || c->constisnull)
572 return false;
573 switch (c->consttype) {
574 case INT2OID:
575 *value = DatumGetInt16(c->constvalue);
576 return true;
577 case INT4OID:
578 *value = DatumGetInt32(c->constvalue);
579 return true;
580 case INT8OID:
581 *value = DatumGetInt64(c->constvalue);
582 return true;
583 default:
584 return false;
585 }
586}
587
588/**
589 * @brief Recognise a hop-counter increment: @c r.hops + 1 over the
590 * recursive table's column @p resno (the counter must increment
591 * its own column).
592 */
593static bool reach_is_hop_increment(Node *n, Index cte_rti, AttrNumber resno) {
594 OpExpr *op = (OpExpr *) reach_strip(n);
595 Var *v = NULL;
596 int64 one;
597 char *opname;
598 bool is_plus;
599 if (op == NULL || !IsA(op, OpExpr) || list_length(op->args) != 2)
600 return false;
601 opname = get_opname(op->opno);
602 is_plus = opname != NULL && strcmp(opname, "+") == 0;
603 if (opname)
604 pfree(opname);
605 if (!is_plus)
606 return false;
607 if (reach_int_const((Node *) lsecond(op->args), &one))
608 v = (Var *) reach_strip((Node *) linitial(op->args));
609 else if (reach_int_const((Node *) linitial(op->args), &one))
610 v = (Var *) reach_strip((Node *) lsecond(op->args));
611 else
612 return false;
613 if (one != 1 || v == NULL || !IsA(v, Var))
614 return false;
615 return v->varno == cte_rti && v->varlevelsup == 0 && v->varattno == resno;
616}
617
618/**
619 * @brief Recognise a hop-bound qual -- @c r.hops < B or @c r.hops <= B
620 * (either orientation) over the recursive table's column
621 * @p hops_pos -- and return the bound and its strictness.
622 */
623static bool reach_is_hop_bound(Node *n, Index cte_rti, AttrNumber hops_pos,
624 int64 *bound, bool *strict) {
625 OpExpr *op = (OpExpr *) reach_strip(n);
626 Var *v;
627 char *opname;
628 bool var_first;
629 if (op == NULL || !IsA(op, OpExpr) || list_length(op->args) != 2)
630 return false;
631 v = (Var *) reach_strip((Node *) linitial(op->args));
632 if (v != NULL && IsA(v, Var) &&
633 reach_int_const((Node *) lsecond(op->args), bound))
634 var_first = true;
635 else {
636 v = (Var *) reach_strip((Node *) lsecond(op->args));
637 if (v == NULL || !IsA(v, Var) ||
638 !reach_int_const((Node *) linitial(op->args), bound))
639 return false;
640 var_first = false;
641 }
642 if (v->varno != cte_rti || v->varlevelsup != 0 || v->varattno != hops_pos)
643 return false;
644 opname = get_opname(op->opno);
645 if (opname == NULL)
646 return false;
647 /* var < B / var <= B; B > var / B >= var are the same bounds. */
648 if (strcmp(opname, var_first ? "<" : ">") == 0)
649 *strict = true;
650 else if (strcmp(opname, var_first ? "<=" : ">=") == 0)
651 *strict = false;
652 else {
653 pfree(opname);
654 return false;
655 }
656 pfree(opname);
657 return true;
658}
659
660/**
661 * @brief Recognise the linear reachability shape of a recursive CTE.
662 *
663 * Accepted (in either arm order, with either qual orientation):
664 *
665 * WITH RECURSIVE reach(v) AS (
666 * SELECT <constant>
667 * UNION
668 * SELECT e.<dst> FROM <edge> e JOIN reach r ON e.<src> = r.v
669 * )
670 *
671 * where @c <edge> is a provenance-tracked base relation (a @c provsql
672 * UUID column), the recursive arm has no other clauses, and the single
673 * join qual is a mergejoinable equality between an edge column and the
674 * CTE's (single) column. The caller has already checked the UNION
675 * (set) shape and the @c provsql.boolean_provenance gate.
676 *
677 * The *hop-counting* variant adds a counter column (in either CTE
678 * position):
679 *
680 * WITH RECURSIVE reach(v, hops) AS (
681 * SELECT <constant>, <int constant>
682 * UNION
683 * SELECT e.<dst>, r.hops + 1 FROM <edge> e JOIN reach r
684 * ON e.<src> = r.v WHERE r.hops < <int constant>
685 * )
686 *
687 * with @c <= accepted too; the bound qual is mandatory (an unbounded
688 * counter never reaches a fixpoint on cyclic data) and the maximum
689 * walk length it implies must not exceed the compiler's cap. The
690 * multi-source and undirected (CASE/IN) forms compose with it.
691 *
692 * @param cte The recursive CTE.
693 * @param cteq Its query (a UNION of two subquery arms).
694 * @param constants OID constants (for the UUID type check).
695 * @param out Output: the recognised pieces.
696 * @return Whether the shape was recognised.
697 */
698static bool detect_reachability_cte(CommonTableExpr *cte, Query *cteq,
699 const constants_t *constants,
700 ReachabilityShape *out) {
701 SetOperationStmt *so = (SetOperationStmt *) cteq->setOperations;
702 Query *arms[2];
703 Query *base = NULL, *rec = NULL;
704 Index edge_rti = 0, cte_rti = 0;
705 TargetEntry *tle;
706 Var *target_var;
707 List *quals = NIL;
708 bool ok = true;
709 Node *join_qual;
710 OpExpr *eq;
711 Var *va, *vb, *edge_var, *cte_var;
712 AttrNumber prov_attno;
713 int i;
714 ListCell *lc;
715 bool hops_mode;
716 AttrNumber node_pos = 1, hops_pos = 0;
717 TargetEntry *base_tles[2], *rec_tles[2];
718 int64 hop_seed = 0;
719
720 if (list_length(cte->ctecolnames) == 1)
721 hops_mode = false;
722 else if (list_length(cte->ctecolnames) == 2)
723 hops_mode = true;
724 else
725 return false;
726
727 if (!IsA(so->larg, RangeTblRef) || !IsA(so->rarg, RangeTblRef))
728 return false;
729 for (i = 0; i < 2; ++i) {
730 RangeTblRef *rtr = (RangeTblRef *) (i == 0 ? so->larg : so->rarg);
731 RangeTblEntry *r = rt_fetch(rtr->rtindex, cteq->rtable);
732 if (r->rtekind != RTE_SUBQUERY || r->subquery == NULL)
733 return false;
734 arms[i] = r->subquery;
735 }
736
737 /* Identify the recursive arm: the one with the self-reference (whose
738 * range-table index the hop analysis below needs early). */
739 {
740 Index rec_self_rti = 0;
741 for (i = 0; i < 2; ++i) {
742 bool has_self = false;
743 Index rti = 0, self_rti = 0;
744 foreach(lc, arms[i]->rtable) {
745 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
746 ++rti;
747 if (r->rtekind == RTE_CTE && r->self_reference &&
748 strcmp(r->ctename, cte->ctename) == 0) {
749 has_self = true;
750 self_rti = rti;
751 }
752 }
753 if (has_self) {
754 if (rec != NULL)
755 return false;
756 rec = arms[i];
757 rec_self_rti = self_rti;
758 } else {
759 if (base != NULL)
760 return false;
761 base = arms[i];
762 }
763 }
764 if (base == NULL || rec == NULL)
765 return false;
766
767 if (hops_mode) {
768 /* Identify the counter column: exactly one recursive-arm target
769 * entry of the form r.hops + 1 over its own column; the node
770 * column is the other one. The base arm seeds the counter with
771 * an integer constant in the matching position. */
772 int n_inc = 0;
773 if (!reach_two_tles(rec, rec_tles) || !reach_two_tles(base, base_tles))
774 return false;
775 for (i = 0; i < 2; ++i)
776 if (reach_is_hop_increment((Node *) rec_tles[i]->expr, rec_self_rti,
777 (AttrNumber) (i + 1))) {
778 hops_pos = (AttrNumber) (i + 1);
779 ++n_inc;
780 }
781 if (n_inc != 1)
782 return false;
783 node_pos = (AttrNumber) (3 - hops_pos);
784 if (!reach_int_const((Node *) base_tles[hops_pos - 1]->expr, &hop_seed))
785 return false;
786 if (hop_seed < PG_INT32_MIN/2 || hop_seed > PG_INT32_MAX/2)
787 return false;
788 }
789 }
790
791 /* Base arm: either SELECT <constant> (no FROM), or
792 * SELECT <column> FROM <relation> -- a (possibly probabilistic when
793 * tracked) source set; no other clauses in both forms. */
794 if (base->setOperations != NULL || base->hasAggs || base->hasSubLinks ||
795 base->hasTargetSRFs || base->groupClause != NIL ||
796 base->distinctClause != NIL || base->jointree == NULL ||
797 base->jointree->quals != NULL)
798 return false;
799 tle = hops_mode ? base_tles[node_pos - 1] : reach_single_tle(base);
800 if (tle == NULL)
801 return false;
802 if (base->jointree->fromlist == NIL) {
803 Node *bexpr = reach_strip((Node *) tle->expr);
804 Const *c;
805 Oid outfunc;
806 bool varlena;
807 if (bexpr == NULL || !IsA(bexpr, Const))
808 return false;
809 c = (Const *) bexpr;
810 if (c->constisnull)
811 return false;
812 getTypeOutputInfo(c->consttype, &outfunc, &varlena);
813 out->source_text = OidOutputFunctionCall(outfunc, c->constvalue);
814 } else {
815 RangeTblEntry *srel = NULL;
816 Index srel_rti = 0;
817 Var *sv;
818 Index rti = 0;
819 if (list_length(base->jointree->fromlist) != 1 ||
820 !IsA(linitial(base->jointree->fromlist), RangeTblRef))
821 return false;
822 foreach(lc, base->rtable) {
823 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
824 ++rti;
825 if (r->rtekind != RTE_RELATION || r->relkind != RELKIND_RELATION)
826 return false;
827 if (srel != NULL)
828 return false;
829 srel = r;
830 srel_rti = rti;
831 }
832 if (srel == NULL)
833 return false;
834 sv = (Var *) reach_strip((Node *) tle->expr);
835 if (sv == NULL || !IsA(sv, Var) || sv->varno != srel_rti ||
836 sv->varlevelsup != 0 || sv->varattno <= 0)
837 return false;
838 /* Do not take the provsql column itself as the vertex. */
839 {
840 AttrNumber sprov = get_attnum(srel->relid, PROVSQL_COLUMN_NAME);
841 if (sprov != InvalidAttrNumber && sv->varattno == sprov)
842 return false;
843 }
844 out->source_relid = srel->relid;
845 out->source_attno = sv->varattno;
846 }
847
848 /* Recursive arm: single tracked base relation joined with the CTE. */
849 if (rec->hasAggs || rec->hasWindowFuncs || rec->hasSubLinks ||
850 rec->hasTargetSRFs || rec->groupClause != NIL ||
851 rec->distinctClause != NIL || rec->sortClause != NIL ||
852 rec->havingQual != NULL || rec->limitOffset != NULL ||
853 rec->limitCount != NULL || rec->setOperations != NULL ||
854 rec->groupingSets != NIL)
855 return false;
856 i = 0;
857 {
858 RangeTblEntry *edge_rte = NULL;
859 foreach(lc, rec->rtable) {
860 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
861 ++i;
862 switch (r->rtekind) {
863 case RTE_RELATION:
864 if (edge_rti != 0 || r->relkind != RELKIND_RELATION)
865 return false;
866 edge_rti = i;
867 edge_rte = r;
868 out->relid = r->relid;
869 break;
870 case RTE_SUBQUERY:
871 /* A derived (join-defined) edge relation: deparsed and gathered
872 * as a subquery; its tokens are validated dynamically by the
873 * gathering (conjunctions of base tuples with pairwise disjoint
874 * supports), and any unsupported provenance shape falls back at
875 * run time. An inlined view lands here too. */
876 if (edge_rti != 0 || r->subquery == NULL)
877 return false;
878 edge_rti = i;
879 edge_rte = r;
880 out->relid = InvalidOid;
881 out->edge_sql = pg_get_querydef(copyObject(r->subquery), false);
882 break;
883 case RTE_CTE:
884 if (cte_rti != 0 || !r->self_reference ||
885 strcmp(r->ctename, cte->ctename) != 0)
886 return false;
887 cte_rti = i;
888 break;
889 case RTE_JOIN:
890 break;
891 default:
892 return false;
893 }
894 }
895 if (edge_rti == 0 || cte_rti == 0)
896 return false;
897
898 if (OidIsValid(out->relid)) {
899 /* The edge relation must be provenance-tracked (a provsql UUID
900 * column). */
901 prov_attno = get_attnum(out->relid, PROVSQL_COLUMN_NAME);
902 if (prov_attno == InvalidAttrNumber ||
903 get_atttype(out->relid, prov_attno) != constants->OID_TYPE_UUID)
904 return false;
905 } else {
906 prov_attno = InvalidAttrNumber;
907 /* Column names of the subquery's output, for the gathering. */
908 out->edge_rte_colnames = edge_rte->eref->colnames;
909 }
910 }
911
912 /* Quals: one qual touching the recursive table (the join: a single
913 * equality in the directed shape, a two-way disjunction in the
914 * undirected one), plus optionally deterministic filters over edge
915 * columns alone (folded into the edge gathering). */
916 reach_collect_quals((Node *) rec->jointree, &quals, &ok);
917 if (!ok)
918 return false;
919 {
920 List *edge_only = NIL;
921 bool have_bound = false;
922 int64 bound = 0;
923 bool strict = false;
924 join_qual = NULL;
925 foreach(lc, quals) {
926 Node *q = (Node *) lfirst(lc);
927 ReachVarnosCtx vctx = {NULL, false};
928 reach_varnos_walker(q, &vctx);
929 if (vctx.other)
930 return false;
931 if (bms_is_member(cte_rti, vctx.varnos)) {
932 /* Touches the recursive table: the hop bound (hop-counting
933 * shape, at most once) or THE join qual. */
934 if (hops_mode && !have_bound &&
935 reach_is_hop_bound(q, cte_rti, hops_pos, &bound, &strict))
936 have_bound = true;
937 else if (join_qual == NULL)
938 join_qual = q;
939 else
940 return false;
941 } else if (bms_is_subset(vctx.varnos, bms_make_singleton(edge_rti))) {
942 /* Edge columns (or constants) only: a row filter, kept if
943 * deterministic. */
944 if (contain_volatile_functions(q))
945 return false;
946 edge_only = lappend(edge_only, q);
947 } else
948 return false;
949 }
950 if (join_qual == NULL)
951 return false;
952 if (hops_mode) {
953 /* The bound is mandatory: an unbounded counter has no fixpoint on
954 * cyclic data. hops < B allows B - seed recursive steps,
955 * hops <= B one more; a bound below the seed allows none. */
956 int64 max_len;
957 if (!have_bound)
958 return false;
959 max_len = bound - hop_seed + (strict ? 0 : 1);
960 if (max_len < 0)
961 max_len = 0;
962 if (max_len > 62) /* ReachabilityCompiler::MAX_HOP_BOUND */
963 return false;
964 out->hop_bound = (int) max_len;
965 out->hop_seed = (int) hop_seed;
966 out->hops_position = hops_pos;
967 }
968 out->node_position = node_pos;
969 if (edge_only != NIL && !OidIsValid(out->relid))
970 return false; /* extra filters on a subquery edge: not deparsable here */
971 if (edge_only != NIL) {
972 /* Deparse the filters against the bare relation, for the
973 * edge-gathering query (whose FROM is the relation itself). */
974 Node *conj = (Node *) make_ands_explicit(edge_only);
975 List *dpcontext;
976 conj = copyObject(conj);
977 ChangeVarNodes(conj, edge_rti, 1, 0);
978 dpcontext = deparse_context_for(get_rel_name(out->relid), out->relid);
979 out->edge_quals = deparse_expression(conj, dpcontext, false, false);
980 }
981 }
982
983 /* Target + join qual, directed shape: SELECT e.dst ... ON e.src = r.v. */
984 tle = hops_mode ? rec_tles[node_pos - 1] : reach_single_tle(rec);
985 if (tle == NULL)
986 return false;
987 {
988 Node *texpr = reach_strip((Node *) tle->expr);
989 if (texpr != NULL && IsA(texpr, Var)) {
990 target_var = (Var *) texpr;
991 if (target_var->varno != edge_rti || target_var->varlevelsup != 0 ||
992 target_var->varattno <= 0 || target_var->varattno == prov_attno)
993 return false;
994 out->dst_attno = target_var->varattno;
995
996 if (!IsA(join_qual, OpExpr))
997 return false;
998 eq = (OpExpr *) join_qual;
999 if (list_length(eq->args) != 2)
1000 return false;
1001 va = (Var *) reach_strip((Node *) linitial(eq->args));
1002 vb = (Var *) reach_strip((Node *) lsecond(eq->args));
1003 if (va == NULL || vb == NULL || !IsA(va, Var) || !IsA(vb, Var) ||
1004 va->varlevelsup != 0 || vb->varlevelsup != 0)
1005 return false;
1006 if (!op_mergejoinable(eq->opno, exprType((Node *) linitial(eq->args))))
1007 return false;
1008 if (va->varno == edge_rti && vb->varno == cte_rti)
1009 edge_var = va, cte_var = vb;
1010 else if (vb->varno == edge_rti && va->varno == cte_rti)
1011 edge_var = vb, cte_var = va;
1012 else
1013 return false;
1014 if (cte_var->varattno != node_pos || edge_var->varattno <= 0 ||
1015 edge_var->varattno == prov_attno)
1016 return false;
1017 out->src_attno = edge_var->varattno;
1018 out->directed = true;
1019 return true;
1020 }
1021
1022 /* Undirected shape:
1023 * SELECT CASE WHEN e.a = r.v THEN e.b ELSE e.a END
1024 * ... ON r.v IN (e.a, e.b)
1025 * The join qual is the two-way disjunction (an OR of two equalities,
1026 * or a ScalarArrayOpExpr over an explicit two-element array). */
1027 if (texpr != NULL && IsA(texpr, CaseExpr)) {
1028 CaseExpr *ce = (CaseExpr *) texpr;
1029 CaseWhen *cw;
1030 OpExpr *weq;
1031 Var *wa, *wb, *wedge, *wcte, *res, *def;
1032 AttrNumber col_a, col_b;
1033
1034 if (ce->arg != NULL || list_length(ce->args) != 1 ||
1035 ce->defresult == NULL)
1036 return false;
1037 cw = (CaseWhen *) linitial(ce->args);
1038 if (!IsA(cw->expr, OpExpr))
1039 return false;
1040 weq = (OpExpr *) cw->expr;
1041 if (list_length(weq->args) != 2 ||
1042 !op_mergejoinable(weq->opno, exprType((Node *) linitial(weq->args))))
1043 return false;
1044 wa = (Var *) reach_strip((Node *) linitial(weq->args));
1045 wb = (Var *) reach_strip((Node *) lsecond(weq->args));
1046 if (wa == NULL || wb == NULL || !IsA(wa, Var) || !IsA(wb, Var) ||
1047 wa->varlevelsup != 0 || wb->varlevelsup != 0)
1048 return false;
1049 if (wa->varno == edge_rti && wb->varno == cte_rti)
1050 wedge = wa, wcte = wb;
1051 else if (wb->varno == edge_rti && wa->varno == cte_rti)
1052 wedge = wb, wcte = wa;
1053 else
1054 return false;
1055 if (wcte->varattno != node_pos)
1056 return false;
1057 res = (Var *) reach_strip((Node *) cw->result);
1058 def = (Var *) reach_strip((Node *) ce->defresult);
1059 if (res == NULL || def == NULL || !IsA(res, Var) || !IsA(def, Var) ||
1060 res->varno != edge_rti || def->varno != edge_rti ||
1061 res->varlevelsup != 0 || def->varlevelsup != 0)
1062 return false;
1063 /* WHEN e.X = r.v THEN e.Y ELSE e.X: the tested column is also the
1064 * default. */
1065 col_a = wedge->varattno; /* X */
1066 col_b = res->varattno; /* Y */
1067 if (def->varattno != col_a || col_a == col_b ||
1068 col_a <= 0 || col_b <= 0 ||
1069 col_a == prov_attno || col_b == prov_attno)
1070 return false;
1071
1072 /* The join disjunction must test r.v against exactly {e.X, e.Y}. */
1073 {
1074 AttrNumber got[2] = {0, 0};
1075 int n = 0;
1076 if (IsA(join_qual, BoolExpr) &&
1077 ((BoolExpr *) join_qual)->boolop == OR_EXPR &&
1078 list_length(((BoolExpr *) join_qual)->args) == 2) {
1079 ListCell *olc;
1080 foreach(olc, ((BoolExpr *) join_qual)->args) {
1081 OpExpr *oeq = (OpExpr *) lfirst(olc);
1082 Var *oa, *ob, *oedge, *octe;
1083 if (!IsA(oeq, OpExpr) || list_length(oeq->args) != 2 ||
1084 !op_mergejoinable(oeq->opno,
1085 exprType((Node *) linitial(oeq->args))))
1086 return false;
1087 oa = (Var *) reach_strip((Node *) linitial(oeq->args));
1088 ob = (Var *) reach_strip((Node *) lsecond(oeq->args));
1089 if (oa == NULL || ob == NULL || !IsA(oa, Var) || !IsA(ob, Var))
1090 return false;
1091 if (oa->varno == edge_rti && ob->varno == cte_rti)
1092 oedge = oa, octe = ob;
1093 else if (ob->varno == edge_rti && oa->varno == cte_rti)
1094 oedge = ob, octe = oa;
1095 else
1096 return false;
1097 if (octe->varattno != node_pos || n >= 2)
1098 return false;
1099 got[n++] = oedge->varattno;
1100 }
1101 } else if (IsA(join_qual, ScalarArrayOpExpr)) {
1102 ScalarArrayOpExpr *sao = (ScalarArrayOpExpr *) join_qual;
1103 Var *scte;
1104 ArrayExpr *arr;
1105 ListCell *alc;
1106 if (!sao->useOr || list_length(sao->args) != 2 ||
1107 !op_mergejoinable(sao->opno,
1108 exprType((Node *) linitial(sao->args))))
1109 return false;
1110 scte = (Var *) reach_strip((Node *) linitial(sao->args));
1111 if (scte == NULL || !IsA(scte, Var) || scte->varno != cte_rti ||
1112 scte->varattno != node_pos)
1113 return false;
1114 arr = (ArrayExpr *) reach_strip((Node *) lsecond(sao->args));
1115 if (arr == NULL || !IsA(arr, ArrayExpr) ||
1116 list_length(arr->elements) != 2)
1117 return false;
1118 foreach(alc, arr->elements) {
1119 Var *ev = (Var *) reach_strip((Node *) lfirst(alc));
1120 if (ev == NULL || !IsA(ev, Var) || ev->varno != edge_rti ||
1121 n >= 2)
1122 return false;
1123 got[n++] = ev->varattno;
1124 }
1125 } else
1126 return false;
1127 if (n != 2 ||
1128 !((got[0] == col_a && got[1] == col_b) ||
1129 (got[0] == col_b && got[1] == col_a)))
1130 return false;
1131 }
1132
1133 out->src_attno = col_a;
1134 out->dst_attno = col_b;
1135 out->directed = false;
1136 return true;
1137 }
1138 }
1139 return false;
1140}
1141#endif
1142
1143#if PG_VERSION_NUM >= 150000
1144/**
1145 * @brief Lower a recursive CTE to a provenance-aware fixpoint (PROTOTYPE).
1146 *
1147 * ProvSQL cannot rewrite @c WITH @c RECURSIVE in place: the recursive term
1148 * forbids the aggregate that provenance-merging needs. Instead, for a recursive
1149 * CTE whose body touches provenance-tracked relations, we deparse the body to
1150 * SQL, run @c provsql.eval_recursive over SPI now (at plan time) -- it evaluates
1151 * @c base @c UNION @c recursive to a fixpoint, letting ProvSQL's own rewriting
1152 * compute the join @c times gates, the untracked base branch's @c gate_one, and
1153 * the @c UNION @c plus merge -- and leaves a tracked temp table named after the
1154 * CTE holding @c (cols..., @c provsql). We then rewrite this CTE reference into
1155 * a plain scan of that table, which the rest of @c process_query handles as an
1156 * ordinary tracked relation.
1157 *
1158 * Returns @c true on success, @c false if the shape is unsupported (the caller
1159 * falls back to the normal error). Boolean provenance, acyclic data, and
1160 * UNION (set) recursion only; the driver guards non-termination. It performs
1161 * SPI work and temp-table creation during planning, and recognises only the
1162 * linear/UNION shape.
1163 */
1164static bool lower_recursive_cte(CommonTableExpr *cte, RangeTblEntry *r,
1165 LoweredCte *entry) {
1166 Query *cteq = (Query *) cte->ctequery;
1167 char *body_text;
1168 StringInfoData cols, coldef, call, scan;
1169 ListCell *lcn, *lct;
1170 bool first = true;
1171 int rc;
1172
1173 if (cteq == NULL || !IsA(cteq, Query))
1174 return false;
1175
1176 /* Only UNION (set) recursion is in scope. Reject UNION ALL (bag semantics,
1177 * which the set-fixpoint driver does not model -- and which is unbounded on a
1178 * graph with several paths) and anything that is not a plain UNION; the
1179 * caller then raises the usual "Recursive CTEs not supported". */
1180 if (cteq->setOperations == NULL ||
1181 !IsA(cteq->setOperations, SetOperationStmt) ||
1182 ((SetOperationStmt *) cteq->setOperations)->op != SETOP_UNION ||
1183 ((SetOperationStmt *) cteq->setOperations)->all)
1184 return false;
1185
1186 /* Reject a term whose target list contains a set-returning function
1187 * (e.g. SELECT unnest(...)). Such a CTE is not a provenance fixpoint we
1188 * can lower, and -- more importantly -- the per-round
1189 * INSERT ... SELECT ... UNION SELECT srf(...) the driver would build
1190 * crashes PostgreSQL's planner: the SRF tlist split leaves a NULL expr
1191 * in the PathTarget, which get_expr_width then dereferences. Bail out so
1192 * the caller raises the usual "Recursive CTEs not supported" error. */
1193 {
1194 ListCell *lc;
1195 foreach(lc, cteq->rtable) {
1196 RangeTblEntry *sub = (RangeTblEntry *) lfirst(lc);
1197 if (sub->rtekind == RTE_SUBQUERY && sub->subquery != NULL &&
1198 sub->subquery->hasTargetSRFs)
1199 return false;
1200 }
1201 }
1202
1203 /* Deparse the whole recursive CTE body to SQL. It references the working
1204 * relation by the CTE name; the driver creates a temp table of that name. */
1205 body_text = pg_get_querydef(cteq, false);
1206
1207 /* User column names (comma list) and column definitions (name type). */
1208 initStringInfo(&cols);
1209 initStringInfo(&coldef);
1210 forboth(lcn, cte->ctecolnames, lct, cte->ctecoltypes) {
1211 char *name = strVal(lfirst(lcn));
1212 Oid typid = lfirst_oid(lct);
1213 if (!first) {
1214 appendStringInfoString(&cols, ", ");
1215 appendStringInfoString(&coldef, ", ");
1216 }
1217 first = false;
1218 appendStringInfoString(&cols, quote_identifier(name));
1219 appendStringInfo(&coldef, "%s %s", quote_identifier(name), format_type_be(typid));
1220 }
1221
1222 if (provsql_verbose >= 20)
1223 provsql_notice("Lowering recursive CTE '%s':\n body = %s\n coldef = %s",
1224 cte->ctename, body_text, coldef.data);
1225
1226 /* Drive the fixpoint now, leaving a tracked temp table `ctename`.
1227 *
1228 * Under the 'absorptive' provenance class (or 'boolean', which
1229 * implies it), a CTE matching the linear reachability shape over a
1230 * tracked base edge relation routes to the decomposition-aligned
1231 * driver, which compiles one certified provenance circuit per
1232 * reachable vertex along a tree decomposition of the data graph
1233 * (linear-size for bounded data treewidth, cyclic data included) and
1234 * falls back to eval_recursive on any failure. The compiled circuit
1235 * is the exact Boolean function of the reachability lineage but only
1236 * the absorptive quotient of its (infinite) recursive semiring
1237 * provenance, hence the class gating; the materialised roots carry
1238 * the 'absorptive' assumption marker accordingly. */
1239 initStringInfo(&call);
1240 {
1241 ReachabilityShape shape = {InvalidOid, 0, 0, NULL, InvalidOid, 0, true,
1242 NULL, NULL, NIL, -1, 0, 0, 1};
1243 constants_t constants = get_constants(true);
1245 detect_reachability_cte(cte, cteq, &constants, &shape)) {
1246 char *src_name;
1247 char *dst_name;
1248 char *coltype = format_type_be(
1249 list_nth_oid(cte->ctecoltypes, shape.node_position - 1));
1250 StringInfoData relarg;
1251 if (OidIsValid(shape.relid)) {
1252 src_name = get_attname(shape.relid, shape.src_attno, false);
1253 dst_name = get_attname(shape.relid, shape.dst_attno, false);
1254 } else {
1255 src_name = strVal(list_nth(shape.edge_rte_colnames,
1256 shape.src_attno - 1));
1257 dst_name = strVal(list_nth(shape.edge_rte_colnames,
1258 shape.dst_attno - 1));
1259 }
1260 initStringInfo(&relarg);
1261 if (OidIsValid(shape.relid))
1262 appendStringInfo(&relarg, "%u::pg_catalog.regclass", shape.relid);
1263 else
1264 appendStringInfoString(&relarg, "NULL::pg_catalog.regclass");
1265 if (provsql_verbose >= 20)
1266 provsql_notice("Recursive CTE '%s' recognised as reachability over %s",
1267 cte->ctename,
1268 OidIsValid(shape.relid) ? get_rel_name(shape.relid)
1269 : "a join-defined edge query");
1270 /* Stash the pieces the post-lowering aggregation planting needs;
1271 * only the plain single-column shape is plantable (a hop-counting
1272 * working table has two columns and per-length tokens). */
1273 if (entry != NULL && shape.hop_bound < 0) {
1274 entry->reach_routed = true;
1275 entry->edge_relid = shape.relid;
1276 entry->src_name = pstrdup(src_name);
1277 entry->dst_name = pstrdup(dst_name);
1278 entry->source_text =
1279 shape.source_text ? pstrdup(shape.source_text) : NULL;
1280 entry->source_relid = shape.source_relid;
1281 entry->source_attname =
1282 OidIsValid(shape.source_relid)
1283 ? get_attname(shape.source_relid, shape.source_attno, false)
1284 : NULL;
1285 entry->directed = shape.directed;
1286 entry->edge_quals =
1287 shape.edge_quals ? pstrdup(shape.edge_quals) : NULL;
1288 entry->edge_sql = shape.edge_sql ? pstrdup(shape.edge_sql) : NULL;
1289 }
1290 appendStringInfo(&call,
1291 "SELECT provsql.eval_reachability(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
1292 relarg.data,
1293 quote_literal_cstr(src_name),
1294 quote_literal_cstr(dst_name),
1295 shape.source_text
1296 ? quote_literal_cstr(shape.source_text) : "NULL",
1297 shape.directed ? "true" : "false",
1298 quote_literal_cstr(cte->ctename),
1299 quote_literal_cstr(cols.data),
1300 quote_literal_cstr(coldef.data),
1301 quote_literal_cstr(coltype),
1302 quote_literal_cstr(body_text),
1303 shape.edge_quals
1304 ? quote_literal_cstr(shape.edge_quals) : "NULL");
1305 if (OidIsValid(shape.source_relid)) {
1306 char *satt = get_attname(shape.source_relid, shape.source_attno,
1307 false);
1308 appendStringInfo(&call, ", %u::pg_catalog.regclass, %s",
1309 shape.source_relid, quote_literal_cstr(satt));
1310 } else if (shape.edge_sql != NULL)
1311 appendStringInfoString(&call, ", NULL, NULL");
1312 if (shape.edge_sql != NULL)
1313 appendStringInfo(&call, ", %s", quote_literal_cstr(shape.edge_sql));
1314 if (shape.hop_bound >= 0)
1315 appendStringInfo(&call,
1316 ", hop_bound => %d, hop_seed => %d, hops_position => %d",
1317 shape.hop_bound, shape.hop_seed,
1318 shape.hops_position);
1319 appendStringInfoString(&call, ")");
1320 } else {
1321 appendStringInfo(&call, "SELECT provsql.eval_recursive(%s, %s, %s, %s)",
1322 quote_literal_cstr(body_text),
1323 quote_literal_cstr(cte->ctename),
1324 quote_literal_cstr(cols.data),
1325 quote_literal_cstr(coldef.data));
1326 }
1327 }
1328 if ((rc = SPI_connect()) != SPI_OK_CONNECT)
1329 provsql_error("Recursive CTE lowering: SPI_connect failed (%d)", rc);
1330 rc = SPI_execute(call.data, false, 0);
1331 SPI_finish();
1332 if (rc < 0)
1333 provsql_error("Recursive CTE lowering: eval_recursive failed (%d)", rc);
1334
1335 /* Replace the CTE reference with a scan of the populated table. */
1336 initStringInfo(&scan);
1337 appendStringInfo(&scan, "SELECT %s FROM %s",
1338 cols.data, quote_identifier(cte->ctename));
1339 {
1340 List *raw = pg_parse_query(scan.data);
1341 List *analyzed = pg_analyze_and_rewrite_fixedparams(
1342 linitial_node(RawStmt, raw), scan.data, NULL, 0, NULL);
1343 r->rtekind = RTE_SUBQUERY;
1344 r->subquery = linitial_node(Query, analyzed);
1345 r->ctename = NULL;
1346 r->ctelevelsup = 0;
1347 }
1348 return true;
1349}
1350#endif
1351
1352/** @brief Context for @c cte_reference_walker. */
1353typedef struct {
1354 const char *name; /**< CTE name searched for. */
1355} CteRefCtx;
1356
1357/**
1358 * @brief Walker: does the tree contain an @c RTE_CTE reference to a CTE
1359 * of the given name?
1360 *
1361 * Descends into subquery RTEs, SubLink subselects and nested WITH bodies,
1362 * so a reference anywhere inside a CTE body is found. Matching is by name
1363 * only: a nested WITH shadowing the name yields a false positive, which
1364 * errs on the side of inlining the referenced CTE (the uniform behaviour
1365 * before untracked CTEs were preserved).
1366 */
1367static bool cte_reference_walker(Node *node, void *context) {
1368 CteRefCtx *ctx = (CteRefCtx *)context;
1369 if (node == NULL)
1370 return false;
1371 if (IsA(node, Query)) {
1372 Query *sub = (Query *)node;
1373 ListCell *lc;
1374 foreach (lc, sub->rtable) {
1375 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
1376 if (r->rtekind == RTE_CTE && r->ctename != NULL &&
1377 strcmp(r->ctename, ctx->name) == 0)
1378 return true;
1379 }
1380 return query_tree_walker(sub, cte_reference_walker, context, 0);
1381 }
1382 return expression_tree_walker(node, cte_reference_walker, context);
1383}
1384
1385/** @brief Does @p q (at any depth) reference a CTE named @p name? */
1386static bool query_references_cte(Query *q, const char *name) {
1387 CteRefCtx ctx;
1388 ctx.name = name;
1389 return cte_reference_walker((Node *)q, &ctx);
1390}
1391
1392/**
1393 * @brief Inline CTE references as subqueries within a query.
1394 *
1395 * Replaces each non-recursive RTE_CTE entry in @p rtable with an
1396 * RTE_SUBQUERY containing a copy of the CTE's query, looking up
1397 * definitions in @p cteList. Recurses into newly inlined subqueries
1398 * to handle nested CTE references (ctelevelsup > 0).
1399 *
1400 * @param rtable Range table to scan for RTE_CTE entries.
1401 * @param cteList CTE definitions to look up names in.
1402 * @param lowered In/out memo of recursive CTEs already lowered (name ->
1403 * scan subquery), so a recursive CTE referenced more than
1404 * once is lowered exactly once and later references reuse
1405 * the first lowering instead of recreating its temp table.
1406 * @param kept CTEs (by pointer) the caller preserves as real CTEs;
1407 * references to them are left in place.
1408 */
1409static void inline_ctes_in_rtable(List *rtable, List *cteList, List **lowered,
1410 List *kept) {
1411 ListCell *lc;
1412 foreach (lc, rtable) {
1413 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
1414 if (r->rtekind == RTE_CTE) {
1415 ListCell *lc2;
1416 foreach (lc2, cteList) {
1417 CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc2);
1418 if (strcmp(cte->ctename, r->ctename) == 0) {
1419 if (list_member_ptr(kept, cte)) {
1420 /* Preserved CTE: the reference stays an RTE_CTE and the body
1421 * runs as native SQL, with PostgreSQL's single-evaluation
1422 * WITH semantics. */
1423 } else if (cte->cterecursive) {
1424#if PG_VERSION_NUM >= 150000
1425 /* A recursive CTE referenced more than once (e.g. once per
1426 * UNION arm) must be lowered exactly once: re-running
1427 * eval_recursive would DROP and recreate its temp table,
1428 * invalidating the OID the first reference's analyzed scan
1429 * bound to. Reuse the earlier lowering when we have it. */
1430 Query *memo = lookup_lowered_cte(*lowered, cte->ctename);
1431 if (memo != NULL) {
1432 r->rtekind = RTE_SUBQUERY;
1433 r->subquery = copyObject(memo);
1434 r->ctename = NULL;
1435 r->ctelevelsup = 0;
1436 } else {
1437 LoweredCte *e = (LoweredCte *)palloc0(sizeof(LoweredCte));
1438 if (lower_recursive_cte(cte, r, e)) {
1439 /* Lowering succeeded; remember the scan subquery so any
1440 * further reference to this CTE reuses it. */
1441 e->name = pstrdup(cte->ctename);
1442 e->subquery = copyObject(r->subquery);
1443 *lowered = lappend(*lowered, e);
1444 } else
1445 /* Unsupported recursion shape (e.g. UNION ALL). */
1446 provsql_error("Recursive CTEs not supported (unsupported recursion shape)");
1447 }
1448#else
1449 provsql_error("Recursive CTEs not supported");
1450#endif
1451 } else {
1452 r->rtekind = RTE_SUBQUERY;
1453 r->subquery = copyObject((Query *)cte->ctequery);
1454 r->ctename = NULL;
1455 r->ctelevelsup = 0;
1456 /* Recurse: the inlined subquery may reference other CTEs
1457 * from the same cteList */
1458 inline_ctes_in_rtable(r->subquery->rtable, cteList, lowered, kept);
1459 }
1460 break;
1461 }
1462 }
1463 } else if (r->rtekind == RTE_SUBQUERY && r->subquery != NULL) {
1464 /* Recurse into existing subqueries (e.g., UNION branches) to
1465 * inline CTE references they may contain */
1466 inline_ctes_in_rtable(r->subquery->rtable, cteList, lowered, kept);
1467 }
1468 }
1469}
1470
1471#if PG_VERSION_NUM >= 150000
1472/** @brief Context for @c reach_member_local_walker. */
1473typedef struct {
1474 Index t_rti; /**< The member relation's RT index. */
1475 bool ok; /**< Cleared on any non-member-local node. */
1476} ReachMemberQualCtx;
1477
1478/**
1479 * @brief Expression walker: clear @c ok on any node that makes a qual
1480 * not a deterministic filter over the member relation alone --
1481 * a Var of another RTE (or an outer reference), a sublink, or a
1482 * placeholder parameter.
1483 */
1484static bool reach_member_local_walker(Node *node, ReachMemberQualCtx *ctx) {
1485 if (node == NULL)
1486 return false;
1487 if (IsA(node, Var)) {
1488 Var *v = (Var *) node;
1489 if (v->varlevelsup != 0 || v->varno != ctx->t_rti)
1490 ctx->ok = false;
1491 return false;
1492 }
1493 if (IsA(node, SubLink) || IsA(node, Param)) {
1494 ctx->ok = false;
1495 return false;
1496 }
1497 return expression_tree_walker(node, reach_member_local_walker, ctx);
1498}
1499
1500/**
1501 * @brief Whether @p qual is a deterministic filter over the member
1502 * relation @p t_rti alone (no CTE / join / outer references, no
1503 * sublinks or parameters). Volatility is checked separately.
1504 */
1505static bool reach_member_local_qual(Node *qual, Index t_rti) {
1506 ReachMemberQualCtx ctx = { t_rti, true };
1507 reach_member_local_walker(qual, &ctx);
1508 return ctx.ok;
1509}
1510
1511/** @brief One detected grouped-reachability aggregation (see below). */
1512typedef struct ReachAggCandidate {
1513 const char *ctename; /**< The recursive CTE being aggregated. */
1514 const char *node_colname; /**< Its (single) column name. */
1515 Oid member_relid; /**< The joined member relation T. */
1516 const char *member_attname; /**< T's join column. */
1517 const char *group_attname; /**< T's grouping column. */
1518 const char *member_quals; /**< Deparsed member-relation-local filter (against T's columns), or NULL. */
1519} ReachAggCandidate;
1520
1521/**
1522 * @brief Detect, before CTE lowering, the grouped-reachability
1523 * aggregation shape:
1524 *
1525 * WITH RECURSIVE reach(v) AS (...)
1526 * SELECT ... FROM reach r JOIN T ON r.v = T.<a> ... GROUP BY T.<g>
1527 *
1528 * The aggregation collapses each group's per-vertex reach tokens into
1529 * one @c provenance_plus -- an OR of *correlated* events (the vertices
1530 * share edges) that no per-vertex certificate covers. When the CTE is
1531 * later reachability-routed, @c plant_reach_aggregations() pre-creates,
1532 * at the canonical address of each group's token multiset, a certified
1533 * any-member-reachable circuit, so the natural aggregation stays on
1534 * the linear evaluation route. Detection is conservative: a single
1535 * GROUP BY column from a single joined relation, one join equality
1536 * against the CTE's single column, no other quals or range-table
1537 * entries; anything else simply skips the planting (the generic path
1538 * is always correct).
1539 *
1540 * @param q The outer query (CTE references still in place).
1541 * @return List of @c ReachAggCandidate.
1542 */
1543static List *detect_reach_aggregations(Query *q) {
1544 List *out = NIL;
1545 ListCell *lc;
1546 Index rti = 0, cte_rti = 0, t_rti = 0;
1547 RangeTblEntry *cte_rte = NULL, *t_rte = NULL;
1548 SortGroupClause *sgc;
1549 TargetEntry *gtle = NULL;
1550 Var *gvar;
1551 List *quals = NIL;
1552 List *member_quals = NIL;
1553 bool ok = true;
1554 OpExpr *eq;
1555 Var *cte_var, *t_var;
1556 CommonTableExpr *cte = NULL;
1557 ReachAggCandidate *cand;
1558
1559 if (q->setOperations != NULL || list_length(q->groupClause) != 1 ||
1560 q->groupingSets != NIL || q->cteList == NIL)
1561 return NIL;
1562
1563 foreach(lc, q->rtable) {
1564 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1565 ++rti;
1566 switch (r->rtekind) {
1567 case RTE_CTE:
1568 if (cte_rti != 0 || r->ctelevelsup != 0)
1569 return NIL;
1570 cte_rti = rti;
1571 cte_rte = r;
1572 break;
1573 case RTE_RELATION:
1574 if (t_rti != 0 || r->relkind != RELKIND_RELATION)
1575 return NIL;
1576 t_rti = rti;
1577 t_rte = r;
1578 break;
1579 case RTE_JOIN:
1580#if PG_VERSION_NUM >= 180000
1581 case RTE_GROUP:
1582 /* PG 18's synthetic grouping RTE; grouping Vars resolved below. */
1583#endif
1584 break;
1585 default:
1586 return NIL;
1587 }
1588 }
1589 if (cte_rti == 0 || t_rti == 0)
1590 return NIL;
1591
1592 /* The referenced CTE must be a recursive one of this query, with a
1593 * single column (the plain reachability shape; hop-counting working
1594 * tables carry per-length tokens and are not plantable). */
1595 foreach(lc, q->cteList) {
1596 CommonTableExpr *c = (CommonTableExpr *) lfirst(lc);
1597 if (strcmp(c->ctename, cte_rte->ctename) == 0) {
1598 cte = c;
1599 break;
1600 }
1601 }
1602 if (cte == NULL || !cte->cterecursive ||
1603 list_length(cte->ctecolnames) != 1)
1604 return NIL;
1605
1606 /* The grouping column: a bare Var of T. */
1607 sgc = (SortGroupClause *) linitial(q->groupClause);
1608 foreach(lc, q->targetList) {
1609 TargetEntry *te = (TargetEntry *) lfirst(lc);
1610 if (te->ressortgroupref == sgc->tleSortGroupRef) {
1611 gtle = te;
1612 break;
1613 }
1614 }
1615 if (gtle == NULL)
1616 return NIL;
1617 gvar = (Var *) reach_strip((Node *) gtle->expr);
1618#if PG_VERSION_NUM >= 180000
1619 /* On PG 18 the grouping TLE's Var points at the synthetic RTE_GROUP;
1620 * resolve it through the group RTE's groupexprs to the source Var. */
1621 if (q->hasGroupRTE && gvar != NULL && IsA(gvar, Var) &&
1622 gvar->varlevelsup == 0) {
1623 Index gidx = 1;
1624 foreach(lc, q->rtable) {
1625 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1626 if (r->rtekind == RTE_GROUP) {
1627 if (gvar->varno == gidx && gvar->varattno >= 1 &&
1628 gvar->varattno <= list_length(r->groupexprs))
1629 gvar = (Var *) reach_strip(
1630 (Node *) list_nth(r->groupexprs, gvar->varattno - 1));
1631 break;
1632 }
1633 ++gidx;
1634 }
1635 }
1636#endif
1637 if (gvar == NULL || !IsA(gvar, Var) || gvar->varno != t_rti ||
1638 gvar->varlevelsup != 0 || gvar->varattno <= 0)
1639 return NIL;
1640
1641 /* The quals: exactly one join equality (CTE column = T column), the
1642 * rest member-relation-local deterministic filters (which restrict
1643 * the members of each group, like the edge-column filters restrict
1644 * the edges -- they carry no provenance). Anything else (a qual
1645 * touching the CTE side, a sublink, a volatile function) forces
1646 * fallback. */
1647 cte_var = t_var = NULL;
1648 reach_collect_quals((Node *) q->jointree, &quals, &ok);
1649 if (!ok || quals == NIL)
1650 return NIL;
1651 foreach(lc, quals) {
1652 Node *qual = (Node *) lfirst(lc);
1653 /* Is this the join equality? An OpExpr with one CTE Var and one
1654 * T Var. */
1655 if (cte_var == NULL && IsA(qual, OpExpr)) {
1656 eq = (OpExpr *) qual;
1657 if (list_length(eq->args) == 2 &&
1658 op_mergejoinable(eq->opno, exprType((Node *) linitial(eq->args)))) {
1659 Var *ja = (Var *) reach_strip((Node *) linitial(eq->args));
1660 Var *jb = (Var *) reach_strip((Node *) lsecond(eq->args));
1661 if (ja != NULL && jb != NULL && IsA(ja, Var) && IsA(jb, Var) &&
1662 ja->varlevelsup == 0 && jb->varlevelsup == 0) {
1663 if (ja->varno == cte_rti && jb->varno == t_rti) {
1664 cte_var = ja;
1665 t_var = jb;
1666 continue;
1667 } else if (jb->varno == cte_rti && ja->varno == t_rti) {
1668 cte_var = jb;
1669 t_var = ja;
1670 continue;
1671 }
1672 }
1673 }
1674 }
1675 /* Otherwise it must be a member-relation-local deterministic
1676 * filter. */
1677 if (!reach_member_local_qual(qual, t_rti) ||
1678 contain_volatile_functions(qual))
1679 return NIL;
1680 member_quals = lappend(member_quals, qual);
1681 }
1682 if (cte_var == NULL)
1683 return NIL;
1684 if (cte_var->varattno != 1 || t_var->varattno <= 0)
1685 return NIL;
1686
1687 cand = (ReachAggCandidate *) palloc(sizeof(ReachAggCandidate));
1688 cand->ctename = pstrdup(cte->ctename);
1689 cand->node_colname = pstrdup(strVal(linitial(cte->ctecolnames)));
1690 cand->member_relid = t_rte->relid;
1691 cand->member_attname = get_attname(t_rte->relid, t_var->varattno, false);
1692 cand->group_attname = get_attname(t_rte->relid, gvar->varattno, false);
1693 cand->member_quals = NULL;
1694 if (member_quals != NIL) {
1695 /* Deparse with the member relation aliased "t" and column
1696 * references forced table-qualified: the planting applies the
1697 * filter as a WHERE on its member-gathering query, which joins the
1698 * working table with the member relation aliased "t" -- so
1699 * "t.<col>" resolves unambiguously there. */
1700 Node *conj = (Node *) make_ands_explicit(member_quals);
1701 List *dpcontext;
1702 conj = copyObject(conj);
1703 ChangeVarNodes(conj, t_rti, 1, 0);
1704 dpcontext = deparse_context_for("t", t_rte->relid);
1705 cand->member_quals = deparse_expression(conj, dpcontext, true, false);
1706 }
1707 out = lappend(out, cand);
1708 return out;
1709}
1710
1711/**
1712 * @brief Plant the certified any-member gates for the aggregations
1713 * detected by @c detect_reach_aggregations(), via
1714 * @c provsql.plant_reach_any_groups over SPI -- best-effort and
1715 * after lowering, so the working table exists and the
1716 * reachability shape's gathering arguments are known.
1717 */
1718static void plant_reach_aggregations(List *candidates, List *lowered) {
1719 ListCell *lc;
1720 foreach(lc, candidates) {
1721 ReachAggCandidate *cand = (ReachAggCandidate *) lfirst(lc);
1722 ListCell *ll;
1723 LoweredCte *entry = NULL;
1724 StringInfoData call;
1725 int rc;
1726 foreach(ll, lowered) {
1727 LoweredCte *e = (LoweredCte *) lfirst(ll);
1728 if (strcmp(e->name, cand->ctename) == 0) {
1729 entry = e;
1730 break;
1731 }
1732 }
1733 if (entry == NULL || !entry->reach_routed)
1734 continue;
1735
1736 initStringInfo(&call);
1737 appendStringInfo(&call,
1738 "SELECT provsql.plant_reach_any_groups(%s, %s, %u::pg_catalog.regclass, %s, %s, ",
1739 quote_literal_cstr(cand->ctename),
1740 quote_literal_cstr(cand->node_colname),
1741 cand->member_relid,
1742 quote_literal_cstr(cand->member_attname),
1743 quote_literal_cstr(cand->group_attname));
1744 if (OidIsValid(entry->edge_relid))
1745 appendStringInfo(&call, "%u::pg_catalog.regclass", entry->edge_relid);
1746 else
1747 appendStringInfoString(&call, "NULL::pg_catalog.regclass");
1748 appendStringInfo(&call, ", %s, %s, %s, %s, %s, ",
1749 quote_literal_cstr(entry->src_name),
1750 quote_literal_cstr(entry->dst_name),
1751 entry->source_text
1752 ? quote_literal_cstr(entry->source_text) : "NULL",
1753 entry->directed ? "true" : "false",
1754 entry->edge_quals
1755 ? quote_literal_cstr(entry->edge_quals) : "NULL");
1756 if (OidIsValid(entry->source_relid))
1757 appendStringInfo(&call, "%u::pg_catalog.regclass, %s, ",
1758 entry->source_relid,
1759 quote_literal_cstr(entry->source_attname));
1760 else
1761 appendStringInfoString(&call, "NULL, NULL, ");
1762 appendStringInfo(&call, "%s, %s)",
1763 entry->edge_sql
1764 ? quote_literal_cstr(entry->edge_sql) : "NULL",
1765 cand->member_quals
1766 ? quote_literal_cstr(cand->member_quals) : "NULL");
1767
1768 if ((rc = SPI_connect()) != SPI_OK_CONNECT)
1769 provsql_error("Reachability aggregation planting: SPI_connect failed (%d)", rc);
1770 rc = SPI_execute(call.data, false, 0);
1771 SPI_finish();
1772 if (rc < 0)
1773 provsql_error("Reachability aggregation planting failed (%d)", rc);
1774 }
1775}
1776
1777/** @brief One detected reachability self-join conjunction (see below). */
1778typedef struct ReachConjCandidate {
1779 const char *ctename; /**< The self-joined recursive CTE. */
1780 const char *node_colname; /**< Its (single) column name. */
1781 List *const_texts; /**< The constant node bindings, as text
1782 * (multiset, one per reference). */
1783} ReachConjCandidate;
1784
1785/**
1786 * @brief Detect, before CTE lowering, the reachability self-join
1787 * conjunction shape:
1788 *
1789 * WITH RECURSIVE reach(v) AS (...)
1790 * SELECT ... FROM reach r1, ..., reach rk
1791 * WHERE r1.v = c1 AND ... AND rk.v = ck
1792 *
1793 * The row's provenance is the @c provenance_times() of the per-vertex
1794 * reach tokens -- a conjunction of *correlated* events (the vertices
1795 * share edges) that entangles the per-vertex certificates. When the
1796 * CTE is later reachability-routed, @c plant_reach_conjunctions()
1797 * pre-creates, at the times-canonical address of that token multiset,
1798 * a certified all-members-reachable circuit, so the natural "are
1799 * these k vertices all reachable" query stays on the linear
1800 * evaluation route (and min-plus evaluation prices the cheapest
1801 * *joint* covering subgraph). Detection is conservative: every
1802 * range-table entry references the same single-column recursive CTE,
1803 * each constrained by exactly one equality against a constant, no
1804 * other quals, grouping, aggregation or DISTINCT; anything else
1805 * simply skips the planting (the generic path is always correct).
1806 *
1807 * @param q The outer query (CTE references still in place).
1808 * @return List of @c ReachConjCandidate.
1809 */
1810static List *detect_reach_conjunctions(Query *q) {
1811 ListCell *lc;
1812 Index rti = 0;
1813 int nb_refs = 0;
1814 RangeTblEntry *cte_rte = NULL;
1815 CommonTableExpr *cte = NULL;
1816 List *quals = NIL;
1817 bool ok = true;
1818 Node **bound; /* per-RTE constant binding (1-based rti) */
1819 List *const_texts = NIL;
1820 ReachConjCandidate *cand;
1821
1822 if (q->setOperations != NULL || q->groupClause != NIL ||
1823 q->groupingSets != NIL || q->distinctClause != NIL ||
1824 q->havingQual != NULL || q->hasAggs || q->hasWindowFuncs ||
1825 q->hasSubLinks || q->cteList == NIL)
1826 return NIL;
1827
1828 foreach(lc, q->rtable) {
1829 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1830 ++rti;
1831 switch (r->rtekind) {
1832 case RTE_CTE:
1833 if (r->ctelevelsup != 0)
1834 return NIL;
1835 if (cte_rte == NULL)
1836 cte_rte = r;
1837 else if (strcmp(cte_rte->ctename, r->ctename) != 0)
1838 return NIL;
1839 ++nb_refs;
1840 break;
1841 case RTE_JOIN:
1842 break;
1843 default:
1844 return NIL;
1845 }
1846 }
1847 if (nb_refs < 2)
1848 return NIL;
1849
1850 /* The referenced CTE must be a recursive one of this query, with a
1851 * single column (the plain reachability shape). */
1852 foreach(lc, q->cteList) {
1853 CommonTableExpr *c = (CommonTableExpr *) lfirst(lc);
1854 if (strcmp(c->ctename, cte_rte->ctename) == 0) {
1855 cte = c;
1856 break;
1857 }
1858 }
1859 if (cte == NULL || !cte->cterecursive ||
1860 list_length(cte->ctecolnames) != 1)
1861 return NIL;
1862
1863 /* The quals: exactly one constant equality per CTE reference,
1864 * nothing else. */
1865 reach_collect_quals((Node *) q->jointree, &quals, &ok);
1866 if (!ok || list_length(quals) != nb_refs)
1867 return NIL;
1868 bound = (Node **) palloc0(sizeof(Node *) * (list_length(q->rtable) + 1));
1869 foreach(lc, quals) {
1870 OpExpr *eq;
1871 Node *na, *nb;
1872 Var *v;
1873 Const *c;
1874 if (!IsA(lfirst(lc), OpExpr))
1875 return NIL;
1876 eq = (OpExpr *) lfirst(lc);
1877 if (list_length(eq->args) != 2 ||
1878 !op_mergejoinable(eq->opno, exprType((Node *) linitial(eq->args))))
1879 return NIL;
1880 na = reach_strip((Node *) linitial(eq->args));
1881 nb = reach_strip((Node *) lsecond(eq->args));
1882 if (na != NULL && IsA(na, Var) && nb != NULL && IsA(nb, Const)) {
1883 v = (Var *) na;
1884 c = (Const *) nb;
1885 } else if (nb != NULL && IsA(nb, Var) && na != NULL && IsA(na, Const)) {
1886 v = (Var *) nb;
1887 c = (Const *) na;
1888 } else
1889 return NIL;
1890 if (v->varlevelsup != 0 || v->varattno != 1 ||
1891 v->varno < 1 || v->varno > (Index) list_length(q->rtable) ||
1892 list_nth_node(RangeTblEntry, q->rtable, v->varno - 1)->rtekind
1893 != RTE_CTE)
1894 return NIL;
1895 if (c->constisnull || bound[v->varno] != NULL)
1896 return NIL;
1897 bound[v->varno] = (Node *) c;
1898 }
1899
1900 /* Every reference bound: collect the constants as text, in
1901 * range-table order (the canonical recipe sorts anyway). */
1902 rti = 0;
1903 foreach(lc, q->rtable) {
1904 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
1905 ++rti;
1906 if (r->rtekind != RTE_CTE)
1907 continue;
1908 if (bound[rti] == NULL)
1909 return NIL;
1910 {
1911 Const *c = (Const *) bound[rti];
1912 Oid out_func;
1913 bool is_varlena;
1914 getTypeOutputInfo(c->consttype, &out_func, &is_varlena);
1915 const_texts = lappend(const_texts,
1916 OidOutputFunctionCall(out_func, c->constvalue));
1917 }
1918 }
1919
1920 cand = (ReachConjCandidate *) palloc(sizeof(ReachConjCandidate));
1921 cand->ctename = pstrdup(cte->ctename);
1922 cand->node_colname = pstrdup(strVal(linitial(cte->ctecolnames)));
1923 cand->const_texts = const_texts;
1924 return list_make1(cand);
1925}
1926
1927/**
1928 * @brief Plant the certified all-members gates for the conjunctions
1929 * detected by @c detect_reach_conjunctions(), via
1930 * @c provsql.plant_reach_cover over SPI -- best-effort and
1931 * after lowering, so the working table exists and the
1932 * reachability shape's gathering arguments are known.
1933 */
1934static void plant_reach_conjunctions(List *candidates, List *lowered) {
1935 ListCell *lc;
1936 foreach(lc, candidates) {
1937 ReachConjCandidate *cand = (ReachConjCandidate *) lfirst(lc);
1938 ListCell *ll;
1939 LoweredCte *entry = NULL;
1940 StringInfoData call;
1941 bool first = true;
1942 int rc;
1943 foreach(ll, lowered) {
1944 LoweredCte *e = (LoweredCte *) lfirst(ll);
1945 if (strcmp(e->name, cand->ctename) == 0) {
1946 entry = e;
1947 break;
1948 }
1949 }
1950 if (entry == NULL || !entry->reach_routed)
1951 continue;
1952
1953 initStringInfo(&call);
1954 appendStringInfo(&call,
1955 "SELECT provsql.plant_reach_cover(%s, %s, ",
1956 quote_literal_cstr(cand->ctename),
1957 quote_literal_cstr(cand->node_colname));
1958 if (OidIsValid(entry->edge_relid))
1959 appendStringInfo(&call, "%u::pg_catalog.regclass", entry->edge_relid);
1960 else
1961 appendStringInfoString(&call, "NULL::pg_catalog.regclass");
1962 appendStringInfo(&call, ", %s, %s, %s, %s, ",
1963 quote_literal_cstr(entry->src_name),
1964 quote_literal_cstr(entry->dst_name),
1965 entry->source_text
1966 ? quote_literal_cstr(entry->source_text) : "NULL",
1967 entry->directed ? "true" : "false");
1968 appendStringInfoString(&call, "ARRAY[");
1969 foreach(ll, cand->const_texts) {
1970 appendStringInfo(&call, "%s%s", first ? "" : ", ",
1971 quote_literal_cstr((const char *) lfirst(ll)));
1972 first = false;
1973 }
1974 appendStringInfo(&call, "]::text[], %s, ",
1975 entry->edge_quals
1976 ? quote_literal_cstr(entry->edge_quals) : "NULL");
1977 if (OidIsValid(entry->source_relid))
1978 appendStringInfo(&call, "%u::pg_catalog.regclass, %s, ",
1979 entry->source_relid,
1980 quote_literal_cstr(entry->source_attname));
1981 else
1982 appendStringInfoString(&call, "NULL, NULL, ");
1983 appendStringInfo(&call, "%s)",
1984 entry->edge_sql
1985 ? quote_literal_cstr(entry->edge_sql) : "NULL");
1986
1987 if ((rc = SPI_connect()) != SPI_OK_CONNECT)
1988 provsql_error("Reachability conjunction planting: SPI_connect failed (%d)", rc);
1989 rc = SPI_execute(call.data, false, 0);
1990 SPI_finish();
1991 if (rc < 0)
1992 provsql_error("Reachability conjunction planting failed (%d)", rc);
1993 }
1994}
1995#endif
1996
1997/**
1998 * @brief Inline CTE references in @p q as subqueries where the rewrite
1999 * needs them, preserving CTEs whose bodies need no rewriting.
2000 */
2001static void inline_ctes(const constants_t *constants, Query *q) {
2002 List *lowered = NIL;
2003 List *kept = NIL;
2004 ListCell *lc;
2005#if PG_VERSION_NUM >= 150000
2006 List *reach_aggs = NIL;
2007 List *reach_conjs = NIL;
2008#endif
2009 if (q->cteList == NIL)
2010 return;
2011
2012 /* A recursive CTE that carries no token provenance is a pure-RV (or
2013 * otherwise untracked) recursion -- e.g. a random_variable path-cost
2014 * fixpoint. There is nothing to lower (no token semiring to compute a
2015 * fixpoint over), and RVs cannot be set-UNIONed (their btree comparator
2016 * raises: a distribution has no ordering), so its UNION ALL must execute
2017 * as native SQL. When every CTE in the WITH clause is untracked and at
2018 * least one is such a recursion, leave the whole WITH intact for
2019 * PostgreSQL to run: any RV comparison in the outer query was already
2020 * lifted by rewrite_probability_events, and collect_provenance skips the
2021 * untracked RTE_CTE entries. If any CTE does carry token provenance we
2022 * fall through to the normal path (which lowers the tracked recursion or
2023 * raises on an unsupported shape) rather than silently dropping its
2024 * tracking. */
2025 {
2026 bool passthrough_recursion = false;
2027 bool any_tracked = false;
2028 foreach (lc, q->cteList) {
2029 CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
2030 if (has_provenance(constants, (Query *) cte->ctequery))
2031 any_tracked = true;
2032 else if (cte->cterecursive)
2033 passthrough_recursion = true;
2034 }
2035 if (passthrough_recursion && !any_tracked)
2036 return;
2037 }
2038
2039 /* Decide, per CTE, whether the rewrite needs to inline it. Inlining
2040 * copies the body into every referencing RTE, so a volatile expression
2041 * inside -- an RV constructor minting a fresh leaf gate per call -- is
2042 * re-evaluated per reference, breaking SQL's single-evaluation WITH
2043 * semantics: a latent random variable built in a CTE and referenced from
2044 * two scopes would silently split into independent leaves. A body that
2045 * needs no rewriting (no tracked relation, no RV comparison, no
2046 * provenance() call: the same has_provenance gate that decides whether a
2047 * top-level query engages the hook) is therefore preserved as a real CTE;
2048 * it runs as native SQL and PostgreSQL evaluates it exactly once.
2049 * "Must inline" propagates through CTE-to-CTE references: a body
2050 * referencing an inlined CTE must itself be inlined, or its reference
2051 * would dangle once that CTE is removed from cteList. */
2052 {
2053 int n = list_length(q->cteList);
2054 bool *must_inline = (bool *)palloc(n * sizeof(bool));
2055 bool changed;
2056 int i = 0;
2057 foreach (lc, q->cteList) {
2058 CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc);
2059 must_inline[i++] = cte->cterecursive ||
2060 has_provenance(constants, (Query *)cte->ctequery);
2061 }
2062 do {
2063 changed = false;
2064 i = 0;
2065 foreach (lc, q->cteList) {
2066 CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc);
2067 if (!must_inline[i]) {
2068 ListCell *lc2;
2069 int j = 0;
2070 foreach (lc2, q->cteList) {
2071 CommonTableExpr *other = (CommonTableExpr *)lfirst(lc2);
2072 if (must_inline[j] &&
2073 query_references_cte((Query *)cte->ctequery, other->ctename)) {
2074 must_inline[i] = true;
2075 changed = true;
2076 break;
2077 }
2078 ++j;
2079 }
2080 }
2081 ++i;
2082 }
2083 } while (changed);
2084 i = 0;
2085 foreach (lc, q->cteList) {
2086 CommonTableExpr *cte = (CommonTableExpr *)lfirst(lc);
2087 if (!must_inline[i])
2088 kept = lappend(kept, cte);
2089 else if (!cte->cterecursive && cte->cterefcount > 1 &&
2090 contain_volatile_functions((Node *)cte->ctequery))
2091 /* The residual multi-evaluation case: this body must be inlined
2092 * (it needs the rewrite) yet contains volatile calls and is
2093 * referenced more than once. Surface it rather than silently
2094 * returning probabilities over decoupled leaves. */
2095 provsql_warning("CTE \"%s\" requires provenance rewriting and is "
2096 "inlined at each of its %d references; its volatile "
2097 "expressions (e.g. random-variable constructors) are "
2098 "re-evaluated per reference and not shared between "
2099 "them",
2100 cte->ctename, cte->cterefcount);
2101 ++i;
2102 }
2103 pfree(must_inline);
2104 }
2105
2106#if PG_VERSION_NUM >= 150000
2107 /* Grouped-reachability aggregations and self-join conjunctions are
2108 * detected before lowering (the CTE reference is still
2109 * recognisable) and planted after (the working table then
2110 * exists). */
2111 reach_aggs = detect_reach_aggregations(q);
2112 reach_conjs = detect_reach_conjunctions(q);
2113#endif
2114 inline_ctes_in_rtable(q->rtable, q->cteList, &lowered, kept);
2115#if PG_VERSION_NUM >= 150000
2116 plant_reach_aggregations(reach_aggs, lowered);
2117 plant_reach_conjunctions(reach_conjs, lowered);
2118#endif
2119 q->cteList = kept;
2120}
2121
2122/**
2123 * @brief Collect all provenance Var nodes reachable from @p q's range table.
2124 *
2125 * Walks every RTE in @p q->rtable:
2126 * - @c RTE_RELATION: looks for a column named @c provsql of type UUID.
2127 * - @c RTE_SUBQUERY: recursively calls @c process_query and splices the
2128 * resulting provenance column back into the parent's column list, also
2129 * patching outer Var attribute numbers if inner columns were removed.
2130 * - @c RTE_CTE: non-recursive CTEs are inlined as @c RTE_SUBQUERY before
2131 * the main loop, then processed as above. Recursive CTEs raise an error.
2132 * - @c RTE_FUNCTION: handled when the function returns a single UUID column
2133 * named @c provsql.
2134 * - @c RTE_JOIN / @c RTE_VALUES / @c RTE_GROUP: handled passively (the
2135 * underlying base-table RTEs supply the tokens).
2136 *
2137 * @param constants Extension OID cache.
2138 * @param q Query whose range table is scanned (subquery RTEs are
2139 * modified in place by the recursive call).
2140 * @param in_boolean_rewrite True when @p q lies under a safe-query (boolean)
2141 * rewrite; threaded into the subquery recursion so the
2142 * joint-width recogniser defers throughout the subtree.
2143 * @param top_level True when @p q's own per-row root is the one the user
2144 * evaluates. Threaded into the subquery recursion so that an
2145 * arm of a top-level @c UNION / @c UNION @c ALL (whose per-row
2146 * token becomes a union output row's provenance verbatim)
2147 * inherits @c top_level and certifies its own inversion-free
2148 * root; non-union subqueries in @c FROM never do.
2149 * @param inv_ctx Inversion-free marker context for @p q, or @c NULL; its
2150 * per-subquery child context is threaded into each recursive
2151 * @c process_query call so a flattened view's base inputs
2152 * receive their order markers.
2153 * @return List of @c Var nodes, one per provenance source; @c NIL if the
2154 * query has no provenance-bearing relation.
2155 */
2156static List *get_provenance_attributes(const constants_t *constants, Query *q,
2157 bool in_boolean_rewrite, bool top_level,
2158 const InvFreeMarkerCtx *inv_ctx) {
2159 List *prov_atts = NIL;
2160
2161 for(Index rteid = 1; rteid <= q->rtable->length; ++rteid) {
2162 RangeTblEntry *r = list_nth_node(RangeTblEntry, q->rtable, rteid-1);
2163
2164 if (r->rtekind == RTE_RELATION) {
2165 ListCell *lc;
2166 AttrNumber attid = 1;
2167
2168 /* PG 14 and 15 leave the OLD/NEW rule-placeholder RTEs (relkind
2169 * = RELKIND_VIEW, inFromCl = false) in the rewritten range table
2170 * for any view body. PG 16+ removes them. They are never
2171 * scanned and the planner does not build a RelOptInfo for them,
2172 * so any Var we point at them later fails find_base_rel().
2173 * Filter them out here; any post-rewrite RTE_RELATION whose
2174 * relkind is still a view is one of these artifacts. */
2175 if (r->relkind == RELKIND_VIEW)
2176 continue;
2177
2178 foreach (lc, r->eref->colnames) {
2179 const char *v = strVal(lfirst(lc));
2180
2181 if (!strcmp(v, PROVSQL_COLUMN_NAME) &&
2182 get_atttype(r->relid, attid) == constants->OID_TYPE_UUID) {
2183 prov_atts =
2184 lappend(prov_atts,
2185 make_provenance_attribute(constants, q, r, rteid, attid));
2186 }
2187
2188 ++attid;
2189 }
2190 } else if (r->rtekind == RTE_SUBQUERY) {
2191 /* An arm of a top-level UNION / UNION ALL is itself a user-evaluated
2192 * provenance root: UNION ALL carries each arm's per-row token verbatim
2193 * (make_provenance_expression, SR_PLUS -> linitial), and a non-ALL
2194 * UNION is lowered to an outer GROUP BY whose plus root is handled
2195 * separately. So such an arm inherits top_level, letting each
2196 * single-CQ arm certify its own inversion-free root. Non-union
2197 * subqueries (views, derived tables in FROM) are never roots. */
2198 bool arm_top_level = top_level && q->setOperations != NULL
2199 && IsA(q->setOperations, SetOperationStmt)
2200 && ((SetOperationStmt *) q->setOperations)->op == SETOP_UNION;
2201 bool *inner_removed = NULL;
2202 int old_targetlist_length =
2203 r->subquery->targetList ? r->subquery->targetList->length : 0;
2204 Query *new_subquery =
2205 process_query(constants, r->subquery, &inner_removed, false, arm_top_level,
2206 in_boolean_rewrite,
2207 (inv_ctx && rteid - 1 < (Index) inv_ctx->natoms)
2208 ? inv_ctx->sub[rteid - 1] : NULL);
2209 if (new_subquery != NULL) {
2210 int i = 0;
2211 int *offset = (int *)palloc(old_targetlist_length * sizeof(int));
2212 unsigned varattnoprovsql;
2213 ListCell *cell, *prev;
2214
2215 r->subquery = new_subquery;
2216
2217 if (inner_removed != NULL) {
2218 for (cell = list_head(r->eref->colnames), prev = NULL;
2219 cell != NULL;) {
2220 if (inner_removed[i]) {
2221 r->eref->colnames =
2222 my_list_delete_cell(r->eref->colnames, cell, prev);
2223 if (prev)
2224 cell = my_lnext(r->eref->colnames, prev);
2225 else
2226 cell = list_head(r->eref->colnames);
2227 } else {
2228 prev = cell;
2229 cell = my_lnext(r->eref->colnames, cell);
2230 }
2231 ++i;
2232 }
2233 for (i = 0; i < old_targetlist_length; ++i) {
2234 offset[i] =
2235 (i == 0 ? 0 : offset[i - 1]) - (inner_removed[i] ? 1 : 0);
2236 }
2237
2238 reduce_varattno_by_offset(q->targetList, rteid, offset);
2239 }
2240
2241 varattnoprovsql = 0;
2242 for (cell = list_head(new_subquery->targetList); cell != NULL;
2243 cell = my_lnext(new_subquery->targetList, cell)) {
2244 TargetEntry *te = (TargetEntry *)lfirst(cell);
2245 ++varattnoprovsql;
2246 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME))
2247 break;
2248 }
2249
2250 /* In a UNION, every branch must expose a provsql column so the set
2251 * operation's columns line up. A branch with no provenance source
2252 * (constant rows, or an untracked relation) is returned unchanged by
2253 * process_query above and has no provsql column; such rows are present
2254 * unconditionally, so their provenance is the multiplicative identity.
2255 * Append a gate_one() provsql column. Set-operation branches never
2256 * carry resjunk entries (those are rejected by the planner), so the
2257 * column lands last -- the same ordinal position as the provsql column
2258 * the provenance-bearing branches get. */
2259 if (cell == NULL && q->setOperations != NULL &&
2260 IsA(q->setOperations, SetOperationStmt) &&
2261 ((SetOperationStmt *)q->setOperations)->op == SETOP_UNION) {
2262 FuncExpr *one_expr = makeNode(FuncExpr);
2263 TargetEntry *one_te;
2264 one_expr->funcid = constants->OID_FUNCTION_GATE_ONE;
2265 one_expr->funcresulttype = constants->OID_TYPE_UUID;
2266 one_expr->args = NIL;
2267 one_expr->location = -1;
2268 one_te = makeTargetEntry((Expr *)one_expr,
2269 list_length(new_subquery->targetList) + 1,
2270 pstrdup(PROVSQL_COLUMN_NAME), false);
2271 new_subquery->targetList = lappend(new_subquery->targetList, one_te);
2272 varattnoprovsql = list_length(new_subquery->targetList);
2273 cell = list_tail(new_subquery->targetList);
2274 }
2275
2276 if (cell != NULL) {
2277 r->eref->colnames = list_insert_nth(r->eref->colnames, varattnoprovsql-1,
2278 makeString(pstrdup(PROVSQL_COLUMN_NAME)));
2279 prov_atts =
2280 lappend(prov_atts, make_provenance_attribute(
2281 constants, q, r, rteid, varattnoprovsql));
2282 }
2283 fix_type_of_aggregation_result(constants, q, rteid,
2284 r->subquery->targetList);
2285 }
2286 } else if (r->rtekind == RTE_JOIN) {
2287 if (r->jointype == JOIN_INNER || r->jointype == JOIN_LEFT ||
2288 r->jointype == JOIN_FULL || r->jointype == JOIN_RIGHT) {
2289 // Nothing to do, there will also be RTE entries for the tables
2290 // that are part of the join, from which we will extract the
2291 // provenance information
2292 } else { // Semijoin (should be feasible, but check whether the second
2293 // provenance information is available) Antijoin (feasible with
2294 // negation)
2295 provsql_error("JOIN type not supported");
2296 }
2297 } else if (r->rtekind == RTE_FUNCTION) {
2298 ListCell *lc;
2299 AttrNumber attid = 1;
2300
2301 foreach (lc, r->functions) {
2302 RangeTblFunction *func = (RangeTblFunction *)lfirst(lc);
2303
2304 if (func->funccolcount == 1) {
2305 FuncExpr *expr = (FuncExpr *)func->funcexpr;
2306 if (expr->funcresulttype == constants->OID_TYPE_UUID &&
2307 !strcmp(get_rte_attribute_name(r, attid), PROVSQL_COLUMN_NAME)) {
2308 prov_atts = lappend(prov_atts, make_provenance_attribute(
2309 constants, q, r, rteid, attid));
2310 }
2311 } else {
2312 provsql_error("FROM function with multiple output "
2313 "attributes not supported");
2314 }
2315
2316 attid += func->funccolcount;
2317 }
2318 } else if (r->rtekind == RTE_VALUES) {
2319 // Nothing to do, no provenance attribute in literal values
2320#if PG_VERSION_NUM >= 120000
2321 } else if (r->rtekind == RTE_RESULT) {
2322 // Empty-FROM RTE (no provenance). Also what the outer-join lowering
2323 // leaves behind when it neutralises an orphaned subquery arm.
2324#endif
2325#if PG_VERSION_NUM >= 180000
2326 } else if (r->rtekind == RTE_GROUP) {
2327 // Introduced in PostgreSQL 18, we already handle group by from
2328 // groupClause
2329#endif
2330 } else if (r->rtekind == RTE_CTE) {
2331 // A CTE left intact by inline_ctes: an untracked body (pure-RV
2332 // recursion, or a non-recursive body needing no rewriting, e.g. a
2333 // volatile RV-constructor latent whose single evaluation must be
2334 // shared) that runs as native SQL. No provenance column to collect
2335 // -- inline_ctes only preserves CTEs that carry none. (A recursive
2336 // CTE that cannot be preserved is refused inside inline_ctes
2337 // itself, on every PostgreSQL version.)
2338 } else {
2339 provsql_error("FROM clause not supported");
2340 }
2341 }
2342
2343 return prov_atts;
2344}
2345
2346/* -------------------------------------------------------------------------
2347 * Target-list surgery
2348 * ------------------------------------------------------------------------- */
2349
2350/**
2351 * @brief Strip provenance UUID columns from @p q's SELECT list.
2352 *
2353 * Scans the target list and removes every @c Var entry whose column name is
2354 * @c provsql and whose type is UUID. The remaining entries have their
2355 * @c resno values decremented to fill the gaps.
2356 *
2357 * @param constants Extension OID cache.
2358 * @param q Query to modify in place.
2359 * @param removed Out-param: allocated boolean array (length =
2360 * original target list length) where @c true means the
2361 * corresponding entry was removed. The caller must
2362 * @c pfree this array when done.
2363 * @return Bitmapset of @c ressortgroupref values whose entries were
2364 * removed (so the caller can clean up GROUP BY / ORDER BY).
2365 */
2366static Bitmapset *
2368 bool **removed) {
2369 int nbRemoved = 0;
2370 int i = 0;
2371 Bitmapset *ressortgrouprefs = NULL;
2372 ListCell *cell, *prev;
2373 *removed = (bool *)palloc(q->targetList->length * sizeof(bool));
2374
2375 for (cell = list_head(q->targetList), prev = NULL; cell != NULL;) {
2376 TargetEntry *rt = (TargetEntry *)lfirst(cell);
2377 (*removed)[i] = false;
2378
2379 if (rt->expr->type == T_Var) {
2380 Var *v = (Var *)rt->expr;
2381
2382 if (v->vartype == constants->OID_TYPE_UUID) {
2383 const char *colname;
2384
2385 if (rt->resname)
2386 colname = rt->resname;
2387 else {
2388 /* This case occurs, for example, when grouping by a column
2389 * that is projected out */
2390 RangeTblEntry *r = (RangeTblEntry *)list_nth(q->rtable, v->varno - 1);
2391 colname = strVal(list_nth(r->eref->colnames, v->varattno - 1));
2392 }
2393
2394 if (!strcmp(colname, PROVSQL_COLUMN_NAME)) {
2395 q->targetList = my_list_delete_cell(q->targetList, cell, prev);
2396
2397 (*removed)[i] = true;
2398 ++nbRemoved;
2399
2400 if (rt->ressortgroupref > 0)
2401 ressortgrouprefs =
2402 bms_add_member(ressortgrouprefs, rt->ressortgroupref);
2403 }
2404 }
2405 }
2406
2407 if ((*removed)[i]) {
2408 if (prev) {
2409 cell = my_lnext(q->targetList, prev);
2410 } else {
2411 cell = list_head(q->targetList);
2412 }
2413 } else {
2414 rt->resno -= nbRemoved;
2415 prev = cell;
2416 cell = my_lnext(q->targetList, cell);
2417 }
2418
2419 ++i;
2420 }
2421
2422 return ressortgrouprefs;
2423}
2424
2425/**
2426 * @brief Strip @c given(evidence) whole-tuple conditioning markers from the
2427 * visible projection, returning the captured evidence expressions.
2428 *
2429 * Walks @p q's target list for visible (non-resjunk) entries whose expression
2430 * is a @c provsql.given(uuid) @c FuncExpr -- the consumed marker emitted by the
2431 * prefix @c | operator / @c given() call. Each match is removed from the
2432 * projection (its @c resno renumbered like @c remove_provenance_attributes_
2433 * select), and its single argument (the per-row evidence token) is collected
2434 * into the returned list, in target-list order. The caller wraps the query's
2435 * output provenance in @c cond(row_provenance, evidence) for each captured
2436 * expression, so multiple markers accumulate as a conjunction of evidence
2437 * (cond folds @c "(X|A)|B = X|(A∧B)").
2438 *
2439 * Returns @c NIL when the query carries no marker (the common case, no cost
2440 * beyond the walk). @p q's target list is modified in place.
2441 */
2442static List *strip_given_markers(const constants_t *constants, Query *q) {
2443 List *evidence = NIL;
2444 int nbRemoved = 0;
2445 ListCell *cell, *prev;
2446
2447 if (!OidIsValid(constants->OID_FUNCTION_GIVEN) || q->targetList == NIL)
2448 return NIL;
2449
2450 for (cell = list_head(q->targetList), prev = NULL; cell != NULL;) {
2451 TargetEntry *rt = (TargetEntry *)lfirst(cell);
2452 bool is_given = false;
2453 List *args = NIL;
2454
2455 /* The marker reaches the rewriter as either a given(...) FuncExpr (the
2456 * function-call spelling) or a prefix `| c` OpExpr (the operator
2457 * spelling, whose opfuncid is given's OID). */
2458 if (!rt->resjunk && IsA(rt->expr, FuncExpr) &&
2459 ((FuncExpr *)rt->expr)->funcid == constants->OID_FUNCTION_GIVEN)
2460 args = ((FuncExpr *)rt->expr)->args;
2461 else if (!rt->resjunk && IsA(rt->expr, OpExpr) &&
2462 ((OpExpr *)rt->expr)->opfuncid == constants->OID_FUNCTION_GIVEN)
2463 args = ((OpExpr *)rt->expr)->args;
2464
2465 if (args != NIL) {
2466 if (list_length(args) != 1)
2467 provsql_error("provsql.given expects exactly one argument");
2468 is_given = true;
2469 evidence = lappend(evidence, linitial(args));
2470 q->targetList = my_list_delete_cell(q->targetList, cell, prev);
2471 ++nbRemoved;
2472 }
2473
2474 if (is_given) {
2475 cell = prev ? my_lnext(q->targetList, prev) : list_head(q->targetList);
2476 } else {
2477 rt->resno -= nbRemoved;
2478 prev = cell;
2479 cell = my_lnext(q->targetList, cell);
2480 }
2481 }
2482
2483 return evidence;
2484}
2485
2486/**
2487 * @brief Semiring operation used to combine provenance tokens.
2488 *
2489 * @c SR_TIMES corresponds to the multiplicative operation (joins, Cartesian
2490 * products), @c SR_PLUS to the additive operation (duplicate elimination), and
2491 * @c SR_MONUS to the monus / set-difference operation (EXCEPT).
2492 *
2493 * @see https://provsql.org/lean-docs/Provenance/QueryRewriting.html
2494 * Lean 4 formalization of rewriting rules (R1)--(R5) and correctness
2495 * theorem @c Query.rewriting_valid.
2496 */
2497typedef enum {
2498 SR_PLUS, ///< Semiring addition (UNION, SELECT DISTINCT)
2499 SR_MONUS, ///< Semiring monus / set difference (EXCEPT)
2500 SR_TIMES ///< Semiring multiplication (JOIN, Cartesian product)
2502
2503/* -------------------------------------------------------------------------
2504 * Semiring expression builders
2505 * ------------------------------------------------------------------------- */
2506
2507/**
2508 * @brief Wrap @p toExpr in a @c provenance_eq gate if @p fromOpExpr is an
2509 * equality between two tracked columns.
2510 *
2511 * Used for where-provenance: each equijoin condition (and some WHERE
2512 * equalities) introduces an @c eq gate that records which attribute positions
2513 * were compared. Because this function is also called for WHERE predicates,
2514 * it applies extra guards and silently returns @p toExpr unchanged when the
2515 * expression does not match the expected shape (both sides must be @c Var
2516 * nodes, possibly wrapped in a @c RelabelType).
2517 *
2518 * @param constants Extension OID cache.
2519 * @param fromOpExpr The equality @c OpExpr to inspect.
2520 * @param toExpr Existing provenance expression to wrap.
2521 * @param columns Per-RTE column-numbering array. EQ gate positions
2522 * carry the same sequential-number caveat as PROJECT
2523 * gate positions (see @c build_column_map()); they are
2524 * only correct when each operand's RTE is either a join
2525 * RTE or a subquery, not a bare provenance-tracked base
2526 * table.
2527 * @return @p toExpr wrapped in @c provenance_eq(toExpr, col1, col2), or
2528 * @p toExpr unchanged if the shape is unsupported.
2529 */
2530static Expr *add_eq_from_OpExpr_to_Expr(const constants_t *constants,
2531 OpExpr *fromOpExpr, Expr *toExpr,
2532 int **columns) {
2533 Datum first_arg;
2534 Datum second_arg;
2535 FuncExpr *fc;
2536 Const *c1;
2537 Const *c2;
2538 Var *v1;
2539 Var *v2;
2540
2541 if (my_lnext(fromOpExpr->args, list_head(fromOpExpr->args))) {
2542 /* Sometimes Var is nested within a RelabelType */
2543 if (IsA(linitial(fromOpExpr->args), Var)) {
2544 v1 = linitial(fromOpExpr->args);
2545 } else if (IsA(linitial(fromOpExpr->args), RelabelType)) {
2546 /* In the WHERE case it can be a Const */
2547 RelabelType *rt1 = linitial(fromOpExpr->args);
2548 if (IsA(rt1->arg, Var)) { /* Can be Param in the WHERE case */
2549 v1 = (Var *)rt1->arg;
2550 } else
2551 return toExpr;
2552 } else
2553 return toExpr;
2554 if (!columns[v1->varno - 1])
2555 return toExpr;
2556 first_arg = Int16GetDatum(columns[v1->varno - 1][v1->varattno - 1]);
2557
2558 if (IsA(lsecond(fromOpExpr->args), Var)) {
2559 v2 = lsecond(fromOpExpr->args);
2560 } else if (IsA(lsecond(fromOpExpr->args), RelabelType)) {
2561 /* In the WHERE case it can be a Const */
2562 RelabelType *rt2 = lsecond(fromOpExpr->args);
2563 if (IsA(rt2->arg, Var)) { /* Can be Param in the WHERE case */
2564 v2 = (Var *)rt2->arg;
2565 } else
2566 return toExpr;
2567 } else
2568 return toExpr;
2569 if (!columns[v2->varno - 1])
2570 return toExpr;
2571 second_arg = Int16GetDatum(columns[v2->varno - 1][v2->varattno - 1]);
2572
2573 fc = makeNode(FuncExpr);
2574 fc->funcid = constants->OID_FUNCTION_PROVENANCE_EQ;
2575 fc->funcvariadic = false;
2576 fc->funcresulttype = constants->OID_TYPE_UUID;
2577 fc->location = -1;
2578
2579 c1 = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int16),
2580 first_arg, false, true);
2581
2582 c2 = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int16),
2583 second_arg, false, true);
2584
2585 fc->args = list_make3(toExpr, c1, c2);
2586 return (Expr *)fc;
2587 }
2588 return toExpr;
2589}
2590
2591/**
2592 * @brief Walk a join-condition or WHERE quals node and add @c eq gates for
2593 * every equality it contains.
2594 *
2595 * Dispatches to @c add_eq_from_OpExpr_to_Expr for simple @c OpExpr nodes
2596 * and iterates over the arguments of an AND @c BoolExpr. OR/NOT inside a
2597 * join ON clause are rejected with an error.
2598 *
2599 * @param constants Extension OID cache.
2600 * @param quals Root of the quals tree (@c OpExpr or @c BoolExpr), or
2601 * @c NULL (in which case @p result is returned unchanged).
2602 * @param result Provenance expression to wrap.
2603 * @param columns Per-RTE column-numbering array.
2604 * @return Updated provenance expression with zero or more @c eq gates added.
2605 */
2606static Expr *add_eq_from_Quals_to_Expr(const constants_t *constants,
2607 Node *quals, Expr *result,
2608 int **columns) {
2609 OpExpr *oe;
2610
2611 if (!quals)
2612 return result;
2613
2614 if (IsA(quals, OpExpr)) {
2615 oe = (OpExpr *)quals;
2616 result = add_eq_from_OpExpr_to_Expr(constants, oe, result, columns);
2617 } /* Sometimes OpExpr is nested within a BoolExpr */
2618 else if (IsA(quals, BoolExpr)) {
2619 BoolExpr *be = (BoolExpr *)quals;
2620 /* In some cases, there can be an OR or a NOT specified with ON clause */
2621 if (be->boolop == OR_EXPR || be->boolop == NOT_EXPR) {
2622 provsql_error("Boolean operators OR and NOT in a join...on "
2623 "clause are not supported");
2624 } else {
2625 ListCell *lc2;
2626 foreach (lc2, be->args) {
2627 if (IsA(lfirst(lc2), OpExpr)) {
2628 oe = (OpExpr *)lfirst(lc2);
2629 result = add_eq_from_OpExpr_to_Expr(constants, oe, result, columns);
2630 }
2631 }
2632 }
2633 } else { /* Handle other cases */
2634 }
2635 return result;
2636}
2637
2638/**
2639 * @brief Build the per-row provenance token for an aggregate rewrite.
2640 *
2641 * Used by both @c make_aggregation_expression (for the agg_token /
2642 * @c provenance_semimod path) and @c make_rv_aggregate_expression (for
2643 * the inline RV-aggregate path). Combines @p prov_atts via
2644 * @c provenance_times (under @c SR_TIMES) or @c provenance_monus
2645 * (under @c SR_MONUS); a single @c prov_att is returned as-is.
2646 *
2647 * @return An @c Expr returning UUID; never @c NULL.
2648 */
2649static Expr *combine_prov_atts(const constants_t *constants,
2650 List *prov_atts, semiring_operation op) {
2651 FuncExpr *combine;
2652
2653 if (my_lnext(prov_atts, list_head(prov_atts)) == NULL)
2654 return (Expr *)linitial(prov_atts);
2655
2656 combine = makeNode(FuncExpr);
2657 if (op == SR_TIMES) {
2658 ArrayExpr *array = makeNode(ArrayExpr);
2659
2660 combine->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
2661 combine->funcvariadic = true;
2662
2663 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
2664 array->element_typeid = constants->OID_TYPE_UUID;
2665 array->elements = prov_atts;
2666 array->location = -1;
2667
2668 combine->args = list_make1(array);
2669 } else { // SR_MONUS
2670 combine->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
2671 combine->args = prov_atts;
2672 }
2673 combine->funcresulttype = constants->OID_TYPE_UUID;
2674 combine->location = -1;
2675 return (Expr *)combine;
2676}
2677
2678/**
2679 * @brief Build an @c Aggref for an RV-summing aggregate over @p arg.
2680 *
2681 * Helper for the @c avg rewrite: the numerator uses @c rv_sum_or_null
2682 * (@c NULL on an empty group) and the denominator uses @c sum, so the two
2683 * differ only in @p aggfnoid. @p arg is an @c Expr of type
2684 * @c random_variable (the wrapped per-row contribution).
2685 */
2686static Aggref *build_rv_sum_aggref(const constants_t *constants,
2687 Oid aggfnoid, Expr *arg) {
2688 TargetEntry *te = makeNode(TargetEntry);
2689 Aggref *agg = makeNode(Aggref);
2690
2691 te->resno = 1;
2692 te->expr = arg;
2693
2694 agg->aggfnoid = aggfnoid;
2695 agg->aggtype = constants->OID_TYPE_RANDOM_VARIABLE;
2696 agg->aggargtypes = list_make1_oid(constants->OID_TYPE_RANDOM_VARIABLE);
2697 agg->aggkind = AGGKIND_NORMAL;
2698 agg->aggtranstype = InvalidOid;
2699 agg->args = list_make1(te);
2700 agg->location = -1;
2701#if PG_VERSION_NUM >= 140000
2702 agg->aggno = agg->aggtransno = -1;
2703#endif
2704 return agg;
2705}
2706
2707/**
2708 * @brief Inline rewrite of an RV-returning aggregate, baking each
2709 * aggregate's identity element into the per-row provenance wrap.
2710 *
2711 * Handles any aggregate whose result type is @c random_variable. Each row
2712 * contributes @c mixture(prov_token, X_i, as_random(identity)), where the
2713 * identity element is chosen per aggregate so the aggregate's final
2714 * function is a plain fold with no gate inspection:
2715 * - @c sum : identity @c 0 (additive), realising
2716 * @f$\mathrm{SUM}(x) = \sum_i \mathbf{1}\{\varphi_i\} \cdot X_i@f$;
2717 * - @c product : identity @c 1 (multiplicative);
2718 * - @c max / @c min : identity @f$\mp\infty@f$ (order-statistic).
2719 *
2720 * @c avg is special: it is rewritten to
2721 * @c rv_sum_or_null(rv_aggregate_semimod(prov, x)) @c /
2722 * @c sum(rv_aggregate_indicator(prov)) -- the "AVG = SUM / COUNT" identity
2723 * lifted into the @c random_variable algebra, with the provenance-weighted
2724 * count as the denominator. Both sums ride on @c sum's fold, so @c avg too
2725 * never inspects a gate.
2726 *
2727 * Any RV aggregate not recognised here (a future addition, or an older
2728 * schema whose helper OIDs are absent) falls back to the additive
2729 * identity-@c 0 wrap and its own @c aggfnoid, the historical behaviour.
2730 *
2731 * Routing happens at @c make_aggregation_expression on
2732 * @c agg_ref->aggtype @c == @c OID_TYPE_RANDOM_VARIABLE. @c SR_PLUS (UNION
2733 * outer level) is handled by the caller; this builder never runs for it.
2734 */
2735static Expr *make_rv_aggregate_expression(const constants_t *constants,
2736 Aggref *agg_ref, List *prov_atts,
2737 semiring_operation op) {
2738 Expr *prov_expr = combine_prov_atts(constants, prov_atts, op);
2739 Expr *rv_arg = ((TargetEntry *)linitial(agg_ref->args))->expr;
2740 Oid aggfnoid = agg_ref->aggfnoid;
2741 FuncExpr *wrap;
2742 Aggref *new_agg;
2743 TargetEntry *te;
2744 double identity = 0.0;
2745 bool baked = false;
2746
2747 /* avg -> rv_sum_or_null(rv_aggregate_semimod(prov, x)) / sum(rv_aggregate_indicator(prov)).
2748 * The numerator sums the per-row mixtures and the denominator sums the
2749 * per-row provenance indicators (the provenance-weighted count), so avg
2750 * inherits sum's sniff-free fold. */
2751 if (OidIsValid(constants->OID_AGG_AVG_RV) &&
2752 aggfnoid == constants->OID_AGG_AVG_RV &&
2753 OidIsValid(constants->OID_AGG_SUM_RV) &&
2754 OidIsValid(constants->OID_AGG_RV_SUM_OR_NULL) &&
2755 OidIsValid(constants->OID_FUNCTION_RV_AGGREGATE_INDICATOR) &&
2756 OidIsValid(constants->OID_FUNCTION_RV_DIV)) {
2757 FuncExpr *num_wrap = makeNode(FuncExpr);
2758 FuncExpr *ind_wrap = makeNode(FuncExpr);
2759 FuncExpr *div = makeNode(FuncExpr);
2760
2761 num_wrap->funcid = constants->OID_FUNCTION_RV_AGGREGATE_SEMIMOD;
2762 num_wrap->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
2763 num_wrap->args = list_make2(prov_expr, rv_arg);
2764 num_wrap->location = -1;
2765
2766 /* prov_expr is consumed again by the indicator wrap; copy it so the two
2767 * Aggrefs own independent node trees. The value-aware indicator is
2768 * NULL exactly when the row's value is NULL, so a NULL cell drops out
2769 * of the count as SQL's AVG requires (the one-argument form would
2770 * deflate the average by counting the NULL-valued row). */
2771 if (OidIsValid(constants->OID_FUNCTION_RV_AGGREGATE_INDICATOR_VALUED)) {
2772 ind_wrap->funcid = constants->OID_FUNCTION_RV_AGGREGATE_INDICATOR_VALUED;
2773 ind_wrap->args = list_make2(copyObject(prov_expr), copyObject(rv_arg));
2774 } else {
2775 ind_wrap->funcid = constants->OID_FUNCTION_RV_AGGREGATE_INDICATOR;
2776 ind_wrap->args = list_make1(copyObject(prov_expr));
2777 }
2778 ind_wrap->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
2779 ind_wrap->location = -1;
2780
2781 div->funcid = constants->OID_FUNCTION_RV_DIV;
2782 div->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
2783 /* Numerator uses rv_sum_or_null (NULL on empty) so an empty group
2784 * divides to NULL; denominator is the ordinary provenance-weighted
2785 * count sum. */
2786 div->args = list_make2(
2787 build_rv_sum_aggref(constants, constants->OID_AGG_RV_SUM_OR_NULL,
2788 (Expr *)num_wrap),
2789 build_rv_sum_aggref(constants, constants->OID_AGG_SUM_RV,
2790 (Expr *)ind_wrap));
2791 div->location = -1;
2792 return (Expr *)div;
2793 }
2794
2795 /* SQL-standard statistic aggregates (covar_pop / covar_samp / corr /
2796 * stddev_pop / stddev_samp / percentile_cont): rewrite to the internal
2797 * indicator-carrying _impl aggregate, whose extra leading argument is the
2798 * row's provenance indicator rv_aggregate_indicator(prov). The _impl
2799 * FFUNC weighs every sum by the indicator (and percentile membership by
2800 * it), so absent rows drop out of the statistic. */
2801 {
2802 Oid impl_oid = InvalidOid;
2803 bool is_percentile = false;
2804 bool is_stat_agg = true;
2805
2806 if (OidIsValid(constants->OID_AGG_COVAR_POP_RV) &&
2807 aggfnoid == constants->OID_AGG_COVAR_POP_RV)
2808 impl_oid = constants->OID_AGG_RV_COVAR_POP_IMPL;
2809 else if (OidIsValid(constants->OID_AGG_COVAR_SAMP_RV) &&
2810 aggfnoid == constants->OID_AGG_COVAR_SAMP_RV)
2811 impl_oid = constants->OID_AGG_RV_COVAR_SAMP_IMPL;
2812 else if (OidIsValid(constants->OID_AGG_CORR_RV) &&
2813 aggfnoid == constants->OID_AGG_CORR_RV)
2814 impl_oid = constants->OID_AGG_RV_CORR_IMPL;
2815 else if (OidIsValid(constants->OID_AGG_STDDEV_POP_RV) &&
2816 aggfnoid == constants->OID_AGG_STDDEV_POP_RV)
2817 impl_oid = constants->OID_AGG_RV_STDDEV_POP_IMPL;
2818 else if (OidIsValid(constants->OID_AGG_STDDEV_SAMP_RV) &&
2819 aggfnoid == constants->OID_AGG_STDDEV_SAMP_RV)
2820 impl_oid = constants->OID_AGG_RV_STDDEV_SAMP_IMPL;
2821 else if (OidIsValid(constants->OID_AGG_PERCENTILE_CONT_RV) &&
2822 aggfnoid == constants->OID_AGG_PERCENTILE_CONT_RV) {
2823 impl_oid = constants->OID_AGG_RV_PERCENTILE_IMPL;
2824 is_percentile = true;
2825 } else
2826 is_stat_agg = false;
2827
2828 if (OidIsValid(impl_oid) &&
2829 OidIsValid(constants->OID_FUNCTION_RV_AGGREGATE_INDICATOR)) {
2830 FuncExpr *ind_wrap = makeNode(FuncExpr);
2831 Aggref *impl_agg = makeNode(Aggref);
2832 List *arg_exprs = NIL;
2833 List *arg_types = NIL;
2834 ListCell *lc;
2835 int resno = 1;
2836
2837 ind_wrap->funcid = constants->OID_FUNCTION_RV_AGGREGATE_INDICATOR;
2838 ind_wrap->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
2839 ind_wrap->args = list_make1(prov_expr);
2840 ind_wrap->location = -1;
2841
2842 if (is_percentile) {
2843 /* percentile_cont(f) WITHIN GROUP (ORDER BY x) -> the NORMAL
2844 * aggregate rv_percentile_impl(f, indicator, x). The input order
2845 * is irrelevant (the sampler sorts each draw), so the ordered-set
2846 * shape is dropped; the direct argument becomes a per-row argument
2847 * (constant within the group by the ordered-set contract). */
2848 Expr *fraction = (Expr *)linitial(agg_ref->aggdirectargs);
2849 arg_exprs = list_make1(copyObject(fraction));
2850 arg_types = list_make1_oid(FLOAT8OID);
2851 }
2852 arg_exprs = lappend(arg_exprs, ind_wrap);
2853 arg_types = lappend_oid(arg_types, constants->OID_TYPE_RANDOM_VARIABLE);
2854 foreach (lc, agg_ref->args) {
2855 TargetEntry *arg_te = (TargetEntry *)lfirst(lc);
2856 arg_exprs = lappend(arg_exprs, arg_te->expr);
2857 arg_types = lappend_oid(arg_types,
2858 constants->OID_TYPE_RANDOM_VARIABLE);
2859 }
2860
2861 impl_agg->aggfnoid = impl_oid;
2862 impl_agg->aggtype = constants->OID_TYPE_RANDOM_VARIABLE;
2863 impl_agg->aggargtypes = arg_types;
2864 impl_agg->aggkind = AGGKIND_NORMAL;
2865 impl_agg->aggtranstype = InvalidOid;
2866 impl_agg->args = NIL;
2867 foreach (lc, arg_exprs) {
2868 TargetEntry *arg_te = makeNode(TargetEntry);
2869 arg_te->resno = resno++;
2870 arg_te->expr = (Expr *)lfirst(lc);
2871 impl_agg->args = lappend(impl_agg->args, arg_te);
2872 }
2873 impl_agg->location = agg_ref->location;
2874#if PG_VERSION_NUM >= 140000
2875 impl_agg->aggno = impl_agg->aggtransno = -1;
2876#endif
2877 return (Expr *)impl_agg;
2878 }
2879 if (is_stat_agg)
2880 provsql_error("statistic aggregate over random_variable requires the "
2881 "rv_*_impl aggregates (schema too old; run ALTER "
2882 "EXTENSION provsql UPDATE)");
2883 }
2884
2885 /* product / max / min bake their identity element into the wrap's
2886 * else-branch; sum (and any unrecognised RV aggregate) keeps identity 0. */
2887 if (OidIsValid(constants->OID_AGG_PRODUCT_RV) &&
2888 aggfnoid == constants->OID_AGG_PRODUCT_RV) {
2889 identity = 1.0;
2890 baked = true;
2891 } else if (OidIsValid(constants->OID_AGG_MAX_RV) &&
2892 aggfnoid == constants->OID_AGG_MAX_RV) {
2893 identity = -get_float8_infinity();
2894 baked = true;
2895 } else if (OidIsValid(constants->OID_AGG_MIN_RV) &&
2896 aggfnoid == constants->OID_AGG_MIN_RV) {
2897 identity = get_float8_infinity();
2898 baked = true;
2899 }
2900
2901 wrap = makeNode(FuncExpr);
2902 if (baked && OidIsValid(constants->OID_FUNCTION_RV_AGGREGATE_SEMIMOD_ID)) {
2903 Const *id_const = makeConst(FLOAT8OID, -1, InvalidOid, sizeof(float8),
2904 Float8GetDatum(identity), false,
2905 FLOAT8PASSBYVAL);
2906 wrap->funcid = constants->OID_FUNCTION_RV_AGGREGATE_SEMIMOD_ID;
2907 wrap->args = list_make3(prov_expr, rv_arg, id_const);
2908 } else {
2909 /* sum, and the fallback for any unrecognised RV aggregate: additive
2910 * identity 0 via the two-argument wrap. */
2911 wrap->funcid = constants->OID_FUNCTION_RV_AGGREGATE_SEMIMOD;
2912 wrap->args = list_make2(prov_expr, rv_arg);
2913 }
2914 wrap->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
2915 wrap->location = -1;
2916
2917 /* Rebuild an Aggref calling the SAME aggregate with the wrapped argument. */
2918 te = makeNode(TargetEntry);
2919 te->resno = 1;
2920 te->expr = (Expr *)wrap;
2921
2922 new_agg = makeNode(Aggref);
2923 new_agg->aggfnoid = aggfnoid;
2924 new_agg->aggtype = constants->OID_TYPE_RANDOM_VARIABLE;
2925 new_agg->aggargtypes = list_make1_oid(constants->OID_TYPE_RANDOM_VARIABLE);
2926 new_agg->aggkind = AGGKIND_NORMAL;
2927 new_agg->aggtranstype = InvalidOid;
2928 new_agg->args = list_make1(te);
2929 new_agg->location = agg_ref->location;
2930#if PG_VERSION_NUM >= 140000
2931 new_agg->aggno = new_agg->aggtransno = -1;
2932#endif
2933
2934 return (Expr *)new_agg;
2935}
2936
2937/**
2938 * @brief Build the provenance expression for a single aggregate function.
2939 *
2940 * For @c SR_PLUS (union context) returns the first provenance attribute
2941 * directly. For @c SR_TIMES or @c SR_MONUS, constructs:
2942 * @code
2943 * provenance_aggregate(fn_oid, result_type,
2944 * original_aggref,
2945 * array_agg(provenance_semimod(arg, times_or_monus_token)))
2946 * @endcode
2947 * COUNT(*) and COUNT(expr) feed the semimodule a per-row 1 / 0-or-1 value so
2948 * the semimodule semantics (scalar × token → token) work, while the gate keeps
2949 * the COUNT identity: only that distinguishes a count from a sum over the same
2950 * values once the group is empty.
2951 *
2952 * @param constants Extension OID cache.
2953 * @param agg_ref The original @c Aggref node from the query.
2954 * @param prov_atts List of provenance @c Var nodes.
2955 * @param op Semiring operation (determines how tokens are combined).
2956 * @param is_scalar Aggregation has no GROUP BY (single always-present row).
2957 * @return Provenance expression of type @c agg_token.
2958 */
2959static Expr *make_aggregation_expression(const constants_t *constants,
2960 Aggref *agg_ref, List *prov_atts,
2961 semiring_operation op, bool is_scalar) {
2962 Expr *result;
2963 FuncExpr *expr, *expr_s;
2964 Aggref *agg = makeNode(Aggref);
2965 FuncExpr *plus = makeNode(FuncExpr);
2966 TargetEntry *te_inner = makeNode(TargetEntry);
2967 Const *fn = makeNode(Const);
2968 Const *typ = makeNode(Const);
2969
2970 if (op == SR_PLUS) {
2971 result = linitial(prov_atts);
2972 } else {
2973 Oid aggregation_function = agg_ref->aggfnoid;
2974
2975 /* Aggregates that return random_variable (sum_rv, avg_rv, and any
2976 * future RV-returning aggregate) get a different rewrite: instead
2977 * of going through provenance_semimod (which builds a gate_value
2978 * from CAST(val AS VARCHAR), nonsensical for an RV), each per-row
2979 * argument is wrapped in mixture(prov, rv, as_random(0)) and the
2980 * original aggregate's SFUNC / FFUNC decide what gate shape to
2981 * build from the resulting mixtures. */
2982 if (OidIsValid(constants->OID_TYPE_RANDOM_VARIABLE) &&
2983 agg_ref->aggtype == constants->OID_TYPE_RANDOM_VARIABLE) {
2984 return make_rv_aggregate_expression(constants, agg_ref, prov_atts, op);
2985 }
2986
2987 if (my_lnext(prov_atts, list_head(prov_atts)) == NULL)
2988 expr = linitial(prov_atts);
2989 else {
2990 expr = makeNode(FuncExpr);
2991 if (op == SR_TIMES) {
2992 ArrayExpr *array = makeNode(ArrayExpr);
2993
2994 expr->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
2995 expr->funcvariadic = true;
2996
2997 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
2998 array->element_typeid = constants->OID_TYPE_UUID;
2999 array->elements = prov_atts;
3000 array->location = -1;
3001
3002 expr->args = list_make1(array);
3003 } else { // SR_MONUS
3004 expr->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
3005 expr->args = prov_atts;
3006 }
3007 expr->funcresulttype = constants->OID_TYPE_UUID;
3008 expr->location = -1;
3009 }
3010
3011 // semimodule function
3012 expr_s = makeNode(FuncExpr);
3013 expr_s->funcid = constants->OID_FUNCTION_PROVENANCE_SEMIMOD;
3014 expr_s->funcresulttype = constants->OID_TYPE_UUID;
3015
3016 // check the particular case of count
3017 if (aggregation_function == F_COUNT_) // count(*): counts every row
3018 {
3019 /* Every row contributes the constant 1, so the VALUE is the sum of
3020 * those -- but the gate keeps the COUNT identity (as count(expr) below
3021 * does). Recording SUM instead would throw away the one thing that
3022 * distinguishes the two over an empty set: a count is 0 there, a sum is
3023 * NULL. Evaluators would then have to guess it back from the values
3024 * being all-unit, which cannot tell count(*) from a sum over a column
3025 * of ones. */
3026 Const *one = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
3027 sizeof(int32), Int32GetDatum(1), false, true);
3028 expr_s->args = list_make2(one, expr);
3029 } else if (aggregation_function == F_COUNT_ANY) // count(expr)
3030 {
3031 /* count(expr) counts only rows where expr IS NOT NULL, but -- unlike the
3032 * other aggregates -- an all-NULL group still has a defined result of 0
3033 * (not NULL), so the row must stay PRESENT in the aggregate to carry the
3034 * group's existence; it just contributes 0. Pass the per-row value
3035 * CASE WHEN expr IS NOT NULL THEN 1 ELSE 0 END: a NULL expr (e.g. the
3036 * NULL-padded rows a LEFT JOIN manufactures) contributes 0 to the count
3037 * yet keeps the group alive, so HAVING count(expr)=0 is correctly true.
3038 * count(*) keeps the constant 1 above. */
3039 Expr *arg = ((TargetEntry *)linitial(agg_ref->args))->expr;
3040 CaseExpr *ce = makeNode(CaseExpr);
3041 CaseWhen *cw = makeNode(CaseWhen);
3042 NullTest *nt = makeNode(NullTest);
3043
3044 nt->arg = (Expr *)arg;
3045 nt->nulltesttype = IS_NOT_NULL;
3046 nt->argisrow = false;
3047 nt->location = -1;
3048
3049 cw->expr = (Expr *)nt;
3050 cw->result = (Expr *)makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
3051 sizeof(int32), Int32GetDatum(1), false,
3052 true);
3053 cw->location = -1;
3054
3055 ce->casetype = constants->OID_TYPE_INT;
3056 ce->casecollid = InvalidOid;
3057 ce->arg = NULL;
3058 ce->args = list_make1(cw);
3059 ce->defresult = (Expr *)makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
3060 sizeof(int32), Int32GetDatum(0), false,
3061 true);
3062 ce->location = -1;
3063
3064 expr_s->args = list_make2(ce, expr);
3065 /* Keep the gate's aggfnoid as count, as count(*) above does: the per-row
3066 * CASE already makes the value 0/1 so the
3067 * VALUE is the SUM of those, but preserving the COUNT identity tells the
3068 * HAVING evaluators that the empty-group result is 0 (a real, comparable
3069 * value) rather than NULL as a genuine sum would be. This is what lets a
3070 * scalar true-on-empty predicate (count(col)=0, <k, <=k) keep the
3071 * all-absent world: enumerate_valid_worlds routes a COUNT with non-unit
3072 * (0/1) values to the value-aware sum_dp with the empty world retained,
3073 * while count(*) (all-unit) keeps using count_enum. */
3074 } else {
3075 expr_s->args =
3076 list_make2(((TargetEntry *)linitial(agg_ref->args))->expr, expr);
3077 }
3078
3079 expr_s->location = -1;
3080
3081 // aggregating all semirings in an array
3082 te_inner->resno = 1;
3083 te_inner->expr = (Expr *)expr_s;
3084 agg->aggfnoid = constants->OID_FUNCTION_ARRAY_AGG;
3085 agg->aggtype = constants->OID_TYPE_UUID_ARRAY;
3086 agg->args = list_make1(te_inner);
3087 agg->aggkind = AGGKIND_NORMAL;
3088 agg->location = -1;
3089#if PG_VERSION_NUM >= 140000
3090 agg->aggno = agg->aggtransno = -1;
3091#endif
3092
3093 agg->aggargtypes = list_make1_oid(constants->OID_TYPE_UUID);
3094
3095 /* An ordered aggregate (e.g. array_agg(x ORDER BY k)) is order-sensitive:
3096 * the HAVING array_agg evaluator matches the per-row values against the
3097 * constant array in children order, which is the order this token
3098 * aggregate collects rows. Carry the original ORDER BY over (sort keys
3099 * re-attached as junk arguments) so that order is the user's on every
3100 * PostgreSQL version; otherwise it is plan order, which only coincides
3101 * when PG >= 16 pre-sorts the ordered-aggregate input
3102 * (enable_presorted_aggregate). */
3103 if (agg_ref->aggorder != NIL) {
3104 AttrNumber sort_resno = 2;
3105 ListCell *lc;
3106 foreach (lc, agg_ref->args) {
3107 TargetEntry *arg_te = (TargetEntry *)lfirst(lc);
3108 TargetEntry *te_sort;
3109 if (arg_te->ressortgroupref == 0)
3110 continue;
3111 te_sort = makeTargetEntry((Expr *)copyObject(arg_te->expr),
3112 sort_resno++, NULL, true);
3113 te_sort->ressortgroupref = arg_te->ressortgroupref;
3114 agg->args = lappend(agg->args, te_sort);
3115 }
3116 agg->aggorder = (List *)copyObject((Node *)agg_ref->aggorder);
3117 }
3118
3119 // final aggregation function
3120 plus->funcid = constants->OID_FUNCTION_PROVENANCE_AGGREGATE;
3121
3122 fn = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
3123 Int32GetDatum(aggregation_function), false, true);
3124
3125 /* The aggregate result-type OID is passed clean (it is also the cast target
3126 * when an agg_token is used in arithmetic, see wrap_agg_token_with_cast).
3127 * The scalar-aggregation flag travels as a separate boolean argument;
3128 * provenance_aggregate sets the high bit of the gate's info2 and folds the
3129 * flag into the gate's content UUID. */
3130 typ = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
3131 Int32GetDatum(agg_ref->aggtype), false, true);
3132
3133 plus->funcresulttype = constants->OID_TYPE_AGG_TOKEN;
3134 plus->args = list_make5(fn, typ, agg_ref, agg,
3135 makeConst(BOOLOID, -1, InvalidOid, sizeof(bool),
3136 BoolGetDatum(is_scalar), false, true));
3137 plus->location = -1;
3138
3139 result = (Expr *)plus;
3140 }
3141
3142 return result;
3143}
3144
3145/* -------------------------------------------------------------------------
3146 * HAVING / WHERE-on-aggregates rewriting
3147 * ------------------------------------------------------------------------- */
3148
3149/* Forward declaration needed because having_BoolExpr_to_provenance and
3150 * having_Expr_to_provenance_cmp are mutually recursive. */
3151static FuncExpr *having_Expr_to_provenance_cmp(Expr *expr, const constants_t *constants, bool negated);
3152
3153/* Forward declaration: defined alongside the other tree walkers
3154 * further down in the file. */
3155static bool needs_having_lift(Node *havingQual, const constants_t *constants);
3156static bool having_entails_group_existence(Expr *expr,
3157 const constants_t *constants,
3158 bool negated);
3159static Node *normalize_bool_agg_having(Node *n);
3160static Node *peel_agg_casts(Node *n);
3161static Node *try_swap_agg_arith(OpExpr *op, const constants_t *constants);
3162
3163/* ----------------------------------------------------------------------
3164 * Normalising constant arithmetic over an aggregate in a comparison.
3165 *
3166 * A comparison such as `sum(x)+1 > 15`, `sum(x)*2 > 30`, or `-sum(x) > 5`
3167 * is rewritten so the aggregate stands alone and the constant arithmetic is
3168 * folded into the threshold (and the operator flipped where the arithmetic
3169 * is monotone-decreasing): `sum(x) > 14`, `sum(x) > 15`, `sum(x) < -5`.
3170 * The aggregate-comparison evaluators then resolve it as usual. This is the
3171 * "push arithmetic to data values" half of the agg_token arithmetic story.
3172 * -------------------------------------------------------------------- */
3173
3174/* Forward declaration: the regular-leaf handling in the HAVING / RV
3175 * converters needs to recognise an rv-free sub-expression, but
3176 * expr_contains_rv_cmp is defined further down with the rest of the RV
3177 * helpers. */
3178static bool expr_contains_rv_cmp(Node *node, const constants_t *constants);
3179
3180/** @brief Context for @c contains_agg_walker. */
3185
3186static bool contains_agg_walker(Node *node, contains_agg_ctx *ctx) {
3187 if (node == NULL)
3188 return false;
3189 if (IsA(node, Var) &&
3190 ((Var *)node)->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
3191 ctx->found = true;
3192 return true;
3193 }
3194 if (IsA(node, FuncExpr) &&
3195 ((FuncExpr *)node)->funcid ==
3197 ctx->found = true;
3198 return true;
3199 }
3200 return expression_tree_walker(node, contains_agg_walker, ctx);
3201}
3202
3203/** @brief Whether an expression subtree references an aggregate (a bare
3204 * provenance_aggregate call or an agg_token Var). */
3205static bool expr_contains_agg(Node *node, const constants_t *constants) {
3206 contains_agg_ctx ctx = {constants, false};
3207 contains_agg_walker(node, &ctx);
3208 return ctx.found;
3209}
3210
3211/** @brief Numeric value of a (possibly cast-wrapped) @c Const; false if the
3212 * node is not a non-NULL @c Const. */
3213static bool const_as_double(Node *n, double *out) {
3214 Const *c;
3215 Oid outfunc;
3216 bool isvarlena;
3217 char *s;
3218
3219 n = peel_agg_casts(n);
3220 if (n == NULL || !IsA(n, Const) || ((Const *)n)->constisnull)
3221 return false;
3222 c = (Const *)n;
3223 getTypeOutputInfo(c->consttype, &outfunc, &isvarlena);
3224 s = OidOutputFunctionCall(outfunc, c->constvalue);
3225 *out = atof(s);
3226 pfree(s);
3227 return true;
3228}
3229
3230/** @brief Build `l <op> r`, resolving the operator by name. */
3231static Node *build_binop(const char *op, Node *l, Node *r) {
3232 ParseState *p = make_parsestate(NULL);
3233 Node *e = (Node *)make_op(p, list_make1(makeString(pstrdup(op))),
3234 l, r, NULL, -1);
3235 free_parsestate(p);
3236 return e;
3237}
3238
3239/**
3240 * @brief Fold constant arithmetic over an aggregate into the comparison
3241 * threshold.
3242 *
3243 * Given a comparison @c OpExpr one of whose sides is an aggregate wrapped in
3244 * constant arithmetic (the other side being aggregate-free), returns an
3245 * equivalent @c "bare_agg <op'> threshold'" @c OpExpr. Returns @c NULL when
3246 * the comparison has no aggregate, has aggregates on both sides (which cannot
3247 * be folded into a scalar threshold), or uses an arithmetic shape we do not
3248 * fold (e.g. a constant divided by the aggregate).
3249 */
3250static OpExpr *normalize_agg_comparison(OpExpr *cmp,
3251 const constants_t *constants) {
3252 Node *a, *b, *agg_side, *thr;
3253 bool a_agg, b_agg;
3254 Oid opno;
3255 OpExpr *res;
3256
3257 if (list_length(cmp->args) != 2)
3258 return NULL;
3259 a = (Node *)linitial(cmp->args);
3260 b = (Node *)lsecond(cmp->args);
3261 a_agg = expr_contains_agg(a, constants);
3262 b_agg = expr_contains_agg(b, constants);
3263 if (a_agg == b_agg) /* none, or aggregate on both sides */
3264 return NULL;
3265
3266 opno = cmp->opno;
3267 if (a_agg) {
3268 agg_side = a; thr = b;
3269 } else {
3270 agg_side = b; thr = a;
3271 opno = get_commutator(opno); /* orient: aggregate on the left */
3272 if (!OidIsValid(opno))
3273 return NULL;
3274 }
3275
3276 /* Conceptually `agg_side <opno> thr`; peel arithmetic from agg_side into
3277 * thr until agg_side is a bare aggregate. */
3278 for (;;) {
3279 Node *peeled = peel_agg_casts(agg_side);
3280 OpExpr *inner;
3281 char *iname;
3282 int nin;
3283 bool flip = false;
3284
3285 if (peeled == NULL || !IsA(peeled, OpExpr)) {
3286 agg_side = peeled;
3287 break;
3288 }
3289 inner = (OpExpr *)peeled;
3290 iname = get_opname(inner->opno);
3291 if (iname == NULL)
3292 return NULL;
3293 nin = list_length(inner->args);
3294
3295 if (nin == 1 && strcmp(iname, "-") == 0) { /* prefix -agg */
3296 thr = build_binop("-", NULL, thr); /* -thr */
3297 flip = true;
3298 agg_side = (Node *)linitial(inner->args);
3299 } else if (nin == 2) {
3300 Node *x = (Node *)linitial(inner->args);
3301 Node *y = (Node *)lsecond(inner->args);
3302 bool x_agg = expr_contains_agg(x, constants);
3303 bool y_agg = expr_contains_agg(y, constants);
3304 Node *c;
3305 bool agg_left;
3306
3307 if (x_agg == y_agg) /* aggregate on both / neither side of inner op */
3308 return NULL;
3309 if (x_agg) { agg_side = x; c = y; agg_left = true; }
3310 else { agg_side = y; c = x; agg_left = false; }
3311
3312 if (strcmp(iname, "+") == 0) {
3313 thr = build_binop("-", thr, c); /* agg+c: thr-c */
3314 } else if (strcmp(iname, "-") == 0) {
3315 if (agg_left)
3316 thr = build_binop("+", thr, c); /* agg-c: thr+c */
3317 else {
3318 thr = build_binop("-", c, thr); /* c-agg: c-thr */
3319 flip = true;
3320 }
3321 } else if (strcmp(iname, "/") == 0) {
3322 /* agg/c <op> thr <=> agg <op> thr*c -- valid for REAL division
3323 * only. An integer (floored) division does not satisfy this
3324 * equivalence, so leave it unfolded and let the possible-worlds
3325 * enumeration evaluate it with the correct integer-division semantics.
3326 * c/agg cannot be folded either way. */
3327 Oid divtype = exprType(peeled);
3328 double cv;
3329 if (divtype == INT2OID || divtype == INT4OID || divtype == INT8OID)
3330 return NULL;
3331 if (!agg_left || !const_as_double(c, &cv) || cv == 0.0)
3332 return NULL;
3333 thr = build_binop("*", thr, c);
3334 if (cv < 0.0)
3335 flip = true;
3336 } else if (strcmp(iname, "*") == 0) {
3337 /* agg*c <op> thr <=> agg <op> thr/c (exact numeric division, so a
3338 * non-integer threshold like 15.5 is preserved). The aggregate
3339 * comparison evaluator handles fractional and high-scale thresholds
3340 * (minimal-scale via trailing-zero trimming on the fast DP path, and
3341 * an exact-enumeration fallback otherwise). */
3342 double cv;
3343 Node *thr_num;
3344 if (!const_as_double(c, &cv) || cv == 0.0)
3345 return NULL;
3346 thr_num = coerce_to_target_type(NULL, thr, exprType(thr),
3347 NUMERICOID, -1, COERCION_EXPLICIT,
3348 COERCE_EXPLICIT_CAST, -1);
3349 if (thr_num == NULL)
3350 return NULL;
3351 thr = build_binop("/", thr_num, c);
3352 if (cv < 0.0)
3353 flip = true;
3354 } else {
3355 return NULL;
3356 }
3357 } else {
3358 return NULL;
3359 }
3360
3361 if (flip) {
3362 opno = get_commutator(opno);
3363 if (!OidIsValid(opno))
3364 return NULL;
3365 }
3366 }
3367
3368 res = makeNode(OpExpr);
3369 res->opno = opno;
3370 res->opresulttype = BOOLOID;
3371 res->opretset = false;
3372 res->opcollid = InvalidOid;
3373 res->inputcollid = cmp->inputcollid;
3374 res->location = cmp->location;
3375 res->args = list_make2(agg_side, thr); /* aggregate on the left */
3376 set_opfuncid(res);
3377 return res;
3378}
3379
3380/**
3381 * @brief Convert a comparison @c OpExpr on aggregate results into a
3382 * @c provenance_cmp gate expression.
3383 *
3384 * Each argument of @p opExpr must be one of:
3385 * - A @c Var of type @c agg_token (or a @c FuncExpr implicit-cast wrapper
3386 * around one) → cast to UUID via @c agg_token_to_uuid.
3387 * - A scalar @c Const, or a bare grouped-column @c Var (necessarily a GROUP BY
3388 * key in a HAVING clause, hence constant within each group) → wrapped in
3389 * @c provenance_semimod(value, gate_one()).
3390 *
3391 * If @p negated is true the operator OID is replaced by its negator so that
3392 * NOT(a < b) becomes a >= b at the provenance level.
3393 *
3394 * @param opExpr The comparison expression from the HAVING clause.
3395 * @param constants Extension OID cache.
3396 * @param negated Whether the expression appears under a NOT.
3397 * @return A @c provenance_cmp(lhs, op_oid, rhs) @c FuncExpr.
3398 */
3399static FuncExpr *having_OpExpr_to_provenance_cmp(OpExpr *opExpr, const constants_t *constants, bool negated) {
3400 FuncExpr *cmpExpr;
3401 Node *arguments[2];
3402 Const *oid;
3403 Oid opno;
3404
3405 /* Fold any constant arithmetic over the aggregate into the threshold
3406 * (e.g. sum(x)+1 > 15 -> sum(x) > 14), so the comparison reduces to the
3407 * bare-aggregate form the rest of this function and the evaluators expect. */
3408 {
3409 OpExpr *norm = normalize_agg_comparison(opExpr, constants);
3410 if (norm != NULL)
3411 opExpr = norm;
3412 }
3413 opno = opExpr->opno;
3414
3415 for (unsigned i = 0; i < 2; ++i) {
3416 Node *node = (Node *)lfirst(list_nth_cell(opExpr->args, i));
3417 Node *agg_node = NULL;
3418
3419 if (IsA(node, FuncExpr)) {
3420 FuncExpr *fe = (FuncExpr *)node;
3421 if (fe->funcformat == COERCE_IMPLICIT_CAST ||
3422 fe->funcformat == COERCE_EXPLICIT_CAST) {
3423 if (fe->args->length == 1)
3424 node = lfirst(list_head(fe->args));
3425 }
3426 }
3427
3428 // Identify the aggregate side. It is either already agg_token-typed (a
3429 // bare provenance_aggregate / agg_token Var, or agg_token arithmetic on a
3430 // materialised column), or an arithmetic OpExpr over aggregates that still
3431 // needs lowering to the native agg_token operators (e.g. the int8-typed
3432 // sum(x)*sum(y) of a live HAVING) -- which try_swap_agg_arith turns into a
3433 // gate_arith. Either way the result agg_token is cast to its UUID, so the
3434 // gate_cmp wraps the aggregate (or the gate_arith over aggregates); the
3435 // possible-worlds evaluator resolves it.
3436 if (exprType(node) == constants->OID_TYPE_AGG_TOKEN) {
3437 agg_node = node;
3438 } else if (IsA(node, OpExpr) && expr_contains_agg(node, constants)) {
3439 Node *swapped = try_swap_agg_arith((OpExpr *)node, constants);
3440 if (swapped != NULL &&
3441 exprType(swapped) == constants->OID_TYPE_AGG_TOKEN)
3442 agg_node = swapped;
3443 }
3444
3445 if (agg_node != NULL) {
3446 // The aggregate side: add an explicit cast of the agg_token to UUID.
3447 FuncExpr *castToUUID = makeNode(FuncExpr);
3448
3449 castToUUID->funcid = constants->OID_FUNCTION_AGG_TOKEN_UUID;
3450 castToUUID->funcresulttype = constants->OID_TYPE_UUID;
3451 castToUUID->args = list_make1(agg_node);
3452 castToUUID->location = -1;
3453
3454 arguments[i] = (Node *)castToUUID;
3455 } else if (!expr_contains_agg(node, constants)) {
3456 // The value side: a literal, a bare grouped-column Var, or a constant
3457 // arithmetic expression folded from the aggregate side by
3458 // normalize_agg_comparison (e.g. the `15 - col` of sum(x)+col > 15).
3459 // A non-agg_token Var in HAVING is necessarily a GROUP BY key, hence
3460 // constant within each group, so any such aggregate-free expression is
3461 // wrapped like a literal in a value gate carrying the (per-group) datum
3462 // with certain provenance.
3463 FuncExpr *oneExpr = makeNode(FuncExpr);
3464 FuncExpr *semimodExpr = makeNode(FuncExpr);
3465
3466 // gate_one() expression
3467 oneExpr->funcid = constants->OID_FUNCTION_GATE_ONE;
3468 oneExpr->funcresulttype = constants->OID_TYPE_UUID;
3469 oneExpr->args = NIL;
3470 oneExpr->location = -1;
3471
3472 // provenance_semimod(value, gate_one())
3473 semimodExpr->funcid = constants->OID_FUNCTION_PROVENANCE_SEMIMOD;
3474 semimodExpr->funcresulttype = constants->OID_TYPE_UUID;
3475 semimodExpr->args = list_make2((Expr *)node, (Expr *)oneExpr);
3476 semimodExpr->location = -1;
3477
3478 arguments[i] = (Node *)semimodExpr;
3479 } else {
3480 provsql_error("cannot handle complex HAVING expressions");
3481 }
3482 }
3483
3484 if (negated) {
3485 opno = get_negator(opno);
3486 if (!opno)
3487 provsql_error("Missing negator");
3488 }
3489
3490 oid = makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
3491 Int32GetDatum(opno), false, true);
3492
3493 cmpExpr = makeNode(FuncExpr);
3494 cmpExpr->funcid = constants->OID_FUNCTION_PROVENANCE_CMP;
3495 cmpExpr->funcresulttype = constants->OID_TYPE_UUID;
3496 cmpExpr->args = list_make3(arguments[0], oid, arguments[1]);
3497 cmpExpr->location = opExpr->location;
3498
3499 return cmpExpr;
3500}
3501
3502/**
3503 * @brief Build @c "⊕(array_agg(K) FILTER (WHERE V IS [NOT] NULL))" -- the
3504 * per-row provenance @c ⊕ over just the value rows (@p filter @c =
3505 * @c IS_NOT_NULL) or just the null-valued rows (@p filter @c = @c IS_NULL)
3506 * of an aggregate's group.
3507 *
3508 * @p base_arr is the aggregate's @c array_agg(provenance_semimod(V, K)) Aggref;
3509 * we copy it, swap its argument to @c K, and add the @c FILTER on @p V (the
3510 * per-row aggregated value, NULL exactly when the row does not contribute). The
3511 * filtered @c array_agg is @c NULL (not an empty array) for a group with no row
3512 * of the requested kind, so it is wrapped in @c COALESCE(..., '{}') -- the STRICT
3513 * @c provenance_plus then yields @c gate_zero rather than NULL.
3514 */
3515static FuncExpr *having_null_filtered_plus(const constants_t *constants,
3516 Aggref *base_arr, Node *V, Node *K,
3517 NullTestType filter) {
3518 Aggref *arr = (Aggref *)copyObject(base_arr);
3519 TargetEntry *te = (TargetEntry *)linitial(arr->args);
3520 NullTest *flt = makeNode(NullTest);
3521 ArrayExpr *empty = makeNode(ArrayExpr);
3522 CoalesceExpr *coal = makeNode(CoalesceExpr);
3523 FuncExpr *plus = makeNode(FuncExpr);
3524
3525 te->expr = (Expr *)copyObject(K); /* array_agg(K) instead of the semimod */
3526
3527 /* The provenance array_agg never carries a user FILTER (make_aggregation_
3528 * expression builds a fresh Aggref), so setting aggfilter directly is safe. */
3529 flt->arg = (Expr *)copyObject(V);
3530 flt->nulltesttype = filter;
3531 flt->argisrow = false;
3532 flt->location = -1;
3533 arr->aggfilter = (Expr *)flt;
3534
3535 empty->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3536 empty->array_collid = InvalidOid;
3537 empty->element_typeid = constants->OID_TYPE_UUID;
3538 empty->elements = NIL;
3539 empty->multidims = false;
3540 empty->location = -1;
3541
3542 coal->coalescetype = constants->OID_TYPE_UUID_ARRAY;
3543 coal->coalescecollid = InvalidOid;
3544 coal->args = list_make2(arr, empty);
3545 coal->location = -1;
3546
3547 plus->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
3548 plus->funcresulttype = constants->OID_TYPE_UUID;
3549 plus->funcvariadic = true;
3550 plus->args = list_make1(coal);
3551 plus->location = -1;
3552 return plus;
3553}
3554
3555/**
3556 * @brief Convert a @c NullTest on an aggregate (@c agg IS [NOT] NULL) into a
3557 * provenance expression.
3558 *
3559 * @c sum / @c avg / @c min / @c max / @c array_agg / @c choose are NULL exactly
3560 * when no value row contributes (every aggregated value absent or NULL). Split
3561 * the group's rows into value rows (@c V @c IS @c NOT @c NULL → tokens @c Kn, the
3562 * rows the aggregate is defined over) and null-valued rows (@c V @c IS @c NULL →
3563 * tokens @c Kz, present but not contributing). Then:
3564 *
3565 * - @c IS @c NOT @c NULL → @c δ(⊕Kn): a value row is present.
3566 * - @c IS @c NULL, scalar (no GROUP BY) → @c "1 ⊖ ⊕Kn": the single result row
3567 * always exists and is NULL exactly when no value row is present.
3568 * - @c IS @c NULL, grouped → @c "δ(⊕Kz) ⊗ (1 ⊖ ⊕Kn)": the group is present via a
3569 * null-valued row while no value row is present, so the aggregate is NULL.
3570 * (When the group has no null-valued rows @c Kz is empty and this collapses to
3571 * @c gate_zero, matching the pre-fix behaviour; the all-NULL-valued group, once
3572 * an unsupported edge, is now handled.)
3573 *
3574 * Splitting on @p V (not the whole-group @c ⊕) is what fixes both directions when
3575 * null-valued rows are present: the old code used @c ⊕ over every row, so
3576 * @c IS @c NOT @c NULL over-counted (a null-only world looked non-NULL) and
3577 * grouped @c IS @c NULL was dropped entirely.
3578 *
3579 * The aggregate must be a direct @c provenance_aggregate call (an @c agg_token
3580 * @c Var coming from a subquery exposes no token array and is rejected).
3581 */
3582static FuncExpr *having_NullTest_to_provenance(NullTest *nt,
3583 const constants_t *constants,
3584 bool negated) {
3585 Node *arg = (Node *)nt->arg;
3586 FuncExpr *pa, *plusKn;
3587 Node *V, *K;
3588 Aggref *base_arr;
3589 bool is_scalar;
3590 NullTestType ntt;
3591
3592 /* Unwrap a single-argument implicit/explicit cast around the aggregate. */
3593 if (IsA(arg, FuncExpr)) {
3594 FuncExpr *fe = (FuncExpr *)arg;
3595 if ((fe->funcformat == COERCE_IMPLICIT_CAST ||
3596 fe->funcformat == COERCE_EXPLICIT_CAST) &&
3597 list_length(fe->args) == 1)
3598 arg = (Node *)linitial(fe->args);
3599 }
3600 if (!IsA(arg, FuncExpr) ||
3601 ((FuncExpr *)arg)->funcid != constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
3602 provsql_error("HAVING IS [NOT] NULL is only supported directly on an "
3603 "aggregate of a provenance-tracked relation");
3604 pa = (FuncExpr *)arg;
3605
3606 /* provenance_aggregate(aggfnoid, aggtype, Aggref, array_agg(semimods),
3607 * is_scalar): the 5th argument is the scalar flag, the 4th is
3608 * array_agg(provenance_semimod(V, K)). Extract the per-row aggregated value V
3609 * and provenance token K (the semimod's two arguments) -- V drives the value /
3610 * null split, K is what we ⊕. We need K rather than the semimod (which carries
3611 * a value gate the probability and Boolean evaluators cannot walk). */
3612 is_scalar = DatumGetBool(((Const *)list_nth(pa->args, 4))->constvalue);
3613 {
3614 Aggref *arr = (Aggref *)list_nth(pa->args, 3);
3615 TargetEntry *te;
3616 FuncExpr *sm;
3617 if (!IsA(arr, Aggref) || list_length(arr->args) != 1)
3618 provsql_error("unexpected aggregate shape in HAVING IS [NOT] NULL");
3619 te = (TargetEntry *)linitial(arr->args);
3620 if (!IsA(te->expr, FuncExpr) ||
3621 ((FuncExpr *)te->expr)->funcid != constants->OID_FUNCTION_PROVENANCE_SEMIMOD)
3622 provsql_error("unexpected aggregate shape in HAVING IS [NOT] NULL");
3623 sm = (FuncExpr *)te->expr;
3624 V = (Node *)list_nth(sm->args, 0); /* per-row aggregated value */
3625 K = (Node *)list_nth(sm->args, 1); /* per-row provenance token */
3626 base_arr = arr;
3627 }
3628
3629 ntt = nt->nulltesttype;
3630 if (negated)
3631 ntt = (ntt == IS_NULL) ? IS_NOT_NULL : IS_NULL;
3632
3633 /* ⊕Kn: the OR of the value-row tokens (V IS NOT NULL). */
3634 plusKn = having_null_filtered_plus(constants, base_arr, V, K, IS_NOT_NULL);
3635
3636 if (ntt == IS_NOT_NULL) {
3637 /* δ(⊕Kn): a value row is present. */
3638 FuncExpr *delta = makeNode(FuncExpr);
3639 delta->funcid = constants->OID_FUNCTION_PROVENANCE_DELTA;
3640 delta->funcresulttype = constants->OID_TYPE_UUID;
3641 delta->args = list_make1(plusKn);
3642 delta->location = -1;
3643 return delta;
3644 }
3645
3646 /* IS NULL: no value row present, i.e. 1 ⊖ ⊕Kn. */
3647 {
3648 FuncExpr *one = makeNode(FuncExpr);
3649 FuncExpr *monus = makeNode(FuncExpr);
3650 one->funcid = constants->OID_FUNCTION_GATE_ONE;
3651 one->funcresulttype = constants->OID_TYPE_UUID;
3652 one->args = NIL;
3653 one->location = -1;
3654 monus->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
3655 monus->funcresulttype = constants->OID_TYPE_UUID;
3656 monus->args = list_make2(one, plusKn); /* 1 ⊖ ⊕Kn */
3657 monus->location = -1;
3658
3659 if (is_scalar)
3660 return monus; /* the single result row always exists */
3661
3662 /* Grouped: the group must also be present, which -- given no value row --
3663 * means a null-valued row is present: δ(⊕Kz) ⊗ (1 ⊖ ⊕Kn). */
3664 {
3665 FuncExpr *plusKz =
3666 having_null_filtered_plus(constants, base_arr, V, K, IS_NULL);
3667 FuncExpr *deltaKz = makeNode(FuncExpr);
3668 FuncExpr *times = makeNode(FuncExpr);
3669 ArrayExpr *factors = makeNode(ArrayExpr);
3670
3671 deltaKz->funcid = constants->OID_FUNCTION_PROVENANCE_DELTA;
3672 deltaKz->funcresulttype = constants->OID_TYPE_UUID;
3673 deltaKz->args = list_make1(plusKz);
3674 deltaKz->location = -1;
3675
3676 factors->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3677 factors->array_collid = InvalidOid;
3678 factors->element_typeid = constants->OID_TYPE_UUID;
3679 factors->elements = list_make2(deltaKz, monus);
3680 factors->multidims = false;
3681 factors->location = -1;
3682
3683 times->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
3684 times->funcresulttype = constants->OID_TYPE_UUID;
3685 times->funcvariadic = true;
3686 times->args = list_make1(factors);
3687 times->location = -1;
3688 return times;
3689 }
3690 }
3691}
3692
3693/**
3694 * @brief Build the deterministic indicator gate for an ordinary (regular)
3695 * comparison: @c regular_indicator(cond) (@c gate_one when @p cond
3696 * holds, @c gate_zero otherwise).
3697 *
3698 * Used for the regular leaves of a MIXED predicate (one that also carries a
3699 * probabilistic comparison), in both the conditioning rewrite and the
3700 * WHERE / HAVING Boolean analysis -- the @c χ case of the HAVING-provenance
3701 * semantics. Under negation, @c χ(¬ψ) = 𝟙 ⊖ χ(ψ): wrap @p expr in @c NOT.
3702 */
3703static FuncExpr *make_regular_indicator(const constants_t *constants,
3704 Expr *expr, bool negated) {
3705 FuncExpr *ind = makeNode(FuncExpr);
3706 if (!OidIsValid(constants->OID_FUNCTION_REGULAR_INDICATOR))
3707 provsql_error("a regular comparison in a probabilistic predicate requires "
3708 "provsql.regular_indicator (schema too old)");
3709 ind->funcid = constants->OID_FUNCTION_REGULAR_INDICATOR;
3710 ind->funcresulttype = constants->OID_TYPE_UUID;
3711 ind->funcretset = false;
3712 ind->funcvariadic = false;
3713 ind->funcformat = COERCE_EXPLICIT_CALL;
3714 ind->funccollid = InvalidOid;
3715 ind->inputcollid = InvalidOid;
3716 ind->args = list_make1(negated
3717 ? (Expr *) makeBoolExpr(NOT_EXPR, list_make1(expr), -1)
3718 : expr);
3719 ind->location = -1;
3720 return ind;
3721}
3722
3723/**
3724 * @brief Convert a Boolean combination of HAVING comparisons into a
3725 * @c provenance_times / @c provenance_plus gate expression.
3726 *
3727 * Applies De Morgan duality when @p negated is true: AND becomes
3728 * @c provenance_plus (OR) and vice-versa. NOT is handled by flipping
3729 * @p negated and delegating to @c having_Expr_to_provenance_cmp.
3730 *
3731 * @param be Boolean expression from the HAVING clause.
3732 * @param constants Extension OID cache.
3733 * @param negated Whether the expression appears under a NOT.
3734 * @return A @c FuncExpr combining the sub-expressions.
3735 */
3736static FuncExpr *having_BoolExpr_to_provenance(BoolExpr *be, const constants_t *constants, bool negated) {
3737 if(be->boolop == NOT_EXPR) {
3738 Expr *expr = (Expr *) lfirst(list_head(be->args));
3739 return having_Expr_to_provenance_cmp(expr, constants, !negated);
3740 } else {
3741 FuncExpr *result;
3742 List *l = NULL;
3743 ListCell *lc;
3744 ArrayExpr *array = makeNode(ArrayExpr);
3745
3746 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3747 array->element_typeid = constants->OID_TYPE_UUID;
3748 array->location = -1;
3749
3750 result = makeNode(FuncExpr);
3751 result->funcresulttype = constants->OID_TYPE_UUID;
3752 result->funcvariadic = true;
3753 result->location = be->location;
3754 result->args = list_make1(array);
3755
3756 if ((be->boolop == AND_EXPR && !negated) || (be->boolop == OR_EXPR && negated))
3757 result->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
3758 else if ((be->boolop == AND_EXPR && negated) || (be->boolop == OR_EXPR && !negated))
3759 result->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
3760 else
3761 provsql_error("Unknown Boolean operator");
3762
3763 foreach (lc, be->args) {
3764 Expr *expr = (Expr *)lfirst(lc);
3765 FuncExpr *arg = having_Expr_to_provenance_cmp(expr, constants, negated);
3766 l = lappend(l, arg);
3767 }
3768
3769 array->elements = l;
3770
3771 return result;
3772 }
3773}
3774
3775/**
3776 * @brief Dispatch a HAVING sub-expression to the appropriate converter.
3777 *
3778 * Entry point for the mutual recursion between
3779 * @c having_BoolExpr_to_provenance and @c having_OpExpr_to_provenance_cmp.
3780 *
3781 * @param expr Sub-expression to convert (@c BoolExpr or @c OpExpr).
3782 * @param constants Extension OID cache.
3783 * @param negated Whether the expression appears under a NOT.
3784 * @return Converted @c FuncExpr.
3785 */
3786static FuncExpr *having_Expr_to_provenance_cmp(Expr *expr, const constants_t *constants, bool negated)
3787{
3788 /* A sub-expression with no aggregate is an ordinary (regular) condition --
3789 * a grouping-column comparison mixed into the HAVING predicate. Its
3790 * predicate-provenance is the deterministic indicator (the χ case), so a
3791 * mixed HAVING such as "SUM(x) > 5 OR region = 'north'" is supported. */
3792 if (!expr_contains_agg((Node *)expr, constants))
3793 return make_regular_indicator(constants, expr, negated);
3794 if (IsA(expr, BoolExpr))
3795 return having_BoolExpr_to_provenance((BoolExpr *)expr, constants, negated);
3796 else if (IsA(expr, OpExpr))
3797 return having_OpExpr_to_provenance_cmp((OpExpr *)expr, constants, negated);
3798 else if (IsA(expr, NullTest))
3799 return having_NullTest_to_provenance((NullTest *)expr, constants, negated);
3800 else
3801 provsql_error("Unknown structure within Boolean expression");
3802}
3803
3804/* -------------------------------------------------------------------------
3805 * Random-variable WHERE-clause rewriting
3806 *
3807 * Mirror of the HAVING trio above. An OpExpr whose @c opfuncid matches
3808 * one of the @c random_variable_{eq,ne,le,lt,ge,gt} procedures is an
3809 * RV comparison; the planner hook lifts it out of @c jointree->quals,
3810 * builds an equivalent @c provenance_cmp(left_uuid, op_oid, right_uuid)
3811 * @c FuncExpr, and conjoins the resulting UUID into the row's
3812 * provenance via @c provenance_times. The lifted WHERE conjunct is
3813 * removed (or the whole WHERE replaced by @c NULL when only RV cmps
3814 * were present); what remains is purely Boolean and the executor
3815 * evaluates it in the usual way. The RV-cmp operators themselves are
3816 * boolean placeholders -- their procedure raises if reached, which can
3817 * happen only when the planner hook is bypassed (e.g. provsql.active
3818 * off).
3819 * ------------------------------------------------------------------------- */
3820
3821/**
3822 * @brief Test whether @p funcoid is one of the @c random_variable_*
3823 * comparison procedures, and if so return its
3824 * @c ComparisonOperator index.
3825 *
3826 * @param constants Extension OID cache.
3827 * @param funcoid Procedure OID to test (typically @c OpExpr->opfuncid).
3828 * @return Index in @c [0..6) on match, @c -1 otherwise. Match indices
3829 * line up with @c ComparisonOperator (EQ=0, NE=1, LE=2, LT=3,
3830 * GE=4, GT=5).
3831 */
3832static int rv_cmp_index(const constants_t *constants, Oid funcoid)
3833{
3834 for (int i = 0; i < 6; ++i) {
3835 if (funcoid == constants->OID_FUNCTION_RV_CMP[i])
3836 return i;
3837 }
3838 return -1;
3839}
3840
3841/**
3842 * @brief Wrap an expression returning @c random_variable in a
3843 * binary-coercible cast to @c uuid.
3844 *
3845 * Operand of the comparison may be a Var, a constant lifted by an
3846 * implicit cast, or another OpExpr (e.g. <tt>a + b</tt>).
3847 * @c random_variable and @c uuid share the same byte layout, so we
3848 * emit a @c RelabelType node -- the planner sees a zero-cost type
3849 * relabel, the executor never dispatches through a runtime
3850 * conversion function.
3851 */
3852static Expr *
3853wrap_random_variable_uuid(Node *operand, const constants_t *constants)
3854{
3855 RelabelType *rt = makeNode(RelabelType);
3856 rt->arg = (Expr *) operand;
3857 rt->resulttype = constants->OID_TYPE_UUID;
3858 rt->resulttypmod = -1;
3859 rt->resultcollid = InvalidOid;
3860 rt->relabelformat = COERCE_IMPLICIT_CAST;
3861 rt->location = -1;
3862 return (Expr *) rt;
3863}
3864
3865/* Forward declaration: the BoolExpr and Expr walkers below are mutually
3866 * recursive (BoolExpr recurses into Expr for each AND/OR child). */
3867static FuncExpr *rv_Expr_to_provenance(Expr *expr,
3868 const constants_t *constants,
3869 bool negated);
3870
3871/**
3872 * @brief True when @p node is a NULL constant (through a coercion).
3873 *
3874 * Detects the literal @c NULL operand of a lifted comparison at planning
3875 * time; a NULL flowing in at execution (a NULL @c random_variable cell)
3876 * is caught by @c provenance_cmp instead.
3877 */
3878static bool
3880{
3881 if (node && IsA(node, RelabelType))
3882 node = (Node *)((RelabelType *)node)->arg;
3883 return node && IsA(node, Const) && ((Const *)node)->constisnull;
3884}
3885
3886/**
3887 * @brief Convert a single RV-comparison @c OpExpr into a
3888 * @c provenance_cmp() FuncExpr returning UUID.
3889 *
3890 * If @p negated is true the operator OID is replaced by its negator
3891 * (so <tt>NOT (a &gt; b)</tt> becomes <tt>a &le; b</tt> at the
3892 * provenance level), exactly as @c having_OpExpr_to_provenance_cmp
3893 * does.
3894 *
3895 * @param opExpr The comparison expression from the WHERE clause.
3896 * Must satisfy @c rv_cmp_index(opExpr->opfuncid) &ge; 0;
3897 * callers are responsible for the type check.
3898 * @param constants Extension OID cache.
3899 * @param negated Whether the expression appears under a NOT.
3900 */
3901static FuncExpr *
3902rv_OpExpr_to_provenance_cmp(OpExpr *opExpr, const constants_t *constants,
3903 bool negated)
3904{
3905 FuncExpr *cmpExpr;
3906 Const *oid_const;
3907 Oid opno = opExpr->opno;
3908 Node *left = (Node *)linitial(opExpr->args);
3909 Node *right = (Node *)lsecond(opExpr->args);
3910
3911 /* A comparison with a NULL operand is unknown under SQL's 3VL in every
3912 * world, and negation fixes unknown, so the lifted conjunct annotates
3913 * the row zero whichever way the comparison points. The runtime dual
3914 * (a NULL random_variable cell reaching provenance_cmp) is handled by
3915 * provenance_cmp itself returning gate_zero. */
3917 FuncExpr *zero = makeNode(FuncExpr);
3918 zero->funcid = constants->OID_FUNCTION_GATE_ZERO;
3919 zero->funcresulttype = constants->OID_TYPE_UUID;
3920 zero->args = NIL;
3921 zero->location = opExpr->location;
3922 return zero;
3923 }
3924
3925 if (negated) {
3926 opno = get_negator(opno);
3927 if (!opno)
3928 provsql_error("Missing negator for random_variable comparison");
3929 }
3930
3931 oid_const = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
3932 sizeof(int32), Int32GetDatum(opno), false, true);
3933
3934 cmpExpr = makeNode(FuncExpr);
3935 cmpExpr->funcid = constants->OID_FUNCTION_PROVENANCE_CMP;
3936 cmpExpr->funcresulttype = constants->OID_TYPE_UUID;
3937 cmpExpr->args = list_make3(
3938 wrap_random_variable_uuid(left, constants),
3939 oid_const,
3940 wrap_random_variable_uuid(right, constants));
3941 cmpExpr->location = opExpr->location;
3942
3943 return cmpExpr;
3944}
3945
3946/**
3947 * @brief Convert a Boolean combination of RV comparisons into a
3948 * @c provenance_times / @c provenance_plus expression.
3949 *
3950 * Same De Morgan handling as @c having_BoolExpr_to_provenance: under
3951 * negation, AND ↔ OR (which means PROVENANCE_TIMES ↔ PROVENANCE_PLUS).
3952 * NOT flips @c negated and recurses.
3953 */
3954static FuncExpr *
3955rv_BoolExpr_to_provenance(BoolExpr *be, const constants_t *constants,
3956 bool negated)
3957{
3958 FuncExpr *result;
3959 ArrayExpr *array;
3960 List *l = NIL;
3961 ListCell *lc;
3962
3963 if (be->boolop == NOT_EXPR) {
3964 Expr *child = (Expr *)linitial(be->args);
3965 return rv_Expr_to_provenance(child, constants, !negated);
3966 }
3967
3968 array = makeNode(ArrayExpr);
3969 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
3970 array->element_typeid = constants->OID_TYPE_UUID;
3971 array->location = -1;
3972
3973 result = makeNode(FuncExpr);
3974 result->funcresulttype = constants->OID_TYPE_UUID;
3975 result->funcvariadic = true;
3976 result->location = be->location;
3977 result->args = list_make1(array);
3978
3979 if ((be->boolop == AND_EXPR && !negated) ||
3980 (be->boolop == OR_EXPR && negated))
3981 result->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
3982 else if ((be->boolop == AND_EXPR && negated) ||
3983 (be->boolop == OR_EXPR && !negated))
3984 result->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
3985 else
3986 provsql_error("Unknown Boolean operator in random_variable WHERE clause");
3987
3988 foreach (lc, be->args) {
3989 FuncExpr *arg = rv_Expr_to_provenance((Expr *)lfirst(lc),
3990 constants, negated);
3991 l = lappend(l, arg);
3992 }
3993 array->elements = l;
3994
3995 return result;
3996}
3997
3998/**
3999 * @brief Dispatch a WHERE sub-expression to the appropriate RV converter.
4000 *
4001 * Entry point for the mutual recursion between
4002 * @c rv_BoolExpr_to_provenance and @c rv_OpExpr_to_provenance_cmp.
4003 */
4004static FuncExpr *
4005rv_Expr_to_provenance(Expr *expr, const constants_t *constants, bool negated)
4006{
4007 /* A sub-expression with no RV comparison is an ordinary (regular)
4008 * per-tuple condition mixed into the predicate. Its predicate-provenance
4009 * is the deterministic indicator (the χ case), so a mixed WHERE such as
4010 * "X > 3 OR region = 'north'" is supported. */
4011 if (!expr_contains_rv_cmp((Node *)expr, constants))
4012 return make_regular_indicator(constants, expr, negated);
4013 if (IsA(expr, BoolExpr))
4014 return rv_BoolExpr_to_provenance((BoolExpr *)expr, constants, negated);
4015 if (IsA(expr, OpExpr)) {
4016 OpExpr *opExpr = (OpExpr *)expr;
4017 if (rv_cmp_index(constants, opExpr->opfuncid) >= 0)
4018 return rv_OpExpr_to_provenance_cmp(opExpr, constants, negated);
4019 }
4020 provsql_error("Unsupported sub-expression in random_variable WHERE clause "
4021 "(only Boolean combinations of RV comparisons, optionally "
4022 "mixed with ordinary comparisons, are accepted)");
4023 return NULL; /* unreachable, silences -Wreturn-type */
4024}
4025
4026/**
4027 * @brief Walker: does @p node contain a probabilistic (random_variable or
4028 * aggregate) comparison?
4029 *
4030 * Distinguishes a conditioning predicate (which has at least one such
4031 * comparison) from a purely-regular one (an ordinary filter, which is NOT a
4032 * conditioning event and is rejected by the @c "X | (predicate)" rewrite).
4033 */
4034static bool expr_has_probabilistic_cmp(Node *node, void *data) {
4035 const constants_t *constants = (const constants_t *)data;
4036 if (node == NULL)
4037 return false;
4038 if (IsA(node, OpExpr)) {
4039 OpExpr *op = (OpExpr *)node;
4040 if (rv_cmp_index(constants, op->opfuncid) >= 0 ||
4041 expr_contains_agg((Node *)op, constants))
4042 return true;
4043 }
4044 return expression_tree_walker(node, expr_has_probabilistic_cmp, data);
4045}
4046
4047/**
4048 * @brief Convert a Boolean predicate into a provenance condition gate.
4049 *
4050 * Carrier-independent counterpart of @c rv_Expr_to_provenance, used by the
4051 * @c "X | (predicate)" rewrite: the predicate is a Boolean combination
4052 * (AND / OR / NOT) of comparisons. A probabilistic comparison -- a
4053 * random_variable comparison (@c "X > 3", lowered by
4054 * @c rv_OpExpr_to_provenance_cmp) or an agg_token comparison (@c "SUM(x) > 5",
4055 * lowered by @c having_OpExpr_to_provenance_cmp) -- becomes its gate. A
4056 * purely-regular SUB-expression (no probabilistic comparison, e.g.
4057 * @c "region = 'north'") becomes the deterministic indicator
4058 * @c regular_indicator(cond) (@c χ: @c gate_one when it holds, @c gate_zero
4059 * otherwise), so a MIXED predicate is supported per the HAVING-provenance
4060 * semantics. Returns the @c uuid gate representing "the predicate holds":
4061 * AND maps to @c provenance_times, OR to @c provenance_plus, NOT flips
4062 * @c negated (De Morgan), mirroring @c rv_BoolExpr_to_provenance.
4063 */
4064static FuncExpr *predicate_to_condition_gate(Expr *expr,
4065 const constants_t *constants,
4066 bool negated) {
4067 /* A purely-regular sub-expression: a single deterministic indicator (not
4068 * decomposed -- a regular OR must not become a sum of indicators). Under
4069 * negation, χ(¬ψ) = 𝟙 ⊖ χ(ψ), i.e. the indicator of the negated comparison,
4070 * so wrap the operand in NOT. */
4071 if (!expr_has_probabilistic_cmp((Node *)expr, (void *)constants)) {
4072 FuncExpr *ind = makeNode(FuncExpr);
4073 if (!OidIsValid(constants->OID_FUNCTION_REGULAR_INDICATOR))
4074 provsql_error("conditioning on an ordinary (regular) comparison requires "
4075 "provsql.regular_indicator (schema too old)");
4076 ind->funcid = constants->OID_FUNCTION_REGULAR_INDICATOR;
4077 ind->funcresulttype = constants->OID_TYPE_UUID;
4078 ind->funcretset = false;
4079 ind->funcvariadic = false;
4080 ind->funcformat = COERCE_EXPLICIT_CALL;
4081 ind->funccollid = InvalidOid;
4082 ind->inputcollid = InvalidOid;
4083 ind->args = list_make1(negated
4084 ? (Expr *) makeBoolExpr(NOT_EXPR, list_make1(expr), -1)
4085 : expr);
4086 ind->location = -1;
4087 return ind;
4088 }
4089
4090 if (IsA(expr, BoolExpr)) {
4091 BoolExpr *be = (BoolExpr *)expr;
4092 ArrayExpr *array;
4093 FuncExpr *result;
4094 List *l = NIL;
4095 ListCell *lc;
4096
4097 if (be->boolop == NOT_EXPR)
4098 return predicate_to_condition_gate((Expr *)linitial(be->args),
4099 constants, !negated);
4100
4101 array = makeNode(ArrayExpr);
4102 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
4103 array->element_typeid = constants->OID_TYPE_UUID;
4104 array->location = -1;
4105
4106 result = makeNode(FuncExpr);
4107 result->funcresulttype = constants->OID_TYPE_UUID;
4108 result->funcvariadic = true;
4109 result->location = be->location;
4110 result->args = list_make1(array);
4111 if ((be->boolop == AND_EXPR && !negated) ||
4112 (be->boolop == OR_EXPR && negated))
4113 result->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
4114 else
4115 result->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
4116
4117 foreach (lc, be->args)
4118 l = lappend(l, predicate_to_condition_gate((Expr *)lfirst(lc),
4119 constants, negated));
4120 array->elements = l;
4121 return result;
4122 }
4123
4124 if (IsA(expr, OpExpr)) {
4125 OpExpr *op = (OpExpr *)expr;
4126 if (rv_cmp_index(constants, op->opfuncid) >= 0)
4127 return rv_OpExpr_to_provenance_cmp(op, constants, negated);
4128 if (expr_contains_agg((Node *)op, constants))
4129 return having_OpExpr_to_provenance_cmp(op, constants, negated);
4130 }
4131
4132 provsql_error("The right operand of the conditioning operator | must be a "
4133 "Boolean combination of random_variable or aggregate "
4134 "comparisons (e.g. \"X | (X > 3)\")");
4135 return NULL; /* unreachable */
4136}
4137
4138/**
4139 * @brief Carrier-routing for an @c "X | (predicate)" placeholder OpExpr.
4140 *
4141 * Maps the placeholder's @c opfuncid to the conditioning constructor to emit
4142 * and its result type; the prefix whole-tuple form (@c given_predicate) maps
4143 * to @c given, whose single argument is the gate (no left operand). Returns
4144 * @c false if @p opfuncid is not a conditioning placeholder.
4145 */
4146static bool cond_predicate_target(const constants_t *constants, Oid opfuncid,
4147 Oid *cond_fn, Oid *result_type,
4148 bool *is_prefix) {
4149 *is_prefix = false;
4150 if (opfuncid == constants->OID_FUNCTION_COND_PREDICATE) {
4151 *cond_fn = constants->OID_FUNCTION_COND;
4152 *result_type = constants->OID_TYPE_UUID;
4153 } else if (opfuncid == constants->OID_FUNCTION_RV_COND_PREDICATE) {
4154 *cond_fn = constants->OID_FUNCTION_RV_COND;
4155 *result_type = constants->OID_TYPE_RANDOM_VARIABLE;
4156 } else if (opfuncid == constants->OID_FUNCTION_AGG_COND_PREDICATE) {
4157 *cond_fn = constants->OID_FUNCTION_AGG_COND;
4158 *result_type = constants->OID_TYPE_AGG_TOKEN;
4159 } else if (opfuncid == constants->OID_FUNCTION_GIVEN_PREDICATE) {
4160 *cond_fn = constants->OID_FUNCTION_GIVEN;
4161 *result_type = constants->OID_TYPE_UUID;
4162 *is_prefix = true;
4163 } else
4164 return false;
4165 return OidIsValid(*cond_fn);
4166}
4167
4168/**
4169 * @brief Mutator: rewrite @c "X | (predicate)" into the carrier's @c cond.
4170 *
4171 * The @c "|" on @c "(carrier, boolean)" -- and the prefix @c "| (boolean)" --
4172 * parses to an @c OpExpr over a conditioning placeholder, whose Boolean
4173 * operand is a combination of probabilistic comparisons. This mutator builds
4174 * the condition gate from that operand (@c predicate_to_condition_gate) and
4175 * replaces the node with @c "cond(X, gate)" for the carrier (or @c given(gate)
4176 * for the prefix whole-tuple form), so the natural @c "X | (X > 3)" /
4177 * @c "SUM(x) | (SUM(x) > 5)" / prefix @c "| (sensor > k)" syntax resolves to
4178 * the existing conditioning surface. The left operand is recursively mutated
4179 * so nested forms (@c "(X | p1) | p2") compose.
4180 */
4181static Node *rewrite_cond_predicate_mutator(Node *node, void *data) {
4182 const constants_t *constants = (const constants_t *)data;
4183 if (node == NULL)
4184 return NULL;
4185 if (IsA(node, OpExpr)) {
4186 OpExpr *op = (OpExpr *)node;
4187 Oid cond_fn, result_type;
4188 bool is_prefix;
4189 /* "(predicate) | (predicate)": both operands are comparison events
4190 * (target | evidence), so neither the uuid | uuid (cond) nor the
4191 * uuid | boolean (cond_predicate) shape applies. Lower each Boolean
4192 * operand to its condition gate and build cond(target, evidence), whose
4193 * probability_evaluate is the correlation-aware Pr(target ∧ evidence) /
4194 * Pr(evidence). Handled before cond_predicate_target since its operands
4195 * are both predicates rather than a pass-through carrier. */
4196 if (OidIsValid(constants->OID_FUNCTION_PREDICATE_COND_PREDICATE) &&
4197 op->opfuncid == constants->OID_FUNCTION_PREDICATE_COND_PREDICATE) {
4198 FuncExpr *target_gate, *evidence_gate, *cond;
4199 if (!expr_has_probabilistic_cmp((Node *)op, (void *)constants))
4200 provsql_error("(predicate) | (predicate) needs at least one "
4201 "random_variable / aggregate comparison; conditioning "
4202 "two purely regular Booleans is not an event -- use a "
4203 "WHERE clause instead");
4204 if (!OidIsValid(constants->OID_FUNCTION_COND))
4205 provsql_error("conditioning two comparison events with | requires "
4206 "provsql.cond (schema too old)");
4207 target_gate = predicate_to_condition_gate((Expr *)linitial(op->args),
4208 constants, false);
4209 evidence_gate = predicate_to_condition_gate((Expr *)llast(op->args),
4210 constants, false);
4211 cond = makeNode(FuncExpr);
4212 cond->funcid = constants->OID_FUNCTION_COND;
4213 cond->funcresulttype = constants->OID_TYPE_UUID;
4214 cond->funcretset = false;
4215 cond->funcvariadic = false;
4216 cond->funcformat = COERCE_EXPLICIT_CALL;
4217 cond->funccollid = InvalidOid;
4218 cond->inputcollid = InvalidOid;
4219 cond->args = list_make2(target_gate, evidence_gate);
4220 cond->location = op->location;
4221 return (Node *) cond;
4222 }
4223 if (cond_predicate_target(constants, op->opfuncid, &cond_fn, &result_type,
4224 &is_prefix)) {
4225 FuncExpr *gate, *cond;
4226 Expr *pred = (Expr *)llast(op->args); /* the Boolean predicate operand */
4227 /* A conditioning predicate must carry at least one probabilistic
4228 * (random_variable / aggregate) comparison. A purely-regular predicate
4229 * is an ordinary deterministic filter, not a conditioning event: reject
4230 * it (the regular-indicator leaf only fires for regular comparisons
4231 * MIXED with probabilistic ones). */
4232 if (!expr_has_probabilistic_cmp((Node *)pred, (void *)constants))
4233 provsql_error("the conditioning operator | needs a predicate with at "
4234 "least one random_variable / aggregate comparison; a "
4235 "purely regular condition is an ordinary filter -- use a "
4236 "WHERE clause instead");
4237 gate = predicate_to_condition_gate(pred, constants, false);
4238 cond = makeNode(FuncExpr);
4239 cond->funcid = cond_fn;
4240 cond->funcresulttype = result_type;
4241 cond->funcretset = false;
4242 cond->funcvariadic = false;
4243 cond->funcformat = COERCE_EXPLICIT_CALL;
4244 cond->funccollid = InvalidOid;
4245 cond->inputcollid = InvalidOid;
4246 if (is_prefix)
4247 cond->args = list_make1(gate); /* given(gate): no left operand */
4248 else {
4249 Expr *target = (Expr *)expression_tree_mutator(
4250 (Node *)linitial(op->args), rewrite_cond_predicate_mutator, data);
4251 cond->args = list_make2(target, gate);
4252 }
4253 cond->location = op->location;
4254 return (Node *) cond;
4255 }
4256 }
4257 /* The function-call spelling given(predicate): the same whole-tuple /
4258 * per-row-evidence marker as the prefix "| (predicate)" OpExpr above, but
4259 * written as a function. Lower its Boolean operand to a condition gate and
4260 * emit given(gate) (the uuid carrier), which the output-conditioning
4261 * rewriter strips at top level and evidence_as_observation turns into an
4262 * observation when executed inside and_agg. */
4263 if (IsA(node, FuncExpr) &&
4264 OidIsValid(constants->OID_FUNCTION_GIVEN_PREDICATE) &&
4265 ((FuncExpr *)node)->funcid == constants->OID_FUNCTION_GIVEN_PREDICATE) {
4266 FuncExpr *fe = (FuncExpr *)node;
4267 Expr *pred = (Expr *)linitial(fe->args);
4268 FuncExpr *gate, *given;
4269 if (!expr_has_probabilistic_cmp((Node *)pred, (void *)constants))
4270 provsql_error("given(predicate) needs a predicate with at least one "
4271 "random_variable / aggregate comparison; a purely regular "
4272 "condition is an ordinary filter -- use a WHERE clause");
4273 gate = predicate_to_condition_gate(pred, constants, false);
4274 given = makeNode(FuncExpr);
4275 given->funcid = constants->OID_FUNCTION_GIVEN;
4276 given->funcresulttype = constants->OID_TYPE_UUID;
4277 given->funcretset = false;
4278 given->funcvariadic = false;
4279 given->funcformat = COERCE_EXPLICIT_CALL;
4280 given->funccollid = InvalidOid;
4281 given->inputcollid = InvalidOid;
4282 given->args = list_make1(gate);
4283 given->location = fe->location;
4284 return (Node *) given;
4285 }
4286 return expression_tree_mutator(node, rewrite_cond_predicate_mutator, data);
4287}
4288
4289/**
4290 * @brief Rewrite every @c "X | (predicate)" in @p q's own clauses.
4291 *
4292 * Runs early in @c process_query (before the FROM-less early return, the
4293 * given()-marker strip and the probabilistic-qual migration), over the target
4294 * list, WHERE and HAVING. No-op on a schema predating the placeholders.
4295 */
4296static void rewrite_cond_predicates(const constants_t *constants, Query *q) {
4297 if (!OidIsValid(constants->OID_FUNCTION_RV_COND_PREDICATE))
4298 return;
4299 q->targetList = (List *)expression_tree_mutator(
4300 (Node *)q->targetList, rewrite_cond_predicate_mutator, (void *)constants);
4301 if (q->jointree && q->jointree->quals)
4302 q->jointree->quals = expression_tree_mutator(
4303 q->jointree->quals, rewrite_cond_predicate_mutator, (void *)constants);
4304 if (q->havingQual)
4305 q->havingQual = expression_tree_mutator(
4306 q->havingQual, rewrite_cond_predicate_mutator, (void *)constants);
4307}
4308
4309/**
4310 * @brief Test whether an Expr (sub-)tree contains any RV comparison.
4311 *
4312 * Used by the WHERE-clause extractor to decide whether a top-level
4313 * conjunct mentions any random_variable comparator and therefore
4314 * needs lifting (or, if the conjunct mixes RV and non-RV operators
4315 * in a way we cannot rewrite, errors).
4316 */
4317static bool
4318expr_contains_rv_cmp(Node *node, const constants_t *constants)
4319{
4320 if (node == NULL)
4321 return false;
4322 if (IsA(node, OpExpr)) {
4323 OpExpr *opExpr = (OpExpr *)node;
4324 if (rv_cmp_index(constants, opExpr->opfuncid) >= 0)
4325 return true;
4326 }
4327 if (IsA(node, BoolExpr)) {
4328 BoolExpr *be = (BoolExpr *)node;
4329 ListCell *lc;
4330 foreach (lc, be->args) {
4331 if (expr_contains_rv_cmp(lfirst(lc), constants))
4332 return true;
4333 }
4334 return false;
4335 }
4336 return false;
4337}
4338
4339/**
4340 * @brief Test whether @p expr is a Boolean combination of @em only
4341 * random_variable comparisons (no other leaves allowed).
4342 *
4343 * Mirrors @c check_expr_on_aggregate / @c check_boolexpr_on_aggregate
4344 * for the agg_token WHERE-to-HAVING migration path. Recursively
4345 * accepts:
4346 * - @c BoolExpr (AND/OR/NOT) all of whose children pass; and
4347 * - @c OpExpr matching one of the @c random_variable_* comparators.
4348 *
4349 * Anything else (a non-RV @c OpExpr, a @c Var, a @c Const, a non-cmp
4350 * @c FuncExpr) makes the expression mixed and unsupportable by the
4351 * RV-only walker, so the function returns @c false and the caller
4352 * raises a clear error.
4353 */
4354static bool
4355check_expr_on_rv(Expr *expr, const constants_t *constants)
4356{
4357 if (expr == NULL)
4358 return false;
4359 /* An rv-free sub-expression is an ordinary comparison: supported as a
4360 * deterministic indicator (the χ case), so a mix of RV and ordinary
4361 * comparisons is accepted. */
4362 if (!expr_contains_rv_cmp((Node *)expr, constants))
4363 return true;
4364 if (IsA(expr, OpExpr))
4365 return rv_cmp_index(constants, ((OpExpr *)expr)->opfuncid) >= 0;
4366 if (IsA(expr, BoolExpr)) {
4367 BoolExpr *be = (BoolExpr *)expr;
4368 ListCell *lc;
4369 foreach (lc, be->args) {
4370 if (!check_expr_on_rv((Expr *)lfirst(lc), constants))
4371 return false;
4372 }
4373 return true;
4374 }
4375 return false;
4376}
4377
4378/* WHERE conjuncts comparing @c random_variable values are classified
4379 * by the unified classifier @c migrate_probabilistic_quals further down
4380 * in this file; both the agg_token and the random_variable migration
4381 * paths are special cases of one walk over @c q->jointree->quals. See
4382 * the comment on @c qual_class for the routing matrix. */
4383
4384/**
4385 * @brief Does a searched @c CASE have at least one RV-comparison guard?
4386 *
4387 * The trigger for lowering an RV-typed @c CASE into a @c gate_case: a @c WHEN
4388 * whose condition carries a random_variable comparison would otherwise raise
4389 * in @c random_variable_cmp_placeholder. A @c CASE over random_variable
4390 * values with only deterministic guards evaluates fine at runtime and is left
4391 * alone.
4392 */
4393static bool
4394case_has_rv_cmp(CaseExpr *ce, const constants_t *constants)
4395{
4396 ListCell *lc;
4397 foreach (lc, ce->args) {
4398 CaseWhen *cw = (CaseWhen *)lfirst(lc);
4399 if (expr_contains_rv_cmp((Node *)cw->expr, constants))
4400 return true;
4401 }
4402 return false;
4403}
4404
4405/**
4406 * @brief Lower an RV-typed searched @c CASE into a @c rv_case(...) call.
4407 *
4408 * Flattens @c "CASE WHEN c_1 THEN v_1 ... ELSE d END" into the wire list
4409 * @c [guard_1, value_1, ..., guard_k, value_k, default] and emits
4410 * @c rv_case(ARRAY[...]) (a @c random_variable). Each guard is built with
4411 * @c predicate_to_condition_gate (the same lift the WHERE / conditioning
4412 * surfaces use, so a Boolean combination of RV comparisons -- optionally mixed
4413 * with ordinary comparisons -- becomes one event token); each value and the
4414 * default are relabelled random_variable -> uuid. @p ce must already have had
4415 * its sub-expressions mutated (so nested RV @c CASE values are themselves
4416 * @c rv_case calls).
4417 */
4418static Node *
4419build_rv_case(CaseExpr *ce, const constants_t *constants)
4420{
4421 List *elements = NIL;
4422 ListCell *lc;
4423 ArrayExpr *array;
4424 FuncExpr *call;
4425
4426 foreach (lc, ce->args) {
4427 CaseWhen *cw = (CaseWhen *)lfirst(lc);
4428 FuncExpr *guard = predicate_to_condition_gate((Expr *)cw->expr,
4429 constants, false);
4430 Expr *value = wrap_random_variable_uuid((Node *)cw->result, constants);
4431 elements = lappend(elements, guard);
4432 elements = lappend(elements, value);
4433 }
4434 /* the ELSE branch is the default value (always the last wire) */
4435 elements = lappend(elements,
4436 wrap_random_variable_uuid((Node *)ce->defresult, constants));
4437
4438 array = makeNode(ArrayExpr);
4439 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
4440 array->element_typeid = constants->OID_TYPE_UUID;
4441 array->elements = elements;
4442 array->location = -1;
4443
4444 call = makeNode(FuncExpr);
4445 call->funcid = constants->OID_FUNCTION_RV_CASE;
4446 call->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
4447 call->funcretset = false;
4448 call->funcvariadic = false;
4449 call->funcformat = COERCE_EXPLICIT_CALL;
4450 call->funccollid = InvalidOid;
4451 call->inputcollid = InvalidOid;
4452 call->args = list_make1(array);
4453 call->location = ce->location;
4454 return (Node *) call;
4455}
4456
4457/* Strip one PostgreSQL cast layer (e.g. the agg_token -> numeric cast the
4458 * aggregate-lowering pass wraps around a nested agg_token) and report whether
4459 * the underlying expression is agg_token-typed. */
4460static bool
4461node_is_agg_token(Node *n, const constants_t *constants)
4462{
4463 if (n != NULL && IsA(n, FuncExpr)) {
4464 FuncExpr *fe = (FuncExpr *)n;
4465 if ((fe->funcformat == COERCE_IMPLICIT_CAST ||
4466 fe->funcformat == COERCE_EXPLICIT_CAST) &&
4467 list_length(fe->args) == 1)
4468 n = (Node *)linitial(fe->args);
4469 }
4470 return n != NULL && exprType(n) == constants->OID_TYPE_AGG_TOKEN;
4471}
4472
4473/* Convert one agg-carrier CASE branch value into the UUID wire the gate_case
4474 * expects: strip the cast the aggregate pass wrapped around it, then cast the
4475 * bare agg_token to its UUID via agg_token_uuid (the same wrapper
4476 * having_OpExpr_to_provenance_cmp uses for the aggregate side of a HAVING
4477 * comparison). Returns NULL for a branch that is not an agg_token (e.g. a bare
4478 * numeric constant -- deferred), so the caller declines the whole CASE. */
4479static Node *
4480agg_arm_to_uuid(Node *arm, const constants_t *constants)
4481{
4482 Node *node = arm;
4483 FuncExpr *castToUUID;
4484
4485 if (node != NULL && IsA(node, FuncExpr)) {
4486 FuncExpr *fe = (FuncExpr *)node;
4487 if ((fe->funcformat == COERCE_IMPLICIT_CAST ||
4488 fe->funcformat == COERCE_EXPLICIT_CAST) &&
4489 list_length(fe->args) == 1)
4490 node = (Node *)linitial(fe->args);
4491 }
4492 if (node == NULL)
4493 return NULL;
4494
4495 if (exprType(node) == constants->OID_TYPE_AGG_TOKEN) {
4496 castToUUID = makeNode(FuncExpr);
4497 castToUUID->funcid = constants->OID_FUNCTION_AGG_TOKEN_UUID;
4498 castToUUID->funcresulttype = constants->OID_TYPE_UUID;
4499 castToUUID->funcretset = false;
4500 castToUUID->funcvariadic = false;
4501 castToUUID->funcformat = COERCE_EXPLICIT_CALL;
4502 castToUUID->funccollid = InvalidOid;
4503 castToUUID->inputcollid = InvalidOid;
4504 castToUUID->args = list_make1(node);
4505 castToUUID->location = -1;
4506 return (Node *)castToUUID;
4507 }
4508
4509 /* A non-agg_token branch (e.g. a bare numeric constant `ELSE 0`, or a
4510 * grouped column): lift it into a value gate via agg_value_gate, the
4511 * aggregate-side analogue of as_random. Coerce to numeric first (CASE
4512 * branches are type-unified, so this is usually a no-op). */
4513 if (OidIsValid(constants->OID_FUNCTION_AGG_VALUE_GATE)) {
4514 FuncExpr *valueGate;
4515 Node *numarg = node;
4516 if (exprType(node) != NUMERICOID) {
4517 numarg = coerce_to_target_type(NULL, node, exprType(node), NUMERICOID, -1,
4518 COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST,
4519 -1);
4520 if (numarg == NULL)
4521 return NULL; /* not coercible to numeric -> decline the CASE */
4522 }
4523 valueGate = makeNode(FuncExpr);
4524 valueGate->funcid = constants->OID_FUNCTION_AGG_VALUE_GATE;
4525 valueGate->funcresulttype = constants->OID_TYPE_UUID;
4526 valueGate->funcretset = false;
4527 valueGate->funcvariadic = false;
4528 valueGate->funcformat = COERCE_EXPLICIT_CALL;
4529 valueGate->funccollid = InvalidOid;
4530 valueGate->inputcollid = InvalidOid;
4531 valueGate->args = list_make1(numarg);
4532 valueGate->location = -1;
4533 return (Node *)valueGate;
4534 }
4535 return NULL;
4536}
4537
4538
4539/* True for a searched CASE whose branches carry aggregate tokens -- the
4540 * aggregate-carrier analogue of case_has_rv_cmp, checked after the aggregate
4541 * pass has lowered the branch aggregates to agg_token. */
4542static bool
4543case_is_agg_carrier(CaseExpr *ce, const constants_t *constants)
4544{
4545 ListCell *lc;
4546
4547 if (ce->arg != NULL || !OidIsValid(constants->OID_FUNCTION_AGG_CASE))
4548 return false;
4549 foreach (lc, ce->args) {
4550 CaseWhen *cw = (CaseWhen *)lfirst(lc);
4551 if (node_is_agg_token((Node *)cw->result, constants))
4552 return true;
4553 }
4554 return node_is_agg_token((Node *)ce->defresult, constants);
4555}
4556
4557/* Build an agg_case(ARRAY[...]) -> agg_token from a searched CASE whose
4558 * branches are aggregates. Mirrors build_rv_case, but the guards are lowered
4559 * by having_Expr_to_provenance_cmp (they compare agg_tokens, already lowered by
4560 * the aggregate pass) and each branch value is cast agg_token -> UUID. Returns
4561 * NULL (declining, so the CASE is left untouched) if a guard or branch is not
4562 * convertible. */
4563static Node *
4564build_agg_case(CaseExpr *ce, const constants_t *constants)
4565{
4566 List *elements = NIL;
4567 ListCell *lc;
4568 ArrayExpr *array;
4569 FuncExpr *call;
4570 Node *value;
4571
4572 foreach (lc, ce->args) {
4573 CaseWhen *cw = (CaseWhen *)lfirst(lc);
4574 FuncExpr *guard =
4575 having_Expr_to_provenance_cmp((Expr *)cw->expr, constants, false);
4576 if (guard == NULL)
4577 return NULL;
4578 value = agg_arm_to_uuid((Node *)cw->result, constants);
4579 if (value == NULL)
4580 return NULL;
4581 elements = lappend(elements, guard);
4582 elements = lappend(elements, value);
4583 }
4584 value = agg_arm_to_uuid((Node *)ce->defresult, constants);
4585 if (value == NULL)
4586 return NULL;
4587 elements = lappend(elements, value);
4588
4589 array = makeNode(ArrayExpr);
4590 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
4591 array->element_typeid = constants->OID_TYPE_UUID;
4592 array->elements = elements;
4593 array->location = -1;
4594
4595 call = makeNode(FuncExpr);
4596 call->funcid = constants->OID_FUNCTION_AGG_CASE;
4597 call->funcresulttype = constants->OID_TYPE_AGG_TOKEN;
4598 call->funcretset = false;
4599 call->funcvariadic = false;
4600 call->funcformat = COERCE_EXPLICIT_CALL;
4601 call->funccollid = InvalidOid;
4602 call->inputcollid = InvalidOid;
4603 call->args = list_make1(array);
4604 call->location = ce->location;
4605 return (Node *)call;
4606}
4607
4608/* Target-list mutator: lower each aggregate-carrier searched CASE into an
4609 * agg_case gate_case. Sub-expressions are mutated first so a CASE nested in a
4610 * branch value lowers before its parent wraps it. */
4611static Node *
4612rewrite_agg_case_mutator(Node *node, void *context)
4613{
4614 const constants_t *constants = (const constants_t *)context;
4615
4616 if (node == NULL)
4617 return NULL;
4618 if (IsA(node, CaseExpr) && case_is_agg_carrier((CaseExpr *)node, constants)) {
4619 CaseExpr *ce = (CaseExpr *)expression_tree_mutator(
4620 node, rewrite_agg_case_mutator, context);
4621 Node *lowered = build_agg_case(ce, constants);
4622 return lowered != NULL ? lowered : (Node *)ce;
4623 }
4624 return expression_tree_mutator(node, rewrite_agg_case_mutator, context);
4625}
4626
4627/* Lower aggregate-carrier CASEs in the target list, after the aggregate pass
4628 * has turned the branch aggregates into agg_tokens. */
4629static void
4630rewrite_agg_cases(const constants_t *constants, Query *q)
4631{
4632 ListCell *lc;
4633
4634 if (!OidIsValid(constants->OID_FUNCTION_AGG_CASE))
4635 return;
4636 foreach (lc, q->targetList) {
4637 TargetEntry *te = (TargetEntry *)lfirst(lc);
4638 te->expr =
4639 (Expr *)rewrite_agg_case_mutator((Node *)te->expr, (void *)constants);
4640 }
4641}
4642
4643/**
4644 * @brief Is @p node a projected random_variable comparison event?
4645 *
4646 * True when the (target-list) expression is itself a random_variable
4647 * comparison -- a bare RV comparator @c OpExpr, or a Boolean combination of
4648 * RV comparisons (optionally mixed with ordinary comparisons) carrying at
4649 * least one RV comparison. Such an expression is lifted into its event token
4650 * (a @c gate_cmp uuid) so @c "SELECT x > y" surfaces the event rather than
4651 * raising inside @c random_variable_cmp_placeholder. Deterministic Booleans
4652 * and agg-only comparisons are left untouched.
4653 */
4654static bool
4655is_projected_rv_event(Node *node, const constants_t *constants)
4656{
4657 if (node == NULL)
4658 return false;
4659 if (!expr_contains_rv_cmp(node, constants))
4660 return false;
4661 if (IsA(node, OpExpr))
4662 return rv_cmp_index(constants, ((OpExpr *)node)->opfuncid) >= 0;
4663 if (IsA(node, BoolExpr))
4664 return check_expr_on_rv((Expr *)node, constants);
4665 return false;
4666}
4667
4668/**
4669 * @brief Mutator: lift the RV surface that can appear in the target list.
4670 *
4671 * Two rewrites, applied wherever they occur in the walked expression:
4672 * - an RV-typed searched @c CASE with an RV-comparison guard becomes a
4673 * @c rv_case(...) call (a @c gate_case);
4674 * - a @c probability(<predicate>) Boolean-overload call whose argument carries
4675 * a probabilistic comparison becomes @c probability_evaluate over the
4676 * argument's event token. A purely-deterministic Boolean argument is left
4677 * in place -- the SQL body returns its @c 0/1 probability, keeping
4678 * @c probability total over Booleans.
4679 *
4680 * Recurses the whole (target-list) expression so both are rewritten wherever
4681 * they appear (e.g. inside @c expected(CASE ... END)).
4682 */
4683static Node *
4685{
4686 const constants_t *constants = (const constants_t *)data;
4687 if (node == NULL)
4688 return NULL;
4689 /* Builtin GREATEST / LEAST over random_variable arguments -> the
4690 * provsql.greatest / provsql.least order-statistic constructor (a
4691 * gate_arith MAX / MIN). Mutate the arguments first (an argument may itself
4692 * be a lowerable RV CASE / GREATEST). */
4693 if (IsA(node, MinMaxExpr)) {
4694 MinMaxExpr *mm = (MinMaxExpr *)node;
4695 if (mm->minmaxtype == constants->OID_TYPE_RANDOM_VARIABLE &&
4696 OidIsValid(constants->OID_FUNCTION_RV_GREATEST) &&
4697 OidIsValid(constants->OID_FUNCTION_RV_LEAST) &&
4698 OidIsValid(constants->OID_TYPE_RANDOM_VARIABLE_ARRAY)) {
4699 FuncExpr *call = makeNode(FuncExpr);
4700 ArrayExpr *arr = makeNode(ArrayExpr);
4701 mm = (MinMaxExpr *)expression_tree_mutator(
4703 arr->array_typeid = constants->OID_TYPE_RANDOM_VARIABLE_ARRAY;
4704 arr->element_typeid = constants->OID_TYPE_RANDOM_VARIABLE;
4705 arr->multidims = false;
4706 arr->elements = mm->args;
4707 arr->location = -1;
4708 call->funcid = (mm->op == IS_GREATEST)
4709 ? constants->OID_FUNCTION_RV_GREATEST
4710 : constants->OID_FUNCTION_RV_LEAST;
4711 call->funcresulttype = constants->OID_TYPE_RANDOM_VARIABLE;
4712 call->funcretset = false;
4713 call->funcvariadic = true; /* VARIADIC random_variable[] */
4714 call->funcformat = COERCE_EXPLICIT_CALL;
4715 call->funccollid = InvalidOid;
4716 call->inputcollid = mm->inputcollid;
4717 call->args = list_make1(arr);
4718 call->location = mm->location;
4719 return (Node *) call;
4720 }
4721 }
4722 /* RV-typed searched CASE with an RV-comparison guard -> gate_case. Mutate
4723 * the CASE's own sub-expressions first (so nested RV cases, and RV
4724 * comparisons inside guards, are already lowered), then flatten. */
4725 if (IsA(node, CaseExpr)) {
4726 CaseExpr *ce = (CaseExpr *)node;
4727 if (ce->arg == NULL &&
4728 OidIsValid(constants->OID_FUNCTION_RV_CASE) &&
4729 ce->casetype == constants->OID_TYPE_RANDOM_VARIABLE &&
4730 case_has_rv_cmp(ce, constants)) {
4731 CaseExpr *mutated = (CaseExpr *)expression_tree_mutator(
4733 return build_rv_case(mutated, constants);
4734 }
4735 }
4736 if (IsA(node, FuncExpr)) {
4737 FuncExpr *fe = (FuncExpr *)node;
4738 if (fe->funcid == constants->OID_FUNCTION_PROBABILITY_PREDICATE &&
4739 fe->args != NIL &&
4740 expr_has_probabilistic_cmp((Node *)linitial(fe->args),
4741 (void *)constants)) {
4742 Expr *pred = (Expr *)linitial(fe->args);
4743 FuncExpr *token = predicate_to_condition_gate(pred, constants, false);
4744 FuncExpr *call = makeNode(FuncExpr);
4745 /* probability_evaluate(token, method, arguments): pass method /
4746 * arguments through if the caller supplied them, else NULL text. */
4747 Node *method_arg = list_length(fe->args) >= 2
4748 ? (Node *) list_nth(fe->args, 1)
4749 : (Node *) makeNullConst(TEXTOID, -1, InvalidOid);
4750 Node *args_arg = list_length(fe->args) >= 3
4751 ? (Node *) list_nth(fe->args, 2)
4752 : (Node *) makeNullConst(TEXTOID, -1, InvalidOid);
4753 call->funcid = constants->OID_FUNCTION_PROBABILITY_EVALUATE;
4754 call->funcresulttype = constants->OID_TYPE_FLOAT;
4755 call->funcretset = false;
4756 call->funcvariadic = false;
4757 call->funcformat = COERCE_EXPLICIT_CALL;
4758 call->funccollid = InvalidOid;
4759 call->inputcollid = InvalidOid;
4760 call->args = list_make3((Expr *)token, method_arg, args_arg);
4761 call->location = fe->location;
4762 return (Node *) call;
4763 }
4764 }
4765 return expression_tree_mutator(node, rewrite_probability_event_mutator, data);
4766}
4767
4768/**
4769 * @brief Mutator: lift any random_variable comparison event to its token.
4770 *
4771 * Run as a second pass over the target list, after
4772 * @c rewrite_probability_event_mutator has lowered the RV surface
4773 * (GREATEST / LEAST, RV @c CASE -> @c rv_case, @c probability(...)). By then
4774 * a comparison's operands are already lowered and every RV @c CASE guard has
4775 * been consumed into an @c rv_case token, so lifting a remaining bare RV
4776 * comparison -- @c "x <= c" wherever it appears, including nested inside a
4777 * scalar/aggregate consumer such as @c expected(x <= c) -- is safe. Without
4778 * this the inner @c OpExpr survives to execution and raises in
4779 * @c random_variable_cmp_placeholder (the placeholder exists precisely to
4780 * catch a comparison the hook failed to lift).
4781 */
4782static Node *
4783lift_rv_event_mutator(Node *node, void *data)
4784{
4785 const constants_t *constants = (const constants_t *)data;
4786 if (node == NULL)
4787 return NULL;
4788 if (is_projected_rv_event(node, constants))
4789 return (Node *) predicate_to_condition_gate((Expr *) node, constants, false);
4790 return expression_tree_mutator(node, lift_rv_event_mutator, data);
4791}
4792
4793/**
4794 * @brief Lift RV-comparison events in @p q's target list into their tokens.
4795 *
4796 * Two related rewrites over the SELECT list (only), run early in
4797 * @c process_query next to @c rewrite_cond_predicates:
4798 * - @c probability(<predicate>) / @c probability_evaluate(<predicate>)
4799 * Boolean overloads whose argument is a probabilistic event become
4800 * @c probability_evaluate over the event token;
4801 * - a projected RV comparison (@c "SELECT x > y") surfaces its @c gate_cmp
4802 * uuid instead of raising in the runtime placeholder.
4803 * WHERE / HAVING quals are deliberately untouched -- those filter positions
4804 * are handled by @c migrate_probabilistic_quals. A set-returning RV consumer
4805 * over a CASE in the FROM list (@c "support(CASE ...)") is out of scope:
4806 * materialise the CASE in a subquery / CTE first, then apply the consumer to
4807 * the resulting @c random_variable column. No-op on a schema predating the
4808 * Boolean @c probability overloads.
4809 */
4810static void
4811rewrite_probability_events(const constants_t *constants, Query *q)
4812{
4813 ListCell *lc;
4814 if (!OidIsValid(constants->OID_FUNCTION_PROBABILITY_EVALUATE))
4815 return;
4816 q->targetList = (List *)expression_tree_mutator(
4817 (Node *)q->targetList, rewrite_probability_event_mutator, (void *)constants);
4818 /* Second pass: lift every remaining RV comparison event to its token,
4819 * wherever it appears in a projected (non-resjunk) column -- both the bare
4820 * top-level "SELECT x > y" and a comparison nested inside a scalar/aggregate
4821 * consumer such as expected(x <= c). Runs after the surface-lowering pass
4822 * so operands are lowered and RV CASE guards are already consumed. */
4823 foreach (lc, q->targetList) {
4824 TargetEntry *te = (TargetEntry *)lfirst(lc);
4825 if (te->resjunk)
4826 continue;
4827 te->expr = (Expr *)lift_rv_event_mutator((Node *)te->expr, (void *)constants);
4828 }
4829}
4830
4831/**
4832 * @brief Lower the RV surface in the values a data-modifying statement
4833 * supplies directly.
4834 *
4835 * A single-row @c "INSERT ... VALUES (expr)" puts @p expr straight into the
4836 * INSERT's own target list, a multi-row one into an @c RTE_VALUES, and an
4837 * @c "UPDATE ... SET c = expr" into the UPDATE's target list; none of those
4838 * positions is a SELECT, so @c process_query -- and with it
4839 * @c rewrite_probability_events -- never sees them. A @c GREATEST / @c LEAST
4840 * or a @c CASE over @c random_variable operands would then survive to
4841 * execution and raise in the btree comparator, even though the identical
4842 * expression one position over (in a @c SELECT list, or in the source of an
4843 * @c "INSERT ... SELECT") is lifted into its order-statistic @c gate_arith.
4844 * @c GREATEST / @c LEAST is SQL grammar rather than an overloadable function,
4845 * so there is no way to reach the lifted form by writing the call
4846 * differently. Apply the surface-lowering pass at all three positions.
4847 *
4848 * The event-lifting second pass that @c rewrite_probability_events runs on a
4849 * SELECT list is deliberately not applied here: it retypes a projected
4850 * comparison from @c boolean to its @c uuid token, and a data-modifying
4851 * statement's column types are already fixed by parse analysis. The
4852 * lowerings applied are all type-preserving, so they cannot desync it the
4853 * same way.
4854 */
4855static void
4856rewrite_dml_rv_surface(const constants_t *constants, Query *q)
4857{
4858 ListCell *lc;
4859 if (!OidIsValid(constants->OID_FUNCTION_PROBABILITY_EVALUATE))
4860 return;
4861 /* Every lowering the mutator performs needs an RV comparator or an RV
4862 * GREATEST / LEAST somewhere in the expression, so this cheap walk keeps
4863 * an ordinary INSERT / UPDATE -- the overwhelmingly common case -- from
4864 * paying for a full copy of its target list. */
4865 if (has_rv_or_provenance_call((Node *)q->targetList, (void *)constants))
4866 q->targetList = (List *)expression_tree_mutator(
4867 (Node *)q->targetList, rewrite_probability_event_mutator,
4868 (void *)constants);
4869 foreach (lc, q->rtable) {
4870 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
4871 if (r->rtekind == RTE_VALUES &&
4872 has_rv_or_provenance_call((Node *)r->values_lists, (void *)constants))
4873 r->values_lists = (List *)expression_tree_mutator(
4874 (Node *)r->values_lists, rewrite_probability_event_mutator,
4875 (void *)constants);
4876 }
4877}
4878
4879/**
4880 * @brief Build the @c ucq_joint_provenance(descriptor) call substituted for
4881 * a recognised unsafe UCQ's existence provenance.
4882 *
4883 * The descriptor (built by @c provsql_joint_width_descriptor from the
4884 * query's syntax) is wrapped as a @c jsonb @c Const; the resulting
4885 * provenance token is the joint-width compiler's certified d-D, so the
4886 * standard probability / Shapley evaluators answer the @c \#P-hard UCQ
4887 * through the one pipeline. Returns @c NULL if the function cannot be
4888 * resolved (e.g. an older schema without it), leaving the normal path.
4889 */
4891 const char *desc, Expr *fallback)
4892{
4893 FuncCandidateList fcl = FuncnameGetCandidatesCompat(
4894 list_make2(makeString("provsql"), makeString("ucq_joint_provenance")),
4895 2, NIL, false, false,
4896 false, false);
4897 FuncExpr *fe;
4898 Const *c;
4899 Datum jb;
4900
4901 if (fcl == NULL)
4902 return NULL;
4903
4904 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
4905 c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
4906
4907 fe = makeNode(FuncExpr);
4908 fe->funcid = fcl->oid;
4909 fe->funcresulttype = constants->OID_TYPE_UUID;
4910 fe->funcretset = false;
4911 fe->funcvariadic = false;
4912 /* (descriptor, fallback token): the joint-width compiler is tried at
4913 * execution; on any failure the fallback (the normal provenance) is
4914 * returned, so the query never fails. */
4915 fe->args = list_make2(c, fallback);
4916 fe->location = -1;
4917 return (Expr *) fe;
4918}
4919
4920/**
4921 * @brief Build the @c ucq_mobius_provenance(descriptor, fallback) call.
4922 *
4923 * The Möbius-inversion route (safe-UCQ Möbius cancellation, the last missing
4924 * exact route of the Dalvi-Suciu dichotomy) shares the joint-width descriptor.
4925 * It is wired as the runtime fallback of the joint-width call (see
4926 * @c make_provenance_expression): the joint-width compiler is tried first
4927 * (strict priority -- it is more general on its inputs), and only on its
4928 * decline (e.g. the joint treewidth exceeds the cap, as for q9 on adversarial
4929 * data) does the Möbius compiler run; on its own decline the @p fallback (the
4930 * normal provenance) is returned, so the query never fails. Returns @c NULL
4931 * if the function cannot be resolved (older schema), leaving @p fallback.
4932 */
4933static Expr *build_mobius_provenance_expr(const constants_t *constants,
4934 const char *desc, Expr *fallback)
4935{
4936 FuncCandidateList fcl = FuncnameGetCandidatesCompat(
4937 list_make2(makeString("provsql"), makeString("ucq_mobius_provenance")),
4938 2, NIL, false, false,
4939 false, false);
4940 FuncExpr *fe;
4941 Const *c;
4942 Datum jb;
4943
4944 if (fcl == NULL)
4945 return fallback;
4946
4947 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
4948 c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
4949
4950 fe = makeNode(FuncExpr);
4951 fe->funcid = fcl->oid;
4952 fe->funcresulttype = constants->OID_TYPE_UUID;
4953 fe->funcretset = false;
4954 fe->funcvariadic = false;
4955 fe->args = list_make2(c, fallback);
4956 fe->location = -1;
4957 return (Expr *) fe;
4958}
4959
4960/**
4961 * @brief Build the per-answer @c ucq_joint_provenance_answer(...) call for a
4962 * recognised non-Boolean UCQ (head variables exposed in the output).
4963 *
4964 * Per output group the head variables are bound to their values; the
4965 * substituted call materialises the head-pinned certified d-D for that
4966 * answer (@c head_vals is @c ARRAY[head Vars], evaluated per group at
4967 * execution). @c fallback (the normal per-answer provenance) is returned
4968 * on any decline. Heads are int4 Vars (the recogniser's restriction), so
4969 * the value array is a plain @c int4[]. Returns @c NULL if the function
4970 * cannot be resolved or there are no heads.
4971 */
4972static Expr *build_joint_width_answer_expr(const constants_t *constants,
4973 const char *desc, List *head_var_idx,
4974 List *head_exprs, Expr *fallback)
4975{
4976 FuncCandidateList fcl = FuncnameGetCandidatesCompat(
4977 list_make2(makeString("provsql"), makeString("ucq_joint_provenance_answer")),
4978 4, NIL, false, false,
4979 false, false);
4980 FuncExpr *fe;
4981 Const *desc_c, *hv_c;
4982 ArrayExpr *vals;
4983 Datum jb;
4984 Datum *hd;
4985 int n = list_length(head_var_idx), i;
4986 ListCell *lc;
4987 ArrayType *arr;
4988
4989 if (fcl == NULL || head_var_idx == NIL)
4990 return NULL;
4991
4992 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
4993 desc_c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
4994
4995 /* The head variables' query-variable indices as an int4[] Const. */
4996 hd = palloc(n * sizeof(Datum));
4997 i = 0;
4998 foreach (lc, head_var_idx)
4999 hd[i++] = Int32GetDatum(lfirst_int(lc));
5000 arr = construct_array(hd, n, INT4OID, sizeof(int32), true, TYPALIGN_INT);
5001 hv_c = makeConst(INT4ARRAYOID, -1, InvalidOid, -1,
5002 PointerGetDatum(arr), false, false);
5003
5004 /* The head values: ARRAY[head Vars cast to text] (text[]), bound per
5005 * group at run time. The cast is the type's output function (CoerceViaIO),
5006 * matching the (col)::text the gather uses for the element dictionary, so
5007 * a head of any type pins correctly. */
5008 vals = makeNode(ArrayExpr);
5009 vals->array_typeid = TEXTARRAYOID;
5010 vals->element_typeid = TEXTOID;
5011 vals->multidims = false;
5012 vals->elements = NIL;
5013 foreach (lc, head_exprs) {
5014 CoerceViaIO *cio = makeNode(CoerceViaIO);
5015 cio->arg = (Expr *) lfirst(lc);
5016 cio->resulttype = TEXTOID;
5017 cio->resultcollid = DEFAULT_COLLATION_OID;
5018 cio->coerceformat = COERCE_IMPLICIT_CAST;
5019 cio->location = -1;
5020 vals->elements = lappend(vals->elements, (Node *) cio);
5021 }
5022 vals->location = -1;
5023
5024 fe = makeNode(FuncExpr);
5025 fe->funcid = fcl->oid;
5026 fe->funcresulttype = constants->OID_TYPE_UUID;
5027 fe->funcretset = false;
5028 fe->funcvariadic = false;
5029 fe->args = list_make4(desc_c, hv_c, (Expr *) vals, fallback);
5030 fe->location = -1;
5031 return (Expr *) fe;
5032}
5033
5034/**
5035 * @brief Build the per-answer @c ucq_mobius_provenance_answer(...) call,
5036 * identical in shape to @c build_joint_width_answer_expr but for the
5037 * Möbius route. Wired as the runtime fallback of the joint-width
5038 * per-answer call, so the joint-width single-DP keeps priority and the
5039 * Möbius head-pinned compile runs only on its decline. Returns
5040 * @p fallback if the function cannot be resolved.
5041 */
5042static Expr *build_mobius_answer_expr(const constants_t *constants,
5043 const char *desc, List *head_var_idx,
5044 List *head_exprs, Expr *fallback)
5045{
5046 FuncCandidateList fcl = FuncnameGetCandidatesCompat(
5047 list_make2(makeString("provsql"), makeString("ucq_mobius_provenance_answer")),
5048 4, NIL, false, false,
5049 false, false);
5050 FuncExpr *fe;
5051 Const *desc_c, *hv_c;
5052 ArrayExpr *vals;
5053 Datum jb;
5054 Datum *hd;
5055 int n = list_length(head_var_idx), i;
5056 ListCell *lc;
5057 ArrayType *arr;
5058
5059 if (fcl == NULL || head_var_idx == NIL)
5060 return fallback;
5061
5062 jb = DirectFunctionCall1(jsonb_in, CStringGetDatum(desc));
5063 desc_c = makeConst(JSONBOID, -1, InvalidOid, -1, jb, false, false);
5064
5065 hd = palloc(n * sizeof(Datum));
5066 i = 0;
5067 foreach (lc, head_var_idx)
5068 hd[i++] = Int32GetDatum(lfirst_int(lc));
5069 arr = construct_array(hd, n, INT4OID, sizeof(int32), true, TYPALIGN_INT);
5070 hv_c = makeConst(INT4ARRAYOID, -1, InvalidOid, -1,
5071 PointerGetDatum(arr), false, false);
5072
5073 vals = makeNode(ArrayExpr);
5074 vals->array_typeid = TEXTARRAYOID;
5075 vals->element_typeid = TEXTOID;
5076 vals->multidims = false;
5077 vals->elements = NIL;
5078 foreach (lc, head_exprs) {
5079 CoerceViaIO *cio = makeNode(CoerceViaIO);
5080 cio->arg = (Expr *) lfirst(lc);
5081 cio->resulttype = TEXTOID;
5082 cio->resultcollid = DEFAULT_COLLATION_OID;
5083 cio->coerceformat = COERCE_IMPLICIT_CAST;
5084 cio->location = -1;
5085 vals->elements = lappend(vals->elements, (Node *) cio);
5086 }
5087 vals->location = -1;
5088
5089 fe = makeNode(FuncExpr);
5090 fe->funcid = fcl->oid;
5091 fe->funcresulttype = constants->OID_TYPE_UUID;
5092 fe->funcretset = false;
5093 fe->funcvariadic = false;
5094 fe->args = list_make4(desc_c, hv_c, (Expr *) vals, fallback);
5095 fe->location = -1;
5096 return (Expr *) fe;
5097}
5098
5099/**
5100 * @brief Wrap a Möbius call in @c mobius_or_null(...): the token if it roots a
5101 * @c gate_mobius (a Möbius success), else NULL (a Möbius decline returns
5102 * the lineage, never a @c gate_mobius). Returns @p mobius_call
5103 * unwrapped if the helper cannot be resolved (older schema).
5104 */
5105static Expr *wrap_mobius_or_null(const constants_t *constants, Expr *mobius_call)
5106{
5107 FuncCandidateList fcl = FuncnameGetCandidatesCompat(
5108 list_make2(makeString("provsql"), makeString("mobius_or_null")),
5109 1, NIL, false, false,
5110 false, false);
5111 FuncExpr *fe;
5112
5113 if (fcl == NULL)
5114 return mobius_call;
5115
5116 fe = makeNode(FuncExpr);
5117 fe->funcid = fcl->oid;
5118 fe->funcresulttype = constants->OID_TYPE_UUID;
5119 fe->funcretset = false;
5120 fe->funcvariadic = false;
5121 fe->args = list_make1(mobius_call);
5122 fe->location = -1;
5123 return (Expr *) fe;
5124}
5125
5126/**
5127 * @brief Combine the Möbius and joint-width routes under Möbius precedence.
5128 *
5129 * Builds @c COALESCE(mobius_or_null(mobius), joint) -- a SHORT-CIRCUITING
5130 * choice: the safe-UCQ Möbius cancellation route (a *guaranteed* PTIME
5131 * \f$O(|D|^k)\f$ exact route for its class -- TID, self-join-free, safe) is
5132 * tried first; on success it roots a @c gate_mobius and @c COALESCE returns it
5133 * without ever evaluating -- hence ever running -- the joint-width compiler.
5134 * Only when Möbius declines (correlated inputs, self-joins, or an unsafe shape:
5135 * @c mobius_or_null then yields NULL) does the joint-width compiler run, with
5136 * the literal @p lineage as its own fallback. Möbius is preferred not because
5137 * joint-width is provably worse -- whether the Möbius class has bounded joint
5138 * treewidth is open (no polynomial d-D is *known* for q9, but none is proved
5139 * impossible for the general d-D class either) -- but because Möbius is a
5140 * guaranteed-terminating route for its class whereas the joint-width compiler
5141 * may grind to its state cap before declining. @p mobius_call / @p joint_call
5142 * are pre-built route expressions (either may be NULL when its debug GUC is
5143 * off); @p lineage is the normal provenance, the final fallback.
5144 */
5145static Expr *combine_safe_routes(const constants_t *constants,
5146 Expr *mobius_call, Expr *joint_call,
5147 Expr *lineage)
5148{
5149 Expr *first;
5150 CoalesceExpr *ce;
5151
5152 if (mobius_call == NULL)
5153 return (joint_call != NULL) ? joint_call : lineage;
5154
5155 first = wrap_mobius_or_null(constants, mobius_call);
5156
5157 ce = makeNode(CoalesceExpr);
5158 ce->coalescetype = constants->OID_TYPE_UUID;
5159 ce->coalescecollid = InvalidOid;
5160 /* On a Möbius decline fall through to joint-width if enabled, else straight
5161 * to the literal lineage. */
5162 ce->args = list_make2(first, (joint_call != NULL) ? joint_call : lineage);
5163 ce->location = -1;
5164 return (Expr *) ce;
5165}
5166
5167/**
5168 * @brief Build the combined provenance expression to be added to the SELECT list.
5169 *
5170 * Combines the tokens in @p prov_atts according to @p op:
5171 * - @c SR_PLUS → use the first token directly (union branch; the outer
5172 * @c array_agg / @c provenance_plus is added later if needed).
5173 * - @c SR_TIMES → wrap all tokens in @c provenance_times(...).
5174 * - @c SR_MONUS → wrap all tokens in @c provenance_monus(...).
5175 *
5176 * When @p aggregation or @p group_by_rewrite is true, wraps the result in
5177 * @c array_agg + @c provenance_plus to collapse groups. A @c provenance_delta
5178 * gate is added for plain aggregations without a HAVING clause.
5179 *
5180 * If a HAVING clause is present it is removed from @p q->havingQual and
5181 * converted into a provenance expression via @c having_Expr_to_provenance_cmp.
5182 *
5183 * If @c provsql_where_provenance is enabled, equality gates (@c provenance_eq)
5184 * are prepended for join conditions and WHERE equalities, and a projection gate
5185 * is appended if the output columns form a proper subset of the input columns.
5186 *
5187 * @param constants Extension OID cache.
5188 * @param q Query being rewritten (HAVING is cleared if present).
5189 * @param prov_atts List of provenance @c Var nodes.
5190 * @param aggregation True if the query contains aggregate functions.
5191 * @param group_by_rewrite True if a GROUP BY requires the plus-aggregate wrapper.
5192 * @param op Semiring operation to use for combining tokens.
5193 * @param columns Per-RTE column-numbering array (for where-provenance).
5194 * For provenance-tracked @c RTE_RELATION entries, the
5195 * -1 sentinel is used to identify them; the PROJECT
5196 * gate positions for their columns use @c varattno
5197 * rather than the query-order-dependent sequential
5198 * numbers (see @c build_column_map() for the
5199 * rationale).
5200 * @param nbcols Total number of non-provenance output columns.
5201 * @param wrap_assumed If true, wrap the result in
5202 * @c assume_boolean so downstream
5203 * probability evaluators may treat it as Boolean.
5204 * @param in_boolean_rewrite True when this query lies under a safe-query
5205 * (boolean) rewrite; the joint-width substitution
5206 * declines so it never pre-empts the read-once form.
5207 * @param inv_cert If non-NULL, a serialised inversion-free certificate
5208 * to attach to the per-row root via @c provsql.annotate
5209 * (transparent for every evaluator; read back by the
5210 * probability dispatcher). Mutually compatible with
5211 * @c wrap_assumed only in principle -- the
5212 * inversion-free path never sets the latter.
5213 * @return The provenance @c Expr to be appended to the target list.
5214 */
5215static Expr *make_provenance_expression(const constants_t *constants, Query *q,
5216 List *prov_atts, bool aggregation,
5217 bool group_by_rewrite,
5218 semiring_operation op, int **columns,
5219 int nbcols, bool wrap_assumed,
5220 bool in_boolean_rewrite,
5221 const char *inv_cert) {
5222 Expr *result;
5223 ListCell *lc_v;
5224 /* Recognise the joint-width substitution BEFORE the aggregation branch
5225 * mutates q (it sets q->hasAggs); the descriptor is applied at the end. */
5226 char *jw_desc = NULL;
5227 bool jw_all_exist = false;
5228 List *jw_head_idx = NIL;
5229 List *jw_head_exprs = NIL;
5230 /* Joint-width is the fallback for the genuinely #P-hard UCQs. It must NOT
5231 * pre-empt a query another route already certifies: @c in_boolean_rewrite is
5232 * set throughout a safe-query rewrite's subtree (so jw defers to it even one
5233 * level down a subquery, where @c wrap_assumed alone is lost), and @c inv_cert
5234 * marks an inversion-free certificate. In both cases the lineage is already
5235 * tractable, so decline. */
5236 /* The Möbius and joint-width routes share this UCQ-existence recognition and
5237 * its descriptor, but are INDEPENDENT: neither GUC gates the other. Build
5238 * the descriptor when EITHER route is enabled; combine_safe_routes then
5239 * wires whichever are on (Möbius first, with precedence). Turning both off
5240 * compares against the literal lineage. */
5242 op != SR_PLUS && (aggregation || group_by_rewrite) &&
5243 !in_boolean_rewrite && inv_cert == NULL)
5244 jw_desc = provsql_joint_width_descriptor(constants, q, &jw_all_exist,
5245 &jw_head_idx, &jw_head_exprs);
5246
5247 if (op == SR_PLUS) {
5248 result = linitial(prov_atts);
5249 } else {
5250 if (my_lnext(prov_atts, list_head(prov_atts)) == NULL) {
5251 result = linitial(prov_atts);
5252 } else {
5253 FuncExpr *expr = makeNode(FuncExpr);
5254 if (op == SR_TIMES) {
5255 ArrayExpr *array = makeNode(ArrayExpr);
5256
5257 expr->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
5258 expr->funcvariadic = true;
5259
5260 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
5261 array->element_typeid = constants->OID_TYPE_UUID;
5262 array->elements = prov_atts;
5263 array->location = -1;
5264
5265 expr->args = list_make1(array);
5266 } else { // SR_MONUS
5267 expr->funcid = constants->OID_FUNCTION_PROVENANCE_MONUS;
5268 expr->args = prov_atts;
5269 }
5270 expr->funcresulttype = constants->OID_TYPE_UUID;
5271 expr->location = -1;
5272
5273 result = (Expr *)expr;
5274 }
5275
5276 if (group_by_rewrite || aggregation) {
5277 Aggref *agg = makeNode(Aggref);
5278 FuncExpr *plus = makeNode(FuncExpr);
5279 TargetEntry *te_inner = makeNode(TargetEntry);
5280
5281 q->hasAggs = true;
5282
5283 te_inner->resno = 1;
5284 te_inner->expr = (Expr *)result;
5285
5286 agg->aggfnoid = constants->OID_FUNCTION_ARRAY_AGG;
5287 agg->aggtype = constants->OID_TYPE_UUID_ARRAY;
5288 agg->args = list_make1(te_inner);
5289 agg->aggkind = AGGKIND_NORMAL;
5290 agg->location = -1;
5291#if PG_VERSION_NUM >= 140000
5292 agg->aggno = agg->aggtransno = -1;
5293#endif
5294
5295 agg->aggargtypes = list_make1_oid(constants->OID_TYPE_UUID);
5296
5297 plus->funcid = constants->OID_FUNCTION_PROVENANCE_PLUS;
5298 plus->args = list_make1(agg);
5299 plus->funcresulttype = constants->OID_TYPE_UUID;
5300 plus->location = -1;
5301
5302 result = (Expr *)plus;
5303 }
5304
5305 /* HAVING quals come in two flavours. A qual that references an
5306 * agg_token Var or a provenance_aggregate() wrapper must be lifted
5307 * into a provenance_cmp gate so the per-group truth value is
5308 * carried by the provenance circuit (and the corresponding gate_agg
5309 * remains evaluable). Anything else -- a deterministic scalar
5310 * predicate, or one over random_variable aggregates collapsed by
5311 * expected() / variance() / moment() to a plain double -- is left
5312 * in q->havingQual for PostgreSQL to evaluate natively, and the
5313 * per-group provenance still gets a delta wrapper. */
5314 {
5315 bool lift_having = q->havingQual != NULL &&
5316 needs_having_lift((Node *) q->havingQual, constants);
5317
5318 if (aggregation && !lift_having) {
5319 if (q->groupClause == NIL && q->groupingSets == NIL) {
5320 /* Scalar aggregation (no GROUP BY): the single result row always
5321 * exists -- even over an empty input (count 0, sum/min/max NULL) -- so
5322 * its existence provenance is gate_one (certain, 1_K in every semiring),
5323 * NOT δ(⊕ tuples) which reads as "the input is non-empty". The per-row
5324 * value provenance lives in the agg_token; the "is this aggregate
5325 * non-empty" condition is recovered separately (the agg_token moment /
5326 * support functions exclude the empty world for NULL-on-empty
5327 * aggregates). δ collapses multiplicity for a *grouped* row, where the
5328 * empty group is no row; for a scalar row that distinction does not
5329 * apply. */
5330 FuncExpr *oneExpr = makeNode(FuncExpr);
5331 oneExpr->funcid = constants->OID_FUNCTION_GATE_ONE;
5332 oneExpr->funcresulttype = constants->OID_TYPE_UUID;
5333 oneExpr->args = NIL;
5334 oneExpr->location = -1;
5335 result = (Expr *)oneExpr;
5336 } else {
5337 FuncExpr *deltaExpr = makeNode(FuncExpr);
5338
5339 // adding the delta gate to the provenance circuit
5340 deltaExpr->funcid = constants->OID_FUNCTION_PROVENANCE_DELTA;
5341 deltaExpr->args = list_make1(result);
5342 deltaExpr->funcresulttype = constants->OID_TYPE_UUID;
5343 deltaExpr->location = -1;
5344
5345 result = (Expr *)deltaExpr;
5346 }
5347 }
5348
5349 if (lift_having) {
5350 /* A lifted comparison supersedes the compared group's delta rather
5351 * than multiplying with it: the cmp gate's enumeration ranges over the
5352 * non-empty worlds of the very same per-row tokens, so it already
5353 * entails the group existence that delta stands for, and conjoining
5354 * both would count that factor twice in a non-idempotent semiring.
5355 *
5356 * That is only licensed when the predicate really does entail
5357 * existence. A disjunct with no aggregate in it does not -- it lowers
5358 * to a deterministic regular_indicator, whose 1 would otherwise claim
5359 * the group exists in every world -- so there the delta stays.
5360 *
5361 * What the supersede removes is precisely that delta. When this level
5362 * owns the aggregation, @c result *is* the group's plus (built just
5363 * above, with no delta wrapped around it), so the comparison stands
5364 * alone. When the comparison instead arrived from a WHERE on a
5365 * subquery's aggregate -- migrate_probabilistic_quals routes such a
5366 * qual to this level's havingQual -- @c result is the product of the
5367 * input tokens here, and only the delta inside it is superseded.
5368 * Those tokens are opaque UUIDs until the query runs, so the decision
5369 * is deferred to provenance_cmp_times, which walks them: a bare delta
5370 * over the compared group disappears, a product keeps its other
5371 * factors (a join partner's annotation among them), and anything else
5372 * -- an earlier comparison on the same group, an input -- is kept and
5373 * multiplied. */
5374 bool entails =
5375 having_entails_group_existence((Expr *) q->havingQual, constants,
5376 false);
5377 Expr *group_plus = result; /* the group's plus, still delta-free */
5378 Expr *cmp = (Expr *) having_Expr_to_provenance_cmp(
5379 (Expr *) q->havingQual, constants, false);
5380
5381 if (!aggregation && !group_by_rewrite && op == SR_TIMES &&
5382 prov_atts != NIL) {
5383 /* The row tokens carry the delta; supersede it only when licensed,
5384 * otherwise multiply everything as it stands. */
5385 FuncExpr *combine = makeNode(FuncExpr);
5386 ArrayExpr *array = makeNode(ArrayExpr);
5387 bool supersede =
5388 entails && OidIsValid(constants->OID_FUNCTION_PROVENANCE_CMP_TIMES);
5389
5390 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
5391 array->element_typeid = constants->OID_TYPE_UUID;
5392 array->elements = list_copy(prov_atts);
5393 array->location = -1;
5394
5395 combine->funcresulttype = constants->OID_TYPE_UUID;
5396 combine->location = -1;
5397 if (supersede) {
5398 combine->funcid = constants->OID_FUNCTION_PROVENANCE_CMP_TIMES;
5399 combine->args = list_make2(cmp, array);
5400 } else {
5401 array->elements = lappend(array->elements, cmp);
5402 combine->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
5403 combine->funcvariadic = true;
5404 combine->args = list_make1(array);
5405 }
5406
5407 result = (Expr *) combine;
5408 } else if (!entails && aggregation) {
5409 /* Fused, and the predicate does not entail existence: wrap the
5410 * group's plus in the delta the supersede would have dropped, and
5411 * multiply the predicate into it. */
5412 FuncExpr *deltaExpr = makeNode(FuncExpr);
5413 FuncExpr *times = makeNode(FuncExpr);
5414 ArrayExpr *array = makeNode(ArrayExpr);
5415
5416 deltaExpr->funcid = constants->OID_FUNCTION_PROVENANCE_DELTA;
5417 deltaExpr->args = list_make1(group_plus);
5418 deltaExpr->funcresulttype = constants->OID_TYPE_UUID;
5419 deltaExpr->location = -1;
5420
5421 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
5422 array->element_typeid = constants->OID_TYPE_UUID;
5423 array->elements = list_make2(deltaExpr, cmp);
5424 array->location = -1;
5425
5426 times->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
5427 times->funcresulttype = constants->OID_TYPE_UUID;
5428 times->funcvariadic = true;
5429 times->args = list_make1(array);
5430 times->location = -1;
5431
5432 result = (Expr *) times;
5433 } else {
5434 result = cmp;
5435 }
5436
5437 q->havingQual = NULL;
5438 }
5439 }
5440 }
5441
5442 /* Part to handle eq gates used for where-provenance.
5443 * Placed before projection gates because they need
5444 * to be deeper in the provenance tree. */
5445 if (provsql_where_provenance && q->jointree) {
5446 ListCell *lc;
5447 foreach (lc, q->jointree->fromlist) {
5448 if (IsA(lfirst(lc), JoinExpr)) {
5449 JoinExpr *je = (JoinExpr *)lfirst(lc);
5450 /* Study equalities coming from From clause */
5451 result =
5452 add_eq_from_Quals_to_Expr(constants, je->quals, result, columns);
5453 }
5454 }
5455 /* Study equalities coming from WHERE clause */
5456 result = add_eq_from_Quals_to_Expr(constants, q->jointree->quals, result,
5457 columns);
5458 }
5459
5461 ArrayExpr *array = makeNode(ArrayExpr);
5462 FuncExpr *fe = makeNode(FuncExpr);
5463 bool projection = false;
5464 int nb_column = 0;
5465 /* Cumulative offset of each RTE within the TIMES gate's concatenated
5466 * locator vector. WhereCircuit::evaluate(TIMES) appends the locator
5467 * vector of each child input in q->rtable order, so a column at
5468 * varattno k of the i-th provenance-tracked base RTE lands at
5469 * prov_offset[i] + k in the concat. varattno alone (the recent fix
5470 * documented at the top of this file) is correct only when there is a
5471 * single provenance-tracked input; for multi-input joins it omits the
5472 * preceding inputs' nb_user_cols and the project gate then reads from
5473 * the wrong table's locator slice.
5474 *
5475 * 1-indexed by rteid for direct indexing via Var->varno; entry 0 is
5476 * unused. Length q->rtable->length + 1. */
5477 int *prov_offset = (int *)palloc0((q->rtable->length + 1) * sizeof(int));
5478 int cum = 0;
5479 Index r;
5480
5481 fe->funcid = constants->OID_FUNCTION_PROVENANCE_PROJECT;
5482 fe->funcvariadic = true;
5483 fe->funcresulttype = constants->OID_TYPE_UUID;
5484 fe->location = -1;
5485
5486 array->array_typeid = constants->OID_TYPE_INT_ARRAY;
5487 array->element_typeid = constants->OID_TYPE_INT;
5488 array->elements = NIL;
5489 array->location = -1;
5490
5491 for (r = 1; r <= (Index)q->rtable->length; ++r) {
5492 prov_offset[r] = cum;
5493 if (columns[r-1]) {
5494 RangeTblEntry *rte_r = (RangeTblEntry *)list_nth(q->rtable, r-1);
5495 int ncols = list_length(rte_r->eref->colnames);
5496 bool is_prov = false;
5497 int nb_user = 0;
5498 int k;
5499 for (k = 0; k < ncols; ++k) {
5500 if (columns[r-1][k] == -1) is_prov = true;
5501 else if (columns[r-1][k] > 0) nb_user++;
5502 }
5503 if (is_prov) cum += nb_user;
5504 }
5505 }
5506
5507 foreach (lc_v, q->targetList) {
5508 TargetEntry *te_v = (TargetEntry *)lfirst(lc_v);
5509 if (IsA(te_v->expr, Var)) {
5510 Var *vte_v = (Var *)te_v->expr;
5511 RangeTblEntry *rte_v =
5512 (RangeTblEntry *)lfirst(list_nth_cell(q->rtable, vte_v->varno - 1));
5513 int value_v;
5514#if PG_VERSION_NUM >= 180000
5515 if (rte_v->rtekind == RTE_GROUP) {
5516 Expr *ge = lfirst(list_nth_cell(rte_v->groupexprs, vte_v->varattno - 1));
5517 if(IsA(ge, Var)) {
5518 Var *v = (Var *) ge;
5519 value_v = columns[v->varno - 1] ?
5520 columns[v->varno - 1][v->varattno - 1] : 0;
5521 } else {
5522 Const *ce = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
5523 sizeof(int32), Int32GetDatum(0), false, true);
5524
5525 array->elements = lappend(array->elements, ce);
5526 value_v = 0;
5527 }
5528 } else
5529#endif
5530 if (rte_v->rtekind != RTE_JOIN) { // Normal RTE
5531 if (rte_v->rtekind == RTE_RELATION && columns[vte_v->varno - 1]) {
5532 /* Determine whether this base table is provenance-tracked by
5533 * scanning for the sentinel -1 entry that build_column_map()
5534 * assigns to the provsql column. */
5535 bool is_prov = false;
5536 int ncols_rte = list_length(rte_v->eref->colnames);
5537 for (int k = 0; k < ncols_rte; k++) {
5538 if (columns[vte_v->varno - 1][k] == -1) {
5539 is_prov = true;
5540 break;
5541 }
5542 }
5543 if (is_prov) {
5544 int raw = columns[vte_v->varno - 1][vte_v->varattno - 1];
5545 /* Local position within this table is `varattno` (the
5546 * provsql column is appended last by add_provenance(), so
5547 * user columns occupy 1..nb_user_cols exactly matching the
5548 * IN gate's Locator vector). We then shift by
5549 * prov_offset[varno] to land in the right slice of the
5550 * TIMES gate's concatenated locator vector when the query
5551 * joins multiple provenance-tracked relations. */
5552 value_v = (raw == -1) ? -1
5553 : (int)vte_v->varattno
5554 + prov_offset[vte_v->varno];
5555 } else {
5556 /* Non-provenance base table: no IN gate exists for it, so
5557 * the position would be out of range regardless. Explicitly
5558 * record 0 so evaluate() returns an empty locator set and
5559 * the positions array stays in sync with the output column
5560 * count. */
5561 Const *ce =
5562 makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
5563 sizeof(int32), Int32GetDatum(0), false, true);
5564 array->elements = lappend(array->elements, ce);
5565 projection = true;
5566 continue;
5567 }
5568 } else {
5569 /* RTE_SUBQUERY and others: the sequential number equals the
5570 * column's 1-indexed position in the subquery's output list,
5571 * which matches what the child gate's evaluate() expects. */
5572 value_v = columns[vte_v->varno - 1] ?
5573 columns[vte_v->varno - 1][vte_v->varattno - 1] : 0;
5574 }
5575 } else { // Join RTE
5576 Var *jav_v = (Var *)lfirst(
5577 list_nth_cell(rte_v->joinaliasvars, vte_v->varattno - 1));
5578 if (jav_v && IsA(jav_v, Var) && columns[jav_v->varno - 1]) {
5579 RangeTblEntry *jrte_v = (RangeTblEntry *)lfirst(
5580 list_nth_cell(q->rtable, jav_v->varno - 1));
5581 if (jrte_v->rtekind == RTE_RELATION) {
5582 /* Provenance-tracking check and varattno fix – same rationale
5583 * as the RTE_RELATION branch above. */
5584 bool is_prov = false;
5585 int ncols_jrte = list_length(jrte_v->eref->colnames);
5586 for (int k = 0; k < ncols_jrte; k++) {
5587 if (columns[jav_v->varno - 1][k] == -1) {
5588 is_prov = true;
5589 break;
5590 }
5591 }
5592 if (is_prov) {
5593 int raw = columns[jav_v->varno - 1][jav_v->varattno - 1];
5594 value_v = (raw == -1) ? -1
5595 : (int)jav_v->varattno
5596 + prov_offset[jav_v->varno];
5597 } else {
5598 Const *ce =
5599 makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
5600 sizeof(int32), Int32GetDatum(0), false, true);
5601 array->elements = lappend(array->elements, ce);
5602 projection = true;
5603 continue;
5604 }
5605 } else {
5606 value_v = columns[jav_v->varno - 1][jav_v->varattno - 1];
5607 }
5608 } else {
5609 value_v = 0;
5610 }
5611 }
5612
5613 /* If this is a valid column */
5614 if (value_v > 0) {
5615 Const *ce =
5616 makeConst(constants->OID_TYPE_INT, -1, InvalidOid, sizeof(int32),
5617 Int32GetDatum(value_v), false, true);
5618
5619 array->elements = lappend(array->elements, ce);
5620
5621 if (value_v != ++nb_column)
5622 projection = true;
5623 } else {
5624 if (value_v != -1)
5625 projection = true;
5626 }
5627 } else { // we have a function in target
5628 Const *ce = makeConst(constants->OID_TYPE_INT, -1, InvalidOid,
5629 sizeof(int32), Int32GetDatum(0), false, true);
5630
5631 array->elements = lappend(array->elements, ce);
5632 projection = true;
5633 }
5634 }
5635
5636 if (nb_column != nbcols)
5637 projection = true;
5638
5639 if (projection) {
5640 fe->args = list_make2(result, array);
5641 result = (Expr *)fe;
5642 } else {
5643 pfree(array);
5644 pfree(fe);
5645 }
5646 }
5647
5648 /* Wrap the finished per-row root in a @c gate_assumed when
5649 * our caller (the safe-query rewrite path in @c process_query) asks
5650 * for it. Wrapping here -- before @c add_to_select and
5651 * @c replace_provenance_function_by_expression -- means every
5652 * per-row root reference in the final target list carries the
5653 * marker uniformly. Subqueries that this same Query body opens
5654 * (per-atom DISTINCT projections inserted by the rewriter) are
5655 * handled by their own deeper @c process_query / @c make_provenance_expression
5656 * calls with @c wrap_assumed = false, so the marker sits
5657 * only at the outermost root that surfaces as the user-visible
5658 * row provenance. */
5659 if (wrap_assumed &&
5660 OidIsValid(constants->OID_FUNCTION_ASSUME_BOOLEAN))
5661 result = wrap_in_assume_boolean(constants, result);
5662
5663 /* Attach the inversion-free tractability certificate to the per-row root.
5664 * The annotation gate is transparent for every evaluator; the probability
5665 * dispatcher reads the certificate back from its extra. */
5666 if (inv_cert != NULL &&
5667 OidIsValid(constants->OID_FUNCTION_ANNOTATE))
5668 result = wrap_in_annotate(constants, result, inv_cert);
5669
5670 /* Joint-width substitution (debug GUC provsql.joint_width, on by
5671 * default). When the Boolean-provenance query forms the *existence*
5672 * of a recognised unsafe UCQ -- a DISTINCT / GROUP-BY that ORs the
5673 * witnesses, the #P-hard case the Dalvi-Suciu dichotomy rules out from
5674 * lifted inference -- wrap the just-built normal provenance so that, at
5675 * execution, the joint-width compiler's certified d-D is used instead
5676 * whenever it applies, and the normal provenance is the fallback on any
5677 * failure (unsupported gate type, width too large, ...). Both give the
5678 * exact same probability; joint-width only makes the #P-hard ones
5679 * tractable. The normal provenance is still built (it is the
5680 * fallback), so this never makes a query fail. */
5681 if (jw_desc != NULL && jw_all_exist) {
5682 /* Möbius precedence (see combine_safe_routes): the safe-UCQ Möbius
5683 * cancellation route -- a guaranteed PTIME exact route for its class -- is
5684 * tried first and, on success, short-circuits past the joint-width
5685 * compiler (which may otherwise grind to its state cap before declining on
5686 * exactly these queries). The joint-width compiler runs only on a Möbius
5687 * decline (correlated inputs, self-joins, unsafe shape), with the normal
5688 * provenance the final fallback, so a recognised query never fails. Both
5689 * give the same probability. */
5690 Expr *mob = provsql_mobius
5691 ? build_mobius_provenance_expr(constants, jw_desc, result)
5692 : NULL;
5693 Expr *jw = provsql_joint_width
5694 ? build_joint_width_provenance_expr(constants, jw_desc, result)
5695 : NULL;
5696 result = combine_safe_routes(constants, mob, jw, result);
5697 } else if (jw_desc != NULL && jw_head_idx != NIL && inv_cert == NULL) {
5698 /* Per-answer (non-Boolean) UCQ, and the inversion-free certifier has
5699 * declined (an inv_cert would be carried on the normal provenance and is
5700 * consumed by the 'inversion-free' method, which the joint-width d-D does
5701 * not provide): same Möbius-precedence dispatch per output group, head
5702 * variables pinned per group, the normal per-answer provenance the final
5703 * fallback. */
5704 Expr *mob = provsql_mobius
5705 ? build_mobius_answer_expr(constants, jw_desc, jw_head_idx,
5706 jw_head_exprs, result)
5707 : NULL;
5708 Expr *jw = provsql_joint_width
5709 ? build_joint_width_answer_expr(constants, jw_desc, jw_head_idx,
5710 jw_head_exprs, result)
5711 : NULL;
5712 result = combine_safe_routes(constants, mob, jw, result);
5713 }
5714
5715 return result;
5716}
5717
5718/* -------------------------------------------------------------------------
5719 * Set-operation & DISTINCT rewriting
5720 * ------------------------------------------------------------------------- */
5721
5722#if PG_VERSION_NUM >= 180000
5723typedef struct {
5724 Index group_rtindex;
5725 List *groupexprs;
5726} resolve_group_rte_ctx;
5727
5728static Node *
5729resolve_group_rte_vars_mutator(Node *node, void *raw_ctx) {
5730 resolve_group_rte_ctx *ctx = (resolve_group_rte_ctx *)raw_ctx;
5731 if (node == NULL)
5732 return NULL;
5733 if (IsA(node, Var)) {
5734 Var *v = (Var *)node;
5735 if (v->varno == ctx->group_rtindex) {
5736 Node *resolved = copyObject(list_nth(ctx->groupexprs, v->varattno - 1));
5737#if PG_VERSION_NUM >= 160000
5738 /* Clear varnullingrels: the group-step nulling bits reference the
5739 * group_rtindex RTE which does not exist in the fresh inner query.
5740 * Leaving them set causes the planner to access simple_rel_array at
5741 * group_rtindex (which has no RelOptInfo), triggering
5742 * "unrecognized RTE kind: 9". */
5743 if (IsA(resolved, Var))
5744 ((Var *)resolved)->varnullingrels = NULL;
5745#endif
5746 return resolved;
5747 }
5748 }
5749 return expression_tree_mutator(node, resolve_group_rte_vars_mutator, raw_ctx);
5750}
5751
5752/**
5753 * @brief Strip PG 18's virtual @c RTE_GROUP entry from @p q in place.
5754 *
5755 * @c parseCheckAggregates() appends an @c RTE_GROUP entry at the end of
5756 * @c q->rtable whenever the query has a @c GROUP @c BY clause; references
5757 * to grouped columns in @c targetList and @c jointree->quals point at that
5758 * synthetic RTE rather than the underlying base tables. ProvSQL's
5759 * rewriters need a flat range-table to do their own index arithmetic, so
5760 * we remove the @c RTE_GROUP and resolve every @c Var(@c group_rtindex,
5761 * @c i) back to its base-table expression before going further.
5762 *
5763 * Idempotent: when @c q->hasGroupRTE is already false, returns without
5764 * doing anything.
5765 */
5766void strip_group_rte_pg18(Query *q) {
5767 resolve_group_rte_ctx grp_ctx;
5768 bool found = false;
5769 ListCell *lc;
5770 Index idx = 1;
5771 int rte_len = 0;
5772
5773 if (!q->hasGroupRTE)
5774 return;
5775
5776 foreach (lc, q->rtable) {
5777 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
5778 if (r->rtekind == RTE_GROUP) {
5779 grp_ctx.group_rtindex = idx;
5780 grp_ctx.groupexprs = r->groupexprs;
5781 found = true;
5782 rte_len = idx - 1;
5783 break;
5784 }
5785 idx++;
5786 }
5787
5788 if (!found)
5789 return;
5790
5791 q->rtable = list_truncate(q->rtable, rte_len);
5792 q->hasGroupRTE = false;
5793
5794 foreach (lc, q->targetList) {
5795 TargetEntry *te = (TargetEntry *) lfirst(lc);
5796 te->expr = (Expr *) resolve_group_rte_vars_mutator(
5797 (Node *) te->expr, &grp_ctx);
5798 }
5799 if (q->jointree && q->jointree->quals)
5800 q->jointree->quals = resolve_group_rte_vars_mutator(
5801 q->jointree->quals, &grp_ctx);
5802 /* HAVING too: when the GROUP BY key is a constant (e.g. GROUP BY 1, or
5803 * any literal grouping expression), PostgreSQL 18 rewrites a matching
5804 * literal on the other side of a HAVING comparison (HAVING count(*) = 1)
5805 * into a grouped Var referencing the RTE_GROUP entry. Left unresolved
5806 * it reaches having_OpExpr_to_provenance_cmp as a bare Var and trips the
5807 * "cannot handle complex HAVING expressions" bail; resolving it back to
5808 * the underlying grouping expression restores the Const the converter
5809 * expects. */
5810 if (q->havingQual)
5811 q->havingQual = resolve_group_rte_vars_mutator(q->havingQual, &grp_ctx);
5812}
5813#endif
5814
5815/* Forward declaration – defined later but needed by rewrite_agg_distinct */
5816static bool provenance_function_walker(Node *node, void *data);
5817
5818/**
5819 * @brief Build the inner GROUP-BY subquery for one @c AGG(DISTINCT key).
5820 *
5821 * Produces:
5822 * @code
5823 * SELECT key_expr, gb_col1, gb_col2, ...
5824 * FROM <same tables as q>
5825 * GROUP BY key_expr, gb_col1, gb_col2, ...
5826 * @endcode
5827 *
5828 * @param q Original query (supplies FROM / WHERE).
5829 * @param key_expr The DISTINCT argument expression.
5830 * @param groupby_tes Non-aggregate target entries that are GROUP BY columns.
5831 * @return Fresh inner @c Query.
5832 */
5833static Query *build_inner_for_distinct_key(Query *q, Expr *key_expr,
5834 List *groupby_tes) {
5835 Query *inner;
5836 List *new_tl = NIL;
5837 List *new_gc = NIL;
5838 ListCell *lc;
5839 int resno = 1, sgref = 1;
5840
5841 inner = copyObject(q);
5842
5843 inner->hasAggs = false;
5844 inner->sortClause = NIL;
5845 inner->limitCount = NULL;
5846 inner->limitOffset = NULL;
5847 inner->distinctClause = NIL;
5848 inner->hasDistinctOn = false;
5849 inner->havingQual = NULL;
5850
5851 /* First column: the DISTINCT key */
5852 {
5853 TargetEntry *kte = makeNode(TargetEntry);
5854 SortGroupClause *sgc = makeNode(SortGroupClause);
5855
5856 kte->expr = copyObject(key_expr);
5857 kte->resno = resno++;
5858 kte->resname = "key";
5859 sgc->tleSortGroupRef = kte->ressortgroupref = sgref++;
5860 get_sort_group_operators(exprType((Node *)kte->expr), true, true, false,
5861 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
5862 new_gc = list_make1(sgc);
5863 new_tl = list_make1(kte);
5864 }
5865
5866 /* Remaining columns: GROUP BY columns from the original query */
5867 foreach (lc, groupby_tes) {
5868 TargetEntry *gyte = copyObject((TargetEntry *)lfirst(lc));
5869 SortGroupClause *sgc = makeNode(SortGroupClause);
5870
5871 gyte->resno = resno++;
5872 gyte->resjunk = false;
5873 sgc->tleSortGroupRef = gyte->ressortgroupref = sgref++;
5874 get_sort_group_operators(exprType((Node *)gyte->expr), true, true, false,
5875 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
5876 new_gc = lappend(new_gc, sgc);
5877 new_tl = lappend(new_tl, gyte);
5878 }
5879
5880 inner->targetList = new_tl;
5881 inner->groupClause = new_gc;
5882 return inner;
5883}
5884
5885/**
5886 * @brief Wrap @p inner in an outer query that applies the original aggregate.
5887 *
5888 * Produces:
5889 * @code
5890 * SELECT AGG(key_col), gb_col1, gb_col2, ...
5891 * FROM inner
5892 * GROUP BY gb_col1, gb_col2, ...
5893 * @endcode
5894 * The DISTINCT flag is cleared; @p inner provides exactly one row per
5895 * (key, group-by) combination, so the plain aggregate gives the right count.
5896 *
5897 * @param orig_agg_te Original @c TargetEntry containing @c AGG(DISTINCT key).
5898 * @param inner Inner query from @c build_inner_for_distinct_key.
5899 * @param n_gb Number of GROUP BY columns (trailing entries in @p inner).
5900 * @param constants Extension OID cache.
5901 * @return Fresh outer @c Query.
5902 */
5903static Query *build_outer_for_distinct_key(TargetEntry *orig_agg_te,
5904 Query *inner, int n_gb,
5905 const constants_t *constants) {
5906 Query *outer = makeNode(Query);
5907 RangeTblEntry *rte = makeNode(RangeTblEntry);
5908 Alias *alias = makeNode(Alias), *eref = makeNode(Alias);
5909 RangeTblRef *rtr = makeNode(RangeTblRef);
5910 FromExpr *jt = makeNode(FromExpr);
5911 List *new_tl = NIL, *new_gc = NIL;
5912 ListCell *lc;
5913 int resno = 1, sgref = 1;
5914 int inner_len = list_length(inner->targetList);
5915 int attno;
5916
5917 /* Wrap inner in a subquery RTE */
5918 alias->aliasname = eref->aliasname = "d";
5919 eref->colnames = NIL;
5920 foreach (lc, inner->targetList) {
5921 TargetEntry *te = lfirst(lc);
5922 eref->colnames = lappend(eref->colnames,
5923 makeString(te->resname ? pstrdup(te->resname) : ""));
5924 }
5925 rte->alias = alias;
5926 rte->eref = eref;
5927 rte->rtekind = RTE_SUBQUERY;
5928 rte->subquery = inner;
5929 rte->inFromCl = true;
5930#if PG_VERSION_NUM < 160000
5931 rte->requiredPerms = ACL_SELECT;
5932#endif
5933
5934 rtr->rtindex = 1;
5935 jt->fromlist = list_make1(rtr);
5936
5937 outer->commandType = CMD_SELECT;
5938 outer->canSetTag = true;
5939 outer->rtable = list_make1(rte);
5940 outer->jointree = jt;
5941 outer->hasAggs = true;
5942
5943 /* First output column: the aggregate over the key (col 1 of inner) */
5944 {
5945 TargetEntry *agg_te = copyObject(orig_agg_te);
5946 Aggref *ar = (Aggref *)agg_te->expr;
5947 Var *key_var = makeNode(Var);
5948 TargetEntry *arg_te = makeNode(TargetEntry);
5949
5950 key_var->varno = 1;
5951 key_var->varattno = 1; /* key is first column of inner */
5952 key_var->vartype = linitial_oid(ar->aggargtypes);
5953 key_var->varcollid = exprCollation((Node *)((TargetEntry *)linitial(ar->args))->expr);
5954 key_var->vartypmod = -1;
5955 key_var->location = -1;
5956 arg_te->resno = 1;
5957 arg_te->expr = (Expr *)key_var;
5958
5959 ar->args = list_make1(arg_te);
5960 ar->aggdistinct = NIL;
5961 agg_te->resno = resno++;
5962 new_tl = list_make1(agg_te);
5963 }
5964
5965 /* Remaining output columns: GROUP BY cols (trailing cols of inner) */
5966 for (attno = inner_len - n_gb + 1; attno <= inner_len; attno++) {
5967 TargetEntry *inner_te = list_nth(inner->targetList, attno - 1);
5968 Var *gb_var = makeNode(Var);
5969 TargetEntry *gb_te = makeNode(TargetEntry);
5970 SortGroupClause *sgc = makeNode(SortGroupClause);
5971
5972 gb_var->varno = 1;
5973 gb_var->varattno = attno;
5974 gb_var->vartype = exprType((Node *)inner_te->expr);
5975 gb_var->varcollid = exprCollation((Node *)inner_te->expr);
5976 gb_var->vartypmod = -1;
5977 gb_var->location = -1;
5978
5979 gb_te->resno = resno++;
5980 gb_te->expr = (Expr *)gb_var;
5981 gb_te->resname = inner_te->resname;
5982
5983 sgc->tleSortGroupRef = gb_te->ressortgroupref = sgref++;
5984 sgc->nulls_first = false;
5985 get_sort_group_operators(gb_var->vartype, true, true, false,
5986 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
5987 new_gc = lappend(new_gc, sgc);
5988 new_tl = lappend(new_tl, gb_te);
5989 }
5990
5991 outer->targetList = new_tl;
5992 outer->groupClause = new_gc;
5993 return outer;
5994}
5995
5996/** @brief Collector for @c AGG(DISTINCT) Aggrefs inside a HAVING clause. */
5997typedef struct having_distinct_ctx {
5998 List *aggs; ///< Aggref* nodes carrying @c aggdistinct, in traversal order
6000
6001/**
6002 * @brief Walker that collects @c AGG(DISTINCT) Aggrefs from an expression.
6003 *
6004 * Does not descend into an @c Aggref's own arguments, so the traversal order
6005 * matches @c replace_having_distinct_mutator below (both stop at every
6006 * @c Aggref), keeping the per-aggregate outer-subquery indices aligned.
6007 */
6008static bool collect_having_distinct_walker(Node *node, void *ctx) {
6009 if (node == NULL)
6010 return false;
6011 if (IsA(node, Aggref)) {
6012 Aggref *ar = (Aggref *) node;
6013 if (list_length(ar->aggdistinct) > 0)
6014 ((having_distinct_ctx *) ctx)->aggs =
6015 lappend(((having_distinct_ctx *) ctx)->aggs, ar);
6016 return false; /* don't recurse into aggregate arguments */
6017 }
6018 return expression_tree_walker(node, collect_having_distinct_walker, ctx);
6019}
6020
6021/** @brief Context for @c replace_having_distinct_mutator: next outer RT index. */
6025
6026/**
6027 * @brief Mutator that replaces each @c AGG(DISTINCT) Aggref in a HAVING
6028 * clause with @c Var(next_rtindex++, 1) -- the deduped count column of
6029 * its outer subquery (built in the same order by
6030 * @c rewrite_agg_distinct). The @c Var is typed as the aggregate's
6031 * result so the surrounding comparison is intercepted by the HAVING
6032 * provenance path exactly as a non-DISTINCT count would be.
6033 */
6034static Node *replace_having_distinct_mutator(Node *node, void *ctx) {
6035 if (node == NULL)
6036 return NULL;
6037 if (IsA(node, Aggref)) {
6038 Aggref *ar = (Aggref *) node;
6039 if (list_length(ar->aggdistinct) > 0) {
6041 Var *v = makeNode(Var);
6042 v->varno = c->next_rtindex++;
6043 v->varattno = 1; /* agg result is col 1 of each outer */
6044 v->vartype = ar->aggtype;
6045 v->vartypmod = -1;
6046 v->varcollid = ar->aggcollid;
6047 v->location = -1;
6048 return (Node *) v;
6049 }
6050 return node; /* non-DISTINCT aggregate: leave for the normal HAVING path */
6051 }
6052 return expression_tree_mutator(node, replace_having_distinct_mutator, ctx);
6053}
6054
6055/**
6056 * @brief Rewrite every @c AGG(DISTINCT key) in @p q using independent subqueries.
6057 *
6058 * For a single DISTINCT aggregate, produces a subquery:
6059 * @code
6060 * SELECT AGG(key), gb... FROM (SELECT key, gb... FROM t GROUP BY key, gb...) GROUP BY gb...
6061 * @endcode
6062 * For multiple DISTINCT aggregates with different keys, produces an JOIN
6063 * of one such subquery per aggregate, joined on the GROUP BY columns.
6064 * Non-DISTINCT aggregates are left untouched.
6065 *
6066 * @c AGG(DISTINCT) aggregates appearing in the @c HAVING clause are handled
6067 * the same way (one deduped outer per aggregate) and the @c HAVING Aggref is
6068 * replaced by a @c Var to its outer's count column, so the comparison's
6069 * provenance is built over the per-distinct-value rows rather than the raw
6070 * tuples.
6071 *
6072 * @param q Query to inspect and possibly rewrite.
6073 * @param constants Extension OID cache.
6074 * @return Rewritten query, or @c NULL if no @c AGG(DISTINCT) was found.
6075 */
6076static Query *rewrite_agg_distinct(Query *q, const constants_t *constants) {
6077 List *distinct_agg_tes = NIL;
6078 List *groupby_tes = NIL;
6079 ListCell *lc;
6080 having_distinct_ctx hctx = { NIL };
6081
6082#if PG_VERSION_NUM >= 180000
6083 /* In PostgreSQL 18, parseCheckAggregates() injects a virtual RTE_GROUP
6084 * entry at the END of the range table. GROUP BY column Vars in the
6085 * SELECT list point to this entry (varno == group_rtindex) instead of
6086 * the underlying base-table RTE.
6087 *
6088 * Strip that entry now, before we do any index arithmetic (fll, rtr->rtindex,
6089 * agg_idx) or copy q->targetList into groupby_tes. Once removed:
6090 * - q->rtable contains only real RTEs, so appending outer-subquery RTEs
6091 * lands at the correct indices.
6092 * - groupby_tes will carry resolved (base-table) Var expressions, so
6093 * the WHERE equalities and the inner-query target list are correct.
6094 * We also resolve the Var(group_rtindex) refs in q's own targetList and
6095 * WHERE clause so the final query doesn't reference the stripped entry. */
6097#endif
6098
6099 /* Extract AGG(DISTINCT) and GROUP BY targets from the target list.
6100 * Regular AGG() aggregations and expressions containing provenance()
6101 * are left untouched. */
6102 foreach (lc, q->targetList) {
6103 TargetEntry *te = lfirst(lc);
6104 if (IsA(te->expr, Aggref)) {
6105 Aggref *ar = (Aggref *)te->expr;
6106 if (list_length(ar->aggdistinct) > 0)
6107 distinct_agg_tes = lappend(distinct_agg_tes, te);
6108 } else if (provenance_function_walker((Node *)te->expr,
6109 (void *)constants)) {
6110 /* Expression contains provenance() – skip it, it will be
6111 * handled later by the provenance rewriter */
6112 } else {
6113 /* Non-aggregate column – treat as GROUP BY key */
6114 TargetEntry *te_copy = copyObject(te);
6115 te_copy->resjunk = false;
6116 groupby_tes = lappend(groupby_tes, te_copy);
6117 }
6118 }
6119
6120 /* Also collect AGG(DISTINCT) aggregates from the HAVING clause; they are
6121 * not TargetEntries, so they get their own list and a Var-replacement
6122 * mutator below. */
6123 if (q->havingQual != NULL)
6124 collect_having_distinct_walker(q->havingQual, &hctx);
6125
6126 if (distinct_agg_tes == NIL && hctx.aggs == NIL)
6127 return NULL;
6128
6129 {
6130 int n_having = list_length(hctx.aggs);
6131 int n_aggs = list_length(distinct_agg_tes) + n_having;
6132 int n_gb = list_length(groupby_tes);
6133 List *outer_queries = NIL;
6134
6135 /* -----------------------------------------------------------------------
6136 * For each DISTINCT aggregate, build:
6137 * inner_i: SELECT key_i, gb... FROM original... GROUP BY key_i, gb...
6138 * outer_i: SELECT AGG(key_i) ASS agg_i, gb... FROM inner_i GROUP BY gb...
6139 *
6140 * Then produce a final query:
6141 * SELECT gb..., agg_0, ..., agg_{N-1}
6142 * FROM original... JOIN outer_0 ON gb... = gb... [JOIN ...]
6143 * keeping the same order for the output columns.
6144 *
6145 * Column order in the final target list follows q->targetList:
6146 * - DISTINCT agg i → Var(n+i, 1) (agg col of outer_i)
6147 * ----------------------------------------------------------------------- */
6148
6149 /* Build one inner + one outer query per DISTINCT aggregate */
6150 foreach (lc, distinct_agg_tes) {
6151 TargetEntry *agg_te = lfirst(lc);
6152 Aggref *ar = (Aggref *)agg_te->expr;
6153 if(list_length(ar->args) != 1)
6154 provsql_error("AGG(DISTINCT) with more than one argument is not supported");
6155 else {
6156 Expr *key_expr = (Expr *)((TargetEntry *)linitial(ar->args))->expr;
6157 Query *inner = build_inner_for_distinct_key(q, key_expr, groupby_tes);
6158 Query *outer = build_outer_for_distinct_key(agg_te, inner, n_gb, constants);
6159 outer_queries = lappend(outer_queries, outer);
6160 }
6161 }
6162
6163 /* Build one inner + one outer query per HAVING-clause DISTINCT aggregate,
6164 * appended after the target-list ones so their RT indices are the last
6165 * n_having entries of the final from-list (matched by the mutator below). */
6166 foreach (lc, hctx.aggs) {
6167 Aggref *ar = lfirst(lc);
6168 if(list_length(ar->args) != 1)
6169 provsql_error("AGG(DISTINCT) with more than one argument is not supported");
6170 else {
6171 TargetEntry *syn = makeNode(TargetEntry);
6172 Expr *key_expr = (Expr *)((TargetEntry *)linitial(ar->args))->expr;
6173 Query *inner = build_inner_for_distinct_key(q, key_expr, groupby_tes);
6174 Query *outer;
6175 syn->expr = (Expr *) copyObject(ar);
6176 syn->resno = 1;
6177 outer = build_outer_for_distinct_key(syn, inner, n_gb, constants);
6178 outer_queries = lappend(outer_queries, outer);
6179 }
6180 }
6181
6182 {
6183 /* One subquery RTE per outer query. They are appended to q->rtable, so
6184 * their range-table indices start at the current rtable length -- which is
6185 * NOT the from-list length when the FROM contains a JoinExpr (an outer
6186 * join is one from-list item but several rtable slots). Index everything
6187 * off rtable_base, or the RangeTblRefs / Vars below would point at the base
6188 * relations the join spans (e.g. "rel 2 already exists" for the join's
6189 * right arm). */
6190 int rtable_base = list_length(q->rtable);
6191 int i = 0;
6192 foreach (lc, outer_queries) {
6193 Query *oq = lfirst(lc);
6194 RangeTblEntry *rte = makeNode(RangeTblEntry);
6195 Alias *alias = makeNode(Alias), *eref = makeNode(Alias);
6196 ListCell *lc2;
6197 char buf[16];
6198
6199 snprintf(buf, sizeof(buf), "d%d", i + 1);
6200 alias->aliasname = eref->aliasname = pstrdup(buf);
6201 eref->colnames = NIL;
6202 foreach (lc2, oq->targetList) {
6203 TargetEntry *te = lfirst(lc2);
6204 eref->colnames = lappend(eref->colnames,
6205 makeString(te->resname ? pstrdup(te->resname) : ""));
6206 }
6207 rte->alias = alias;
6208 rte->eref = eref;
6209 rte->rtekind = RTE_SUBQUERY;
6210 rte->subquery = oq;
6211 rte->inFromCl = true;
6212#if PG_VERSION_NUM < 160000
6213 rte->requiredPerms = ACL_SELECT;
6214#endif
6215 q->rtable = lappend(q->rtable, rte);
6216 i++;
6217 }
6218
6219 /* Build FROM list and WHERE conditions for the implicit join.
6220 * Use a simple FROM original..., outer_i, ... WHERE original.gb_j = outer_i.gb_j */
6221 {
6222 FromExpr *jt = q->jointree;
6223 List *from_list = jt->fromlist;
6224 List *where_args = NIL;
6225
6226 for (i = rtable_base + 1; i <= rtable_base + n_aggs; i++) {
6227 RangeTblRef *rtr = makeNode(RangeTblRef);
6228 ListCell *lc2;
6229 unsigned j=0;
6230
6231 rtr->rtindex = i;
6232 from_list = lappend(from_list, rtr);
6233
6234 /* outer_0.gb_j = outer_i.gb_j for each GROUP BY column j */
6235 foreach(lc2, groupby_tes) {
6236 TargetEntry *gb_te = lfirst(lc2);
6237 int gb_attno = ++j + 1; /* col 1 = agg, cols 2+ = GB */
6238 Oid ytype = exprType((Node *)gb_te->expr);
6239 Oid opno = find_equality_operator(ytype, ytype);
6240 Operator opInfo = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
6241 Form_pg_operator opform;
6242 OpExpr *oe = makeNode(OpExpr);
6243 Expr *le = copyObject(gb_te->expr);
6244 Var *rv = makeNode(Var);
6245 Oid collation=exprCollation((Node*) le);
6246
6247 if (!HeapTupleIsValid(opInfo))
6248 provsql_error("could not find equality operator for type %u",
6249 ytype);
6250 opform = (Form_pg_operator)GETSTRUCT(opInfo);
6251
6252 oe->opno = opno;
6253 oe->opfuncid = opform->oprcode;
6254 oe->opresulttype = opform->oprresult;
6255 oe->opcollid = InvalidOid;
6256 oe->inputcollid = collation;
6257 oe->location = -1;
6258 ReleaseSysCache(opInfo);
6259
6260 rv->varno = i; rv->varattno = gb_attno;
6261 rv->vartype = ytype; rv->varcollid = collation;
6262 rv->vartypmod = -1; rv->location = -1;
6263
6264 oe->args = list_make2(le, rv);
6265 where_args = lappend(where_args, oe);
6266 }
6267 }
6268
6269 if (list_length(where_args) == 0) {
6270 jt->quals = NULL;
6271 } else if (list_length(where_args) == 1) {
6272 jt->quals = linitial(where_args);
6273 } else {
6274 BoolExpr *be = makeNode(BoolExpr);
6275 be->boolop = AND_EXPR;
6276 be->args = where_args;
6277 be->location = -1;
6278 jt->quals = (Node *)be;
6279 }
6280 }
6281
6282 /* Build final target list in original column order.
6283 * DISTINCT agg i → Var(i+1, 1); GROUP BY col j → Var(1, 2+j). */
6284 {
6285 int agg_idx = rtable_base + 1;
6286 ListCell *lc2;
6287
6288 foreach (lc2, q->targetList) {
6289 TargetEntry *te = lfirst(lc2);
6290
6291 if (IsA(te->expr, Aggref) &&
6292 ((Aggref *)te->expr)->aggdistinct != NIL) {
6293 Var *v = makeNode(Var);
6294 v->varno = agg_idx++; /* outer_{agg_idx} RTE */
6295 v->varattno = 1; /* agg result is col 1 of each outer */
6296 v->vartypmod = -1;
6297 v->location = -1;
6298 te->expr = (Expr*)v;
6299 }
6300 }
6301 }
6302
6303 /* Replace HAVING-clause DISTINCT aggregates with Vars to their outer
6304 * subqueries -- the last n_having entries of the from-list, in the same
6305 * order collect_having_distinct_walker visited them. */
6306 if (n_having > 0) {
6308 /* HAVING outers are appended after the target-list ones. */
6309 hrc.next_rtindex = rtable_base + (n_aggs - n_having) + 1;
6310 q->havingQual = replace_having_distinct_mutator(q->havingQual, &hrc);
6311 }
6312
6313 return q;
6314 }
6315 }
6316}
6317
6318
6319/* -------------------------------------------------------------------------
6320 * Aggregation replacement mutator
6321 * ------------------------------------------------------------------------- */
6322
6323/** @brief Context for the @c aggregation_mutator tree walker. */
6325 List *prov_atts; ///< List of provenance Var nodes
6326 semiring_operation op; ///< Semiring operation for combining tokens
6327 const constants_t *constants; ///< Extension OID cache
6328 bool is_scalar; ///< Aggregation has no GROUP BY (single always-present row)
6330
6331/**
6332 * @brief Tree-mutator that replaces Aggrefs with provenance-aware aggregates.
6333 * @param node Current expression tree node.
6334 * @param ctx Pointer to an @c aggregation_mutator_context (prov_atts,
6335 * op, and constants).
6336 * @return Possibly modified node.
6337 */
6338static Node *aggregation_mutator(Node *node, void *ctx) {
6340 if (node == NULL)
6341 return NULL;
6342
6343 if (IsA(node, Aggref)) {
6344 Aggref *ar_v = (Aggref *)node;
6345 return (Node *)make_aggregation_expression(context->constants, ar_v,
6346 context->prov_atts, context->op,
6347 context->is_scalar);
6348 }
6349
6350 return expression_tree_mutator(node, aggregation_mutator, ctx);
6351}
6352
6353/**
6354 * @brief Wrap a @c provenance_aggregate FuncExpr with a cast to the
6355 * original aggregate return type.
6356 *
6357 * @param prov_agg The provenance_aggregate FuncExpr to wrap.
6358 * @param constants Extension OID cache.
6359 * @return Cast FuncExpr wrapping @p prov_agg.
6360 */
6361static Node *wrap_agg_token_with_cast(FuncExpr *prov_agg,
6362 const constants_t *constants) {
6363 Const *typ_const = (Const *)lsecond(prov_agg->args);
6364 Oid target_type = DatumGetObjectId(typ_const->constvalue);
6365 CoercionPathType pathtype;
6366 Oid castfuncid;
6367
6368 pathtype = find_coercion_pathway(target_type,
6369 constants->OID_TYPE_AGG_TOKEN,
6370 COERCION_EXPLICIT, &castfuncid);
6371 if (pathtype == COERCION_PATH_FUNC && OidIsValid(castfuncid)) {
6372 FuncExpr *cast = makeNode(FuncExpr);
6373 cast->funcid = castfuncid;
6374 cast->funcresulttype = target_type;
6375 cast->funcretset = false;
6376 cast->funcvariadic = false;
6377 cast->funcformat = COERCE_IMPLICIT_CAST;
6378 cast->args = list_make1(prov_agg);
6379 cast->location = -1;
6380 return (Node *)cast;
6381 }
6382
6383 provsql_error("no cast from agg_token to %s for arithmetic on aggregate",
6384 format_type_be(target_type));
6385 return (Node *)prov_agg; /* unreachable */
6386}
6387
6388/**
6389 * @brief Wrap an @c agg_token expression in a cast to @p target_type.
6390 *
6391 * Companion to @c wrap_agg_token_with_cast for @c agg_token values that are
6392 * not a bare @c provenance_aggregate call (e.g. the result of @c agg_token
6393 * arithmetic): the original aggregate type is not recoverable from the node,
6394 * so we cast to the type the consuming context requires.
6395 */
6396static Node *cast_agg_token_to_type(Node *arg, Oid target_type,
6397 const constants_t *constants) {
6398 CoercionPathType pathtype;
6399 Oid castfuncid;
6400
6401 pathtype = find_coercion_pathway(target_type, constants->OID_TYPE_AGG_TOKEN,
6402 COERCION_EXPLICIT, &castfuncid);
6403 if (pathtype == COERCION_PATH_FUNC && OidIsValid(castfuncid)) {
6404 FuncExpr *cast = makeNode(FuncExpr);
6405 cast->funcid = castfuncid;
6406 cast->funcresulttype = target_type;
6407 cast->funcretset = false;
6408 cast->funcvariadic = false;
6409 cast->funcformat = COERCE_IMPLICIT_CAST;
6410 cast->args = list_make1(arg);
6411 cast->location = -1;
6412 return (Node *)cast;
6413 }
6414
6415 provsql_error("no cast from agg_token to %s for arithmetic on aggregate",
6416 format_type_be(target_type));
6417 return arg; /* unreachable */
6418}
6419
6420/**
6421 * @brief Cast @c provenance_aggregate arguments of an operator or
6422 * function when the formal parameter type requires it.
6423 *
6424 * For each argument in @p args that is a @c provenance_aggregate call,
6425 * check the corresponding formal parameter type of the parent function
6426 * @p parent_funcid. If the formal type is polymorphic or @c agg_token
6427 * itself, the argument is left alone. Otherwise a cast to the original
6428 * aggregate return type is inserted.
6429 *
6430 * @param args Argument list to inspect (modified in place).
6431 * @param parent_funcid OID of the parent function / operator implementor.
6432 * @param constants Extension OID cache.
6433 */
6434static void maybe_cast_agg_token_args(List *args, Oid parent_funcid,
6435 const constants_t *constants) {
6436 HeapTuple tp;
6437 Form_pg_proc procForm;
6438 ListCell *lc;
6439 int i;
6440
6441 tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(parent_funcid));
6442 if (!HeapTupleIsValid(tp))
6443 return;
6444 procForm = (Form_pg_proc) GETSTRUCT(tp);
6445
6446 i = 0;
6447 foreach(lc, args) {
6448 Node *arg = lfirst(lc);
6449
6450 /* Any agg_token-typed argument (a bare provenance_aggregate, OR the
6451 * result of agg_token arithmetic produced by try_swap_agg_arith) must
6452 * be cast back to a scalar when the consuming function/operator does not
6453 * itself accept agg_token: this is the boundary where an agg_token
6454 * "bubbling up" through arithmetic meets a scalar context (e.g. ROUND,
6455 * ORDER BY) and its provenance can no longer be carried. A bare
6456 * provenance_aggregate is cast to its own aggregate type; a swapped
6457 * arithmetic result is cast to whatever the parent expects. */
6458 if (i < procForm->pronargs && exprType(arg) == constants->OID_TYPE_AGG_TOKEN) {
6459 Oid formal_type = procForm->proargtypes.values[i];
6460
6461 if (formal_type != constants->OID_TYPE_AGG_TOKEN &&
6462 !IsPolymorphicType(formal_type)) {
6463 if (IsA(arg, FuncExpr) &&
6464 ((FuncExpr *)arg)->funcid == constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
6465 lfirst(lc) = wrap_agg_token_with_cast((FuncExpr *)arg, constants);
6466 else
6467 lfirst(lc) = cast_agg_token_to_type(arg, formal_type, constants);
6468 }
6469 }
6470 i++;
6471 }
6472
6473 ReleaseSysCache(tp);
6474}
6475
6476/**
6477 * @brief Peel implicit/explicit cast FuncExprs and RelabelTypes that wrap a
6478 * single argument, returning the underlying expression.
6479 *
6480 * Used to see through the coercions the parser inserts around an aggregate
6481 * (e.g. the @c int8->numeric cast in @c count(*)/2.0) so the underlying
6482 * @c agg_token / @c provenance_aggregate can be recognised.
6483 */
6484static Node *peel_agg_casts(Node *n) {
6485 for (;;) {
6486 if (n != NULL && IsA(n, FuncExpr)) {
6487 FuncExpr *fe = (FuncExpr *)n;
6488 if ((fe->funcformat == COERCE_IMPLICIT_CAST ||
6489 fe->funcformat == COERCE_EXPLICIT_CAST) &&
6490 list_length(fe->args) == 1) {
6491 n = (Node *)linitial(fe->args);
6492 continue;
6493 }
6494 } else if (n != NULL && IsA(n, RelabelType)) {
6495 n = (Node *)((RelabelType *)n)->arg;
6496 continue;
6497 }
6498 return n;
6499 }
6500}
6501
6502/**
6503 * @brief Rebuild an arithmetic operator over an aggregate so the result
6504 * stays an @c agg_token (provenance preserved).
6505 *
6506 * When an arithmetic operator (@c + @c - @c * @c /, or prefix unary @c -)
6507 * has an @c agg_token operand (after peeling the parser's coercions), the
6508 * default rewriting would cast that @c agg_token to its scalar aggregate
6509 * type, silently dropping provenance. Instead, we re-resolve the operator
6510 * against the @c agg_token operand via @c make_op, which selects the native
6511 * @c agg_token arithmetic operators -- the arithmetic is then recorded
6512 * symbolically as a @c gate_arith over the operand provenance, exactly like
6513 * arithmetic on @c random_variable. Returns the rebuilt @c agg_token
6514 * expression, or @c NULL if @p op is not arithmetic over an aggregate.
6515 */
6516static Node *try_swap_agg_arith(OpExpr *op, const constants_t *constants) {
6517 char *opname;
6518 bool is_arith;
6519 int nargs = list_length(op->args);
6520 Node *l, *r, *lp, *rp;
6521 ParseState *pstate;
6522 Expr *newop;
6523
6524 if (nargs < 1 || nargs > 2)
6525 return NULL;
6526 opname = get_opname(op->opno);
6527 if (opname == NULL)
6528 return NULL;
6529 is_arith = strcmp(opname, "+") == 0 || strcmp(opname, "-") == 0 ||
6530 strcmp(opname, "*") == 0 || strcmp(opname, "/") == 0;
6531 if (!is_arith) {
6532 pfree(opname);
6533 return NULL;
6534 }
6535
6536 if (nargs == 2) {
6537 l = (Node *)linitial(op->args);
6538 r = (Node *)lsecond(op->args);
6539 } else { /* prefix unary minus */
6540 l = NULL;
6541 r = (Node *)linitial(op->args);
6542 }
6543 lp = l ? peel_agg_casts(l) : NULL;
6544 rp = peel_agg_casts(r);
6545
6546 if (!((lp && exprType(lp) == constants->OID_TYPE_AGG_TOKEN) ||
6547 exprType(rp) == constants->OID_TYPE_AGG_TOKEN)) {
6548 pfree(opname);
6549 return NULL;
6550 }
6551
6552 /* Feed make_op the peeled (uncast) operand wherever it exposes an
6553 * agg_token, so resolution picks the agg_token operator; keep the
6554 * original node for the non-agg operand to preserve its own coercions. */
6555 if (lp && exprType(lp) == constants->OID_TYPE_AGG_TOKEN)
6556 l = lp;
6557 if (exprType(rp) == constants->OID_TYPE_AGG_TOKEN)
6558 r = rp;
6559
6560 pstate = make_parsestate(NULL);
6561 newop = make_op(pstate, list_make1(makeString(opname)), l, r, NULL, -1);
6562 free_parsestate(pstate);
6563 pfree(opname);
6564 return (Node *)newop;
6565}
6566
6567/**
6568 * @brief Tree-mutator that casts @c provenance_aggregate results back
6569 * to the original aggregate return type where needed.
6570 *
6571 * After the aggregation mutator replaces Aggrefs with
6572 * @c provenance_aggregate calls (returning @c agg_token), this
6573 * post-processing step inserts casts where the surrounding expression
6574 * expects a different type (e.g. a non-arithmetic function over an
6575 * aggregate). Arithmetic over an aggregate is instead kept as an
6576 * @c agg_token via @c try_swap_agg_arith so its provenance survives;
6577 * arguments to functions that accept @c agg_token or polymorphic types
6578 * are left alone.
6579 *
6580 * @param node Current expression tree node.
6581 * @param ctx Pointer to the @c constants_t OID cache.
6582 * @return Possibly modified node.
6583 */
6584static Node *cast_agg_token_mutator(Node *node, void *ctx) {
6585 const constants_t *constants = (const constants_t *)ctx;
6586 Node *result;
6587
6588 if (node == NULL)
6589 return NULL;
6590
6591 /* Recurse first, then fix up arguments at this level. */
6592 result = expression_tree_mutator(node, cast_agg_token_mutator, ctx);
6593
6594 if (IsA(result, OpExpr)) {
6595 OpExpr *op = (OpExpr *)result;
6596 Node *swapped = try_swap_agg_arith(op, constants);
6597 if (swapped != NULL)
6598 return swapped;
6599 set_opfuncid(op);
6600 maybe_cast_agg_token_args(op->args, op->opfuncid, constants);
6601 } else if (IsA(result, FuncExpr)) {
6602 FuncExpr *fe = (FuncExpr *)result;
6603 if (fe->funcid != constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
6604 maybe_cast_agg_token_args(fe->args, fe->funcid, constants);
6605 } else if (IsA(result, CaseExpr)) {
6606 /* A searched CASE whose branches became agg_token under a
6607 * non-agg_token CASE type. The agg_case lowering replaces the whole
6608 * CASE when it applies; when it does not (a schema whose upgrade path
6609 * predates agg_case, or a shape build_agg_case declines), the branches
6610 * MUST be cast back to the CASE's result type: the executor would
6611 * otherwise reinterpret the fixed-length agg_token datum as a value of
6612 * the CASE type -- for a varlena type such as numeric that reads a
6613 * garbage length header out of the token's UUID text and crashes (or
6614 * silently corrupts the materialised tuple). */
6615 CaseExpr *ce = (CaseExpr *)result;
6616 if (ce->casetype != constants->OID_TYPE_AGG_TOKEN) {
6617 ListCell *lc;
6618 foreach (lc, ce->args) {
6619 CaseWhen *cw = (CaseWhen *)lfirst(lc);
6620 if (exprType((Node *)cw->result) == constants->OID_TYPE_AGG_TOKEN)
6621 cw->result = (Expr *)cast_agg_token_to_type(
6622 (Node *)cw->result, ce->casetype, constants);
6623 }
6624 if (ce->defresult != NULL &&
6625 exprType((Node *)ce->defresult) == constants->OID_TYPE_AGG_TOKEN)
6626 ce->defresult = (Expr *)cast_agg_token_to_type(
6627 (Node *)ce->defresult, ce->casetype, constants);
6628 }
6629 }
6630
6631 return result;
6632}
6633
6634/**
6635 * @brief Push distributive constant arithmetic into an aggregate's argument.
6636 *
6637 * Rewrites `f(x) <op> c` to `f(x <op'> c)` when @c f distributes over the
6638 * arithmetic, so the result is a clean aggregate over transformed per-row
6639 * values rather than a @c gate_arith wrapping the aggregate. Run before the
6640 * aggregate is lowered, so the provenance machinery then builds an ordinary
6641 * @c gate_agg. Only the cases that distribute without flipping the aggregate
6642 * and without integer-division rounding are handled (the rest fall through to
6643 * the gate_arith path):
6644 * - sum, avg: @c *c (either side), unary @c -; avg also @c +c / @c -c.
6645 * - min, max: @c +c (either side), @c -c (aggregate on the left).
6646 * The transformed argument must keep the original argument's type (so the
6647 * aggregate's function/type stay valid); otherwise no push happens. Returns
6648 * the rewritten @c Aggref, or @c NULL when @p op is not such a case.
6649 */
6650static Node *try_push_into_aggref(OpExpr *op, const constants_t *constants) {
6651 char *opname = get_opname(op->opno);
6652 int nargs = list_length(op->args);
6653 Node *l = NULL, *r = NULL, *aggn = NULL, *cn = NULL, *old_arg, *new_arg = NULL;
6654 Aggref *ar, *newar;
6655 char *aggnm;
6656 bool agg_left = true, plus, minus, times;
6657 bool is_sum, is_avg, is_min, is_max;
6658
6659 if (opname == NULL)
6660 return NULL;
6661 plus = strcmp(opname, "+") == 0;
6662 minus = strcmp(opname, "-") == 0;
6663 times = strcmp(opname, "*") == 0;
6664 if (!(plus || minus || times)) /* division is skipped (rounding) */
6665 return NULL;
6666
6667 if (nargs == 1) { /* prefix unary minus */
6668 if (!minus)
6669 return NULL;
6670 aggn = peel_agg_casts((Node *)linitial(op->args));
6671 } else if (nargs == 2) {
6672 l = peel_agg_casts((Node *)linitial(op->args));
6673 r = peel_agg_casts((Node *)lsecond(op->args));
6674 if (IsA(l, Aggref) && IsA(r, Const)) { aggn = l; cn = r; agg_left = true; }
6675 else if (IsA(r, Aggref) && IsA(l, Const)) { aggn = r; cn = l; agg_left = false; }
6676 else return NULL;
6677 } else
6678 return NULL;
6679
6680 if (!IsA(aggn, Aggref))
6681 return NULL;
6682 ar = (Aggref *)aggn;
6683 /* Need a single ordinary argument: skip count(*) (aggstar), DISTINCT /
6684 * FILTER / ORDER BY aggregates, and RV-returning aggregates. */
6685 if (ar->aggstar || list_length(ar->args) != 1 ||
6686 ar->aggdistinct != NIL || ar->aggfilter != NULL || ar->aggorder != NIL)
6687 return NULL;
6688 if (OidIsValid(constants->OID_TYPE_RANDOM_VARIABLE) &&
6689 ar->aggtype == constants->OID_TYPE_RANDOM_VARIABLE)
6690 return NULL;
6691
6692 aggnm = get_func_name(ar->aggfnoid);
6693 if (aggnm == NULL)
6694 return NULL;
6695 is_sum = strcmp(aggnm, "sum") == 0; is_avg = strcmp(aggnm, "avg") == 0;
6696 is_min = strcmp(aggnm, "min") == 0; is_max = strcmp(aggnm, "max") == 0;
6697 pfree(aggnm);
6698 if (!(is_sum || is_avg || is_min || is_max))
6699 return NULL;
6700
6701 old_arg = (Node *)((TargetEntry *)linitial(ar->args))->expr;
6702
6703 if (nargs == 1) { /* -f(x) */
6704 if (is_sum || is_avg)
6705 new_arg = build_binop("-", NULL, old_arg); /* -x */
6706 } else if (times) { /* f(x)*c, c*f(x) */
6707 if (is_sum || is_avg)
6708 new_arg = agg_left ? build_binop("*", old_arg, cn)
6709 : build_binop("*", cn, old_arg);
6710 } else if (plus) { /* f(x)+c, c+f(x) */
6711 if (is_avg || is_min || is_max)
6712 new_arg = agg_left ? build_binop("+", old_arg, cn)
6713 : build_binop("+", cn, old_arg);
6714 } else /* minus */ {
6715 if (agg_left) { /* f(x)-c */
6716 if (is_avg || is_min || is_max)
6717 new_arg = build_binop("-", old_arg, cn);
6718 } else { /* c-f(x): only avg (no flip) */
6719 if (is_avg)
6720 new_arg = build_binop("-", cn, old_arg);
6721 }
6722 }
6723 if (new_arg == NULL)
6724 return NULL;
6725
6726 /* Keep the aggregate's argument type, so its function/return type stay valid. */
6727 if (exprType(new_arg) != exprType(old_arg))
6728 return NULL;
6729
6730 newar = (Aggref *)copyObject(ar);
6731 ((TargetEntry *)linitial(newar->args))->expr = (Expr *)new_arg;
6732 return (Node *)newar;
6733}
6734
6735/** @brief Tree-mutator applying @c try_push_into_aggref bottom-up. */
6736static Node *push_arith_into_agg_mutator(Node *node, void *ctx) {
6737 if (node == NULL)
6738 return NULL;
6739 node = expression_tree_mutator(node, push_arith_into_agg_mutator, ctx);
6740 if (IsA(node, OpExpr)) {
6741 Node *pushed = try_push_into_aggref((OpExpr *)node, (const constants_t *)ctx);
6742 if (pushed != NULL)
6743 return pushed;
6744 }
6745 return node;
6746}
6747
6748/**
6749 * @brief Replace every @c Aggref in @p q with a provenance-aware aggregate.
6750 *
6751 * Walks the query tree and substitutes each @c Aggref node with the result
6752 * of @c make_aggregation_expression, which wraps the original aggregate in
6753 * the semimodule machinery (@c provenance_semimod + @c array_agg +
6754 * @c provenance_aggregate).
6755 *
6756 * @param constants Extension OID cache.
6757 * @param q Query to mutate in place.
6758 * @param prov_atts List of provenance @c Var nodes.
6759 * @param op Semiring operation for combining tokens across rows.
6760 */
6761static void
6763 Query *q, List *prov_atts,
6764 semiring_operation op) {
6765
6766 /* A scalar aggregation (no GROUP BY / GROUPING SETS) yields a single,
6767 * always-present result row; mark its agg gates so the value-aware evaluators
6768 * treat the empty-input world as real (vs the "no row" of a grouped query). */
6769 bool is_scalar = (q->groupClause == NIL && q->groupingSets == NIL);
6770 aggregation_mutator_context context = {prov_atts, op, constants, is_scalar};
6771 ListCell *lc;
6772
6773 /* First push distributive constant arithmetic into aggregate arguments
6774 * (sum(x)*2 -> sum(2*x)), so those become clean aggregates rather than a
6775 * gate_arith over the aggregate. */
6776 query_tree_mutator(q, push_arith_into_agg_mutator, (void *)constants,
6777 QTW_DONT_COPY_QUERY | QTW_IGNORE_RT_SUBQUERIES);
6778
6779 query_tree_mutator(q, aggregation_mutator, &context,
6780 QTW_DONT_COPY_QUERY | QTW_IGNORE_RT_SUBQUERIES);
6781
6782 /* Post-processing: for target-list entries where a provenance_aggregate
6783 * result is nested inside an outer expression (e.g. SUM(id)+1),
6784 * insert a cast from agg_token back to the original aggregate return
6785 * type. Standalone provenance_aggregate entries are left as agg_token
6786 * so they display as "value (*)". */
6787 foreach(lc, q->targetList) {
6788 TargetEntry *te = (TargetEntry *)lfirst(lc);
6789 if (te->expr == NULL)
6790 continue;
6791 /* Skip standalone provenance_aggregate calls */
6792 if (IsA(te->expr, FuncExpr) &&
6793 ((FuncExpr *)te->expr)->funcid == constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
6794 continue;
6795 te->expr = (Expr *)cast_agg_token_mutator((Node *)te->expr,
6796 (void *)constants);
6797 }
6798}
6799
6800/**
6801 * @brief Append the provenance expression to @p q's target list.
6802 *
6803 * Inserts a new @c TargetEntry named @c provsql immediately before any
6804 * @c resjunk entries (which must remain last) and adjusts the @c resno
6805 * of subsequent entries accordingly.
6806 *
6807 * @param q Query to modify in place.
6808 * @param provenance Expression to add (becomes the @c provsql output column).
6809 */
6810static void add_to_select(Query *q, Expr *provenance) {
6811 TargetEntry *newte = makeNode(TargetEntry);
6812 bool inserted = false;
6813 unsigned resno = 0;
6814
6815 newte->expr = provenance;
6816 newte->resname = (char *)PROVSQL_COLUMN_NAME;
6817
6818 if (IsA(provenance, Var)) {
6819 RangeTblEntry *rte = list_nth(q->rtable, ((Var *)provenance)->varno - 1);
6820 newte->resorigtbl = rte->relid;
6821 newte->resorigcol = ((Var *)provenance)->varattno;
6822 }
6823
6824 /* Make sure to insert before all resjunk Target Entry */
6825 for (ListCell *cell = list_head(q->targetList); cell != NULL;) {
6826 TargetEntry *te = (TargetEntry *)lfirst(cell);
6827
6828 if (!inserted)
6829 ++resno;
6830
6831 if (te->resjunk) {
6832 if (!inserted) {
6833 newte->resno = resno;
6834 q->targetList = list_insert_nth(q->targetList, resno - 1, newte);
6835 cell = list_nth_cell(q->targetList, resno);
6836 te = (TargetEntry *)lfirst(cell);
6837 inserted = true;
6838 }
6839
6840 ++te->resno;
6841 }
6842
6843 cell = my_lnext(q->targetList, cell);
6844 }
6845
6846 if (!inserted) {
6847 newte->resno = resno + 1;
6848 q->targetList = lappend(q->targetList, newte);
6849 }
6850}
6851
6852/* -------------------------------------------------------------------------
6853 * Provenance function replacement
6854 * ------------------------------------------------------------------------- */
6855
6856/** @brief Context for the @c provenance_mutator tree walker. */
6858 Expr *provsql; ///< Provenance expression to substitute for provenance() calls
6859 const constants_t *constants; ///< Extension OID cache
6860 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].
6861 bool inside_aggref; ///< @c true while descending the argument tree of an @c Aggref node.
6863
6864/**
6865 * @brief @c expression_tree_walker predicate: returns @c true on the first
6866 * @c Aggref it encounters.
6867 *
6868 * Used to decide whether the provenance expression about to be substituted
6869 * would inject a nested aggregate when a @c provenance() call lives inside
6870 * another @c Aggref's argument tree.
6871 */
6872static bool
6873expr_contains_aggref_walker(Node *node, void *context) {
6874 if (node == NULL)
6875 return false;
6876 if (IsA(node, Aggref))
6877 return true;
6878 return expression_tree_walker(node, expr_contains_aggref_walker, context);
6879}
6880
6881/**
6882 * @brief Tree-mutator that replaces provenance() calls with the actual provenance expression.
6883 * @param node Current expression tree node.
6884 * @param ctx Pointer to a @c provenance_mutator_context (provenance
6885 * expression and constants).
6886 * @return Possibly modified node.
6887 */
6888static Node *provenance_mutator(Node *node, void *ctx) {
6890 if (node == NULL)
6891 return NULL;
6892
6893 if (IsA(node, Aggref)) {
6894 /* Descend into the Aggref's arguments with @c inside_aggref set so we
6895 * can refuse substitutions that would create a nested same-level
6896 * aggregate. Save and restore the flag so sibling sub-expressions
6897 * outside this Aggref see the original value. */
6898 bool saved = context->inside_aggref;
6899 Node *result;
6900 context->inside_aggref = true;
6901 result = expression_tree_mutator(node, provenance_mutator, ctx);
6902 context->inside_aggref = saved;
6903 return result;
6904 }
6905
6906 if (IsA(node, FuncExpr)) {
6907 FuncExpr *f = (FuncExpr *)node;
6908
6909 if (f->funcid == context->constants->OID_FUNCTION_PROVENANCE) {
6910 if (context->inside_aggref && context->provsql_has_aggref) {
6912 "applying an SQL aggregate on top of a ProvSQL-introduced "
6913 "aggregation is not supported: the inner provenance() would "
6914 "be substituted with an expression containing an aggregate, "
6915 "producing a nested same-level aggregate that PostgreSQL "
6916 "rejects. Evaluate the per-row provenance in a subquery "
6917 "and aggregate the resulting scalar outside, or drop the "
6918 "surrounding aggregate.");
6919 }
6920 return (Node *)copyObject(context->provsql);
6921 }
6922 } else if (IsA(node, RangeTblEntry) || IsA(node, RangeTblFunction)) {
6923 // A provenance() expression in a From (not within a subquery) is
6924 // non-sensical
6925 return node;
6926 }
6927
6928 return expression_tree_mutator(node, provenance_mutator, ctx);
6929}
6930
6931/**
6932 * @brief Replace every explicit @c provenance() call in @p q with @p provsql.
6933 *
6934 * Users can write @c provenance() in the target list or WHERE to refer to the
6935 * provenance token of the current tuple. This mutator substitutes those calls
6936 * with the actual computed provenance expression.
6937 *
6938 * @param constants Extension OID cache.
6939 * @param q Query to mutate in place.
6940 * @param provsql Provenance expression to substitute.
6941 */
6942static void
6944 Query *q, Expr *provsql) {
6946
6947 context.provsql = provsql;
6948 context.constants = constants;
6949 context.provsql_has_aggref =
6950 expr_contains_aggref_walker((Node *) provsql, NULL);
6951 context.inside_aggref = false;
6952
6953 query_tree_mutator(q, provenance_mutator, &context,
6954 QTW_DONT_COPY_QUERY | QTW_IGNORE_RT_SUBQUERIES);
6955}
6956
6957/**
6958 * @brief Convert a SELECT DISTINCT into an equivalent GROUP BY.
6959 *
6960 * ProvSQL cannot handle DISTINCT directly (it would collapse provenance
6961 * tokens that should remain separate). This function moves every entry
6962 * from @p q->distinctClause into @p q->groupClause (skipping any that are
6963 * already there) and clears @p q->distinctClause.
6964 *
6965 * @param q Query to modify in place.
6966 */
6968 // First check which are already in the group by clause
6969 // Should be either none or all as "SELECT DISTINCT a, b ... GROUP BY a"
6970 // is invalid
6971 Bitmapset *already_in_group_by = NULL;
6972 ListCell *lc;
6973 foreach (lc, q->groupClause) {
6974 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc);
6975 already_in_group_by =
6976 bms_add_member(already_in_group_by, sgc->tleSortGroupRef);
6977 }
6978
6979 foreach (lc, q->distinctClause) {
6980 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc);
6981 if (!bms_is_member(sgc->tleSortGroupRef, already_in_group_by)) {
6982 q->groupClause = lappend(q->groupClause, sgc);
6983 }
6984 }
6985
6986 q->distinctClause = NULL;
6987}
6988
6989/**
6990 * @brief Normalise a supported @c SELECT @c DISTINCT into a @c GROUP @c BY.
6991 *
6992 * Wraps @c transform_distinct_into_group_by() with the validity guards
6993 * (DISTINCT ON and DISTINCT-on-aggregate-results stay rejected; a
6994 * DISTINCT not covering the whole target list is inconsistent).
6995 *
6996 * Called twice on the main rewrite path: once *before* @c inline_ctes()
6997 * so the recursive-reachability detectors see the @c GROUP @c BY form
6998 * (a @c SELECT @c DISTINCT region aggregation is provenance-identical
6999 * to its @c GROUP @c BY twin), and once at the late site --
7000 * idempotent, since the first call clears @c distinctClause, so the
7001 * second is a no-op for any query the first already normalised. The
7002 * target list carries only the user's columns at both call sites (the
7003 * provsql output column is spliced later), so the length guard reads
7004 * the same either way.
7005 *
7006 * @param q Query to normalise in place.
7007 */
7009 if (!q->distinctClause)
7010 return;
7011 if (q->hasDistinctOn)
7012 provsql_error("DISTINCT ON not supported");
7013 else if (q->hasAggs)
7014 provsql_error("DISTINCT on aggregate results not supported");
7015 else if (list_length(q->distinctClause) < list_length(q->targetList))
7016 provsql_error("Inconsistent DISTINCT and GROUP BY clauses not "
7017 "supported");
7018 else
7020}
7021
7022/**
7023 * @brief Remove sort/group references that belonged to removed provenance columns.
7024 *
7025 * After @c remove_provenance_attributes_select strips provenance entries from
7026 * the target list, any GROUP BY, ORDER BY, or DISTINCT clause that referenced
7027 * them by @c tleSortGroupRef must be cleaned up.
7028 *
7029 * @param q Query to modify in place.
7030 * @param removed_sortgrouprefs Bitmapset of @c ressortgroupref values to remove.
7031 */
7032static void
7034 const Bitmapset *removed_sortgrouprefs) {
7035 List **lists[3] = {&q->groupClause, &q->distinctClause, &q->sortClause};
7036 int i = 0;
7037
7038 for (i = 0; i < 3; ++i) {
7039 ListCell *cell, *prev;
7040
7041 for (cell = list_head(*lists[i]), prev = NULL; cell != NULL;) {
7042 SortGroupClause *sgc = (SortGroupClause *)lfirst(cell);
7043 if (bms_is_member(sgc->tleSortGroupRef, removed_sortgrouprefs)) {
7044 *lists[i] = my_list_delete_cell(*lists[i], cell, prev);
7045
7046 if (prev) {
7047 cell = my_lnext(*lists[i], prev);
7048 } else {
7049 cell = list_head(*lists[i]);
7050 }
7051 } else {
7052 prev = cell;
7053 cell = my_lnext(*lists[i], cell);
7054 }
7055 }
7056 }
7057}
7058
7059/**
7060 * @brief Strip the provenance column's type info from a set-operation node.
7061 *
7062 * When a provenance column is removed from a UNION/EXCEPT query's target list,
7063 * the matching entries in the @c SetOperationStmt's @c colTypes, @c colTypmods,
7064 * and @c colCollations lists must also be removed.
7065 *
7066 * @param q Query containing @c setOperations.
7067 * @param removed Boolean array (from @c remove_provenance_attributes_select)
7068 * indicating which columns were removed.
7069 */
7070static void remove_provenance_attribute_setoperations(Query *q, bool *removed) {
7071 SetOperationStmt *so = (SetOperationStmt *)q->setOperations;
7072 List **lists[3] = {&so->colTypes, &so->colTypmods, &so->colCollations};
7073 int i = 0;
7074
7075 for (i = 0; i < 3; ++i) {
7076 ListCell *cell, *prev;
7077 int j;
7078
7079 for (cell = list_head(*lists[i]), prev = NULL, j = 0; cell != NULL; ++j) {
7080 if (removed[j]) {
7081 *lists[i] = my_list_delete_cell(*lists[i], cell, prev);
7082
7083 if (prev) {
7084 cell = my_lnext(*lists[i], prev);
7085 } else {
7086 cell = list_head(*lists[i]);
7087 }
7088 } else {
7089 prev = cell;
7090 cell = my_lnext(*lists[i], cell);
7091 }
7092 }
7093 }
7094}
7095
7096/**
7097 * @brief Wrap a non-ALL set operation in an outer GROUP BY query.
7098 *
7099 * UNION / EXCEPT (without ALL) would deduplicate tuples before ProvSQL can
7100 * attach provenance tokens. To avoid this, the set operation is converted to
7101 * UNION ALL / EXCEPT ALL and a new outer query is built that groups the results
7102 * by all non-provenance columns, collecting tokens into an array for the
7103 * @c provenance_plus evaluation.
7104 *
7105 * After this rewrite the recursive call to @c process_query handles the
7106 * now-ALL inner set operation normally.
7107 *
7108 * @param q Query whose @c setOperations is non-ALL (modified to ALL in place).
7109 * @return New outer query that wraps @p q as a subquery RTE.
7110 */
7112 Query *new_query = makeNode(Query);
7113 RangeTblEntry *rte = makeNode(RangeTblEntry);
7114 FromExpr *jointree = makeNode(FromExpr);
7115 RangeTblRef *rtr = makeNode(RangeTblRef);
7116
7117 SetOperationStmt *stmt = (SetOperationStmt *)q->setOperations;
7118
7119 ListCell *lc;
7120 int sortgroupref = 0;
7121
7122 stmt->all = true;
7123 // we might leave sub nodes of the SetOperationsStmt tree with all = false
7124 // but only for recursive trees of operators and only union can be recursive
7125 // https://doxygen.postgresql.org/prepunion_8c_source.html#l00479
7126 // we will set therefore set them later in process_set_operation_union
7127
7128 rte->rtekind = RTE_SUBQUERY;
7129 rte->subquery = q;
7130 rte->eref = copyObject(((RangeTblEntry *)linitial(q->rtable))->eref);
7131 rte->inFromCl = true;
7132#if PG_VERSION_NUM < 160000
7133 // For PG_VERSION_NUM >= 160000, rte->perminfoindex==0 so no need to
7134 // care about permissions
7135 rte->requiredPerms = ACL_SELECT;
7136#endif
7137
7138 rtr->rtindex = 1;
7139 jointree->fromlist = list_make1(rtr);
7140
7141 new_query->commandType = CMD_SELECT;
7142 new_query->canSetTag = true;
7143 new_query->rtable = list_make1(rte);
7144 new_query->jointree = jointree;
7145 new_query->targetList = copyObject(q->targetList);
7146
7147 if (new_query->targetList) {
7148 foreach (lc, new_query->targetList) {
7149 TargetEntry *te = (TargetEntry *)lfirst(lc);
7150 SortGroupClause *sgc = makeNode(SortGroupClause);
7151
7152 sgc->tleSortGroupRef = te->ressortgroupref = ++sortgroupref;
7153
7154 get_sort_group_operators(exprType((Node *)te->expr), false, true, false,
7155 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
7156
7157 new_query->groupClause = lappend(new_query->groupClause, sgc);
7158 }
7159 } else {
7160 GroupingSet *gs = makeNode(GroupingSet);
7161 gs->kind = GROUPING_SET_EMPTY;
7162 gs->content = 0;
7163 gs->location = -1;
7164 new_query->groupingSets = list_make1(gs);
7165 }
7166
7167 return new_query;
7168}
7169
7170/* -------------------------------------------------------------------------
7171 * Detection walkers
7172 * ------------------------------------------------------------------------- */
7173
7174/**
7175 * @brief Tree walker that returns true if any @c provenance() call is found.
7176 *
7177 * Used to detect whether a query explicitly calls @c provenance(), which
7178 * triggers the substitution in @c replace_provenance_function_by_expression.
7179 * @param node Current expression tree node.
7180 * @param data Pointer to @c constants_t (cast from @c void*).
7181 * @return @c true if a @c provenance() call is found anywhere in @p node.
7182 */
7183static bool provenance_function_walker(Node *node, void *data) {
7184 const constants_t *constants = (const constants_t *)data;
7185 if (node == NULL)
7186 return false;
7187
7188 if (IsA(node, FuncExpr)) {
7189 FuncExpr *f = (FuncExpr *)node;
7190
7191 if (f->funcid == constants->OID_FUNCTION_PROVENANCE)
7192 return true;
7193 }
7194
7195 return expression_tree_walker(node, provenance_function_walker, data);
7196}
7197
7198/**
7199 * @brief Check whether a @c provenance() call appears in the GROUP BY list.
7200 *
7201 * When the user writes @c GROUP BY provenance(), ProvSQL must not add its own
7202 * group-by wrapper (the query is already grouping on the token).
7203 *
7204 * @param constants Extension OID cache.
7205 * @param q Query to inspect.
7206 * @return True if any GROUP BY key contains a @c provenance() call.
7207 */
7209 Query *q) {
7210 ListCell *lc;
7211
7212 /* Build the set of ressortgrouprefs that are actually in GROUP BY
7213 * (not ORDER BY or DISTINCT, which also set ressortgroupref). */
7214 Bitmapset *group_refs = NULL;
7215 foreach (lc, q->groupClause) {
7216 SortGroupClause *sgc = (SortGroupClause *)lfirst(lc);
7217 group_refs = bms_add_member(group_refs, sgc->tleSortGroupRef);
7218 }
7219
7220 foreach (lc, q->targetList) {
7221 TargetEntry *te = (TargetEntry *)lfirst(lc);
7222 if (te->ressortgroupref > 0 &&
7223 bms_is_member(te->ressortgroupref, group_refs)) {
7224 if(expression_tree_walker((Node *)te, provenance_function_walker,
7225 (void *)constants)) {
7226 return true;
7227 }
7228
7229#if PG_VERSION_NUM >= 180000
7230 // Starting from PostgreSQL 18, the content of the GROUP BY is not
7231 // in the groupClause but in an associated RTE_GROUP RangeTblEntry
7232 if(IsA(te->expr, Var)) {
7233 Var *v = (Var *) te->expr;
7234 RangeTblEntry *r = (RangeTblEntry *)list_nth(q->rtable, v->varno - 1);
7235 if(r->rtekind == RTE_GROUP)
7236 if(expression_tree_walker((Node *) r->groupexprs, provenance_function_walker,
7237 (void *)constants)) {
7238 return true;
7239 }
7240 }
7241#endif
7242 }
7243 }
7244
7245 return false;
7246}
7247
7248/**
7249 * @brief Tree walker that detects any provenance-bearing relation or provenance() call.
7250 * @param node Current expression tree node.
7251 * @param data Pointer to @c constants_t (cast from @c void*).
7252 * @return @c true if provenance rewriting is needed for this node.
7253 */
7254/**
7255 * @brief Recursive helper for @c has_provenance_walker that detects
7256 * rv_cmp @c OpExpr and @c provenance() @c FuncExpr in
7257 * expression subtrees.
7258 *
7259 * Stops at Query boundaries: @c SubLink subselects (used as
7260 * scalar/array subqueries in expressions) are not rewritten by the
7261 * outer planner_hook pass, so a tracked relation inside one must not
7262 * cause the OUTER query's gate to engage. Only the @c testexpr of a
7263 * SubLink is followed (it lives in the outer's evaluation scope).
7264 */
7265static bool has_rv_or_provenance_call(Node *node, void *data) {
7266 const constants_t *constants = (const constants_t *)data;
7267 if (node == NULL)
7268 return false;
7269
7270 if (IsA(node, OpExpr)) {
7271 OpExpr *op = (OpExpr *)node;
7272 if (rv_cmp_index(constants, op->opfuncid) >= 0)
7273 return true;
7274 }
7275
7276 if (IsA(node, FuncExpr)) {
7277 FuncExpr *f = (FuncExpr *)node;
7278 if (f->funcid == constants->OID_FUNCTION_PROVENANCE)
7279 return true;
7280 }
7281
7282 /* A builtin GREATEST / LEAST over random_variable arguments: engage the
7283 * gate so the planner hook lifts it into a gate_arith MAX / MIN order
7284 * statistic (otherwise it would fall to the meaningless-comparison error). */
7285 if (IsA(node, MinMaxExpr)) {
7286 MinMaxExpr *mm = (MinMaxExpr *)node;
7287 if (mm->minmaxtype == constants->OID_TYPE_RANDOM_VARIABLE)
7288 return true;
7289 }
7290
7291 if (IsA(node, SubLink)) {
7292 SubLink *sl = (SubLink *)node;
7293 return has_rv_or_provenance_call((Node *)sl->testexpr, data);
7294 }
7295
7296 /* Query nodes are opaque here; expression_tree_walker returns false
7297 * on them. Explicit short-circuit just makes the intent obvious. */
7298 if (IsA(node, Query))
7299 return false;
7300
7301 return expression_tree_walker(node, has_rv_or_provenance_call, data);
7302}
7303
7304/**
7305 * @brief Walker (this query level only): true if an @c EXPR_SUBLINK whose body
7306 * is a decorrelatable value subquery over a provenance-tracked base
7307 * relation appears in an expression.
7308 *
7309 * Lets the planner gate engage for a scalar subquery over a tracked relation
7310 * even when the OUTER query has no tracked relation -- decorrelate_scalar_
7311 * sublinks then handles it (wrapping the untracked outer with a certain
7312 * gate_one() provenance and warning that its tuple provenance is lost). The
7313 * shape conditions mirror decorrelate's subselect validation, so engagement
7314 * implies the decorrelation succeeds (no engage-then-error regression); a
7315 * non-decorrelatable scalar subquery still leaves the gate untouched and runs
7316 * as plain SQL. Does not descend into nested Query / SubLink subselects.
7317 */
7318/* -------------------------------------------------------------------------
7319 * (A) Inert provenance() fetches.
7320 *
7321 * A scalar SubLink whose subselect's *sole* visible output is a bare
7322 * provenance() call -- e.g. `(SELECT provenance() FROM tests WHERE ...)` --
7323 * is an *inert* read of a tuple's identity token: it must yield the token
7324 * as a plain uuid value without coupling that relation into the
7325 * surrounding row's lineage (the basis of the conditioning operator's
7326 * evidence). Such a SubLink's subselect is processed early
7327 * (provenance() resolved to that scope's token, no provsql column
7328 * appended) and recorded here, so the coupling-time detectors
7329 * (has_provenance, the tracked-sublink and decorrelation-value walkers)
7330 * treat it as an untracked value subquery. A subselect that brings along
7331 * any other column stays correlated (ordinary behaviour).
7332 *
7333 * The list holds the processed subselect Query pointers (stable: processed
7334 * in place). Saved/restored around provsql_planner for re-entrancy.
7335 * ------------------------------------------------------------------------- */
7336static List *provsql_inert_subselects = NIL;
7337
7338/** @brief Is @p q a recorded inert provenance()-fetch subselect? */
7339static bool is_inert_subselect(Query *q) {
7340 return q != NULL && list_member_ptr(provsql_inert_subselects, q);
7341}
7342
7343/** @brief Does @p sl wrap a recorded inert provenance()-fetch subselect?
7344 * Such a SubLink is an untracked scalar value: the decorrelation /
7345 * move-to-FROM passes must leave it alone (moving or aggregating it would
7346 * couple its relation into the outer lineage and wrap the token in an
7347 * aggregate gate). */
7348static bool sublink_is_inert(SubLink *sl) {
7349 return sl != NULL && sl->subselect && IsA(sl->subselect, Query) &&
7350 is_inert_subselect((Query *)sl->subselect);
7351}
7352
7353/**
7354 * @brief Whether @p sub's sole non-junk output is a bare provenance() call.
7355 *
7356 * Checked before resolution (the target is still the raw provenance()
7357 * FuncExpr). The supported shape is deliberately narrow -- a plain scan
7358 * projecting only provenance(): exactly one visible target entry, and it
7359 * is provenance(), with no aggregation / grouping / HAVING / set-op /
7360 * DISTINCT / window (those carry probabilistic-provenance semantics an
7361 * inert, physically-evaluated fetch would not honour, so they stay on the
7362 * ordinary path). A subselect bringing any other column likewise stays
7363 * correlated.
7364 */
7366 Query *sub) {
7367 TargetEntry *only = NULL;
7368 ListCell *lc;
7369 if (sub == NULL || !IsA(sub, Query))
7370 return false;
7371 foreach (lc, sub->targetList) {
7372 TargetEntry *te = (TargetEntry *)lfirst(lc);
7373 if (te->resjunk)
7374 continue;
7375 if (only != NULL)
7376 return false; /* a second visible column -> stays correlated */
7377 only = te;
7378 }
7379 return only != NULL && IsA(only->expr, FuncExpr) &&
7380 ((FuncExpr *)only->expr)->funcid == constants->OID_FUNCTION_PROVENANCE;
7381}
7382
7383/** @brief Walker: set found if an inert provenance()-fetch SubLink is present
7384 * in this query's *own* clauses (not descending into other scopes). */
7385static bool inert_fetch_sublink_walker(Node *node, void *data) {
7386 if (node == NULL)
7387 return false;
7388 if (IsA(node, SubLink)) {
7389 SubLink *sl = (SubLink *)node;
7390 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
7391 IsA(sl->subselect, Query) &&
7393 (Query *)sl->subselect))
7394 return true;
7395 return expression_tree_walker((Node *)sl->testexpr,
7397 }
7398 if (IsA(node, Query))
7399 return false; /* a nested scope handles its own */
7400 return expression_tree_walker(node, inert_fetch_sublink_walker, data);
7401}
7402
7403/** @brief Does @p q's own target list / jointree / HAVING contain an inert
7404 * provenance()-fetch SubLink? Such a query must be rewritten so the early
7405 * inert pass resolves the fetch, even with an otherwise-untracked outer. */
7406static bool query_has_inert_fetch(const constants_t *constants, Query *q) {
7407 if (inert_fetch_sublink_walker((Node *)q->targetList, (void *)constants))
7408 return true;
7409 if (q->jointree &&
7410 inert_fetch_sublink_walker((Node *)q->jointree, (void *)constants))
7411 return true;
7412 if (q->havingQual &&
7413 inert_fetch_sublink_walker(q->havingQual, (void *)constants))
7414 return true;
7415 return false;
7416}
7417
7418static bool decorr_value_sublink_walker(Node *node, void *data) {
7419 const constants_t *constants = (const constants_t *)data;
7420 if (node == NULL)
7421 return false;
7422 if (IsA(node, SubLink)) {
7423 SubLink *sl = (SubLink *)node;
7424 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
7425 IsA(sl->subselect, Query) &&
7426 is_inert_subselect((Query *)sl->subselect))
7427 return false; /* inert fetch: an untracked value, do not decorrelate */
7428 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
7429 IsA(sl->subselect, Query)) {
7430 Query *sub = (Query *)sl->subselect;
7431 if (!sub->hasAggs && !sub->groupClause && !sub->groupingSets &&
7432 !sub->distinctClause && !sub->setOperations && !sub->hasWindowFuncs &&
7433 !sub->hasSubLinks && !sub->limitCount && !sub->limitOffset &&
7434 !sub->cteList && list_length(sub->rtable) == 1 &&
7435 list_length(sub->targetList) == 1 && sub->jointree &&
7436 list_length(sub->jointree->fromlist) == 1 &&
7437 IsA(linitial(sub->jointree->fromlist), RangeTblRef)) {
7438 RangeTblEntry *qr = (RangeTblEntry *)linitial(sub->rtable);
7439 if (qr->rtekind == RTE_RELATION) {
7440 ListCell *lc;
7441 AttrNumber a = 0;
7442 foreach (lc, qr->eref->colnames) {
7443 ++a;
7444 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME) &&
7445 get_atttype(qr->relid, a) == constants->OID_TYPE_UUID)
7446 return true;
7447 }
7448 }
7449 }
7450 }
7451 return false; /* do not descend into the subselect */
7452 }
7453 if (IsA(node, Query))
7454 return false; /* nested queries are handled by has_provenance_walker */
7455 return expression_tree_walker(node, decorr_value_sublink_walker, data);
7456}
7457
7458static bool has_provenance_walker(Node *node, void *data) {
7459 const constants_t *constants = (const constants_t *)data;
7460 if (node == NULL)
7461 return false;
7462
7463 if (IsA(node, Query)) {
7464 Query *q = (Query *)node;
7465 ListCell *rc;
7466
7467 /* Walk into CTE subqueries explicitly: they will be inlined as
7468 * subqueries by the rewriter, so a tracked-table inside one (or
7469 * an rv_cmp / provenance() call) matters for this query. */
7470 foreach (rc, q->cteList) {
7471 CommonTableExpr *cte = (CommonTableExpr *)lfirst(rc);
7472 if (has_provenance_walker((Node *)cte->ctequery, data))
7473 return true;
7474 }
7475
7476 /* Walk this query's own expressions for rv_cmp OpExpr and
7477 * provenance() FuncExpr. Use the SubLink-aware walker so we
7478 * don't descend into expression-context subqueries (they get
7479 * planned standalone; an rv_cmp inside one matters only to
7480 * that planning pass).
7481 *
7482 * This intentionally replaces a single query_tree_walker call:
7483 * that helper recurses with the passed walker into BOTH rtable
7484 * RTEs (RTE_SUBQUERY) and SubLink subselects, which would erase
7485 * the SubLink/RTE_SUBQUERY distinction we need. */
7486 if (has_rv_or_provenance_call((Node *)q->targetList, data))
7487 return true;
7488 if (has_rv_or_provenance_call((Node *)q->jointree, data))
7489 return true;
7490 if (has_rv_or_provenance_call((Node *)q->havingQual, data))
7491 return true;
7492 if (has_rv_or_provenance_call((Node *)q->returningList, data))
7493 return true;
7494
7495 /* A decorrelatable value scalar subquery over a tracked relation engages
7496 * the gate even with an untracked outer (handled with a warning). */
7497 if (decorr_value_sublink_walker((Node *)q->targetList, data))
7498 return true;
7499 if (q->jointree &&
7500 decorr_value_sublink_walker((Node *)q->jointree->quals, data))
7501 return true;
7502
7503 /* An (unresolved) inert provenance()-fetch sublink also engages the
7504 * gate, at every level -- including a FROM subquery whose only
7505 * provenance is the fetch -- so the early inert pass resolves it.
7506 * (After resolution the subselect no longer reads as a pure fetch,
7507 * so this does not re-fire.) */
7508 if (query_has_inert_fetch(constants, q))
7509 return true;
7510
7511 foreach (rc, q->rtable) {
7512 RangeTblEntry *r = (RangeTblEntry *)lfirst(rc);
7513 if (r->rtekind == RTE_RELATION) {
7514 ListCell *lc;
7515 AttrNumber attid = 1;
7516
7517 foreach (lc, r->eref->colnames) {
7518 const char *v = strVal(lfirst(lc));
7519
7520 if (!strcmp(v, PROVSQL_COLUMN_NAME) &&
7521 get_atttype(r->relid, attid) == constants->OID_TYPE_UUID) {
7522 return true;
7523 }
7524
7525 ++attid;
7526 }
7527 } else if (r->rtekind == RTE_FUNCTION) {
7528 ListCell *lc;
7529 AttrNumber attid = 1;
7530
7531 foreach (lc, r->functions) {
7532 RangeTblFunction *func = (RangeTblFunction *)lfirst(lc);
7533
7534 if (func->funccolcount == 1) {
7535 FuncExpr *expr = (FuncExpr *)func->funcexpr;
7536 if (expr->funcresulttype == constants->OID_TYPE_UUID &&
7537 !strcmp(get_rte_attribute_name(r, attid),
7539 return true;
7540 }
7541 }
7542
7543 attid += func->funccolcount;
7544 }
7545 } else if (r->rtekind == RTE_SUBQUERY && r->subquery != NULL) {
7546 /* A FROM-source subquery contributes its provenance to ours;
7547 * process_query recurses on it explicitly, so we must detect
7548 * tracked relations / rv_cmp / provenance() inside it. */
7549 if (has_provenance_walker((Node *)r->subquery, data))
7550 return true;
7551 }
7552 }
7553 }
7554
7555 /* For non-Query nodes, use the expression-only walker. It detects
7556 * rv_cmp OpExpr and provenance() FuncExpr inside arbitrary
7557 * sub-expressions (BoolExpr around an rv comparison, RV cmp under
7558 * IS-DISTINCT-FROM, ...) but stops at Query boundaries so a sibling
7559 * subquery's tracked rtable doesn't make THIS query's gate engage
7560 * (subqueries have their own planner_hook pass). */
7561 return has_rv_or_provenance_call(node, data);
7562}
7563
7564/**
7565 * @brief Return true if @p q involves any provenance-bearing relation or
7566 * contains an explicit @c provenance() call.
7567 *
7568 * This is the gate condition checked by @c provsql_planner before doing any
7569 * rewriting: if neither condition holds the query is passed through unchanged.
7570 *
7571 * @param constants Extension OID cache.
7572 * @param q Query to inspect.
7573 * @return True if provenance rewriting is needed.
7574 */
7575static bool has_provenance(const constants_t *constants, Query *q) {
7576 /* An already-processed inert provenance() fetch is an untracked value
7577 * subquery: it contributes no lineage to whatever references it.
7578 * (Detection of an *unresolved* inert fetch -- which must engage the
7579 * gate -- lives in has_provenance_walker, so it fires at every level.) */
7580 if (is_inert_subselect(q))
7581 return false;
7582 return has_provenance_walker((Node *)q, (void *)constants);
7583}
7584
7585/** @brief Context for @c sublink_over_tracked_walker. */
7586typedef struct {
7588 bool found;
7590
7591/** @brief Walker: set @c found if a @c SubLink whose subselect (transitively)
7592 * involves a provenance-tracked relation is reached. */
7593static bool sublink_over_tracked_walker(Node *node, void *cx) {
7595 if (node == NULL || c->found)
7596 return false;
7597 if (IsA(node, SubLink)) {
7598 SubLink *sl = (SubLink *)node;
7599 if (IsA(sl->subselect, Query) &&
7600 has_provenance(c->constants, (Query *)sl->subselect)) {
7601 c->found = true;
7602 return true;
7603 }
7604 /* Not tracked at this level: fall through to descend (the subselect, for
7605 * nested sublinks, and the testexpr). */
7606 }
7607 if (IsA(node, Query))
7608 return query_tree_walker((Query *)node, sublink_over_tracked_walker, cx, 0);
7609 return expression_tree_walker(node, sublink_over_tracked_walker, cx);
7610}
7611
7612/**
7613 * @brief Does any @c SubLink in @p q's own clauses have a subselect that
7614 * (transitively) involves a provenance-tracked relation?
7615 *
7616 * Distinguishes the @c "Subqueries not supported" cases (a sublink over a tracked
7617 * @c Q, which needs the rewrite passes) from a harmless one whose body touches no
7618 * tracked relation -- a deterministic filter/value (untracked data is certain, so
7619 * the same in every possible world) that Postgres can evaluate directly, leaving
7620 * the row's provenance unchanged. Only @p q's own expressions are inspected, not
7621 * its range table (the outer relation is tracked, and FROM subqueries get their
7622 * own @c process_query pass).
7623 */
7624static bool query_has_tracked_sublink(const constants_t *constants, Query *q) {
7626 c.constants = constants;
7627 c.found = false;
7628 sublink_over_tracked_walker((Node *)q->targetList, &c);
7629 if (!c.found && q->jointree)
7630 sublink_over_tracked_walker((Node *)q->jointree, &c);
7631 if (!c.found && q->havingQual)
7632 sublink_over_tracked_walker(q->havingQual, &c);
7633 return c.found;
7634}
7635
7636/**
7637 * @brief Collect @c SubLink nodes sitting in a "direct", decorrelatable position:
7638 * a target-list entry that @e is the sublink, or a WHERE/HAVING boolean
7639 * factor or a direct operand of a comparison.
7640 *
7641 * These are exactly the positions the rewrite passes (@c rewrite_predicate_sublinks,
7642 * @c decorrelate_scalar_sublinks…) consume. A tracked sublink still in such a
7643 * position after those passes is a genuinely unsupported @e direct form (a
7644 * @c GROUP @c BY body, a multi-relation @c EXISTS…) that must raise the clean
7645 * error. A tracked sublink anywhere @e else is nested inside an expression
7646 * (arithmetic, a function argument); those are let through with a warning instead
7647 * -- Postgres evaluates the sublink normally (correct value), the row keeps the
7648 * outer relation's provenance, and the subquery's data is treated as certain.
7649 */
7650static void collect_direct_qual_sublinks(Node *node, List **out) {
7651 if (node == NULL)
7652 return;
7653 if (IsA(node, SubLink)) {
7654 /* A bare sublink boolean factor (EXISTS / IN / NOT …). */
7655 *out = lappend(*out, node);
7656 return;
7657 }
7658 if (IsA(node, BoolExpr)) {
7659 ListCell *lc;
7660 foreach (lc, ((BoolExpr *)node)->args)
7661 collect_direct_qual_sublinks((Node *)lfirst(lc), out);
7662 return;
7663 }
7664 if (IsA(node, OpExpr)) {
7665 /* A comparison whose direct operand is the sublink (a coercion in between is
7666 * fine, but arithmetic is not -- that makes the sublink nested). */
7667 ListCell *lc;
7668 foreach (lc, ((OpExpr *)node)->args) {
7669 Node *a = (Node *)lfirst(lc);
7670 if (IsA(a, RelabelType))
7671 a = (Node *)((RelabelType *)a)->arg;
7672 if (IsA(a, SubLink))
7673 *out = lappend(*out, a);
7674 }
7675 return;
7676 }
7677}
7678
7679static bool oj_wrap_body_with_match_ind(const constants_t *constants,
7680 Query *sub);
7681
7682/** @brief Sentinel eref alias marking join RTEs that ProvSQL itself
7683 * constructs (the EXCEPT antijoin, the sublink decorrelation):
7684 * their monus construction accounts for the null-padded rows, so
7685 * @c check_unlowered_outer_joins skips them. */
7686#define PROVSQL_JOIN_ALIAS "provsql_join"
7687
7688/** @brief Context for @c sublink_classify_walker. */
7689typedef struct {
7691 List *direct; /* sublinks in a decorrelatable position */
7692 List *nested; /* tracked EXPR_SUBLINKs nested in an expression */
7693 bool has_unsupported_direct; /* a tracked sublink in a direct position remains */
7695
7696/**
7697 * @brief Walker classifying each tracked @c SubLink of a query as either a
7698 * still-unsupported @e direct form or an @e arithmetic-nested one.
7699 *
7700 * Stops descending at a tracked sublink (its subselect is Postgres' business once
7701 * we decide to pass it through); keeps descending through untracked sublinks so a
7702 * tracked one nested deeper is still found.
7703 */
7704static bool sublink_classify_walker(Node *node, void *cx) {
7706 if (node == NULL)
7707 return false;
7708 if (IsA(node, SubLink)) {
7709 SubLink *sl = (SubLink *)node;
7710 if (IsA(sl->subselect, Query) &&
7711 has_provenance(c->constants, (Query *)sl->subselect)) {
7712 if (list_member_ptr(c->direct, sl) || sl->subLinkType != EXPR_SUBLINK)
7713 c->has_unsupported_direct = true;
7714 else
7715 c->nested = lappend(c->nested, sl);
7716 return false; /* do not descend into a tracked sublink */
7717 }
7718 /* untracked: fall through to descend (a tracked one may be nested inside) */
7719 }
7720 if (IsA(node, Query))
7721 return query_tree_walker((Query *)node, sublink_classify_walker, cx, 0);
7722 return expression_tree_walker(node, sublink_classify_walker, cx);
7723}
7724
7725/**
7726 * @brief Partition @p q's remaining tracked sublinks into unsupported-direct vs
7727 * arithmetic-nested. Returns the list of nested @c SubLink nodes (for
7728 * warnings) and sets @p *has_direct if any unsupported direct form remains.
7729 */
7730static List *classify_remaining_sublinks(const constants_t *constants, Query *q,
7731 bool *has_direct) {
7733 ListCell *lc;
7734
7735 c.constants = constants;
7736 c.direct = NIL;
7737 c.nested = NIL;
7738 c.has_unsupported_direct = false;
7739
7740 /* Direct positions: a target entry that IS the sublink (a coercion allowed). */
7741 foreach (lc, q->targetList) {
7742 Node *e = (Node *)((TargetEntry *)lfirst(lc))->expr;
7743 if (e && IsA(e, RelabelType))
7744 e = (Node *)((RelabelType *)e)->arg;
7745 if (e && IsA(e, SubLink))
7746 c.direct = lappend(c.direct, e);
7747 }
7748 if (q->jointree && q->jointree->quals)
7749 collect_direct_qual_sublinks(q->jointree->quals, &c.direct);
7750 if (q->havingQual)
7751 collect_direct_qual_sublinks(q->havingQual, &c.direct);
7752
7753 sublink_classify_walker((Node *)q->targetList, &c);
7754 if (q->jointree)
7755 sublink_classify_walker((Node *)q->jointree, &c);
7756 if (q->havingQual)
7757 sublink_classify_walker(q->havingQual, &c);
7758
7759 *has_direct = c.has_unsupported_direct;
7760 return c.nested;
7761}
7762
7763/**
7764 * @brief Walker: true if @p node (descending through nested queries) contains
7765 * an explicit @c provenance() call.
7766 */
7767static bool calls_provenance_walker(Node *node, void *data) {
7768 if (node == NULL)
7769 return false;
7770 if (IsA(node, FuncExpr) &&
7771 ((FuncExpr *)node)->funcid ==
7772 ((const constants_t *)data)->OID_FUNCTION_PROVENANCE)
7773 return true;
7774 if (IsA(node, Query))
7775 return query_tree_walker((Query *)node, calls_provenance_walker, data, 0);
7776 return expression_tree_walker(node, calls_provenance_walker, data);
7777}
7778
7779/**
7780 * @brief Walker: true if a @c SubLink subselect calls @c provenance().
7781 *
7782 * A @c SubLink subselect (scalar / @c IN / @c EXISTS) is planned standalone, so
7783 * it never goes through this hook -- a @c provenance() call inside one is never
7784 * rewritten and falls through to its runtime stub (NULL or a misleading error).
7785 * ProvSQL does not propagate provenance through a @c SubLink, so we detect the
7786 * @c provenance() use up front and raise a clear error instead.
7787 *
7788 * Only the explicit @c provenance() call is flagged, not a mere read of a
7789 * tracked relation's columns: @c (SELECT @c array_agg(provsql) @c FROM @c t) and
7790 * other plain column reads inside a @c SubLink are legitimate and must keep
7791 * working. Tracked relations reached through the @c FROM clause
7792 * (@c RTE_SUBQUERY) are fully supported and never reach this walker's @c SubLink
7793 * arm.
7794 */
7795static bool provenance_in_sublink_walker(Node *node, void *data) {
7796 if (node == NULL)
7797 return false;
7798 if (IsA(node, Query))
7799 return query_tree_walker((Query *)node, provenance_in_sublink_walker, data, 0);
7800 if (IsA(node, SubLink)) {
7801 SubLink *sl = (SubLink *)node;
7802 if (sl->subselect && IsA(sl->subselect, Query) &&
7803 calls_provenance_walker(sl->subselect, data)) {
7804 /* A scalar sublink whose sole output is provenance() is an inert
7805 * token fetch, handled by process_query; allow it. Any other
7806 * provenance() in a sublink is the unsupported form. */
7807 if (sl->subLinkType == EXPR_SUBLINK &&
7809 (Query *)sl->subselect))
7810 return false;
7811 return true;
7812 }
7813 }
7814 return expression_tree_walker(node, provenance_in_sublink_walker, data);
7815}
7816
7817/**
7818 * @brief Remove the auto-added @c provsql output column from a rewritten query.
7819 *
7820 * The inverse of @c add_to_select: drops the @c TargetEntry named
7821 * @c PROVSQL_COLUMN_NAME and decrements the @c resno of every later entry, so
7822 * the column numbering stays contiguous. Used when a query was rewritten for
7823 * its own provenance semantics (HAVING lifting, @c provenance() resolution) but
7824 * the caller cannot store the provenance -- e.g. an @c INSERT @c ... @c SELECT
7825 * whose target table has no provsql column.
7826 */
7827static void remove_provsql_from_select(Query *q) {
7828 ListCell *lc;
7829 ListCell *prev = NULL;
7830 int removed_resno = -1;
7831
7832 foreach (lc, q->targetList) {
7833 TargetEntry *te = (TargetEntry *)lfirst(lc);
7834 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME)) {
7835 removed_resno = te->resno;
7836 q->targetList = my_list_delete_cell(q->targetList, lc, prev);
7837 break;
7838 }
7839 prev = lc;
7840 }
7841
7842 if (removed_resno < 0)
7843 return;
7844
7845 foreach (lc, q->targetList) {
7846 TargetEntry *te = (TargetEntry *)lfirst(lc);
7847 if (te->resno > removed_resno)
7848 --te->resno;
7849 }
7850}
7851
7852/**
7853 * @brief Tree walker that detects any Var of type agg_token.
7854 * @param node Current expression tree node.
7855 * @param data Pointer to a @c constants_t (extension OID cache).
7856 * @return @c true if an agg_token Var is found in @p node.
7857 */
7858static bool aggtoken_walker(Node *node, void *data) {
7859 const constants_t *constants = (const constants_t *) data;
7860 if (node == NULL)
7861 return false;
7862
7863 if (IsA(node, Var)) {
7864 Var *v = (Var *) node;
7865 if(v->vartype == constants->OID_TYPE_AGG_TOKEN)
7866 return true;
7867 }
7868
7869 return expression_tree_walker(node, aggtoken_walker, data);
7870}
7871
7872/**
7873 * @brief Return true if @p node contains a @c Var of type @c agg_token.
7874 *
7875 * Used to detect whether a WHERE clause references an aggregate result
7876 * (which must be moved to HAVING).
7877 *
7878 * @param node Expression tree to inspect.
7879 * @param constants Extension OID cache.
7880 * @return True if an @c agg_token @c Var is found anywhere in @p node.
7881 */
7882static bool has_aggtoken(Node *node, const constants_t *constants) {
7883 return expression_tree_walker(node, aggtoken_walker, (void*) constants);
7884}
7885
7886/**
7887 * @brief Walker for @c needs_having_lift: detect any operand shape that
7888 * the HAVING-lift rewriter (@c having_OpExpr_to_provenance_cmp)
7889 * needs to handle specially.
7890 *
7891 * Returns @c true on:
7892 * - a @c Var of type @c agg_token; or
7893 * - a @c FuncExpr whose @c funcid is @c provenance_aggregate (the
7894 * wrapper the planner-hook puts around aggregates over tracked
7895 * non-RV columns -- yields @c agg_token).
7896 *
7897 * Anything else (deterministic scalars, plain @c Const, @c FuncExpr
7898 * over @c random_variable like @c expected / @c variance / @c moment,
7899 * comparisons of those) is left for PostgreSQL to evaluate natively;
7900 * the HAVING-lift never needs to touch it.
7901 */
7902static bool having_lift_walker(Node *node, void *data) {
7903 const constants_t *constants = (const constants_t *) data;
7904 if (node == NULL)
7905 return false;
7906
7907 if (IsA(node, Var)) {
7908 Var *v = (Var *) node;
7909 if (v->vartype == constants->OID_TYPE_AGG_TOKEN)
7910 return true;
7911 }
7912
7913 if (IsA(node, FuncExpr)) {
7914 FuncExpr *fe = (FuncExpr *) node;
7915 if (fe->funcid == constants->OID_FUNCTION_PROVENANCE_AGGREGATE)
7916 return true;
7917 }
7918
7919 return expression_tree_walker(node, having_lift_walker, data);
7920}
7921
7922/**
7923 * @brief Return true if @p havingQual contains anything the HAVING-lift
7924 * path needs to handle (an @c agg_token Var or a
7925 * @c provenance_aggregate wrapper). A qual that returns @c false
7926 * is left in place for PostgreSQL to evaluate, while the
7927 * per-group provenance still gets a @c gate_delta wrapper.
7928 *
7929 * This is what lets a HAVING like @c expected(avg(rv)) > 20 work
7930 * directly: @c provsql.avg returns @c random_variable (not
7931 * @c agg_token), @c expected collapses to a scalar @c double, and the
7932 * surrounding comparison is a plain Boolean that PostgreSQL can filter
7933 * groups by without any provenance-side rewriting.
7934 */
7935static bool needs_having_lift(Node *havingQual, const constants_t *constants) {
7936 return expression_tree_walker(havingQual, having_lift_walker,
7937 (void *) constants);
7938}
7939
7940/**
7941 * @brief Whether a lifted HAVING predicate already entails that the group
7942 * exists.
7943 *
7944 * A comparison on an aggregate does: the possible-world enumeration behind its
7945 * @c gate_cmp ranges over the non-empty worlds of the group's own tokens, so
7946 * the gate is @c 0 wherever the group is empty. That is what lets the lift
7947 * supersede the group's δ instead of multiplying with it.
7948 *
7949 * An aggregate-free atom does not. Its predicate-provenance is the
7950 * deterministic indicator @c regular_indicator, which is @c 1 or @c 0 by the
7951 * value of a grouping column and says nothing about whether any row is
7952 * present. Superseding the δ in front of one would claim the group exists in
7953 * every world -- so @c HAVING @c count(*) @c >= @c 4 @c OR @c g @c = @c 1
7954 * would report certainty for a group that is empty half the time.
7955 *
7956 * The two combine as the semiring does: under ⊗ one entailing factor makes the
7957 * product entail (the other factor cannot resurrect an empty group), while
7958 * under ⊕ every disjunct must entail, since any one of them alone can make the
7959 * sum non-zero. @p negated tracks De Morgan, matching
7960 * @c having_BoolExpr_to_provenance: the complement of an aggregate comparison
7961 * is another comparison over the same non-empty worlds, so negation preserves
7962 * entailment at the atoms.
7963 */
7964static bool having_entails_group_existence(Expr *expr,
7965 const constants_t *constants,
7966 bool negated)
7967{
7968 if (expr == NULL)
7969 return false;
7970
7971 /* An aggregate-free atom becomes a regular_indicator: no existence. */
7972 if (!expr_contains_agg((Node *) expr, constants))
7973 return false;
7974
7975 if (IsA(expr, BoolExpr)) {
7976 BoolExpr *be = (BoolExpr *) expr;
7977 ListCell *lc;
7978 bool conjunction;
7979
7980 if (be->boolop == NOT_EXPR)
7981 return having_entails_group_existence((Expr *) linitial(be->args),
7982 constants, !negated);
7983
7984 conjunction = (be->boolop == AND_EXPR) ? !negated : negated;
7985
7986 foreach (lc, be->args) {
7987 bool child = having_entails_group_existence((Expr *) lfirst(lc),
7988 constants, negated);
7989 if (conjunction && child)
7990 return true; /* ⊗: one entailing factor is enough */
7991 if (!conjunction && !child)
7992 return false; /* ⊕: every disjunct must entail */
7993 }
7994 return !conjunction;
7995 }
7996
7997 /* An atom that survived the aggregate test above is a comparison or an
7998 * IS [NOT] NULL on an aggregate; both are 0 on the empty group. */
7999 return true;
8000}
8001
8002/* bool_or / bool_and / its every() alias -- the boolean aggregates that the
8003 * HAVING boolean-domain evaluator handles. */
8004static bool is_supported_bool_agg(Oid aggfnoid) {
8005 char *name = get_func_name(aggfnoid);
8006 bool yes;
8007 if (!name)
8008 return false;
8009 yes = strcmp(name, "bool_or") == 0 || strcmp(name, "bool_and") == 0 ||
8010 strcmp(name, "every") == 0;
8011 pfree(name);
8012 return yes;
8013}
8014
8015/* Normalise a bare boolean aggregate used directly as a HAVING condition --
8016 * @c "HAVING bool_or(x)", @c "HAVING NOT(every(x))" -- into the explicit
8017 * @c "agg = true" comparison, so the existing aggregate-comparison recognition
8018 * (@c needs_having_lift) and the boolean-domain HAVING evaluator handle it.
8019 * Descends the Boolean structure (AND / OR / NOT) but not into comparison
8020 * operands: an aggregate already inside a comparison is left untouched. */
8021static Node *normalize_bool_agg_having(Node *n) {
8022 if (n == NULL)
8023 return NULL;
8024 if (IsA(n, BoolExpr)) {
8025 BoolExpr *be = (BoolExpr *) n;
8026 ListCell *lc;
8027 foreach (lc, be->args)
8028 lfirst(lc) = normalize_bool_agg_having((Node *) lfirst(lc));
8029 return n;
8030 }
8031 if (IsA(n, Aggref)) {
8032 Aggref *ar = (Aggref *) n;
8033 if (ar->aggtype == BOOLOID && is_supported_bool_agg(ar->aggfnoid)) {
8034 OpExpr *eq = makeNode(OpExpr);
8035 eq->opno = BooleanEqualOperator;
8036 eq->opfuncid = get_opcode(BooleanEqualOperator);
8037 eq->opresulttype = BOOLOID;
8038 eq->opretset = false;
8039 eq->opcollid = InvalidOid;
8040 eq->inputcollid = InvalidOid;
8041 eq->args = list_make2(ar, makeBoolConst(true, false));
8042 eq->location = -1;
8043 return (Node *) eq;
8044 }
8045 }
8046 return n;
8047}
8048
8049/**
8050 * @brief Rewrite an EXCEPT query into a LEFT JOIN with monus provenance.
8051 *
8052 * EXCEPT cannot be handled directly because it deduplicates. This function
8053 * transforms:
8054 * @code
8055 * SELECT … FROM A EXCEPT SELECT … FROM B
8056 * @endcode
8057 * into a LEFT JOIN of A and B on equality of all non-provenance columns,
8058 * clears @c setOperations, and leaves the monus token combination to
8059 * @c make_provenance_expression (which will see @c SR_MONUS).
8060 *
8061 * Only simple (non-chained) EXCEPT is supported; chained EXCEPT raises an
8062 * error.
8063 *
8064 * @param constants Extension OID cache.
8065 * @param q Query to rewrite in place.
8066 * @return Always true (errors out on unsupported cases).
8067 */
8068static bool transform_except_into_join(const constants_t *constants, Query *q) {
8069 SetOperationStmt *setOps = (SetOperationStmt *)q->setOperations;
8070 RangeTblEntry *rte = makeNode(RangeTblEntry);
8071 FromExpr *fe = makeNode(FromExpr);
8072 JoinExpr *je = makeNode(JoinExpr);
8073 BoolExpr *expr = makeNode(BoolExpr);
8074 ListCell *lc;
8075 int attno = 1;
8076
8077 if (!IsA(setOps->larg, RangeTblRef) || !IsA(setOps->rarg, RangeTblRef)) {
8078 provsql_error("Unsupported chain of EXCEPT operations");
8079 }
8080
8081 expr->boolop = AND_EXPR;
8082 expr->location = -1;
8083 expr->args = NIL;
8084
8085 foreach (lc, q->targetList) {
8086 TargetEntry *te = (TargetEntry *)lfirst(lc);
8087 Var *v;
8088
8089 if (!IsA(te->expr, Var))
8090 provsql_error("EXCEPT query format not supported");
8091
8092 v = (Var *)te->expr;
8093
8094 if (v->vartype != constants->OID_TYPE_UUID) {
8095 /* SQL's EXCEPT matches tuples syntactically (two NULLs are the same
8096 * value), so the antijoin condition is the NULL-identical
8097 * "l IS NOT DISTINCT FROM r" -- a DistinctExpr under a NOT -- and
8098 * not the plain "=", which never matches a NULL row and would leave
8099 * NULL rows of the left side unremoved. */
8100 DistinctExpr *oe = makeNode(DistinctExpr);
8101 Oid opno = find_equality_operator(v->vartype, v->vartype);
8102 Operator opInfo = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
8103 Form_pg_operator opform;
8104 Var *leftArg, *rightArg;
8105
8106 if (!HeapTupleIsValid(opInfo))
8107 provsql_error("could not find operator with OID %u to compare variables of type %u",
8108 opno, v->vartype);
8109
8110 opform = (Form_pg_operator)GETSTRUCT(opInfo);
8111 leftArg = makeNode(Var);
8112 rightArg = makeNode(Var);
8113
8114 oe->opno = opno;
8115 oe->opfuncid = opform->oprcode;
8116 oe->opresulttype = opform->oprresult;
8117 oe->opcollid = InvalidOid;
8118 oe->inputcollid = DEFAULT_COLLATION_OID;
8119
8120 leftArg->varno = ((RangeTblRef *)setOps->larg)->rtindex;
8121 rightArg->varno = ((RangeTblRef *)setOps->rarg)->rtindex;
8122 leftArg->varattno = rightArg->varattno = attno;
8123
8124#if PG_VERSION_NUM >= 130000
8125 leftArg->varnosyn = rightArg->varnosyn = 0;
8126 leftArg->varattnosyn = rightArg->varattnosyn = 0;
8127#else
8128 leftArg->varnoold = leftArg->varno;
8129 rightArg->varnoold = rightArg->varno;
8130 leftArg->varoattno = rightArg->varoattno = attno;
8131#endif
8132
8133 leftArg->vartype = rightArg->vartype = v->vartype;
8134 leftArg->varcollid = rightArg->varcollid = InvalidOid;
8135 leftArg->vartypmod = rightArg->vartypmod = -1;
8136 leftArg->location = rightArg->location = -1;
8137
8138 oe->args = list_make2(leftArg, rightArg);
8139 oe->location = -1;
8140 /* IS NOT DISTINCT FROM has no node of its own: it is the negation
8141 * of the DistinctExpr. */
8142 expr->args = lappend(expr->args,
8143 makeBoolExpr(NOT_EXPR, list_make1(oe), -1));
8144
8145 ReleaseSysCache(opInfo);
8146 }
8147
8148 ++attno;
8149 }
8150
8151 /* Populate the JOIN RTE's eref / joinaliasvars / joinleftcols /
8152 * joinrightcols by walking the larg and rarg subqueries' targetLists.
8153 * Execution doesn't need these (outer Vars reference the input RTEs
8154 * directly), but PostgreSQL's ruleutils deparser walks them when
8155 * pg_get_querydef / EXPLAIN VERBOSE traverse the rewritten tree and
8156 * segfaults on NULL eref. Non-USING LEFT JOIN: joinmergedcols = 0,
8157 * output is left columns followed by right columns. */
8158 {
8159 RangeTblRef *larg_ref = (RangeTblRef *)setOps->larg;
8160 RangeTblRef *rarg_ref = (RangeTblRef *)setOps->rarg;
8161 RangeTblEntry *larg_rte =
8162 (RangeTblEntry *)list_nth(q->rtable, larg_ref->rtindex - 1);
8163 RangeTblEntry *rarg_rte =
8164 (RangeTblEntry *)list_nth(q->rtable, rarg_ref->rtindex - 1);
8165 List *aliasvars = NIL;
8166 List *leftcols = NIL;
8167 List *rightcols = NIL;
8168 List *colnames = NIL;
8169 ListCell *lc_te;
8170 int colno;
8171
8172 colno = 1;
8173 foreach (lc_te, larg_rte->subquery->targetList) {
8174 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
8175 if (te->resjunk) {
8176 colno++;
8177 continue;
8178 }
8179 aliasvars = lappend(aliasvars,
8180 makeVar(larg_ref->rtindex, colno,
8181 exprType((Node *)te->expr),
8182 exprTypmod((Node *)te->expr),
8183 exprCollation((Node *)te->expr),
8184 0));
8185 leftcols = lappend_int(leftcols, colno);
8186 rightcols = lappend_int(rightcols, 0);
8187 colnames = lappend(colnames,
8188 makeString(pstrdup(te->resname ? te->resname
8189 : "?column?")));
8190 colno++;
8191 }
8192 colno = 1;
8193 foreach (lc_te, rarg_rte->subquery->targetList) {
8194 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
8195 if (te->resjunk) {
8196 colno++;
8197 continue;
8198 }
8199 aliasvars = lappend(aliasvars,
8200 makeVar(rarg_ref->rtindex, colno,
8201 exprType((Node *)te->expr),
8202 exprTypmod((Node *)te->expr),
8203 exprCollation((Node *)te->expr),
8204 0));
8205 leftcols = lappend_int(leftcols, 0);
8206 rightcols = lappend_int(rightcols, colno);
8207 colnames = lappend(colnames,
8208 makeString(pstrdup(te->resname ? te->resname
8209 : "?column?")));
8210 colno++;
8211 }
8212
8213 rte->alias = NULL;
8214 rte->eref = makeAlias(PROVSQL_JOIN_ALIAS, colnames);
8215 rte->joinaliasvars = aliasvars;
8216#if PG_VERSION_NUM >= 130000
8217 rte->joinleftcols = leftcols;
8218 rte->joinrightcols = rightcols;
8219 rte->joinmergedcols = 0;
8220#else
8221 (void) leftcols;
8222 (void) rightcols;
8223#endif
8224 }
8225
8226 rte->rtekind = RTE_JOIN;
8227 rte->jointype = JOIN_LEFT;
8228
8229 q->rtable = lappend(q->rtable, rte);
8230
8231 je->jointype = JOIN_LEFT;
8232
8233 je->larg = setOps->larg;
8234 je->rarg = setOps->rarg;
8235 je->quals = (Node *)expr;
8236 je->rtindex = list_length(q->rtable);
8237
8238 fe->fromlist = list_make1(je);
8239
8240 q->jointree = fe;
8241
8242 // TODO: Add group by in the right-side table
8243
8244 q->setOperations = 0;
8245
8246 return true;
8247}
8248
8249/* -------------------------------------------------------------------------
8250 * Outer-join lowering (LEFT JOIN)
8251 *
8252 * ProvSQL builds provenance by annotating the all-present instance, which is
8253 * sound for monotone SPJU but WRONG for the non-monotone outer join: the
8254 * null-padded row (r, NULL) of a LEFT JOIN appears only in the *smaller*
8255 * worlds where the right side has no match for r, so for a left row that does
8256 * match in the actual instance ProvSQL has nothing to annotate. The
8257 * RTE_JOIN arm of process_query treats LEFT/FULL/RIGHT exactly
8258 * like INNER, emitting only the matched branch.
8259 *
8260 * The fix is a structural transform applied in the planner hook before
8261 * provenance discovery. R ⟕_θ S is rewritten as
8262 *
8263 * ( SELECT R.cols, S.cols FROM R JOIN S ON θ ) -- matched (⊗)
8264 * UNION ALL -- ⊎ (plus)
8265 * ( SELECT R.cols, NULL,…,NULL
8266 * FROM ( SELECT R.cols FROM R
8267 * EXCEPT ALL -- ProvSQL's −
8268 * SELECT R.cols FROM R JOIN S ON θ ) ) -- R(r)⊗(1⊖⊕match)
8269 *
8270 * Both UNION ALL and EXCEPT ALL → − are native (process_set_operation_union /
8271 * transform_except_into_join), so this code is pure parse-tree construction
8272 * plus an outer Var remap: the recursive process_query passes over the
8273 * constructed subqueries do all the provenance work. The antijoin provenance
8274 * R(r) ⊖ ⊕_match (R(r)⊗S(s)) that ProvSQL's EXCEPT (NOT-IN semantics) builds
8275 * equals R(r) ⊗ (1 ⊖ ⊕_match S(s)), exactly the paper's null-padded branch.
8276 * ------------------------------------------------------------------------- */
8277
8278/**
8279 * @brief Rename the @c provsql column in @p rel's @c eref so a later
8280 * @c get_provenance_attributes pass does not re-detect @p rel as a
8281 * provenance source.
8282 *
8283 * Used when a relation's provenance has already been captured elsewhere -- by
8284 * an explode-style subquery (the aggregation rewrite) or, in the outer-join
8285 * lowering, by the replacement UNION subquery, leaving the original base
8286 * relation orphaned in the range table. Renaming only the (unreferenced)
8287 * @c eref entry is enough: detection matches on the @c eref colname.
8288 */
8289static void hide_provsql_colname(RangeTblEntry *rel) {
8290 ListCell *lc;
8291 foreach (lc, rel->eref->colnames) {
8292 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME)) {
8293 lfirst(lc) = makeString(pstrdup("_provsql_inner"));
8294 break;
8295 }
8296 }
8297}
8298
8299/** @brief Per-relation user-column descriptor for the outer-join lowering. */
8300typedef struct oj_cols {
8301 int n; ///< number of user (non-provsql, non-dropped) columns
8302 AttrNumber *attno; ///< original attribute number in the base relation
8303 Oid *type; ///< column type OID
8304 int32 *typmod; ///< column typmod
8305 Oid *coll; ///< column collation OID
8306 char **name; ///< column name
8307} oj_cols;
8308
8309/** @brief Collect the user columns (skipping @c provsql and dropped columns)
8310 * of an outer-join arm: a base relation or a subquery. For a relation
8311 * the column @c attno is its catalog attribute number; for a subquery
8312 * it is the target entry's @c resno. */
8313static void oj_collect_cols(const constants_t *constants, RangeTblEntry *rel,
8314 oj_cols *out) {
8315 ListCell *lc;
8316
8317 if (rel->rtekind == RTE_SUBQUERY) {
8318 int cap = list_length(rel->subquery->targetList);
8319 out->attno = (AttrNumber *)palloc(cap * sizeof(AttrNumber));
8320 out->type = (Oid *)palloc(cap * sizeof(Oid));
8321 out->typmod = (int32 *)palloc(cap * sizeof(int32));
8322 out->coll = (Oid *)palloc(cap * sizeof(Oid));
8323 out->name = (char **)palloc(cap * sizeof(char *));
8324 out->n = 0;
8325 foreach (lc, rel->subquery->targetList) {
8326 TargetEntry *te = (TargetEntry *)lfirst(lc);
8327 if (te->resjunk)
8328 continue;
8329 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME))
8330 continue;
8331 out->attno[out->n] = te->resno;
8332 out->type[out->n] = exprType((Node *)te->expr);
8333 out->typmod[out->n] = exprTypmod((Node *)te->expr);
8334 out->coll[out->n] = exprCollation((Node *)te->expr);
8335 out->name[out->n] = pstrdup(te->resname ? te->resname : "?column?");
8336 ++out->n;
8337 }
8338 return;
8339 }
8340
8341 {
8342 AttrNumber attid = 0;
8343 int cap = list_length(rel->eref->colnames);
8344 out->attno = (AttrNumber *)palloc(cap * sizeof(AttrNumber));
8345 out->type = (Oid *)palloc(cap * sizeof(Oid));
8346 out->typmod = (int32 *)palloc(cap * sizeof(int32));
8347 out->coll = (Oid *)palloc(cap * sizeof(Oid));
8348 out->name = (char **)palloc(cap * sizeof(char *));
8349 out->n = 0;
8350 foreach (lc, rel->eref->colnames) {
8351 const char *v = strVal(lfirst(lc));
8352 Oid t;
8353 int32 tm;
8354 Oid c;
8355 ++attid;
8356 if (v[0] == '\0') /* dropped column */
8357 continue;
8358 if (!strcmp(v, PROVSQL_COLUMN_NAME))
8359 continue;
8360 get_atttypetypmodcoll(rel->relid, attid, &t, &tm, &c);
8361 out->attno[out->n] = attid;
8362 out->type[out->n] = t;
8363 out->typmod[out->n] = tm;
8364 out->coll[out->n] = c;
8365 out->name[out->n] = pstrdup(v);
8366 ++out->n;
8367 }
8368 }
8369}
8370
8371/** @brief True if @p rel contributes provenance: a base relation with a
8372 * @c provsql UUID column, or a subquery over tracked relations. */
8373static bool oj_rte_has_provsql(const constants_t *constants,
8374 RangeTblEntry *rel) {
8375 ListCell *lc;
8376 AttrNumber attid = 0;
8377
8378 if (rel->rtekind == RTE_SUBQUERY) {
8379 if (rel->subquery == NULL)
8380 return false;
8381 if (has_provenance(constants, rel->subquery))
8382 return true;
8383 /* Also tracked if the subquery already exposes a provsql UUID column
8384 * (e.g. the synthetic gate_one() column the wrap adds for an untracked
8385 * outer). */
8386 foreach (lc, rel->subquery->targetList) {
8387 TargetEntry *te = (TargetEntry *)lfirst(lc);
8388 if (!te->resjunk && te->resname &&
8389 !strcmp(te->resname, PROVSQL_COLUMN_NAME) &&
8390 exprType((Node *)te->expr) == constants->OID_TYPE_UUID)
8391 return true;
8392 }
8393 return false;
8394 }
8395
8396 foreach (lc, rel->eref->colnames) {
8397 ++attid;
8398 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME) &&
8399 get_atttype(rel->relid, attid) == constants->OID_TYPE_UUID)
8400 return true;
8401 }
8402 return false;
8403}
8404
8405/** @brief Wrap a constructed @c Query as an @c RTE_SUBQUERY, building its
8406 * @c eref->colnames from the (non-junk) target list. */
8407static RangeTblEntry *oj_make_subquery_rte(Query *sub) {
8408 RangeTblEntry *rte = makeNode(RangeTblEntry);
8409 List *colnames = NIL;
8410 ListCell *lc;
8411
8412 foreach (lc, sub->targetList) {
8413 TargetEntry *te = (TargetEntry *)lfirst(lc);
8414 if (te->resjunk)
8415 continue;
8416 colnames = lappend(colnames,
8417 makeString(pstrdup(te->resname ? te->resname
8418 : "?column?")));
8419 }
8420
8421 rte->rtekind = RTE_SUBQUERY;
8422 rte->subquery = sub;
8423 rte->alias = NULL;
8424 rte->eref = makeAlias("unnamed_subquery", colnames);
8425 rte->lateral = false;
8426 rte->inFromCl = true;
8427#if PG_VERSION_NUM < 160000
8428 rte->requiredPerms = 0;
8429#endif
8430 return rte;
8431}
8432
8433/** @brief Copy an outer-join arm RTE into the range table of subquery @p sub.
8434 * A base relation carries its permission info (PG 16+); a subquery has no
8435 * direct permissions (its inner query keeps its own rteperminfos). */
8436static RangeTblEntry *oj_copy_rel(Query *outer, Query *sub,
8437 RangeTblEntry *orig) {
8438 RangeTblEntry *c = copyObject(orig);
8439#if PG_VERSION_NUM >= 160000
8440 if (orig->rtekind == RTE_RELATION && orig->perminfoindex != 0) {
8441 RTEPermissionInfo *pi = getRTEPermissionInfo(outer->rteperminfos, orig);
8442 sub->rteperminfos = lappend(sub->rteperminfos, copyObject(pi));
8443 c->perminfoindex = list_length(sub->rteperminfos);
8444 } else {
8445 c->perminfoindex = 0;
8446 }
8447#else
8448 (void)outer;
8449 (void)sub;
8450#endif
8451 return c;
8452}
8453
8454/** @brief Neutralise an outer-join arm RTE left orphaned after the lowering so
8455 * get_provenance_attributes does not re-pick it up as a provenance source: a
8456 * base relation has its provsql column renamed; a subquery (which would still
8457 * be processed) is turned into an inert RTE_RESULT. */
8458static void oj_neutralize_orphan_arm(RangeTblEntry *rel) {
8459 if (rel->rtekind == RTE_SUBQUERY) {
8460#if PG_VERSION_NUM >= 120000
8461 rel->rtekind = RTE_RESULT;
8462 rel->subquery = NULL;
8463#else
8464 /* No RTE_RESULT before PostgreSQL 12: leave an inert zero-column
8465 * subquery (a bare SELECT) instead. get_provenance_attributes
8466 * recurses into it, finds no relations, and adds nothing. */
8467 Query *empty = makeNode(Query);
8468 empty->commandType = CMD_SELECT;
8469 empty->canSetTag = true;
8470 empty->jointree = makeFromExpr(NIL, NULL);
8471 rel->subquery = empty;
8472#endif
8473 rel->eref = makeAlias("*RESULT*", NIL);
8474#if PG_VERSION_NUM >= 160000
8475 rel->perminfoindex = 0;
8476#endif
8477 return;
8478 }
8480}
8481
8482/** @brief Var-renumber context: map @c varno @c from[i] → @c to[i]. */
8483typedef struct oj_renum_ctx {
8485 Index from[2];
8486 Index to[2];
8487} oj_renum_ctx;
8488
8489static Node *oj_renum_mut(Node *node, void *cx) {
8490 oj_renum_ctx *c = (oj_renum_ctx *)cx;
8491 if (node == NULL)
8492 return NULL;
8493 if (IsA(node, Var)) {
8494 Var *v = (Var *)node;
8495 if (v->varlevelsup == 0) {
8496 int i;
8497 for (i = 0; i < c->npairs; ++i)
8498 if (v->varno == c->from[i]) {
8499 v = (Var *)copyObject(v);
8500 v->varno = c->to[i];
8501#if PG_VERSION_NUM >= 160000
8502 v->varnullingrels = NULL;
8503#endif
8504#if PG_VERSION_NUM >= 130000
8505 v->varnosyn = 0;
8506 v->varattnosyn = 0;
8507#endif
8508 return (Node *)v;
8509 }
8510 }
8511 return node;
8512 }
8513 return expression_tree_mutator(node, oj_renum_mut, cx);
8514}
8515
8516/** @brief Build the inner-join scan subquery
8517 * @c "SELECT [R.cols][, S.cols] FROM R JOIN S ON θ".
8518 *
8519 * Projects R's columns when @p select_r and S's columns when @p select_s, in
8520 * R-then-S order. R is copied at index 1, S at index 2, the synthetic join
8521 * RTE at index 3; θ is copied and its base-relation varnos remapped
8522 * (R_idx→1, S_idx→2). */
8523static Query *oj_build_join_query(const constants_t *constants, Query *outer,
8524 RangeTblEntry *R, RangeTblEntry *S,
8525 Index R_idx, Index S_idx, oj_cols *Rc,
8526 oj_cols *Sc, Node *theta, bool select_r,
8527 bool select_s) {
8528 Query *sub = makeNode(Query);
8529 RangeTblEntry *Rcopy, *Scopy, *jrte = makeNode(RangeTblEntry);
8530 JoinExpr *je = makeNode(JoinExpr);
8531 RangeTblRef *lr = makeNode(RangeTblRef), *rr = makeNode(RangeTblRef);
8532 FromExpr *fe = makeNode(FromExpr);
8533 List *tl = NIL, *av = NIL, *lcols = NIL, *rcols = NIL, *cn = NIL;
8534 Node *theta2;
8535 oj_renum_ctx rctx;
8536 int i;
8537
8538 sub->commandType = CMD_SELECT;
8539 sub->canSetTag = true;
8540 Rcopy = oj_copy_rel(outer, sub, R);
8541 Scopy = oj_copy_rel(outer, sub, S);
8542
8543 /* Synthetic join RTE: eref / joinaliasvars / joinleftcols / joinrightcols
8544 * kept consistent so the ruleutils deparser does not segfault. */
8545 for (i = 0; i < Rc->n; ++i) {
8546 av = lappend(av, makeVar(1, Rc->attno[i], Rc->type[i], Rc->typmod[i],
8547 Rc->coll[i], 0));
8548 lcols = lappend_int(lcols, Rc->attno[i]);
8549 rcols = lappend_int(rcols, 0);
8550 cn = lappend(cn, makeString(pstrdup(Rc->name[i])));
8551 }
8552 for (i = 0; i < Sc->n; ++i) {
8553 av = lappend(av, makeVar(2, Sc->attno[i], Sc->type[i], Sc->typmod[i],
8554 Sc->coll[i], 0));
8555 lcols = lappend_int(lcols, 0);
8556 rcols = lappend_int(rcols, Sc->attno[i]);
8557 cn = lappend(cn, makeString(pstrdup(Sc->name[i])));
8558 }
8559 jrte->rtekind = RTE_JOIN;
8560 jrte->jointype = JOIN_INNER;
8561 jrte->alias = NULL;
8562 jrte->eref = makeAlias("unnamed_join", cn);
8563 jrte->joinaliasvars = av;
8564#if PG_VERSION_NUM >= 130000
8565 jrte->joinleftcols = lcols;
8566 jrte->joinrightcols = rcols;
8567 jrte->joinmergedcols = 0;
8568#endif
8569 jrte->inFromCl = true;
8570
8571 sub->rtable = list_make3(Rcopy, Scopy, jrte);
8572
8573 rctx.npairs = 2;
8574 rctx.from[0] = R_idx; rctx.to[0] = 1;
8575 rctx.from[1] = S_idx; rctx.to[1] = 2;
8576 theta2 = oj_renum_mut(copyObject(theta), &rctx);
8577
8578 lr->rtindex = 1;
8579 rr->rtindex = 2;
8580 je->jointype = JOIN_INNER;
8581 je->larg = (Node *)lr;
8582 je->rarg = (Node *)rr;
8583 je->quals = theta2;
8584 je->isNatural = false;
8585 je->usingClause = NIL;
8586 je->rtindex = 3;
8587 fe->fromlist = list_make1(je);
8588 sub->jointree = fe;
8589
8590 if (select_r)
8591 for (i = 0; i < Rc->n; ++i) {
8592 Var *v = makeVar(1, Rc->attno[i], Rc->type[i], Rc->typmod[i],
8593 Rc->coll[i], 0);
8594 tl = lappend(tl, makeTargetEntry((Expr *)v, list_length(tl) + 1,
8595 pstrdup(Rc->name[i]), false));
8596 }
8597 if (select_s)
8598 for (i = 0; i < Sc->n; ++i) {
8599 Var *v = makeVar(2, Sc->attno[i], Sc->type[i], Sc->typmod[i],
8600 Sc->coll[i], 0);
8601 tl = lappend(tl, makeTargetEntry((Expr *)v, list_length(tl) + 1,
8602 pstrdup(Sc->name[i]), false));
8603 }
8604 sub->targetList = tl;
8605
8606 return sub;
8607}
8608
8609/** @brief Build the plain-scan subquery @c "SELECT R.cols FROM R". */
8610static Query *oj_build_rel_query(const constants_t *constants, Query *outer,
8611 RangeTblEntry *R, oj_cols *Rc) {
8612 Query *sub = makeNode(Query);
8613 RangeTblEntry *Rcopy;
8614 RangeTblRef *rtr = makeNode(RangeTblRef);
8615 FromExpr *fe = makeNode(FromExpr);
8616 List *tl = NIL;
8617 int i;
8618
8619 sub->commandType = CMD_SELECT;
8620 sub->canSetTag = true;
8621 Rcopy = oj_copy_rel(outer, sub, R);
8622 sub->rtable = list_make1(Rcopy);
8623 rtr->rtindex = 1;
8624 fe->fromlist = list_make1(rtr);
8625 sub->jointree = fe;
8626
8627 for (i = 0; i < Rc->n; ++i) {
8628 Var *v = makeVar(1, Rc->attno[i], Rc->type[i], Rc->typmod[i], Rc->coll[i],
8629 0);
8630 tl = lappend(tl, makeTargetEntry((Expr *)v, i + 1, pstrdup(Rc->name[i]),
8631 false));
8632 }
8633 sub->targetList = tl;
8634 return sub;
8635}
8636
8637/** @brief Build the difference subquery for the kept side of an outer join:
8638 * @c "SELECT X.cols FROM X EXCEPT ALL SELECT X.cols FROM R JOIN S ON θ",
8639 * where X = R when @p keep_left, else S.
8640 *
8641 * Processed natively as an EXCEPT (→ ProvSQL's −), yielding per distinct kept
8642 * tuple x the monus provenance X(x) ⊖ ⊕_match (R(r)⊗S(s)) =
8643 * X(x) ⊗ (1 ⊖ ⊕_match Y(y)) -- the null-padded antijoin branch of the join. */
8644static Query *oj_build_diff(const constants_t *constants, Query *outer,
8645 RangeTblEntry *R, RangeTblEntry *S, Index R_idx,
8646 Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta,
8647 bool keep_left) {
8648 RangeTblEntry *kept_rel = keep_left ? R : S;
8649 oj_cols *Kc = keep_left ? Rc : Sc;
8650 Query *ls = oj_build_rel_query(constants, outer, kept_rel, Kc);
8651 /* Matched-projection arm projects the kept side's columns only. */
8652 Query *mp = oj_build_join_query(constants, outer, R, S, R_idx, S_idx, Rc, Sc,
8653 theta, keep_left, !keep_left);
8654 RangeTblEntry *ls_rte = oj_make_subquery_rte(ls);
8655 RangeTblEntry *mp_rte = oj_make_subquery_rte(mp);
8656 Query *D = makeNode(Query);
8657 SetOperationStmt *so = makeNode(SetOperationStmt);
8658 RangeTblRef *l = makeNode(RangeTblRef), *r = makeNode(RangeTblRef);
8659 FromExpr *fe = makeNode(FromExpr);
8660 List *tl = NIL;
8661 int i;
8662
8663 D->commandType = CMD_SELECT;
8664 D->canSetTag = true;
8665 D->rtable = list_make2(ls_rte, mp_rte);
8666
8667 l->rtindex = 1;
8668 r->rtindex = 2;
8669 so->op = SETOP_EXCEPT;
8670 /* EXCEPT ALL = the pure multiset difference q₁−q₂ (NOT IN), which keeps every
8671 * kept-side row with its multiplicity -- exactly the antijoin's null-padded
8672 * rows. group_set_difference_right_arm groups the matched-projection arm so
8673 * the monus is X(x) ⊖ ⊕(R(r)⊗S(s)) = X(x) ⊗ (1 ⊖ ⊕ Y(y)). */
8674 so->all = true;
8675 so->larg = (Node *)l;
8676 so->rarg = (Node *)r;
8677 for (i = 0; i < Kc->n; ++i) {
8678 so->colTypes = lappend_oid(so->colTypes, Kc->type[i]);
8679 so->colTypmods = lappend_int(so->colTypmods, Kc->typmod[i]);
8680 so->colCollations = lappend_oid(so->colCollations, Kc->coll[i]);
8681 }
8682 D->setOperations = (Node *)so;
8683 fe->fromlist = NIL;
8684 D->jointree = fe;
8685
8686 for (i = 0; i < Kc->n; ++i) {
8687 Var *v = makeVar(1, i + 1, Kc->type[i], Kc->typmod[i], Kc->coll[i], 0);
8688 tl = lappend(tl, makeTargetEntry((Expr *)v, i + 1, pstrdup(Kc->name[i]),
8689 false));
8690 }
8691 D->targetList = tl;
8692 return D;
8693}
8694
8695/** @brief Build a null-padded antijoin arm in R-then-S column order.
8696 *
8697 * For @p keep_left it emits the left-unmatched rows
8698 * @c "SELECT D.cols, NULL,… FROM (R EXCEPT ALL R⋈S) D" (S columns NULL); for
8699 * the right side it emits @c "SELECT NULL,…, D.cols FROM (S EXCEPT ALL R⋈S) D"
8700 * (R columns NULL). The kept side's columns come from the difference @c D
8701 * (which also carries the antijoin provenance); the other side is typed NULL
8702 * constants. */
8703static Query *oj_build_antijoin(const constants_t *constants, Query *outer,
8704 RangeTblEntry *R, RangeTblEntry *S,
8705 Index R_idx, Index S_idx, oj_cols *Rc,
8706 oj_cols *Sc, Node *theta, bool keep_left) {
8707 Query *D = oj_build_diff(constants, outer, R, S, R_idx, S_idx, Rc, Sc, theta,
8708 keep_left);
8709 RangeTblEntry *D_rte = oj_make_subquery_rte(D);
8710 Query *A = makeNode(Query);
8711 RangeTblRef *rtr = makeNode(RangeTblRef);
8712 FromExpr *fe = makeNode(FromExpr);
8713 List *tl = NIL;
8714 int i, kept = 0; /* next column position in the difference D */
8715
8716 A->commandType = CMD_SELECT;
8717 A->canSetTag = true;
8718 A->rtable = list_make1(D_rte);
8719 rtr->rtindex = 1;
8720 fe->fromlist = list_make1(rtr);
8721 A->jointree = fe;
8722
8723 /* R columns: from D when keep_left, else typed NULL. */
8724 for (i = 0; i < Rc->n; ++i) {
8725 Expr *e;
8726 if (keep_left)
8727 e = (Expr *)makeVar(1, ++kept, Rc->type[i], Rc->typmod[i], Rc->coll[i],
8728 0);
8729 else
8730 e = (Expr *)makeNullConst(Rc->type[i], Rc->typmod[i], Rc->coll[i]);
8731 tl = lappend(tl, makeTargetEntry(e, list_length(tl) + 1,
8732 pstrdup(Rc->name[i]), false));
8733 }
8734 /* S columns: typed NULL when keep_left, else from D. */
8735 for (i = 0; i < Sc->n; ++i) {
8736 Expr *e;
8737 if (keep_left)
8738 e = (Expr *)makeNullConst(Sc->type[i], Sc->typmod[i], Sc->coll[i]);
8739 else
8740 e = (Expr *)makeVar(1, ++kept, Sc->type[i], Sc->typmod[i], Sc->coll[i],
8741 0);
8742 tl = lappend(tl, makeTargetEntry(e, list_length(tl) + 1,
8743 pstrdup(Sc->name[i]), false));
8744 }
8745 A->targetList = tl;
8746 return A;
8747}
8748
8749/** @brief Build the column-type lists (R-then-S, user columns only) shared by
8750 * every set-operation node of the replacement union. */
8751static void oj_build_coltype_lists(oj_cols *Rc, oj_cols *Sc, List **types,
8752 List **typmods, List **collations) {
8753 int i;
8754 *types = *typmods = *collations = NIL;
8755 for (i = 0; i < Rc->n; ++i) {
8756 *types = lappend_oid(*types, Rc->type[i]);
8757 *typmods = lappend_int(*typmods, Rc->typmod[i]);
8758 *collations = lappend_oid(*collations, Rc->coll[i]);
8759 }
8760 for (i = 0; i < Sc->n; ++i) {
8761 *types = lappend_oid(*types, Sc->type[i]);
8762 *typmods = lappend_int(*typmods, Sc->typmod[i]);
8763 *collations = lappend_oid(*collations, Sc->coll[i]);
8764 }
8765}
8766
8767/** @brief Build the UNION-ALL of the matched arm and the outer join's
8768 * antijoin arm(s): the full outer-join relation in R-then-S column
8769 * order with one combined @c provsql column.
8770 *
8771 * @p jointype selects which null-padded antijoin branches are added:
8772 * @c JOIN_LEFT adds the left (R-kept) branch, @c JOIN_RIGHT the right
8773 * (S-kept) branch, @c JOIN_FULL both. */
8774static Query *oj_build_union(const constants_t *constants, Query *outer,
8775 RangeTblEntry *R, RangeTblEntry *S, Index R_idx,
8776 Index S_idx, oj_cols *Rc, oj_cols *Sc, Node *theta,
8777 JoinType jointype) {
8778 List *arms = NIL; /* list of arm Query* */
8779 List *types, *typmods, *collations;
8780 Query *Q = makeNode(Query);
8781 FromExpr *fe = makeNode(FromExpr);
8782 List *tl = NIL;
8783 Node *tree;
8784 ListCell *lc;
8785 int i, pos = 0, k;
8786
8787 /* Matched arm (R ⋈ S), then the requested antijoin branches. */
8788 arms = lappend(arms, oj_build_join_query(constants, outer, R, S, R_idx,
8789 S_idx, Rc, Sc, theta, true, true));
8790 if (jointype == JOIN_LEFT || jointype == JOIN_FULL)
8791 arms = lappend(arms, oj_build_antijoin(constants, outer, R, S, R_idx,
8792 S_idx, Rc, Sc, theta, true));
8793 if (jointype == JOIN_RIGHT || jointype == JOIN_FULL)
8794 arms = lappend(arms, oj_build_antijoin(constants, outer, R, S, R_idx,
8795 S_idx, Rc, Sc, theta, false));
8796
8797 Q->commandType = CMD_SELECT;
8798 Q->canSetTag = true;
8799 foreach (lc, arms)
8800 Q->rtable = lappend(Q->rtable, oj_make_subquery_rte((Query *)lfirst(lc)));
8801
8802 oj_build_coltype_lists(Rc, Sc, &types, &typmods, &collations);
8803
8804 /* Left-deep UNION ALL tree over the arm RTEs (indices 1..n). Every
8805 * SetOperationStmt node carries its own colTypes lists -- process_set_
8806 * operation_union appends the UUID type to each node in place. */
8807 {
8808 RangeTblRef *first = makeNode(RangeTblRef);
8809 first->rtindex = 1;
8810 tree = (Node *)first;
8811 }
8812 for (k = 2; k <= list_length(arms); ++k) {
8813 SetOperationStmt *so = makeNode(SetOperationStmt);
8814 RangeTblRef *rtr = makeNode(RangeTblRef);
8815 rtr->rtindex = k;
8816 so->op = SETOP_UNION;
8817 so->all = true;
8818 so->larg = tree;
8819 so->rarg = (Node *)rtr;
8820 so->colTypes = list_copy(types);
8821 so->colTypmods = list_copy(typmods);
8822 so->colCollations = list_copy(collations);
8823 tree = (Node *)so;
8824 }
8825 Q->setOperations = tree;
8826 fe->fromlist = NIL;
8827 Q->jointree = fe;
8828
8829 /* Leader target list: one Var per user column, referencing the first arm. */
8830 for (i = 0; i < Rc->n; ++i) {
8831 Var *v = makeVar(1, ++pos, Rc->type[i], Rc->typmod[i], Rc->coll[i], 0);
8832 tl = lappend(tl, makeTargetEntry((Expr *)v, pos, pstrdup(Rc->name[i]),
8833 false));
8834 }
8835 for (i = 0; i < Sc->n; ++i) {
8836 Var *v = makeVar(1, ++pos, Sc->type[i], Sc->typmod[i], Sc->coll[i], 0);
8837 tl = lappend(tl, makeTargetEntry((Expr *)v, pos, pstrdup(Sc->name[i]),
8838 false));
8839 }
8840 Q->targetList = tl;
8841 return Q;
8842}
8843
8844/** @brief Walker context: detect a Var referencing the join RTE index. */
8845typedef struct oj_joinref_ctx {
8848
8849static bool oj_joinref_walker(Node *node, void *cx) {
8850 oj_joinref_ctx *c = (oj_joinref_ctx *)cx;
8851 if (node == NULL)
8852 return false;
8853 if (IsA(node, Var)) {
8854 Var *v = (Var *)node;
8855 return (v->varlevelsup == 0 && v->varno == c->join_idx);
8856 }
8857 return expression_tree_walker(node, oj_joinref_walker, cx);
8858}
8859
8860/** @brief True if any outer Var references the join RTE directly (USING /
8861 * whole-row / alias.col references the conservative remap cannot
8862 * resolve through @c joinaliasvars yet). */
8863static bool oj_refs_join_index(Query *q, Index join_idx) {
8865 c.join_idx = join_idx;
8866 if (oj_joinref_walker((Node *)q->targetList, &c))
8867 return true;
8868 if (q->jointree && q->jointree->quals &&
8869 oj_joinref_walker(q->jointree->quals, &c))
8870 return true;
8871 if (q->havingQual && oj_joinref_walker(q->havingQual, &c))
8872 return true;
8873 return false;
8874}
8875
8876/** @brief Outer Var remap context for the LEFT-join lowering: base-relation
8877 * Vars (R_idx / S_idx) are retargeted to the new subquery (new_idx)
8878 * with their attribute number mapped to the subquery column position. */
8879typedef struct oj_outer_ctx {
8881 AttrNumber *R_map, *S_map;
8882} oj_outer_ctx;
8883
8884static Node *oj_outer_remap(Node *node, void *cx) {
8885 oj_outer_ctx *c = (oj_outer_ctx *)cx;
8886 if (node == NULL)
8887 return NULL;
8888 if (IsA(node, Var)) {
8889 Var *v = (Var *)node;
8890 if (v->varlevelsup == 0 &&
8891 (v->varno == c->R_idx || v->varno == c->S_idx)) {
8892 v = (Var *)copyObject(v);
8893 if ((Index)((Var *)node)->varno == c->R_idx)
8894 v->varattno = c->R_map[v->varattno];
8895 else
8896 v->varattno = c->S_map[v->varattno];
8897 v->varno = c->new_idx;
8898#if PG_VERSION_NUM >= 160000
8899 v->varnullingrels = NULL;
8900#endif
8901#if PG_VERSION_NUM >= 130000
8902 v->varnosyn = 0;
8903 v->varattnosyn = 0;
8904#endif
8905 return (Node *)v;
8906 }
8907 return node;
8908 }
8909 return expression_tree_mutator(node, oj_outer_remap, cx);
8910}
8911
8912/**
8913 * @brief Walker: does the jointree fragment @p n reference a
8914 * provenance-tracked RTE of @p q?
8915 *
8916 * Join arms are @c RangeTblRef leaves or nested @c JoinExpr nodes. Base
8917 * relations and subqueries are tested with @c oj_rte_has_provsql; any
8918 * other RTE kind (CTE, function, VALUES) counts as tracked when its
8919 * @c eref exposes a @c provsql column, the same name convention the
8920 * provenance discovery matches on.
8921 */
8922static bool jointree_arm_has_tracked(const constants_t *constants,
8923 Query *q, Node *n)
8924{
8925 if (n == NULL)
8926 return false;
8927 if (IsA(n, RangeTblRef)) {
8928 RangeTblEntry *rte = rt_fetch(((RangeTblRef *)n)->rtindex, q->rtable);
8929 ListCell *lc;
8930 if (rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY)
8931 return oj_rte_has_provsql(constants, rte);
8932 foreach (lc, rte->eref->colnames) {
8933 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME))
8934 return true;
8935 }
8936 return false;
8937 }
8938 if (IsA(n, JoinExpr))
8939 return jointree_arm_has_tracked(constants, q, ((JoinExpr *)n)->larg) ||
8940 jointree_arm_has_tracked(constants, q, ((JoinExpr *)n)->rarg);
8941 if (IsA(n, FromExpr)) {
8942 ListCell *lc;
8943 foreach (lc, ((FromExpr *)n)->fromlist)
8944 if (jointree_arm_has_tracked(constants, q, (Node *)lfirst(lc)))
8945 return true;
8946 }
8947 return false;
8948}
8949
8950/**
8951 * @brief Refuse outer joins that survived @c lower_outer_joins with a
8952 * provenance-tracked relation on a null-padded side.
8953 *
8954 * The @c RTE_JOIN arm of @c get_provenance_attributes treats every join
8955 * like an inner join: a null-padded row would silently get the ⊗ of the
8956 * in-scope tokens, although it exists only in the worlds where the
8957 * tracked padded side has no match. ProvSQL-generated antijoins carry
8958 * the @c PROVSQL_JOIN_ALIAS sentinel and are sound (their monus accounts
8959 * for the padding). A user outer join whose padded side is fully
8960 * untracked is also sound as-is: its match set is deterministic, so the
8961 * padded rows correctly keep just the other arm's tokens. Everything
8962 * else raises, symmetrically with the semi/anti-join refusal.
8963 */
8964static void check_unlowered_outer_joins(const constants_t *constants,
8965 Query *q, Node *n)
8966{
8967 JoinExpr *je;
8968
8969 if (n == NULL)
8970 return;
8971 if (IsA(n, FromExpr)) {
8972 ListCell *lc;
8973 foreach (lc, ((FromExpr *)n)->fromlist)
8974 check_unlowered_outer_joins(constants, q, (Node *)lfirst(lc));
8975 return;
8976 }
8977 if (!IsA(n, JoinExpr))
8978 return;
8979
8980 je = (JoinExpr *)n;
8981 check_unlowered_outer_joins(constants, q, je->larg);
8982 check_unlowered_outer_joins(constants, q, je->rarg);
8983
8984 if (je->jointype != JOIN_LEFT && je->jointype != JOIN_RIGHT &&
8985 je->jointype != JOIN_FULL)
8986 return;
8987
8988 if (je->rtindex > 0) {
8989 RangeTblEntry *jrte = rt_fetch(je->rtindex, q->rtable);
8990 if (jrte->eref && jrte->eref->aliasname &&
8991 !strcmp(jrte->eref->aliasname, PROVSQL_JOIN_ALIAS))
8992 return;
8993 }
8994
8995 if (((je->jointype == JOIN_LEFT || je->jointype == JOIN_FULL) &&
8996 jointree_arm_has_tracked(constants, q, je->rarg)) ||
8997 ((je->jointype == JOIN_RIGHT || je->jointype == JOIN_FULL) &&
8998 jointree_arm_has_tracked(constants, q, je->larg)))
9000 "unsupported %s JOIN: a provenance-tracked relation sits on the "
9001 "null-padded side of a join that could not be lowered (only a "
9002 "two-relation outer join with no outer reference to the join RTE "
9003 "is); rewrite the query or remove provenance from the null-padded "
9004 "side",
9005 je->jointype == JOIN_LEFT ? "LEFT"
9006 : je->jointype == JOIN_RIGHT ? "RIGHT" : "FULL");
9007}
9008
9009/**
9010 * @brief Lower a top-level outer @c JOIN of two base relations into the
9011 * UNION-ALL of its matched and null-padded antijoin arms.
9012 *
9013 * Fires only on @c jointree->fromlist ==
9014 * @c [JoinExpr(JOIN_LEFT|JOIN_RIGHT|JOIN_FULL, RTR, RTR)] whose arms are
9015 * provenance-tracked base relations and where no outer Var references the join
9016 * RTE directly. Everything else falls through unchanged. Returns @c true if
9017 * the query was rewritten.
9018 */
9019static bool lower_outer_joins(const constants_t *constants, Query *q) {
9020 JoinExpr *je;
9021 RangeTblRef *lref, *rref;
9022 Index R_idx, S_idx, join_idx;
9023 RangeTblEntry *R_rte, *S_rte;
9024 oj_cols Rc, Sc;
9025 Node *theta;
9026 Query *Q;
9027 AttrNumber *R_map, *S_map;
9028 int ncolR, ncolS, i;
9029 oj_outer_ctx octx;
9030
9031 if (q->commandType != CMD_SELECT)
9032 return false;
9033 if (!q->jointree || list_length(q->jointree->fromlist) != 1)
9034 return false;
9035 if (!IsA(linitial(q->jointree->fromlist), JoinExpr))
9036 return false;
9037 je = (JoinExpr *)linitial(q->jointree->fromlist);
9038 if (je->jointype != JOIN_LEFT && je->jointype != JOIN_RIGHT &&
9039 je->jointype != JOIN_FULL)
9040 return false;
9041 if (!IsA(je->larg, RangeTblRef) || !IsA(je->rarg, RangeTblRef))
9042 return false;
9043
9044 lref = (RangeTblRef *)je->larg;
9045 rref = (RangeTblRef *)je->rarg;
9046 R_idx = lref->rtindex;
9047 S_idx = rref->rtindex;
9048 join_idx = je->rtindex;
9049 R_rte = list_nth_node(RangeTblEntry, q->rtable, R_idx - 1);
9050 S_rte = list_nth_node(RangeTblEntry, q->rtable, S_idx - 1);
9051
9052 if ((R_rte->rtekind != RTE_RELATION && R_rte->rtekind != RTE_SUBQUERY) ||
9053 (S_rte->rtekind != RTE_RELATION && S_rte->rtekind != RTE_SUBQUERY))
9054 return false;
9055 if ((R_rte->rtekind == RTE_SUBQUERY && R_rte->lateral) ||
9056 (S_rte->rtekind == RTE_SUBQUERY && S_rte->lateral))
9057 return false;
9058 if (!oj_rte_has_provsql(constants, R_rte) ||
9059 !oj_rte_has_provsql(constants, S_rte))
9060 return false;
9061 if (oj_refs_join_index(q, join_idx))
9062 return false;
9063
9064#if PG_VERSION_NUM >= 180000
9065 /* Flatten PG 18's synthetic RTE_GROUP so grouped-column Vars are base-
9066 * relation Vars again, which the remap below can retarget. */
9068#endif
9069
9070 theta = je->quals;
9071 oj_collect_cols(constants, R_rte, &Rc);
9072 oj_collect_cols(constants, S_rte, &Sc);
9073 ncolR = (R_rte->rtekind == RTE_SUBQUERY)
9074 ? list_length(R_rte->subquery->targetList)
9075 : list_length(R_rte->eref->colnames);
9076 ncolS = (S_rte->rtekind == RTE_SUBQUERY)
9077 ? list_length(S_rte->subquery->targetList)
9078 : list_length(S_rte->eref->colnames);
9079
9080 Q = oj_build_union(constants, q, R_rte, S_rte, R_idx, S_idx, &Rc, &Sc, theta,
9081 je->jointype);
9082
9083 /* The combined provenance now lives in the replacement subquery Q. The
9084 * original arm RTEs are left orphaned in the outer range table; neutralise
9085 * them so get_provenance_attributes does not pick them up again. */
9088
9089 R_map = (AttrNumber *)palloc0((ncolR + 1) * sizeof(AttrNumber));
9090 S_map = (AttrNumber *)palloc0((ncolS + 1) * sizeof(AttrNumber));
9091 for (i = 0; i < Rc.n; ++i)
9092 R_map[Rc.attno[i]] = i + 1;
9093 for (i = 0; i < Sc.n; ++i)
9094 S_map[Sc.attno[i]] = Rc.n + i + 1;
9095
9096 /* Replace the JOIN RTE slot in place with the new subquery, reusing the
9097 * join's range-table index for the outer reference. Reusing the *join*
9098 * slot (rather than the left relation's) leaves no orphaned RTE_JOIN in the
9099 * range table -- an orphaned join RTE without a matching JoinExpr trips the
9100 * planner ("so where are the outer joins?"). The two base-relation RTEs are
9101 * left orphaned, which the planner tolerates (they are simply not scanned). */
9102 {
9103 RangeTblEntry *J_rte =
9104 list_nth_node(RangeTblEntry, q->rtable, join_idx - 1);
9105 List *cn = NIL;
9106
9107 J_rte->rtekind = RTE_SUBQUERY;
9108 J_rte->subquery = Q;
9109 J_rte->jointype = JOIN_INNER;
9110 J_rte->joinaliasvars = NIL;
9111#if PG_VERSION_NUM >= 130000
9112 J_rte->joinleftcols = NIL;
9113 J_rte->joinrightcols = NIL;
9114 J_rte->joinmergedcols = 0;
9115#endif
9116 J_rte->relid = InvalidOid;
9117 J_rte->relkind = 0;
9118#if PG_VERSION_NUM >= 120000
9119 J_rte->rellockmode = 0;
9120#endif
9121 J_rte->inh = false;
9122 J_rte->lateral = false;
9123 J_rte->tablesample = NULL;
9124#if PG_VERSION_NUM >= 160000
9125 J_rte->perminfoindex = 0;
9126#else
9127 J_rte->selectedCols = NULL;
9128 J_rte->insertedCols = NULL;
9129 J_rte->updatedCols = NULL;
9130 J_rte->requiredPerms = ACL_SELECT;
9131#endif
9132 for (i = 0; i < Rc.n; ++i)
9133 cn = lappend(cn, makeString(pstrdup(Rc.name[i])));
9134 for (i = 0; i < Sc.n; ++i)
9135 cn = lappend(cn, makeString(pstrdup(Sc.name[i])));
9136 J_rte->eref = makeAlias("unnamed_subquery", cn);
9137 }
9138
9139 {
9140 RangeTblRef *newr = makeNode(RangeTblRef);
9141 newr->rtindex = join_idx;
9142 q->jointree->fromlist = list_make1(newr);
9143 }
9144
9145 octx.R_idx = R_idx;
9146 octx.S_idx = S_idx;
9147 octx.new_idx = join_idx;
9148 octx.R_map = R_map;
9149 octx.S_map = S_map;
9150 q->targetList = (List *)oj_outer_remap((Node *)q->targetList, &octx);
9151 if (q->jointree->quals)
9152 q->jointree->quals = oj_outer_remap(q->jointree->quals, &octx);
9153 if (q->havingQual)
9154 q->havingQual = oj_outer_remap(q->havingQual, &octx);
9155
9156 return true;
9157}
9158
9159/* -------------------------------------------------------------------------
9160 * Scalar-subquery decorrelation
9161 *
9162 * A correlated scalar subquery (SELECT Q.x FROM Q WHERE corr), used as a
9163 * top-level target-list entry of a query whose FROM is a single tracked base
9164 * relation R, is decorrelated to a LEFT JOIN:
9165 *
9166 * SELECT R.cols, choose(Q.x)
9167 * FROM R LEFT JOIN Q ON corr
9168 * GROUP BY R.cols
9169 * HAVING count(Q.key) <= 1
9170 *
9171 * The corrected outer-join lowering (lower_outer_joins, which runs next)
9172 * supplies the 0-match NULL row, choose() picks the single matched value, and
9173 * the count<=1 HAVING gates out the (SQL-illegal) >=2-match worlds -- no
9174 * gate-level special case. Anything outside this shape returns false and the
9175 * caller's "Subqueries not supported" error still fires.
9176 * ------------------------------------------------------------------------- */
9177
9178/** @brief Mutator: lift a scalar subquery's body into the outer query level.
9179 * Var(level 0, varno @c q_old) -> Var(level 0, varno @c q_new) [the pulled-up
9180 * Q]; the correlated outer Var(level 1) -> Var(level 0). */
9181typedef struct oj_decorr_ctx {
9182 Index q_old;
9183 Index q_new;
9185
9186static Node *oj_decorr_var_mut(Node *node, void *cx) {
9187 oj_decorr_ctx *c = (oj_decorr_ctx *)cx;
9188 if (node == NULL)
9189 return NULL;
9190 if (IsA(node, Var)) {
9191 Var *v = (Var *)node;
9192 if (v->varlevelsup == 1) {
9193 v = (Var *)copyObject(v);
9194 v->varlevelsup = 0;
9195 return (Node *)v;
9196 }
9197 if (v->varlevelsup == 0 && v->varno == c->q_old) {
9198 v = (Var *)copyObject(v);
9199 v->varno = c->q_new;
9200#if PG_VERSION_NUM >= 130000
9201 v->varnosyn = 0;
9202 v->varattnosyn = 0;
9203#endif
9204 return (Node *)v;
9205 }
9206 return node;
9207 }
9208 return expression_tree_mutator(node, oj_decorr_var_mut, cx);
9209}
9210
9211/** @brief Walker: count SubLink nodes (capturing the first), and capture a Var
9212 * referencing varno @p target_varno (level 0) -- used to find a Q column for
9213 * the count() key. */
9214typedef struct oj_sublink_scan {
9217 Index target_varno; /* find any level-0 Var on this rel */
9220
9221static bool oj_sublink_scan_walker(Node *node, void *cx) {
9223 if (node == NULL)
9224 return false;
9225 if (IsA(node, SubLink) && !sublink_is_inert((SubLink *)node)) {
9226 s->n_sublinks++;
9227 if (s->found_sublink == NULL)
9228 s->found_sublink = (SubLink *)node;
9229 }
9230 if (IsA(node, Var)) {
9231 Var *v = (Var *)node;
9232 if (s->found_var == NULL && v->varlevelsup == 0 &&
9233 v->varno == s->target_varno)
9234 s->found_var = v;
9235 }
9236 return expression_tree_walker(node, oj_sublink_scan_walker, cx);
9237}
9238
9239/** @brief Mutator: replace the specific @c SubLink node @p target (by pointer)
9240 * with @p replacement. */
9241typedef struct oj_sl_replace_ctx {
9242 SubLink *target;
9245
9246static Node *oj_sl_replace_mut(Node *node, void *cx) {
9248 if (node == NULL)
9249 return NULL;
9250 if (node == (Node *)c->target)
9251 return c->replacement;
9252 return expression_tree_mutator(node, oj_sl_replace_mut, cx);
9253}
9254
9255/** @brief Walker: true if the subtree contains the specific SubLink @p cx. */
9256static bool oj_contains_sublink_walker(Node *node, void *cx) {
9257 if (node == NULL)
9258 return false;
9259 if (node == (Node *)cx)
9260 return true;
9261 return expression_tree_walker(node, oj_contains_sublink_walker, cx);
9262}
9263
9264/** @brief Build an @c Aggref for a single-argument aggregate. */
9265static Aggref *oj_make_aggref(Oid aggfnoid, Oid aggtype, Oid argtype,
9266 Expr *arg) {
9267 Aggref *agg = makeNode(Aggref);
9268 TargetEntry *te = makeNode(TargetEntry);
9269 te->resno = 1;
9270 te->expr = arg;
9271 agg->aggfnoid = aggfnoid;
9272 agg->aggtype = aggtype;
9273 agg->aggtranstype = InvalidOid;
9274 agg->aggargtypes = list_make1_oid(argtype);
9275 agg->args = list_make1(te);
9276 agg->aggkind = AGGKIND_NORMAL;
9277 agg->aggsplit = AGGSPLIT_SIMPLE;
9278 agg->location = -1;
9279#if PG_VERSION_NUM >= 140000
9280 agg->aggno = agg->aggtransno = -1;
9281#endif
9282 return agg;
9283}
9284
9285/**
9286 * @brief Build @c "count(Q.key) <op> n" over the decorrelated LEFT-JOIN group.
9287 *
9288 * @p found_var is some Q column from the correlation (NULL on the null-padded
9289 * antijoin rows, so it counts only genuine matches); it is re-pointed to the
9290 * pulled-up Q at @p q_idx. Used for the scalar-subquery at-most-one-row gate
9291 * (@c "<= 1") and the WHERE-comparison non-empty gate (@c ">= 1").
9292 */
9293static OpExpr *oj_count_cmp(Var *found_var, Index q_idx, const char *opstr,
9294 int64 n) {
9295 Var *qkey = (Var *)copyObject((Node *)found_var);
9296 Aggref *cnt;
9297 OpExpr *op = makeNode(OpExpr);
9298 Oid o;
9299
9300 qkey->varno = q_idx;
9301#if PG_VERSION_NUM >= 130000
9302 qkey->varnosyn = 0;
9303 qkey->varattnosyn = 0;
9304#endif
9305 cnt = oj_make_aggref(F_COUNT_ANY, INT8OID, qkey->vartype, (Expr *)qkey);
9306
9307 o = OpernameGetOprid(list_make1(makeString((char *)opstr)), INT8OID, INT8OID);
9308 op->opno = o;
9309 op->opfuncid = get_opcode(o);
9310 op->opresulttype = BOOLOID;
9311 op->opcollid = InvalidOid;
9312 op->inputcollid = InvalidOid;
9313 op->args = list_make2(cnt, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
9314 Int64GetDatum(n), false, FLOAT8PASSBYVAL));
9315 op->location = -1;
9316 return op;
9317}
9318
9319/** @brief Build @c "count(DISTINCT v) <op> n" -- the at-most-one-DISTINCT-value
9320 * gate of a @c "SELECT DISTINCT v" body (NULLs, on the null-padded antijoin
9321 * rows, are ignored by @c count, so an empty group counts 0). */
9322static OpExpr *oj_count_distinct_cmp(Expr *valexpr, const char *opstr,
9323 int64 n) {
9324 Aggref *cnt = makeNode(Aggref);
9325 TargetEntry *arg = makeTargetEntry((Expr *)copyObject((Node *)valexpr), 1,
9326 NULL, false);
9327 SortGroupClause *sgc = makeNode(SortGroupClause);
9328 OpExpr *op = makeNode(OpExpr);
9329 Oid o;
9330
9331 arg->ressortgroupref = 1;
9332 sgc->tleSortGroupRef = 1;
9333 get_sort_group_operators(exprType((Node *)valexpr), false, true, false,
9334 &sgc->sortop, &sgc->eqop, NULL, &sgc->hashable);
9335
9336 cnt->aggfnoid = F_COUNT_ANY;
9337 cnt->aggtype = INT8OID;
9338 cnt->aggtranstype = InvalidOid;
9339 cnt->aggargtypes = list_make1_oid(exprType((Node *)valexpr));
9340 cnt->args = list_make1(arg);
9341 cnt->aggdistinct = list_make1(sgc);
9342 cnt->aggkind = AGGKIND_NORMAL;
9343 cnt->aggsplit = AGGSPLIT_SIMPLE;
9344 cnt->location = -1;
9345#if PG_VERSION_NUM >= 140000
9346 cnt->aggno = cnt->aggtransno = -1;
9347#endif
9348
9349 o = OpernameGetOprid(list_make1(makeString((char *)opstr)), INT8OID, INT8OID);
9350 op->opno = o;
9351 op->opfuncid = get_opcode(o);
9352 op->opresulttype = BOOLOID;
9353 op->opcollid = InvalidOid;
9354 op->inputcollid = InvalidOid;
9355 op->args = list_make2(cnt, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
9356 Int64GetDatum(n), false, FLOAT8PASSBYVAL));
9357 op->location = -1;
9358 return op;
9359}
9360
9361/** @brief Var-remap context for the FROM-wrapping pre-step: a Var at
9362 * @c target_level on relation @c varno / attribute @c varattno is retargeted to
9363 * @c newidx column @c pos[varno][varattno]. Descent into @c skip (the
9364 * SubLink) is suppressed. */
9365typedef struct oj_wrap_ctx {
9367 Index newidx;
9369 int **pos; /* pos[varno][varattno] -> R' column (1-based), or 0 */
9370 SubLink *skip;
9371} oj_wrap_ctx;
9372
9373static Node *oj_wrap_remap_mut(Node *node, void *cx) {
9374 oj_wrap_ctx *c = (oj_wrap_ctx *)cx;
9375 if (node == NULL)
9376 return NULL;
9377 if (c->skip && node == (Node *)c->skip)
9378 return node;
9379 if (IsA(node, Var)) {
9380 Var *v = (Var *)node;
9381 if ((int)v->varlevelsup == c->target_level && (int)v->varno >= 1 &&
9382 (int)v->varno <= c->rtlen && c->pos[v->varno] != NULL &&
9383 v->varattno >= 1 && c->pos[v->varno][v->varattno] > 0) {
9384 Var *nv = (Var *)copyObject(v);
9385 nv->varno = c->newidx;
9386 nv->varattno = c->pos[v->varno][v->varattno];
9387#if PG_VERSION_NUM >= 130000
9388 nv->varnosyn = 0;
9389 nv->varattnosyn = 0;
9390#endif
9391 return (Node *)nv;
9392 }
9393 return node;
9394 }
9395 return expression_tree_mutator(node, oj_wrap_remap_mut, cx);
9396}
9397
9398/**
9399 * @brief Wrap a non-single-relation outer FROM into a derived subquery R' so a
9400 * scalar subquery can be decorrelated onto it.
9401 *
9402 * Builds R' = the outer FROM (all its base relations + join RTEs) with the
9403 * non-subquery WHERE conjuncts, exposing every base-relation user column. The
9404 * outer query is rewritten to @c "FROM R'" with all references (the target
9405 * list, the SubLink's correlation at level 1, and -- for a WHERE SubLink -- the
9406 * conjunct that will move to HAVING) retargeted to R''s columns. The FROM must
9407 * consist only of base relations and join RTEs (no nested subqueries / VALUES /
9408 * functions); returns @c false otherwise, leaving @p q untouched.
9409 */
9410static bool oj_wrap_outer_from(const constants_t *constants, Query *q,
9411 SubLink *sl, bool in_where) {
9412 int rtlen = list_length(q->rtable);
9413 int **pos = (int **)palloc0((rtlen + 1) * sizeof(int *));
9414 Query *Rp = makeNode(Query);
9415 RangeTblEntry *rp_rte;
9416 RangeTblRef *rtr = makeNode(RangeTblRef);
9417 FromExpr *outer_fe = makeNode(FromExpr);
9418 List *rp_tl = NIL;
9419 Node *subquery_conj = NULL; /* the WHERE conjunct holding the SubLink */
9420 List *kept_conj = NIL;
9421 oj_wrap_ctx wc;
9422 ListCell *lc;
9423 int idx, posn = 0;
9424 bool any_tracked = false;
9425
9426 /* Only base relations and join RTEs are supported in the wrapped FROM. */
9427 idx = 0;
9428 foreach (lc, q->rtable) {
9429 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
9430 ++idx;
9431 if (r->rtekind == RTE_RELATION) {
9432 if (oj_rte_has_provsql(constants, r))
9433 any_tracked = true;
9434 } else if (r->rtekind != RTE_JOIN) {
9435 return false;
9436 }
9437 }
9438
9439 /* R' exposes every base-relation user column; record (rtindex,attno)->pos. */
9440 idx = 0;
9441 foreach (lc, q->rtable) {
9442 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
9443 oj_cols rc;
9444 int j;
9445 ++idx;
9446 if (r->rtekind != RTE_RELATION)
9447 continue;
9448 oj_collect_cols(constants, r, &rc);
9449 pos[idx] = (int *)palloc0((list_length(r->eref->colnames) + 1) * sizeof(int));
9450 for (j = 0; j < rc.n; ++j) {
9451 Var *v = makeVar(idx, rc.attno[j], rc.type[j], rc.typmod[j], rc.coll[j], 0);
9452 rp_tl = lappend(rp_tl, makeTargetEntry((Expr *)v, ++posn,
9453 pstrdup(rc.name[j]), false));
9454 pos[idx][rc.attno[j]] = posn;
9455 }
9456 }
9457
9458 /* When no FROM relation is provenance-tracked, the outer tuples are certain
9459 * (exactly like joining an untracked table): give R' a synthetic gate_one()
9460 * provsql column so the decorrelation / outer-join lowering treat it as a
9461 * certain-provenance arm. No warning -- no provenance is lost, the outer
9462 * simply contributes the identity and the subquery's provenance flows. */
9463 if (!any_tracked) {
9464 FuncExpr *one = makeNode(FuncExpr);
9465 one->funcid = constants->OID_FUNCTION_GATE_ONE;
9466 one->funcresulttype = constants->OID_TYPE_UUID;
9467 one->args = NIL;
9468 one->location = -1;
9469 rp_tl = lappend(rp_tl, makeTargetEntry((Expr *)one, ++posn,
9470 pstrdup(PROVSQL_COLUMN_NAME), false));
9471 }
9472
9473 /* Split the WHERE: the conjunct holding the SubLink (for a WHERE SubLink)
9474 * stays in the outer query (it becomes HAVING); the rest move into R'. */
9475 if (q->jointree->quals) {
9476 Node *quals = q->jointree->quals;
9477 List *conjs = (IsA(quals, BoolExpr) &&
9478 ((BoolExpr *)quals)->boolop == AND_EXPR)
9479 ? ((BoolExpr *)quals)->args
9480 : list_make1(quals);
9481 foreach (lc, conjs) {
9482 Node *cnode = (Node *)lfirst(lc);
9483 if (in_where && oj_contains_sublink_walker(cnode, sl))
9484 subquery_conj = cnode;
9485 else
9486 kept_conj = lappend(kept_conj, cnode);
9487 }
9488 }
9489
9490 /* Build R'. */
9491 Rp->commandType = CMD_SELECT;
9492 Rp->canSetTag = true;
9493 Rp->rtable = q->rtable;
9494 Rp->jointree = makeNode(FromExpr);
9495 Rp->jointree->fromlist = q->jointree->fromlist;
9496 Rp->jointree->quals =
9497 (kept_conj == NIL)
9498 ? NULL
9499 : (list_length(kept_conj) == 1 ? (Node *)linitial(kept_conj)
9500 : (Node *)makeBoolExpr(AND_EXPR, kept_conj,
9501 -1));
9502 Rp->targetList = rp_tl;
9503#if PG_VERSION_NUM >= 160000
9504 Rp->rteperminfos = q->rteperminfos;
9505#endif
9506
9507 /* Retarget references to R': the outer target list (skipping the SubLink),
9508 * the SubLink body's correlation (level 1), and the retained subquery
9509 * conjunct (level 0). */
9510 wc.newidx = 1;
9511 wc.rtlen = rtlen;
9512 wc.pos = pos;
9513
9514 wc.target_level = 0;
9515 wc.skip = sl;
9516 q->targetList = (List *)oj_wrap_remap_mut((Node *)q->targetList, &wc);
9517 if (subquery_conj)
9518 subquery_conj = oj_wrap_remap_mut(subquery_conj, &wc);
9519
9520 /* The SubLink body's correlated (level-1) references to the FROM relations
9521 * become level-1 references to R'. Walk its target list and quals directly
9522 * (the mutator does not descend into a Query node). */
9523 {
9524 Query *sub = (Query *)sl->subselect;
9525 wc.target_level = 1;
9526 wc.skip = NULL;
9527 sub->targetList = (List *)oj_wrap_remap_mut((Node *)sub->targetList, &wc);
9528 if (sub->jointree && sub->jointree->quals)
9529 sub->jointree->quals = oj_wrap_remap_mut(sub->jointree->quals, &wc);
9530 }
9531
9532 /* Rebuild the outer query: FROM R', WHERE = the retained subquery conjunct. */
9533 rp_rte = oj_make_subquery_rte(Rp);
9534 q->rtable = list_make1(rp_rte);
9535#if PG_VERSION_NUM >= 160000
9536 q->rteperminfos = NIL;
9537#endif
9538 rtr->rtindex = 1;
9539 outer_fe->fromlist = list_make1(rtr);
9540 outer_fe->quals = subquery_conj;
9541 q->jointree = outer_fe;
9542 return true;
9543}
9544
9545/**
9546 * @brief Is @p sub a subselect that the predicate-sublink rewrite can turn into
9547 * a correlated @c "SELECT count(*) FROM Q WHERE corr"?
9548 *
9549 * Requires a body FROM over base relations, at least one of them tracked --
9550 * a single relation Q, or a comma-join that @c oj_wrap_body_from collapses
9551 * downstream into one derived cross-product subquery (untracked relations
9552 * ride along in the derived subquery, contributing the neutral provenance
9553 * they would in any join) -- and a (correlated) WHERE, with none of the
9554 * shapes @c decorrelate_scalar_sublinks rejects downstream (aggregates,
9555 * grouping, set ops, LIMIT, nested sublinks, CTEs). The targetList is
9556 * replaced wholesale by @c count(*), so its width is irrelevant here.
9557 * @p corr_supplied is set for @c IN / @c NOT @c IN, whose correlation comes
9558 * from the testexpr and is ANDed into the (possibly empty) subselect WHERE
9559 * by the caller. Bodies arrive here already canonicalised to the
9560 * comma-join form by @c normalize_inner_joins (a body it declined -- an
9561 * outer join, a whole-row join reference -- fails the fromlist check).
9562 */
9564 Query *sub,
9565 bool corr_supplied) {
9566 ListCell *lc;
9567 if (!IsA(sub, Query) || sub->commandType != CMD_SELECT)
9568 return false;
9569 if (sub->groupClause || sub->groupingSets || sub->hasAggs ||
9570 sub->distinctClause || sub->setOperations || sub->hasWindowFuncs ||
9571 sub->hasSubLinks || sub->limitCount || sub->limitOffset || sub->cteList ||
9572 sub->rtable == NIL)
9573 return false;
9574 if (!sub->jointree || (!corr_supplied && !sub->jointree->quals))
9575 return false; /* an uncorrelated predicate has no Q key for count() */
9576 /* Base relations only, at least one tracked (the multi-relation body is
9577 * collapsed downstream by oj_wrap_body_from into one derived cross-product
9578 * subquery D -- same preconditions checked here). */
9579 {
9580 bool any_tracked = false;
9581 foreach (lc, sub->rtable) {
9582 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
9583 if (r->rtekind != RTE_RELATION)
9584 return false;
9585 if (oj_rte_has_provsql(constants, r))
9586 any_tracked = true;
9587 }
9588 if (!any_tracked)
9589 return false; /* untracked body: PostgreSQL's native sublink machinery */
9590 }
9591 foreach (lc, sub->jointree->fromlist) {
9592 if (!IsA(lfirst(lc), RangeTblRef))
9593 return false;
9594 }
9595 return true;
9596}
9597
9598/**
9599 * @brief Turn a predicate subselect into the boolean @c "(SELECT count(*) FROM Q
9600 * WHERE corr) >= 1" (semijoin) or @c "... = 0" (antijoin).
9601 *
9602 * @c EXISTS / @c IN are existence tests (@c "⊕Q present"), so they are exactly
9603 * @c "count(*) >= 1"; @c NOT @c EXISTS / @c NOT @c IN are their antijoin duals,
9604 * @c "count(*) = 0". Lowering them to a correlated count() comparison lets the
9605 * aggregate-body arm of @c decorrelate_scalar_sublinks do the rest: it rewrites
9606 * @c count(*) to @c count(Q.key) over the @c "R ⟕ Q" group (so the null-padded
9607 * antijoin row is not counted) and lifts the comparison into @c HAVING -- i.e.
9608 * the semijoin @c R⊗⊕Q and the antijoin @c R⊗(1⊖⊕Q) fall out of the existing
9609 * outer-join lowering.
9610 *
9611 * @p extra_corr (for @c IN / @c NOT @c IN) is the @c "Q.col = x" correlation
9612 * lifted out of the testexpr; it is ANDed into the subselect's WHERE. @c EXISTS
9613 * passes @c NULL, its correlation already living in the subselect.
9614 */
9615static Node *build_count_predicate(Query *subselect, Node *extra_corr,
9616 bool antijoin) {
9617 Query *sq = (Query *)copyObject(subselect);
9618 Aggref *cnt = makeNode(Aggref);
9619 SubLink *sl = makeNode(SubLink);
9620 OpExpr *op = makeNode(OpExpr);
9621 Oid o;
9622
9623 if (extra_corr)
9624 sq->jointree->quals =
9625 sq->jointree->quals
9626 ? (Node *)makeBoolExpr(AND_EXPR,
9627 list_make2(sq->jointree->quals, extra_corr), -1)
9628 : extra_corr;
9629
9630 cnt->aggfnoid = F_COUNT_; /* count(*) */
9631 cnt->aggtype = INT8OID;
9632 cnt->aggtranstype = InvalidOid;
9633 cnt->aggargtypes = NIL;
9634 cnt->args = NIL;
9635 cnt->aggstar = true;
9636 cnt->aggkind = AGGKIND_NORMAL;
9637 cnt->aggsplit = AGGSPLIT_SIMPLE;
9638 cnt->location = -1;
9639#if PG_VERSION_NUM >= 140000
9640 cnt->aggno = cnt->aggtransno = -1;
9641#endif
9642 sq->targetList =
9643 list_make1(makeTargetEntry((Expr *)cnt, 1, pstrdup("count"), false));
9644 sq->hasAggs = true;
9645
9646 sl->subLinkType = EXPR_SUBLINK;
9647 sl->subselect = (Node *)sq;
9648 sl->testexpr = NULL;
9649 sl->operName = NIL;
9650 sl->location = -1;
9651
9652 o = OpernameGetOprid(list_make1(makeString(antijoin ? "=" : ">=")), INT8OID,
9653 INT8OID);
9654 op->opno = o;
9655 op->opfuncid = get_opcode(o);
9656 op->opresulttype = BOOLOID;
9657 op->opcollid = InvalidOid;
9658 op->inputcollid = InvalidOid;
9659 op->args = list_make2(sl, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
9660 Int64GetDatum(antijoin ? 0 : 1), false,
9661 FLOAT8PASSBYVAL));
9662 op->location = -1;
9663 return (Node *)op;
9664}
9665
9666/** @brief Context for @c oj_param_repl_mut. */
9667typedef struct {
9671
9672/** @brief Replace every @c PARAM_SUBLINK with @p paramid by @p replacement. */
9673static Node *oj_param_repl_mut(Node *node, void *cx) {
9675 if (node == NULL)
9676 return NULL;
9677 if (IsA(node, Param)) {
9678 Param *p = (Param *)node;
9679 if (p->paramkind == PARAM_SUBLINK && p->paramid == c->paramid)
9680 return copyObject(c->replacement);
9681 return node;
9682 }
9683 return expression_tree_mutator(node, oj_param_repl_mut, cx);
9684}
9685
9686/** @brief Does the body's range table reach at least one provenance-tracked
9687 * relation? Bodies over untracked relations only are left to PostgreSQL's
9688 * native sublink machinery. */
9689static bool oj_body_has_tracked_relation(const constants_t *constants,
9690 Query *body) {
9691 ListCell *lc;
9692 foreach (lc, body->rtable) {
9693 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
9694 if ((r->rtekind == RTE_RELATION || r->rtekind == RTE_SUBQUERY) &&
9695 oj_rte_has_provsql(constants, r))
9696 return true;
9697 }
9698 return false;
9699}
9700
9701/**
9702 * @brief Normalize quantified comparisons over a single bare-aggregate body
9703 * into plain scalar comparisons.
9704 *
9705 * An aggregate body without @c GROUP @c BY returns exactly one row, so
9706 * @c "x op ANY (SELECT agg(..) …)" and @c "x op ALL (…)" are the scalar
9707 * comparison @c "x op (SELECT agg(..) …)" (NULL semantics included), and a
9708 * @c NOT-wrapped form (@c NOT @c IN) is the negator-operator comparison. The
9709 * conjunct's @c PARAM_SUBLINK placeholder is substituted by the
9710 * @c EXPR_SUBLINK body, after which the scalar paths lower it: the
9711 * HAVING-gated cross-joined subquery for a constant comparand
9712 * (@c move_uncorrelated_where_predicates) or the @c "R ⟕ Q" decorrelation for
9713 * an outer-column comparand (@c decorrelate_scalar_sublinks). Runs before
9714 * @c rewrite_uncorrelated_antijoin so a normalized count() comparison that is
9715 * true on the empty body still gets the antijoin treatment there.
9716 */
9718 const constants_t *constants, Query *q) {
9719 Node *quals;
9720 List *conjs, *newconjs = NIL;
9721 ListCell *lc;
9722 bool changed = false;
9723
9724 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
9725 !q->jointree->quals)
9726 return false;
9727
9728 quals = q->jointree->quals;
9729 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
9730 ? ((BoolExpr *)quals)->args
9731 : list_make1(quals);
9732
9733 foreach (lc, conjs) {
9734 Node *c = (Node *)lfirst(lc);
9735 Node *inner = c, *rewritten = NULL;
9736 bool neg = false;
9737
9738 if (IsA(c, BoolExpr) && ((BoolExpr *)c)->boolop == NOT_EXPR &&
9739 list_length(((BoolExpr *)c)->args) == 1) {
9740 neg = true;
9741 inner = (Node *)linitial(((BoolExpr *)c)->args);
9742 }
9743 if (IsA(inner, SubLink) &&
9744 (((SubLink *)inner)->subLinkType == ANY_SUBLINK ||
9745 ((SubLink *)inner)->subLinkType == ALL_SUBLINK) &&
9746 IsA(((SubLink *)inner)->subselect, Query)) {
9747 SubLink *sl = (SubLink *)inner;
9748 Query *body = (Query *)sl->subselect;
9749 if (body->commandType == CMD_SELECT && body->hasAggs &&
9750 !body->groupClause && !body->groupingSets && !body->setOperations &&
9751 !body->hasWindowFuncs && !body->hasSubLinks && !body->limitCount &&
9752 !body->limitOffset && !body->cteList &&
9753 list_length(body->targetList) == 1 &&
9754 IsA(((TargetEntry *)linitial(body->targetList))->expr, Aggref) &&
9755 sl->testexpr && IsA(sl->testexpr, OpExpr) &&
9756 list_length(((OpExpr *)sl->testexpr)->args) == 2 &&
9757 oj_body_has_tracked_relation(constants, body)) {
9758 OpExpr *op = (OpExpr *)copyObject(sl->testexpr);
9759 Oid opno = neg ? get_negator(op->opno) : op->opno;
9760 if (OidIsValid(opno)) {
9761 SubLink *esl = makeNode(SubLink);
9763 esl->subLinkType = EXPR_SUBLINK;
9764 esl->testexpr = NULL;
9765 esl->operName = NIL;
9766 esl->subselect = (Node *)copyObject(body);
9767 esl->location = -1;
9768 op->opno = opno;
9769 op->opfuncid = get_opcode(opno);
9770 pc.paramid = 1;
9771 pc.replacement = (Node *)esl;
9772 rewritten = oj_param_repl_mut((Node *)op, &pc);
9773 }
9774 }
9775 }
9776 newconjs = lappend(newconjs, rewritten ? rewritten : c);
9777 if (rewritten)
9778 changed = true;
9779 }
9780
9781 if (changed)
9782 q->jointree->quals = (list_length(newconjs) == 1)
9783 ? (Node *)linitial(newconjs)
9784 : (Node *)makeBoolExpr(AND_EXPR, newconjs, -1);
9785 return changed;
9786}
9787
9788/**
9789 * @brief Conservative provably-not-NULL test for the sublink lift.
9790 *
9791 * True only for a non-NULL constant or (through binary-compatible
9792 * coercions) a @c Var on a base-relation column declared @c NOT @c NULL,
9793 * resolved in @p q at @p levelsup. Everything else -- expressions,
9794 * subquery outputs, outer-join-nullable Vars -- conservatively counts as
9795 * nullable.
9796 */
9797static bool expr_provably_not_null(Node *e, const Query *q, Index levelsup)
9798{
9799 while (e && IsA(e, RelabelType))
9800 e = (Node *)((RelabelType *)e)->arg;
9801 if (e == NULL)
9802 return false;
9803 if (IsA(e, Const))
9804 return !((Const *)e)->constisnull;
9805 if (IsA(e, Var)) {
9806 const Var *v = (const Var *)e;
9807 RangeTblEntry *rte;
9808 HeapTuple atttup;
9809 bool notnull;
9810 if (v->varlevelsup != levelsup || v->varattno <= 0 ||
9811 v->varno <= 0 || (int)v->varno > list_length(q->rtable))
9812 return false;
9813 rte = rt_fetch(v->varno, q->rtable);
9814 if (rte->rtekind != RTE_RELATION)
9815 return false;
9816 atttup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(rte->relid),
9817 Int16GetDatum(v->varattno));
9818 if (!HeapTupleIsValid(atttup))
9819 return false;
9820 notnull = ((Form_pg_attribute) GETSTRUCT(atttup))->attnotnull;
9821 ReleaseSysCache(atttup);
9822 return notnull;
9823 }
9824 return false;
9825}
9826
9827/**
9828 * @brief Build the per-row correlation for a quantified sublink (@c IN /
9829 * @c op @c ANY / @c op @c ALL), setting @p *antijoin.
9830 *
9831 * The testexpr is @c "x op Param(subselect output)" (single column), or -- for a
9832 * row @c IN -- a @c BoolExpr @c AND of per-column @c "xᵢ = Paramᵢ". For each we
9833 * copy the op, sink the outer operand one level, and substitute the subselect's
9834 * paramid-th output column for the @c PARAM_SUBLINK placeholder, keeping any
9835 * coercions (e.g. a @c varchar->text relabel) intact. @c ANY is a semijoin
9836 * (@c *antijoin = false, operator kept); @c ALL is the universal dual, the
9837 * antijoin (@c *antijoin = true, operator negated -- @c "∀q. x op q" =
9838 * @c "¬∃q. x ¬op q"). Returns @c NULL for unsupported shapes (a @c RowCompareExpr,
9839 * a multi-column @c ALL, a bad paramid…).
9840 *
9841 * NULL semantics: when the lift's @e final sense is the antijoin
9842 * (@p neg XOR the base @c ALL sense -- @c NOT @c IN, @c op @c ALL,
9843 * @c NOT @c (op @c ANY)), a subquery row makes the outer row a non-answer
9844 * not only when the correlation is @e true but also when it is @e unknown
9845 * (SQL's 3VL: negation fixes u and the top level then filters it), i.e.
9846 * when either operand is NULL. Each conjunct therefore becomes
9847 * @c "(xᵢ ¬op qᵢ) OR xᵢ IS NULL OR qᵢ IS NULL", with the per-side guards
9848 * omitted when that side is provably non-nullable (the common NULL-free
9849 * path keeps its current form). The semijoin sense needs no guards:
9850 * matching only the rows where the correlation is @e true is exactly
9851 * SQL's own conflation of u with f at the top level. @p *guarded reports
9852 * whether any guard was emitted (the caller must then re-key the count
9853 * through @c oj_wrap_body_with_match_ind).
9854 */
9855static Node *extract_quantified_corr(SubLink *sl, bool *antijoin, bool neg,
9856 const Query *outerq, bool *guarded) {
9857 Query *sub = (Query *)sl->subselect;
9858 List *opexprs, *conjs = NIL;
9859 ListCell *lc;
9860 bool negate_op;
9861 bool null_guards;
9862
9863 *guarded = false;
9864
9865 /* ANY (IN, op ANY) is a semijoin: ∃q. x op q. ALL (op ALL) is its universal
9866 * dual: ∀q. x op q = ¬∃q. x ¬op q -- the antijoin, with the operator negated
9867 * in the per-row correlation. */
9868 if (sl->subLinkType == ANY_SUBLINK) {
9869 *antijoin = false;
9870 negate_op = false;
9871 } else if (sl->subLinkType == ALL_SUBLINK) {
9872 *antijoin = true;
9873 negate_op = true;
9874 } else {
9875 return NULL;
9876 }
9877
9878 /* The final sense after an enclosing NOT; guards are an antijoin matter. */
9879 null_guards = *antijoin ^ neg;
9880
9881 /* The testexpr is a single "x op Param" (single-column), or -- only for a row
9882 * IN -- a BoolExpr AND of per-column "xᵢ = Paramᵢ". */
9883 if (IsA(sl->testexpr, OpExpr))
9884 opexprs = list_make1(sl->testexpr);
9885 else if (sl->subLinkType == ANY_SUBLINK && IsA(sl->testexpr, BoolExpr) &&
9886 ((BoolExpr *)sl->testexpr)->boolop == AND_EXPR)
9887 opexprs = ((BoolExpr *)sl->testexpr)->args;
9888 else
9889 return NULL;
9890
9891 foreach (lc, opexprs) {
9892 OpExpr *oe = (OpExpr *)lfirst(lc);
9893 Node *rhs, *qcol, *ci;
9894 Param *p;
9896
9897 if (!IsA(oe, OpExpr) || list_length(oe->args) != 2)
9898 return NULL;
9899 rhs = (Node *)lsecond(oe->args);
9900 if (IsA(rhs, RelabelType))
9901 rhs = (Node *)((RelabelType *)rhs)->arg; /* varchar->text etc. */
9902 if (!IsA(rhs, Param))
9903 return NULL;
9904 p = (Param *)rhs;
9905 if (p->paramkind != PARAM_SUBLINK || p->paramid < 1 ||
9906 p->paramid > list_length(sub->targetList))
9907 return NULL;
9908
9909 /* Build "xᵢ <op'> Q.colᵢ": copy the testexpr op (negating it for ALL),
9910 * sink the outer operand a level, and substitute the subselect's
9911 * paramid-th output column for its PARAM_SUBLINK placeholder. */
9912 ci = copyObject((Node *)oe);
9913 if (negate_op) {
9914 Oid negop = get_negator(((OpExpr *)ci)->opno);
9915 if (!OidIsValid(negop))
9916 return NULL;
9917 ((OpExpr *)ci)->opno = negop;
9918 ((OpExpr *)ci)->opfuncid = get_opcode(negop);
9919 }
9920 IncrementVarSublevelsUp(ci, 1, 0);
9921 qcol = copyObject(
9922 (Node *)((TargetEntry *)list_nth(sub->targetList, p->paramid - 1))->expr);
9923 ctx.paramid = p->paramid;
9924 ctx.replacement = qcol;
9925 ci = oj_param_repl_mut(ci, &ctx);
9926
9927 /* Antijoin sense: an unknown correlation also removes the outer row,
9928 * so a NULL on either side counts as a match. Guard only the sides
9929 * that can actually be NULL. */
9930 if (null_guards) {
9931 List *disj = list_make1(ci);
9932 /* The outer operand (tested against outerq before the sublevel
9933 * sink, at levelsup 0). */
9934 if (!expr_provably_not_null((Node *)linitial(((OpExpr *)oe)->args),
9935 outerq, 0)) {
9936 NullTest *nx = makeNode(NullTest);
9937 nx->arg = (Expr *)copyObject(linitial(((OpExpr *)ci)->args));
9938 nx->nulltesttype = IS_NULL;
9939 nx->argisrow = false;
9940 nx->location = -1;
9941 disj = lappend(disj, nx);
9942 }
9943 if (!expr_provably_not_null(qcol, sub, 0)) {
9944 NullTest *nq = makeNode(NullTest);
9945 nq->arg = (Expr *)copyObject(qcol);
9946 nq->nulltesttype = IS_NULL;
9947 nq->argisrow = false;
9948 nq->location = -1;
9949 disj = lappend(disj, nq);
9950 }
9951 if (list_length(disj) > 1) {
9952 ci = (Node *)makeBoolExpr(OR_EXPR, disj, -1);
9953 *guarded = true;
9954 }
9955 }
9956 conjs = lappend(conjs, ci);
9957 }
9958
9959 if (conjs == NIL)
9960 return NULL;
9961 return (list_length(conjs) == 1)
9962 ? (Node *)linitial(conjs)
9963 : (Node *)makeBoolExpr(AND_EXPR, conjs, -1);
9964}
9965
9966/**
9967 * @brief Rewrite top-level @c EXISTS / @c IN WHERE conjuncts (optionally negated)
9968 * over tracked relations into correlated @c count(*) comparisons.
9969 *
9970 * A pre-pass for @c decorrelate_scalar_sublinks: each qualifying conjunct (a
9971 * bare @c EXISTS / @c IN sublink, or one wrapped in a single @c NOT -- i.e.
9972 * @c NOT @c EXISTS / @c NOT @c IN) is replaced by the @c build_count_predicate
9973 * form, after which the scalar-subquery decorrelation lowers the count()
9974 * comparison to the @c "R ⟕ Q" semijoin / antijoin. A body may mix tracked
9975 * and untracked relations (e.g. a station lookup joined into the subquery);
9976 * JOIN-syntax bodies arrive already canonicalised to the comma-join form by
9977 * @c normalize_inner_joins. Conjuncts whose subselect is not
9978 * decorrelatable (untracked / uncorrelated / outer-joined) are left
9979 * untouched, so they hit the usual unsupported-subquery error.
9980 */
9981static bool rewrite_predicate_sublinks(const constants_t *constants, Query *q) {
9982 Node *quals;
9983 List *conjs, *newconjs = NIL;
9984 ListCell *lc;
9985 bool changed = false;
9986
9987 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
9988 !q->jointree->quals)
9989 return false;
9990
9991 quals = q->jointree->quals;
9992 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
9993 ? ((BoolExpr *)quals)->args
9994 : list_make1(quals);
9995
9996 foreach (lc, conjs) {
9997 Node *c = (Node *)lfirst(lc);
9998 Node *inner = c, *rewritten = NULL;
9999 bool neg = false;
10000 SubLink *sl;
10001
10002 if (IsA(c, BoolExpr) && ((BoolExpr *)c)->boolop == NOT_EXPR &&
10003 list_length(((BoolExpr *)c)->args) == 1) {
10004 neg = true;
10005 inner = (Node *)linitial(((BoolExpr *)c)->args);
10006 }
10007 if (IsA(inner, SubLink) && IsA(((SubLink *)inner)->subselect, Query)) {
10008 sl = (SubLink *)inner;
10009 if (sl->subLinkType == EXISTS_SUBLINK &&
10011 (Query *)sl->subselect, false)) {
10012 /* EXISTS / NOT EXISTS: correlation already in the subselect WHERE. */
10013 rewritten =
10014 build_count_predicate((Query *)sl->subselect, NULL, neg);
10015 } else if (sl->subLinkType == ANY_SUBLINK ||
10016 sl->subLinkType == ALL_SUBLINK) {
10017 /* IN / NOT IN / op ANY / op ALL: correlation lifted from the testexpr.
10018 * ANY is a semijoin, ALL its antijoin dual; a wrapping NOT flips that. */
10019 bool base_antijoin;
10020 bool guarded = false;
10021 Node *corr = extract_quantified_corr(sl, &base_antijoin, neg, q,
10022 &guarded);
10023 if (corr &&
10025 (Query *)sl->subselect, true)) {
10026 if (guarded) {
10027 /* The guards let a matched Q row be NULL in every data column,
10028 * so the count key must be the wrap's constant indicator;
10029 * re-extract so the correlation targets the wrapped body. */
10030 if (oj_wrap_body_with_match_ind(constants,
10031 (Query *)sl->subselect))
10032 corr = extract_quantified_corr(sl, &base_antijoin, neg, q,
10033 &guarded);
10034 else
10035 corr = NULL;
10036 }
10037 if (corr)
10038 rewritten = build_count_predicate((Query *)sl->subselect, corr,
10039 base_antijoin ^ neg);
10040 }
10041 }
10042 }
10043 newconjs = lappend(newconjs, rewritten ? rewritten : c);
10044 if (rewritten)
10045 changed = true;
10046 }
10047
10048 if (changed)
10049 q->jointree->quals = (list_length(newconjs) == 1)
10050 ? (Node *)linitial(newconjs)
10051 : (Node *)makeBoolExpr(AND_EXPR, newconjs, -1);
10052 return changed;
10053}
10054
10055/**
10056 * @brief Rewrite a top-level @c ARRAY(SELECT Q.col FROM Q WHERE corr) target-list
10057 * entry into the aggregate body @c (SELECT array_agg(Q.col) FROM Q WHERE
10058 * corr).
10059 *
10060 * A pre-pass for @c decorrelate_scalar_sublinks: an @c ARRAY_SUBLINK collects the
10061 * correlated rows into an array, which is exactly @c array_agg over the group, so
10062 * mutating it into an @c EXPR_SUBLINK aggregate body lets the aggregate arm lower
10063 * it to @c array_agg(Q.col) over the @c "R ⟕ Q" group -- no @c count gate, since
10064 * an array may have zero, one, or many elements. Subselects that are not
10065 * decorrelatable (untracked / multi-relation / uncorrelated) are left untouched.
10066 */
10067static bool rewrite_array_sublinks(const constants_t *constants, Query *q) {
10068 ListCell *lc;
10069 bool changed = false;
10070
10071 if (q->commandType != CMD_SELECT || !q->hasSubLinks)
10072 return false;
10073
10074 foreach (lc, q->targetList) {
10075 TargetEntry *te = (TargetEntry *)lfirst(lc);
10076 SubLink *sl;
10077 Query *sub;
10078 TargetEntry *innerte;
10079 Oid elemtype, arrtype;
10080 oj_sublink_scan scan;
10081 Aggref *agg;
10082 NullTest *nt;
10083
10084 if (!IsA(te->expr, SubLink))
10085 continue;
10086 sl = (SubLink *)te->expr;
10087 if (sl->subLinkType != ARRAY_SUBLINK || !IsA(sl->subselect, Query))
10088 continue;
10089 sub = (Query *)sl->subselect;
10090 if (!predicate_subselect_decorrelatable(constants, sub, false))
10091 continue;
10092 /* Exactly one non-junk output column (the element value). A body ORDER BY
10093 * adds junk sort-key entries to the targetList; those become the ordered
10094 * array_agg's extra args (see below), so they are allowed here. */
10095 {
10096 int nreal = 0;
10097 ListCell *tlc;
10098 foreach (tlc, sub->targetList)
10099 if (!((TargetEntry *)lfirst(tlc))->resjunk)
10100 ++nreal;
10101 if (nreal != 1 || ((TargetEntry *)linitial(sub->targetList))->resjunk)
10102 continue;
10103 }
10104
10105 innerte = (TargetEntry *)linitial(sub->targetList);
10106 elemtype = exprType((Node *)innerte->expr);
10107 arrtype = get_array_type(elemtype);
10108 if (!OidIsValid(arrtype))
10109 continue; /* no array type for this element (e.g. a pseudo-type) */
10110
10111 /* A Q column from the correlation, to key the null-padded-row filter. */
10112 scan.n_sublinks = 0;
10113 scan.found_sublink = NULL;
10114 scan.target_varno = 1; /* Q is rtindex 1 in the subselect */
10115 scan.found_var = NULL;
10116 oj_sublink_scan_walker(sub->jointree->quals, &scan);
10117 if (scan.found_var == NULL)
10118 continue; /* uncorrelated: decorrelation would bail anyway */
10119
10120 if (sub->sortClause) {
10121 /* ARRAY(SELECT v FROM Q WHERE corr ORDER BY key) -> the ordered aggregate
10122 * array_agg(v ORDER BY key): the body's ORDER BY moves inside the
10123 * aggregate (where it survives the regroup into the R ⟕ Q group), exactly
10124 * as the LIMIT-1 argmax path does for choose(). The aggregate's args are
10125 * the value plus the junk sort-key entries, its aggorder the body's
10126 * sortClause; decorrelate's Var-remap pulls every arg's Q reference up to
10127 * the joined Q. */
10128 List *args = NIL, *argtypes = NIL;
10129 ListCell *alc;
10130 agg = makeNode(Aggref);
10131 foreach (alc, sub->targetList) {
10132 TargetEntry *ate = (TargetEntry *)copyObject(lfirst(alc));
10133 args = lappend(args, ate);
10134 argtypes = lappend_oid(argtypes, exprType((Node *)ate->expr));
10135 }
10136 agg->aggfnoid = F_ARRAY_AGG_ANYNONARRAY;
10137 agg->aggtype = arrtype;
10138 agg->aggtranstype = InvalidOid;
10139 agg->aggargtypes = argtypes;
10140 agg->args = args;
10141 agg->aggorder = (List *)copyObject((Node *)sub->sortClause);
10142 agg->aggkind = AGGKIND_NORMAL;
10143 agg->aggsplit = AGGSPLIT_SIMPLE;
10144 agg->location = -1;
10145#if PG_VERSION_NUM >= 140000
10146 agg->aggno = agg->aggtransno = -1;
10147#endif
10148 } else {
10149 agg = oj_make_aggref(F_ARRAY_AGG_ANYNONARRAY, arrtype, elemtype,
10150 innerte->expr);
10151 }
10152 /* array_agg keeps NULLs in its value, so the LEFT JOIN's null-padded
10153 * antijoin row (Q key IS NULL) would inject a spurious NULL element.
10154 * Filter it out; a genuinely-NULL matched element (Q key non-NULL) is still
10155 * collected. decorrelate's Var-remap retargets this Q key to the pulled-up
10156 * Q just like the aggregate argument. */
10157 nt = makeNode(NullTest);
10158 nt->arg = (Expr *)copyObject((Node *)scan.found_var);
10159 nt->nulltesttype = IS_NOT_NULL;
10160 nt->argisrow = false;
10161 nt->location = -1;
10162 agg->aggfilter = (Expr *)nt;
10163
10164 /* The single body output is now the array_agg; the ORDER BY (if any) lives
10165 * inside it, so the query-level sortClause and the junk sort-key targetList
10166 * entries are dropped. */
10167 sub->targetList = list_make1(makeTargetEntry(
10168 (Expr *)agg, 1, innerte->resname ? pstrdup(innerte->resname) : NULL,
10169 false));
10170 sub->sortClause = NIL;
10171 sub->hasAggs = true;
10172 sl->subLinkType = EXPR_SUBLINK;
10173 changed = true;
10174 }
10175 return changed;
10176}
10177
10178/**
10179 * @brief Collapse a multi-table scalar-subquery body FROM into one derived
10180 * cross-product subquery @c D, so the decorrelation can treat the body as
10181 * @c "SELECT val FROM D WHERE W" with @c D a single tracked subquery.
10182 *
10183 * Mirror of @c oj_wrap_outer_from, but for the SubLink body: every body relation
10184 * must be a base relation, at least one of them tracked, and the FROM a
10185 * comma-join (JOIN-syntax bodies arrive already canonicalised to that form
10186 * by @c normalize_inner_joins). @c D exposes every base user column (@c oj_collect_cols); the body's own
10187 * (level-0) references are retargeted to @c D, while the correlated level-1
10188 * references to the outer query are left untouched. The body WHERE @c W
10189 * (correlation + inter-table join) stays in place: it becomes the
10190 * @c "R LEFT JOIN D" ON clause, and @c get_provenance_attributes later
10191 * processes @c D recursively, giving it the @c Q1 ⊗ … ⊗ Qn provenance of its
10192 * tracked relations (an untracked relation contributes the neutral 1).
10193 */
10194static bool oj_wrap_body_from(const constants_t *constants, Query *sub) {
10195 int rtlen = list_length(sub->rtable);
10196 int **pos;
10197 Query *D = makeNode(Query);
10198 RangeTblEntry *d_rte;
10199 RangeTblRef *rtr = makeNode(RangeTblRef);
10200 List *d_tl = NIL;
10201 oj_wrap_ctx wc;
10202 ListCell *lc;
10203 int idx, posn = 0;
10204
10205 if (!sub->jointree || sub->jointree->fromlist == NIL)
10206 return false;
10207 {
10208 bool any_tracked = false;
10209 foreach (lc, sub->rtable) {
10210 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
10211 if (r->rtekind != RTE_RELATION)
10212 return false;
10213 if (oj_rte_has_provsql(constants, r))
10214 any_tracked = true;
10215 }
10216 if (!any_tracked)
10217 return false;
10218 }
10219 foreach (lc, sub->jointree->fromlist) {
10220 if (!IsA(lfirst(lc), RangeTblRef))
10221 return false; /* only a plain comma-join, no explicit JoinExprs */
10222 }
10223
10224 /* D exposes every base user column; record (rtindex,attno) -> D column. */
10225 pos = (int **)palloc0((rtlen + 1) * sizeof(int *));
10226 idx = 0;
10227 foreach (lc, sub->rtable) {
10228 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
10229 oj_cols rc;
10230 int j;
10231 ++idx;
10232 oj_collect_cols(constants, r, &rc);
10233 pos[idx] =
10234 (int *)palloc0((list_length(r->eref->colnames) + 1) * sizeof(int));
10235 for (j = 0; j < rc.n; ++j) {
10236 Var *v =
10237 makeVar(idx, rc.attno[j], rc.type[j], rc.typmod[j], rc.coll[j], 0);
10238 d_tl = lappend(d_tl, makeTargetEntry((Expr *)v, ++posn,
10239 pstrdup(rc.name[j]), false));
10240 pos[idx][rc.attno[j]] = posn;
10241 }
10242 }
10243
10244 /* D = SELECT <body base user cols> FROM <body fromlist> (cross product). */
10245 D->commandType = CMD_SELECT;
10246 D->canSetTag = true;
10247 D->rtable = sub->rtable;
10248 D->jointree = makeNode(FromExpr);
10249 D->jointree->fromlist = sub->jointree->fromlist;
10250 D->jointree->quals = NULL;
10251 D->targetList = d_tl;
10252#if PG_VERSION_NUM >= 160000
10253 D->rteperminfos = sub->rteperminfos;
10254#endif
10255
10256 /* Retarget the body's own (level-0) Vars to D; level-1 (outer) Vars stay. */
10257 wc.newidx = 1;
10258 wc.rtlen = rtlen;
10259 wc.pos = pos;
10260 wc.target_level = 0;
10261 wc.skip = NULL;
10262 sub->targetList = (List *)oj_wrap_remap_mut((Node *)sub->targetList, &wc);
10263 if (sub->jointree->quals)
10264 sub->jointree->quals = oj_wrap_remap_mut(sub->jointree->quals, &wc);
10265
10266 /* Rebuild the body over D. */
10267 d_rte = oj_make_subquery_rte(D);
10268 sub->rtable = list_make1(d_rte);
10269#if PG_VERSION_NUM >= 160000
10270 sub->rteperminfos = NIL;
10271#endif
10272 rtr->rtindex = 1;
10273 sub->jointree->fromlist = list_make1(rtr);
10274 return true;
10275}
10276
10277/** @brief Column name of the constant match indicator added by
10278 * @c oj_wrap_body_with_match_ind. */
10279#define PROVSQL_MATCH_IND_COLNAME "provsql_match_ind"
10280
10281/**
10282 * @brief Wrap a NULL-guarded antijoin body into a derived subquery @c D
10283 * carrying a constant match-indicator column.
10284 *
10285 * Under the 3VL guards of @c extract_quantified_corr a corr-matched @c Q row
10286 * can be NULL in @e every data column, so after decorrelation no data column
10287 * can key the matched / null-padded distinction that the @c count(*) @c ->
10288 * @c count(Q.key) rewrite needs. The indicator is a constant @c TRUE
10289 * projected by @c D: non-NULL on every genuine row, NULL on the padded
10290 * antijoin row like any other @c D column. The @c aggstar arm of
10291 * @c decorrelate_scalar_sublinks prefers it as the count key.
10292 */
10293static bool oj_wrap_body_with_match_ind(const constants_t *constants,
10294 Query *sub) {
10295 RangeTblEntry *d_rte;
10296 Query *D;
10297
10298 if (!oj_wrap_body_from(constants, sub))
10299 return false;
10300
10301 d_rte = (RangeTblEntry *)linitial(sub->rtable);
10302 D = d_rte->subquery;
10303 D->targetList = lappend(
10304 D->targetList,
10305 makeTargetEntry((Expr *)makeBoolConst(true, false),
10306 list_length(D->targetList) + 1,
10307 pstrdup(PROVSQL_MATCH_IND_COLNAME), false));
10308 d_rte->eref->colnames = lappend(
10309 d_rte->eref->colnames, makeString(pstrdup(PROVSQL_MATCH_IND_COLNAME)));
10310 return true;
10311}
10312
10313/**
10314 * @brief Build the derived single-row aggregate @c D for an UNcorrelated scalar
10315 * subquery body, to be cross-joined into the outer FROM.
10316 *
10317 * Aggregate body @c "SELECT agg(..) FROM Q [WHERE]" -> @c D is the body itself
10318 * (always one row). Value body @c "SELECT val FROM Q [WHERE]" -> @c D is
10319 * @c "SELECT choose(val) FROM Q [WHERE] HAVING count(*) <= 1" -- one row, with
10320 * the scalar subquery's at-most-one-row rule baked into the moved subquery.
10321 * Returns @c NULL unless the body is an uncorrelated clean SELECT over tracked
10322 * base relations (a comma-join is fine; @c D is then an inner join).
10323 *
10324 * Faithful to ProvSQL aggregates: an empty @c Q yields an empty group, hence a
10325 * @c gate_zero row that drops out -- exactly what a hand-written derived
10326 * aggregate does; the correlated path's 0-match NULL row is not reconstructed.
10327 */
10329 Query *body) {
10330 Query *D;
10331 TargetEntry *vte;
10332 ListCell *lc;
10333
10334 if (!IsA(body, Query) || body->commandType != CMD_SELECT)
10335 return NULL;
10336 if (body->groupClause || body->groupingSets || body->distinctClause ||
10337 body->setOperations || body->hasWindowFuncs || body->hasSubLinks ||
10338 body->limitCount || body->limitOffset || body->cteList ||
10339 list_length(body->targetList) != 1)
10340 return NULL;
10341 if (!body->jointree || body->jointree->fromlist == NIL)
10342 return NULL;
10343 /* Correlated bodies are the LEFT-JOIN decorrelation's job, not this one. */
10344 if (contain_vars_of_level((Node *)body->targetList, 1) ||
10345 (body->jointree->quals && contain_vars_of_level(body->jointree->quals, 1)))
10346 return NULL;
10347 /* Every FROM relation must be a tracked base relation, so D is a processable
10348 * tracked subquery (a comma-join is fine -- D is then an inner join). */
10349 foreach (lc, body->rtable) {
10350 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
10351 if (r->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, r))
10352 return NULL;
10353 }
10354 foreach (lc, body->jointree->fromlist) {
10355 if (!IsA(lfirst(lc), RangeTblRef))
10356 return NULL;
10357 }
10358
10359 vte = (TargetEntry *)linitial(body->targetList);
10360
10361 if (body->hasAggs) {
10362 /* Aggregate body: a single bare aggregate (one row, no grouping). */
10363 if (!IsA(vte->expr, Aggref))
10364 return NULL;
10365 D = (Query *)copyObject(body);
10366 } else {
10367 /* Value body: pick the single value with choose(), gate the >1-row worlds
10368 * with HAVING count(*) <= 1. */
10369 Aggref *cnt;
10370 OpExpr *le;
10371 Oid le_op;
10372
10373 if (!OidIsValid(constants->OID_FUNCTION_CHOOSE))
10374 return NULL;
10375 D = (Query *)copyObject(body);
10376 vte = (TargetEntry *)linitial(D->targetList);
10377 vte->expr = (Expr *)oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
10378 exprType((Node *)vte->expr),
10379 exprType((Node *)vte->expr), vte->expr);
10380 D->hasAggs = true;
10381
10382 cnt = makeNode(Aggref);
10383 cnt->aggfnoid = F_COUNT_; /* count(*) */
10384 cnt->aggtype = INT8OID;
10385 cnt->aggtranstype = InvalidOid;
10386 cnt->aggargtypes = NIL;
10387 cnt->args = NIL;
10388 cnt->aggstar = true;
10389 cnt->aggkind = AGGKIND_NORMAL;
10390 cnt->aggsplit = AGGSPLIT_SIMPLE;
10391 cnt->location = -1;
10392#if PG_VERSION_NUM >= 140000
10393 cnt->aggno = cnt->aggtransno = -1;
10394#endif
10395 le = makeNode(OpExpr);
10396 le_op = OpernameGetOprid(list_make1(makeString("<=")), INT8OID, INT8OID);
10397 le->opno = le_op;
10398 le->opfuncid = get_opcode(le_op);
10399 le->opresulttype = BOOLOID;
10400 le->opcollid = InvalidOid;
10401 le->inputcollid = InvalidOid;
10402 le->args = list_make2(cnt, makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
10403 Int64GetDatum(1), false,
10404 FLOAT8PASSBYVAL));
10405 le->location = -1;
10406 D->havingQual = (Node *)le;
10407 }
10408 return D;
10409}
10410
10411/** @brief Is @p sub an uncorrelated clean SELECT over tracked base relations (a
10412 * comma-join is fine)? The targetList is not inspected (callers replace it). */
10414 Query *sub) {
10415 ListCell *lc;
10416 if (!IsA(sub, Query) || sub->commandType != CMD_SELECT)
10417 return false;
10418 if (sub->groupClause || sub->groupingSets || sub->distinctClause ||
10419 sub->setOperations || sub->hasWindowFuncs || sub->hasSubLinks ||
10420 sub->limitCount || sub->limitOffset || sub->cteList)
10421 return false;
10422 if (!sub->jointree || sub->jointree->fromlist == NIL)
10423 return false;
10424 if (contain_vars_of_level((Node *)sub->targetList, 1) ||
10425 (sub->jointree->quals && contain_vars_of_level(sub->jointree->quals, 1)))
10426 return false; /* correlated */
10427 foreach (lc, sub->rtable) {
10428 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
10429 if (r->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, r))
10430 return false;
10431 }
10432 foreach (lc, sub->jointree->fromlist) {
10433 if (!IsA(lfirst(lc), RangeTblRef))
10434 return false;
10435 }
10436 return true;
10437}
10438
10439/** @brief A fresh @c count(*) @c Aggref (returns @c int8). */
10440static Aggref *oj_make_count_star(void) {
10441 Aggref *cnt = makeNode(Aggref);
10442 cnt->aggfnoid = F_COUNT_;
10443 cnt->aggtype = INT8OID;
10444 cnt->aggtranstype = InvalidOid;
10445 cnt->aggargtypes = NIL;
10446 cnt->args = NIL;
10447 cnt->aggstar = true;
10448 cnt->aggkind = AGGKIND_NORMAL;
10449 cnt->aggsplit = AGGSPLIT_SIMPLE;
10450 cnt->location = -1;
10451#if PG_VERSION_NUM >= 140000
10452 cnt->aggno = cnt->aggtransno = -1;
10453#endif
10454 return cnt;
10455}
10456
10457/** @brief Build the one-row @c "SELECT 1 FROM <body FROM> HAVING <pred>" gated
10458 * subquery: @p body supplies the FROM (and any uncorrelated WHERE), @p pred the
10459 * aggregate comparison that becomes its provenance. */
10460static Query *oj_having_gated_subquery(Query *body, Node *pred) {
10461 Query *D = (Query *)copyObject(body);
10462 D->targetList = list_make1(makeTargetEntry(
10463 (Expr *)makeConst(INT4OID, -1, InvalidOid, sizeof(int32), Int32GetDatum(1),
10464 false, true),
10465 1, pstrdup("exists"), false));
10466 D->havingQual = pred;
10467 D->hasAggs = true;
10468 return D;
10469}
10470
10471/**
10472 * @brief Handle UNcorrelated @c EXISTS and uncorrelated aggregate comparisons in
10473 * WHERE by cross-joining a HAVING-gated one-row subquery.
10474 *
10475 * @c EXISTS (SELECT … FROM Q) -> @c "SELECT 1 FROM Q HAVING count(*) >= 1";
10476 * @c "(SELECT agg(..) FROM Q) OP v" (v not referencing the outer) ->
10477 * @c "SELECT 1 FROM Q HAVING agg(..) OP v". The gated @c D is appended to the
10478 * FROM, so the conjunct's truth becomes @c "R ⊗ [predicate]" -- ProvSQL's HAVING
10479 * annotates (the one aggregate row is always materialised, gated), so no
10480 * actual-instance row is needed. Faithful to ProvSQL aggregates: the empty-Q
10481 * world drops (so @c NOT @c EXISTS, satisfied only by the empty group, is left
10482 * rejected). Correlated predicates are handled by @c rewrite_predicate_sublinks.
10483 */
10485 Query *q) {
10486 Node *quals;
10487 List *conjs, *newconjs = NIL;
10488 ListCell *lc;
10489 bool changed = false;
10490
10491 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
10492 !q->jointree->quals)
10493 return false;
10494
10495 quals = q->jointree->quals;
10496 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
10497 ? ((BoolExpr *)quals)->args
10498 : list_make1(quals);
10499
10500 foreach (lc, conjs) {
10501 Node *c = (Node *)lfirst(lc);
10502 Query *D = NULL;
10503
10504 if (IsA(c, SubLink) && ((SubLink *)c)->subLinkType == EXISTS_SUBLINK &&
10505 IsA(((SubLink *)c)->subselect, Query) &&
10507 (Query *)((SubLink *)c)->subselect)) {
10508 /* EXISTS -> HAVING count(*) >= 1. */
10509 OpExpr *ge = makeNode(OpExpr);
10510 Oid o = OpernameGetOprid(list_make1(makeString(">=")), INT8OID, INT8OID);
10511 ge->opno = o;
10512 ge->opfuncid = get_opcode(o);
10513 ge->opresulttype = BOOLOID;
10514 ge->opcollid = InvalidOid;
10515 ge->inputcollid = InvalidOid;
10516 ge->args = list_make2(oj_make_count_star(),
10517 makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
10518 Int64GetDatum(1), false, FLOAT8PASSBYVAL));
10519 ge->location = -1;
10520 D = oj_having_gated_subquery((Query *)((SubLink *)c)->subselect,
10521 (Node *)ge);
10522 } else if (IsA(c, OpExpr) && list_length(((OpExpr *)c)->args) == 2) {
10523 OpExpr *op = (OpExpr *)c;
10524 Node *l = (Node *)linitial(op->args), *r = (Node *)lsecond(op->args);
10525 SubLink *sl = NULL;
10526 Node *val = NULL;
10527 bool sublink_left = false;
10528
10529 if (IsA(l, SubLink)) {
10530 sl = (SubLink *)l;
10531 val = r;
10532 sublink_left = true;
10533 } else if (IsA(r, SubLink)) {
10534 sl = (SubLink *)r;
10535 val = l;
10536 }
10537 if (sl != NULL && sl->subLinkType == EXPR_SUBLINK &&
10538 IsA(sl->subselect, Query)) {
10539 Query *sub = (Query *)sl->subselect;
10540 if (sub->hasAggs && list_length(sub->targetList) == 1 &&
10541 IsA(((TargetEntry *)linitial(sub->targetList))->expr, Aggref) &&
10542 oj_uncorrelated_body_over_tracked(constants, sub) &&
10543 !contain_vars_of_level(val, 0)) {
10544 /* (agg) OP v -> HAVING agg OP v, the aggregate copied from the body. */
10545 Node *agg =
10546 copyObject((Node *)((TargetEntry *)linitial(sub->targetList))->expr);
10547 OpExpr *pred = (OpExpr *)copyObject((Node *)op);
10548 pred->args = sublink_left ? list_make2(agg, copyObject(val))
10549 : list_make2(copyObject(val), agg);
10550 D = oj_having_gated_subquery(sub, (Node *)pred);
10551 } else if (!sub->hasAggs && list_length(sub->targetList) == 1 &&
10552 !((TargetEntry *)linitial(sub->targetList))->resjunk &&
10553 OidIsValid(constants->OID_FUNCTION_CHOOSE) &&
10554 oj_uncorrelated_body_over_tracked(constants, sub) &&
10555 !contain_vars_of_level(val, 0)) {
10556 /* (value) OP v -> HAVING (choose(value) OP v) AND count(*) <= 1. The
10557 * scalar subquery's single value is picked by choose() and compared;
10558 * count(*) <= 1 enforces the at-most-one-row rule. Empty Q gives
10559 * choose() = NULL (the comparison is NULL, so the gated row drops --
10560 * matching SQL's NULL-valued scalar subquery); the >1-row world (a SQL
10561 * runtime error) is gated out by count(*) <= 1. */
10562 Expr *bodyval = ((TargetEntry *)linitial(sub->targetList))->expr;
10563 Aggref *ch = oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
10564 exprType((Node *)bodyval),
10565 exprType((Node *)bodyval),
10566 (Expr *)copyObject((Node *)bodyval));
10567 OpExpr *cmp = (OpExpr *)copyObject((Node *)op);
10568 Oid leo = OpernameGetOprid(list_make1(makeString("<=")), INT8OID,
10569 INT8OID);
10570 OpExpr *le1 = makeNode(OpExpr);
10571
10572 cmp->args = sublink_left ? list_make2(ch, copyObject(val))
10573 : list_make2(copyObject(val), ch);
10574 le1->opno = leo;
10575 le1->opfuncid = get_opcode(leo);
10576 le1->opresulttype = BOOLOID;
10577 le1->opcollid = InvalidOid;
10578 le1->inputcollid = InvalidOid;
10579 le1->args =
10580 list_make2(oj_make_count_star(),
10581 makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
10582 Int64GetDatum(1), false, FLOAT8PASSBYVAL));
10583 le1->location = -1;
10585 sub, (Node *)makeBoolExpr(AND_EXPR, list_make2(cmp, le1), -1));
10586 }
10587 }
10588 }
10589
10590 if (D != NULL) {
10591 RangeTblEntry *d_rte = oj_make_subquery_rte(D);
10592 RangeTblRef *rtr = makeNode(RangeTblRef);
10593 q->rtable = lappend(q->rtable, d_rte);
10594 rtr->rtindex = list_length(q->rtable);
10595 q->jointree->fromlist = lappend(q->jointree->fromlist, rtr);
10596 changed = true; /* the conjunct is now carried by D's HAVING gate */
10597 } else {
10598 newconjs = lappend(newconjs, c);
10599 }
10600 }
10601
10602 if (changed) {
10603 oj_sublink_scan scan;
10604 q->jointree->quals =
10605 (newconjs == NIL)
10606 ? NULL
10607 : (list_length(newconjs) == 1 ? (Node *)linitial(newconjs)
10608 : (Node *)makeBoolExpr(AND_EXPR, newconjs,
10609 -1));
10610 scan.n_sublinks = 0;
10611 scan.found_sublink = NULL;
10612 scan.target_varno = 0;
10613 scan.found_var = NULL;
10614 oj_sublink_scan_walker((Node *)q->targetList, &scan);
10615 if (q->jointree->quals)
10616 oj_sublink_scan_walker(q->jointree->quals, &scan);
10617 if (scan.n_sublinks == 0)
10618 q->hasSubLinks = false;
10619 }
10620 return changed;
10621}
10622
10623/** @brief Build the @c "<cnt> <op> const" OpExpr for an antijoin's HAVING, where
10624 * @p cnt is a @c count aggregate (@c count(*) or @c count(col)). */
10625static OpExpr *oj_count_const_cmp(Oid opno, Oid inputcollid, Aggref *cnt,
10626 Node *constarg) {
10627 OpExpr *op = makeNode(OpExpr);
10628 op->opno = opno;
10629 op->opfuncid = get_opcode(opno);
10630 op->opresulttype = BOOLOID;
10631 op->opcollid = InvalidOid;
10632 op->inputcollid = inputcollid;
10633 op->args = list_make2(cnt, copyObject(constarg));
10634 op->location = -1;
10635 return op;
10636}
10637
10638/** @brief Does @c 0 satisfy the @c int8 comparison @c "0 <opno> c"? Detects
10639 * @c count(*) predicates that hold on the empty group (so the HAVING-gate would
10640 * drop them and the antijoin construction is needed instead). */
10641static bool oj_zero_satisfies(Oid opno, Const *c) {
10642 return DatumGetBool(OidFunctionCall2Coll(get_opcode(opno), c->constcollid,
10643 Int64GetDatum(0), c->constvalue));
10644}
10645
10646/**
10647 * @brief Rewrite an uncorrelated WHERE predicate that is satisfied by the empty
10648 * group -- @c NOT @c EXISTS, or @c "(SELECT count(*) FROM Q) <op> const"
10649 * with @c "0 <op> const" true (e.g. @c "< k", @c "<= k", @c "= 0") -- into
10650 * the EXCEPT-ALL antijoin.
10651 *
10652 * Such a predicate is @c "NOT P" for a @c P that is FALSE on the empty group
10653 * (@c EXISTS, @c count(*) @c >= @c k…), so it is the m-semiring antijoin
10654 * @c "R ⊗ (1 ⊖ ⟦P⟧)". We materialise @c ⟦P⟧ as the one-row HAVING-gated subquery
10655 * @c D = @c "SELECT 1 FROM Q [WHERE w] HAVING count(*) <negated op> const"
10656 * (count(*) always yields a row, so @c ⟦P⟧ is correctly captured even when the
10657 * group is empty), then take the difference @c "R EXCEPT ALL π_R(R × D)" via
10658 * @c oj_build_diff -- ProvSQL's NOT-IN EXCEPT-ALL, giving each kept tuple
10659 * @c "R(r) ⊖ (R(r) ⊗ ⟦P⟧) = R(r) ⊗ (1 ⊖ ⟦P⟧)", multiplicity preserved and correct
10660 * in every semiring.
10661 *
10662 * Runs before @c rewrite_predicate_sublinks / @c move_uncorrelated_where_predicates:
10663 * those would instead push the raw predicate into a HAVING-gate, whose empty
10664 * group is @c gate_zero -- dropping exactly the world this predicate selects (a
10665 * silent under-count: @c count(*)=0 → p=0, @c count(*)<2 → @c P(=1) not @c P(≤1)).
10666 */
10667static bool rewrite_uncorrelated_antijoin(const constants_t *constants,
10668 Query *q) {
10669 Node *quals;
10670 List *conjs, *newconjs = NIL;
10671 ListCell *lc;
10672 RangeTblRef *r_ref;
10673 RangeTblEntry *R_rte;
10674 Index R_idx;
10675 Query *q_body = NULL; /* the uncorrelated Q body of the matched predicate */
10676 Node *neg_having = NULL; /* the false-on-empty count(*) predicate for D */
10677 Query *Diff;
10678 RangeTblEntry *d_rte;
10679 oj_cols Rc, Dc;
10680
10681 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree ||
10682 !q->jointree->quals || q->setOperations)
10683 return false;
10684 if (list_length(q->jointree->fromlist) != 1 ||
10685 !IsA(linitial(q->jointree->fromlist), RangeTblRef))
10686 return false;
10687 r_ref = (RangeTblRef *)linitial(q->jointree->fromlist);
10688 R_idx = r_ref->rtindex;
10689 R_rte = list_nth_node(RangeTblEntry, q->rtable, R_idx - 1);
10690 if (R_rte->rtekind != RTE_RELATION || !oj_rte_has_provsql(constants, R_rte))
10691 return false;
10692
10693 quals = q->jointree->quals;
10694 conjs = (IsA(quals, BoolExpr) && ((BoolExpr *)quals)->boolop == AND_EXPR)
10695 ? ((BoolExpr *)quals)->args
10696 : list_make1(quals);
10697 foreach (lc, conjs) {
10698 Node *c = (Node *)lfirst(lc);
10699
10700 if (q_body == NULL && IsA(c, BoolExpr) &&
10701 ((BoolExpr *)c)->boolop == NOT_EXPR &&
10702 list_length(((BoolExpr *)c)->args) == 1) {
10703 /* NOT EXISTS(Q) == NOT (count(*) >= 1). */
10704 Node *inner = (Node *)linitial(((BoolExpr *)c)->args);
10705 if (IsA(inner, SubLink) &&
10706 ((SubLink *)inner)->subLinkType == EXISTS_SUBLINK &&
10707 IsA(((SubLink *)inner)->subselect, Query) &&
10709 constants, (Query *)((SubLink *)inner)->subselect)) {
10710 Oid ge = OpernameGetOprid(list_make1(makeString(">=")), INT8OID,
10711 INT8OID);
10712 q_body = (Query *)((SubLink *)inner)->subselect;
10713 neg_having = (Node *)oj_count_const_cmp(
10714 ge, InvalidOid, oj_make_count_star(),
10715 (Node *)makeConst(INT8OID, -1, InvalidOid, sizeof(int64),
10716 Int64GetDatum(1), false, FLOAT8PASSBYVAL));
10717 continue; /* drop this conjunct -- carried by the antijoin */
10718 }
10719 } else if (q_body == NULL && IsA(c, OpExpr) &&
10720 list_length(((OpExpr *)c)->args) == 2) {
10721 /* (SELECT count(*) FROM Q) <op> const, when 0 <op> const is true. */
10722 OpExpr *op = (OpExpr *)c;
10723 Node *l = (Node *)linitial(op->args), *r = (Node *)lsecond(op->args);
10724 /* count(*) is int8 on the left (so 0::int8 is the right empty value for
10725 * oj_zero_satisfies); the literal may be int4 or int8 (PG has cross-type
10726 * int8/int4 comparison operators, so it is not coerced). */
10727 if (IsA(l, SubLink) && IsA(r, Const) && !((Const *)r)->constisnull &&
10728 exprType(l) == INT8OID) {
10729 SubLink *sl = (SubLink *)l;
10730 Oid neg;
10731 if (sl->subLinkType == EXPR_SUBLINK && IsA(sl->subselect, Query)) {
10732 Query *s = (Query *)sl->subselect;
10733 TargetEntry *te = (list_length(s->targetList) == 1)
10734 ? (TargetEntry *)linitial(s->targetList)
10735 : NULL;
10736 Aggref *cnt = (te && IsA(te->expr, Aggref)) ? (Aggref *)te->expr
10737 : NULL;
10738 /* count(*) (F_COUNT_) or count(col) (F_COUNT_ANY): both return 0 on
10739 * the empty group, so a predicate true at 0 needs the antijoin. D's
10740 * HAVING reuses the original count aggregate (so count(col)'s NULL
10741 * semantics are preserved). */
10742 if (cnt &&
10743 (cnt->aggfnoid == F_COUNT_ || cnt->aggfnoid == F_COUNT_ANY) &&
10744 oj_uncorrelated_body_over_tracked(constants, s) &&
10745 oj_zero_satisfies(op->opno, (Const *)r) &&
10746 OidIsValid((neg = get_negator(op->opno)))) {
10747 q_body = s;
10748 neg_having = (Node *)oj_count_const_cmp(
10749 neg, op->inputcollid, (Aggref *)copyObject(cnt), r);
10750 continue;
10751 }
10752 }
10753 }
10754 }
10755 newconjs = lappend(newconjs, c);
10756 }
10757 if (q_body == NULL)
10758 return false;
10759
10760 /* D = SELECT 1 FROM <Q body> HAVING <false-on-empty count(*) predicate>. */
10761 d_rte = oj_make_subquery_rte(oj_having_gated_subquery(q_body, neg_having));
10762 oj_collect_cols(constants, R_rte, &Rc);
10763 oj_collect_cols(constants, d_rte, &Dc);
10764
10765 /* Diff = R EXCEPT ALL π_R(R × D) = R(r) ⊗ (1 ⊖ ⟦P⟧). D is a self-contained
10766 * subquery, so oj_build_diff copies it into the matched arm (no outer perms). */
10767 Diff = oj_build_diff(constants, q, R_rte, d_rte, R_idx,
10768 R_idx /* S_idx unused: theta is NULL */, &Rc, &Dc, NULL,
10769 true /* keep_left */);
10770 lfirst(list_nth_cell(q->rtable, R_idx - 1)) =
10771 (void *)oj_make_subquery_rte(Diff);
10772
10773 q->jointree->quals =
10774 (newconjs == NIL)
10775 ? NULL
10776 : (list_length(newconjs) == 1
10777 ? (Node *)linitial(newconjs)
10778 : (Node *)makeBoolExpr(AND_EXPR, newconjs, -1));
10779 {
10780 oj_sublink_scan scan;
10781 scan.n_sublinks = 0;
10782 scan.found_sublink = NULL;
10783 scan.target_varno = 0;
10784 scan.found_var = NULL;
10785 oj_sublink_scan_walker((Node *)q->targetList, &scan);
10786 if (q->jointree->quals)
10787 oj_sublink_scan_walker(q->jointree->quals, &scan);
10788 if (scan.n_sublinks == 0)
10789 q->hasSubLinks = false;
10790 }
10791 return true;
10792}
10793
10794/**
10795 * @brief Move uncorrelated scalar subqueries that are direct target-list entries
10796 * into a cross-joined derived aggregate in the outer FROM.
10797 *
10798 * An uncorrelated @c (SELECT agg/val FROM Q …) is a single constant value: it
10799 * becomes a one-row derived table @c D (see @c oj_build_uncorrelated_from_subquery)
10800 * appended to the FROM as a cross-join, and the target entry is replaced by a Var
10801 * to @c D's column. Restricted to a direct target-list entry so the (aggregate)
10802 * @c agg_token flows straight to the output column: nesting it inside arithmetic
10803 * would coerce the @c agg_token to a scalar and silently drop its provenance.
10804 * Runs before @c decorrelate_scalar_sublinks; correlated sublinks (and ones in
10805 * other positions) are left untouched for the remaining paths.
10806 */
10808 Query *q) {
10809 ListCell *lc;
10810 bool changed = false;
10811
10812 if (q->commandType != CMD_SELECT || !q->hasSubLinks || !q->jointree)
10813 return false;
10814
10815 foreach (lc, q->targetList) {
10816 TargetEntry *te = (TargetEntry *)lfirst(lc);
10817 SubLink *sl;
10818 Query *D;
10819 RangeTblEntry *d_rte;
10820 RangeTblRef *rtr;
10821 TargetEntry *dte;
10822 Index d_idx;
10823
10824 if (!IsA(te->expr, SubLink))
10825 continue;
10826 sl = (SubLink *)te->expr;
10827 if (sl->subLinkType != EXPR_SUBLINK || !IsA(sl->subselect, Query))
10828 continue;
10829 if (sublink_is_inert(sl))
10830 continue; /* an inert fetch stays an untracked scalar subquery */
10831 D = oj_build_uncorrelated_from_subquery(constants, (Query *)sl->subselect);
10832 if (D == NULL)
10833 continue;
10834
10835 d_rte = oj_make_subquery_rte(D);
10836 rtr = makeNode(RangeTblRef);
10837 dte = (TargetEntry *)linitial(D->targetList);
10838 q->rtable = lappend(q->rtable, d_rte);
10839 d_idx = list_length(q->rtable);
10840 rtr->rtindex = d_idx;
10841 q->jointree->fromlist = lappend(q->jointree->fromlist, rtr);
10842 te->expr = (Expr *)makeVar(d_idx, 1, exprType((Node *)dte->expr),
10843 exprTypmod((Node *)dte->expr),
10844 exprCollation((Node *)dte->expr), 0);
10845 changed = true;
10846 }
10847
10848 if (changed) {
10849 oj_sublink_scan scan;
10850 /* Some correlated sublinks may remain; clear hasSubLinks only if none do. */
10851 scan.n_sublinks = 0;
10852 scan.found_sublink = NULL;
10853 scan.target_varno = 0;
10854 scan.found_var = NULL;
10855 oj_sublink_scan_walker((Node *)q->targetList, &scan);
10856 if (q->jointree->quals)
10857 oj_sublink_scan_walker(q->jointree->quals, &scan);
10858 if (scan.n_sublinks == 0)
10859 q->hasSubLinks = false;
10860 }
10861 return changed;
10862}
10863
10864/** @brief Is @p limitCount the literal 1? Unwraps the @c int4->int8 coercion
10865 * PostgreSQL wraps a @c "LIMIT 1" literal in. */
10866static bool oj_limit_count_is_one(Node *limitCount) {
10867 Node *n = limitCount;
10868 Const *c;
10869 if (n == NULL)
10870 return false;
10871 if (IsA(n, FuncExpr) && list_length(((FuncExpr *)n)->args) == 1)
10872 n = (Node *)linitial(((FuncExpr *)n)->args);
10873 if (IsA(n, RelabelType))
10874 n = (Node *)((RelabelType *)n)->arg;
10875 if (!IsA(n, Const) || ((Const *)n)->constisnull)
10876 return false;
10877 c = (Const *)n;
10878 if (c->consttype == INT8OID)
10879 return DatumGetInt64(c->constvalue) == 1;
10880 if (c->consttype == INT4OID)
10881 return DatumGetInt32(c->constvalue) == 1;
10882 if (c->consttype == INT2OID)
10883 return DatumGetInt16(c->constvalue) == 1;
10884 return false;
10885}
10886
10887/**
10888 * @brief Can two scalar-subquery bodies share a single decorrelating LEFT JOIN?
10889 *
10890 * True when both are plain value bodies (no aggregate / DISTINCT / ORDER BY /
10891 * LIMIT) over the same single relation @c Q with the same correlation @c WHERE,
10892 * differing only in the one selected value. Then a single @c "R ⟕ Q ON corr"
10893 * group serves both: one @c count(Q.key) @c <= @c 1 gate, a @c choose() per
10894 * sublink. Used to decorrelate several correlated target-list sublinks that
10895 * share a @c (Q, @c corr) -- e.g. @c "(SELECT Q.x WHERE Q.k=R.k),
10896 * (SELECT Q.y WHERE Q.k=R.k)" -- in one pass.
10897 */
10898/** @brief @c equal() on two single-RTE rtables, ignoring per-RTE ACL fields.
10899 *
10900 * Before PostgreSQL 16 the permission bookkeeping (@c requiredPerms,
10901 * @c selectedCols…) lived inside @c RangeTblEntry, so two sublink bodies
10902 * over the same @c Q differing only in the selected value column would
10903 * spuriously compare unequal (their @c selectedCols differ). PG16 moved
10904 * those fields out into @c Query.rteperminfos and a plain @c equal()
10905 * suffices. */
10906static bool oj_rtables_coalescible(List *rta, List *rtb) {
10907#if PG_VERSION_NUM >= 160000
10908 return equal(rta, rtb);
10909#else
10910 RangeTblEntry *a = (RangeTblEntry *)copyObject(linitial(rta));
10911 RangeTblEntry *b = (RangeTblEntry *)copyObject(linitial(rtb));
10912 a->requiredPerms = b->requiredPerms = 0;
10913 a->checkAsUser = b->checkAsUser = InvalidOid;
10914 a->selectedCols = b->selectedCols = NULL;
10915 a->insertedCols = b->insertedCols = NULL;
10916 a->updatedCols = b->updatedCols = NULL;
10917#if PG_VERSION_NUM >= 120000
10918 a->extraUpdatedCols = b->extraUpdatedCols = NULL;
10919#endif
10920 return equal(a, b);
10921#endif
10922}
10923
10924static bool oj_sub_bodies_coalescible(Query *a, Query *b) {
10925 if (a->hasAggs || b->hasAggs || a->distinctClause || b->distinctClause ||
10926 a->sortClause || b->sortClause || a->limitCount || b->limitCount ||
10927 a->limitOffset || b->limitOffset)
10928 return false;
10929 if (list_length(a->targetList) != 1 || list_length(b->targetList) != 1 ||
10930 list_length(a->rtable) != 1 || list_length(b->rtable) != 1)
10931 return false;
10932 return oj_rtables_coalescible(a->rtable, b->rtable) &&
10933 equal(a->jointree, b->jointree);
10934}
10935
10936/**
10937 * @brief Is @p node a binary/unary @c +,-,*,/ operator expression?
10938 *
10939 * Mirrors the predicate in @c try_swap_agg_arith: exactly the arithmetic
10940 * operators whose @c agg_token overloads build a @c gate_arith token that
10941 * carries provenance through the operation. Used to decide whether a scalar
10942 * sublink nested inside a target-list expression sits in provenance-carrying
10943 * arithmetic (so it can be lifted to @c choose()) or in something opaque (a
10944 * function call, @c CASE …) that cannot propagate provenance.
10945 */
10946static bool oj_is_arith_opexpr(Node *node) {
10947 char *opname;
10948 bool is_arith;
10949 int nargs;
10950 if (node == NULL || !IsA(node, OpExpr))
10951 return false;
10952 nargs = list_length(((OpExpr *)node)->args);
10953 if (nargs < 1 || nargs > 2)
10954 return false;
10955 opname = get_opname(((OpExpr *)node)->opno);
10956 if (opname == NULL)
10957 return false;
10958 is_arith = strcmp(opname, "+") == 0 || strcmp(opname, "-") == 0 ||
10959 strcmp(opname, "*") == 0 || strcmp(opname, "/") == 0;
10960 pfree(opname);
10961 return is_arith;
10962}
10963
10964/**
10965 * @brief Is SubLink @p sl reachable from @p node through arithmetic only?
10966 *
10967 * True iff @p sl is nested inside @p node through a chain of nothing but
10968 * arithmetic @c OpExprs (@c oj_is_arith_opexpr) and casts. Cast peeling uses
10969 * @c peel_agg_casts -- the same @c RelabelType / 1-arg cast @c FuncExpr set the
10970 * downstream @c try_swap_agg_arith peels -- so detection and execution agree
10971 * (e.g. an @c int sublink divided by a @c numeric: the implicit cast is peeled
10972 * here and again when the agg_token operator is resolved). Such a sublink can
10973 * be lifted to a @c choose() aggregate in place: the surrounding @c +,-,*,/
10974 * then carry the subquery's provenance via @c gate_arith. Any other enclosing
10975 * node returns @c false, leaving the sublink to the warning passthrough (its
10976 * provenance genuinely cannot flow through, e.g. a non-cast function argument).
10977 */
10978static bool oj_tl_sublink_in_arith(Node *node, SubLink *sl) {
10979 if (node == NULL)
10980 return false;
10981 node = peel_agg_casts(node);
10982 if (node == (Node *)sl)
10983 return true;
10984 if (oj_is_arith_opexpr(node)) {
10985 ListCell *lc;
10986 foreach (lc, ((OpExpr *)node)->args)
10987 if (oj_tl_sublink_in_arith((Node *)lfirst(lc), sl))
10988 return true;
10989 }
10990 return false;
10991}
10992
10993/** @brief Context for @c oj_replace_sublink_mut. */
10995 SubLink *target; /* the SubLink node to swap out (matched by pointer) */
10996 Node *repl; /* what to put in its place (the choose() Aggref) */
10998
10999/**
11000 * @brief Replace one specific @c SubLink node with @p repl, in place.
11001 *
11002 * Used to lift a sublink nested in target-list arithmetic to its @c choose()
11003 * aggregate without disturbing the surrounding operators, so the arithmetic
11004 * survives and carries the lifted token's provenance.
11005 */
11006static Node *oj_replace_sublink_mut(Node *node, void *cx) {
11008 if (node == NULL)
11009 return NULL;
11010 if (node == (Node *)c->target)
11011 return c->repl;
11012 return expression_tree_mutator(node, oj_replace_sublink_mut, cx);
11013}
11014
11015/**
11016 * @brief Decorrelate a single top-level scalar subquery into a LEFT JOIN.
11017 *
11018 * Restricted (v1) to: a @c CMD_SELECT whose FROM is a single tracked base
11019 * relation R, with exactly one SubLink in the whole query, that SubLink being
11020 * an @c EXPR_SUBLINK that is the direct expression of a target-list entry,
11021 * whose body is @c "SELECT val FROM Q [WHERE corr]" over a single base relation
11022 * Q referencing only Q (level 0) and R (level 1). Returns @c true if the
11023 * query was rewritten in place.
11024 */
11025static bool decorrelate_scalar_sublinks(const constants_t *constants,
11026 Query *q) {
11027 RangeTblRef *r_ref;
11028 Index R_idx, Q_idx, join_idx;
11029 RangeTblEntry *R_rte, *Q_rte_orig, *Q_copy, *jrte;
11030 Query *sub;
11031 SubLink *sl = NULL;
11032 TargetEntry *sl_te = NULL;
11033 Expr *valexpr;
11034 Node *theta;
11035 oj_cols Rc, Qc;
11036 oj_decorr_ctx dctx;
11037 oj_sublink_scan scan;
11038 ListCell *lc;
11039 int i, n_tl_sublinks = 0;
11040 bool in_where = false;
11041 bool is_agg_body = false;
11042 bool is_limit1 = false; /* ORDER BY … LIMIT 1 value body: argmax via choose */
11043 bool is_distinct = false; /* SELECT DISTINCT body: count(DISTINCT v) <= 1 gate */
11044 bool coalesce = false; /* >1 target-list sublinks sharing one (Q, corr) */
11045 bool nested_in_tl = false; /* the lone sublink is nested in target-list arithmetic */
11046 List *co_sls = NIL, *co_tes = NIL; /* parallel: each sublink + its target entry */
11047 Expr *repl_expr; /* what replaces the SubLink: choose(val) or the aggregate */
11048
11049 if (!OidIsValid(constants->OID_FUNCTION_CHOOSE))
11050 return false;
11051 if (q->commandType != CMD_SELECT || !q->hasSubLinks)
11052 return false;
11053 if (q->groupClause || q->groupingSets || q->hasAggs || q->distinctClause ||
11054 q->setOperations || q->havingQual || q->hasWindowFuncs)
11055 return false;
11056 if (!q->jointree || q->jointree->fromlist == NIL)
11057 return false;
11058
11059 /* Exactly one SubLink in the whole query. It is either the direct expr of a
11060 * target-list entry (its value flows to choose()), or it sits inside a WHERE
11061 * conjunct (a comparison that will be lifted to HAVING on choose()). */
11062 scan.n_sublinks = 0;
11063 scan.found_sublink = NULL;
11064 scan.target_varno = 0;
11065 scan.found_var = NULL;
11066 oj_sublink_scan_walker((Node *)q->targetList, &scan);
11067 n_tl_sublinks = scan.n_sublinks;
11068 if (q->jointree->quals)
11069 oj_sublink_scan_walker(q->jointree->quals, &scan);
11070
11071 /* Several correlated target-list sublinks sharing one (Q, corr) coalesce onto
11072 * a single LEFT JOIN: every one a direct EXPR_SUBLINK target entry, all bodies
11073 * coalescible (same Q + correlation, differing only in the value). Then below
11074 * builds one R ⟕ Q group with a choose() per sublink and a single count gate. */
11075 if (scan.n_sublinks > 1) {
11076 Query *rep = NULL;
11077 if (n_tl_sublinks != scan.n_sublinks)
11078 return false; /* a WHERE sublink in the mix: not coalescible */
11079 foreach (lc, q->targetList) {
11080 TargetEntry *te = (TargetEntry *)lfirst(lc);
11081 SubLink *e;
11082 if (te->expr == NULL || !IsA(te->expr, SubLink))
11083 continue; /* plain column / expression: kept as a GROUP BY key below */
11084 e = (SubLink *)te->expr;
11085 if (e->subLinkType != EXPR_SUBLINK || !IsA(e->subselect, Query))
11086 return false;
11087 if (rep == NULL)
11088 rep = (Query *)e->subselect;
11089 else if (!oj_sub_bodies_coalescible(rep, (Query *)e->subselect))
11090 return false;
11091 co_sls = lappend(co_sls, e);
11092 co_tes = lappend(co_tes, te);
11093 }
11094 /* All sublinks must be direct target entries (none nested in an expression). */
11095 if (list_length(co_sls) != scan.n_sublinks)
11096 return false;
11097 coalesce = true;
11098 sl = (SubLink *)linitial(co_sls);
11099 sl_te = (TargetEntry *)linitial(co_tes);
11100 } else {
11101 if (scan.n_sublinks != 1)
11102 return false;
11103 sl = scan.found_sublink;
11104 if (sl == NULL || sl->subLinkType != EXPR_SUBLINK ||
11105 !IsA(sl->subselect, Query))
11106 return false;
11107
11108 /* Is it a direct target-list entry? Otherwise it must be in WHERE; a
11109 * SubLink nested inside a target-list expression (arithmetic, comparison)
11110 * is not supported. */
11111 foreach (lc, q->targetList) {
11112 TargetEntry *te = (TargetEntry *)lfirst(lc);
11113 if (te->expr == (Expr *)sl) {
11114 sl_te = te;
11115 break;
11116 }
11117 }
11118 if (sl_te == NULL) {
11119 if (n_tl_sublinks > 0) {
11120 /* Nested inside a target-list expression. If the enclosing operators are
11121 * all agg_token-tracked arithmetic (+,-,*,/, casts), lift the sublink to
11122 * choose() in place below: the surrounding arithmetic then carries the
11123 * subquery's provenance through a gate_arith token. Any other nesting
11124 * (function argument, CASE, …) cannot propagate provenance, so bail and
11125 * let the caller pass it through with a warning. */
11126 foreach (lc, q->targetList) {
11127 TargetEntry *te = (TargetEntry *)lfirst(lc);
11128 if (te->expr && oj_tl_sublink_in_arith((Node *)te->expr, sl)) {
11129 sl_te = te;
11130 nested_in_tl = true;
11131 break;
11132 }
11133 }
11134 if (!nested_in_tl)
11135 return false; /* non-arithmetic target-list nesting */
11136 } else {
11137 in_where = true;
11138 /* A WHERE SubLink must be a direct operand of a comparison (its value is
11139 * lifted to a HAVING cmp gate on choose()). If it is nested inside
11140 * arithmetic -- (SELECT …) + 1 > k -- the comparison cannot be lifted; bail
11141 * so the caller passes the sublink through with a warning instead. */
11142 {
11143 List *direct = NIL;
11144 collect_direct_qual_sublinks(q->jointree->quals, &direct);
11145 if (!list_member_ptr(direct, sl))
11146 return false;
11147 }
11148 }
11149 }
11150 } /* end single-sublink branch */
11151
11152 /* The body must be SELECT val FROM Q [WHERE corr], Q a single base rel, where
11153 * val is either a plain value (decorrelated with choose() + count(...)<=1) or
11154 * a single bare aggregate (decorrelated to that aggregate over the LEFT-JOIN
11155 * group, no count gate). LIMIT / OFFSET would pick a bounded, order-dependent
11156 * subset and a CTE would be dropped; reject those. */
11157 sub = (Query *)sl->subselect;
11158 if (sub->commandType != CMD_SELECT || sub->groupClause ||
11159 sub->groupingSets || sub->setOperations || sub->hasWindowFuncs ||
11160 sub->hasSubLinks || sub->limitOffset || sub->cteList)
11161 return false;
11162 /* SELECT DISTINCT v: the at-most-one-row rule counts distinct VALUES, so the
11163 * gate becomes count(DISTINCT v) <= 1 (admitting many rows of one value). Not
11164 * combined with an aggregate body or LIMIT. */
11165 if (sub->distinctClause != NIL) {
11166 if (sub->hasDistinctOn || sub->hasAggs || sub->limitCount)
11167 return false;
11168 is_distinct = true;
11169 }
11170 /* LIMIT: a bare LIMIT picks an arbitrary row (rejected), but an ORDER BY …
11171 * LIMIT 1 value body is the argmax -- decorrelated to choose(val ORDER BY key)
11172 * with no count gate (LIMIT 1 never errors on >1 rows). */
11173 if (sub->limitCount) {
11174 if (!sub->sortClause || sub->hasAggs ||
11175 !oj_limit_count_is_one(sub->limitCount))
11176 return false;
11177 is_limit1 = true;
11178 }
11179 /* Exactly one non-junk output column (the scalar value); ORDER BY adds junk
11180 * sort-key entries, which become the choose aggregate's order arguments. */
11181 {
11182 int nreal = 0;
11183 ListCell *tlc;
11184 foreach (tlc, sub->targetList)
11185 if (!((TargetEntry *)lfirst(tlc))->resjunk)
11186 ++nreal;
11187 if (nreal != 1 || ((TargetEntry *)linitial(sub->targetList))->resjunk)
11188 return false;
11189 }
11190 if (sub->hasAggs &&
11191 !IsA(((TargetEntry *)linitial(sub->targetList))->expr, Aggref))
11192 return false; /* aggregate body must be a single bare aggregate */
11193 is_agg_body = sub->hasAggs;
11194 if (!sub->jointree || sub->jointree->fromlist == NIL)
11195 return false;
11196 /* A multi-table body (Q1, Q2, … in FROM) is collapsed into a single derived
11197 * cross-product subquery D, after which the body is "SELECT val FROM D WHERE
11198 * W" -- the single-Q path below handles D exactly as it handles a subquery R. */
11199 if (list_length(sub->rtable) != 1 && !oj_wrap_body_from(constants, sub))
11200 return false;
11201 if (list_length(sub->jointree->fromlist) != 1 ||
11202 !IsA(linitial(sub->jointree->fromlist), RangeTblRef))
11203 return false;
11204 Q_rte_orig = list_nth_node(RangeTblEntry, sub->rtable, 0);
11205 if ((Q_rte_orig->rtekind != RTE_RELATION &&
11206 !(Q_rte_orig->rtekind == RTE_SUBQUERY && !Q_rte_orig->lateral)) ||
11207 !oj_rte_has_provsql(constants, Q_rte_orig))
11208 return false;
11209
11210 /* Determine R. If the outer FROM is already a single tracked relation or
11211 * (non-lateral) subquery, use it directly; otherwise wrap the whole FROM into
11212 * a derived subquery R' (the subquery-arm lowering then handles R' LEFT JOIN
11213 * Q either way). */
11214 R_rte = NULL;
11215 if (list_length(q->jointree->fromlist) == 1 &&
11216 IsA(linitial(q->jointree->fromlist), RangeTblRef)) {
11217 r_ref = (RangeTblRef *)linitial(q->jointree->fromlist);
11218 R_rte = list_nth_node(RangeTblEntry, q->rtable, r_ref->rtindex - 1);
11219 if ((R_rte->rtekind == RTE_RELATION ||
11220 (R_rte->rtekind == RTE_SUBQUERY && !R_rte->lateral)) &&
11221 oj_rte_has_provsql(constants, R_rte))
11222 R_idx = r_ref->rtindex;
11223 else
11224 R_rte = NULL;
11225 }
11226 if (R_rte == NULL) {
11227 /* The coalesce path re-finds many sublink target entries; the outer-FROM
11228 * wrap only re-finds one. Restrict coalesce to a single base R (the common
11229 * case); a wrap-needing R with several sublinks stays for a later pass. */
11230 if (coalesce)
11231 return false;
11232 /* A nested-in-arithmetic sublink over a multi-table / subquery FROM (the
11233 * wrap path) is deferred: oj_wrap_outer_from re-finds the sublink's target
11234 * entry by exact pointer below, which a nested sublink would not match.
11235 * Decline before the wrap mutates q, so the warning passthrough applies. */
11236 if (nested_in_tl)
11237 return false;
11238 if (!oj_wrap_outer_from(constants, q, sl, in_where))
11239 return false;
11240 R_idx = 1;
11241 R_rte = list_nth_node(RangeTblEntry, q->rtable, 0);
11242 sub = (Query *)sl->subselect; /* remapped in place by the wrap */
11243 /* The wrap rebuilt the target list (the SubLink node itself is preserved),
11244 * so re-find the target entry that still carries the SubLink. */
11245 sl_te = NULL;
11246 foreach (lc, q->targetList) {
11247 TargetEntry *te = (TargetEntry *)lfirst(lc);
11248 if (te->expr == (Expr *)sl) {
11249 sl_te = te;
11250 break;
11251 }
11252 }
11253 }
11254
11255 /* The correlation normally references some Q column (so count() has a key
11256 * that is NULL on the null-padded antijoin rows). A bare body with no
11257 * Q-referencing WHERE at all is fine when it is a non-star aggregate --
11258 * e.g. "(SELECT max(x) FROM Q) > R.col", whose comparison lifts to HAVING
11259 * over the R ⟕ Q ON TRUE group below: such a body needs no key (no
11260 * at-most-one-row gate, no count(*) -> count(Q.key) rewrite, and the
11261 * aggregate ignores the null-padded row by itself). Value bodies (count
11262 * gates) and count(*) (key rewrite) do need a genuine Q column: decline. */
11263 scan.n_sublinks = 0;
11264 scan.target_varno = 1; /* Q is at index 1 inside the body */
11265 scan.found_var = NULL;
11266 if (sub->jointree->quals)
11267 oj_sublink_scan_walker(sub->jointree->quals, &scan);
11268 if (scan.found_var == NULL &&
11269 (!is_agg_body ||
11270 ((Aggref *)((TargetEntry *)linitial(sub->targetList))->expr)->aggstar))
11271 return false;
11272
11273 /* ---- Commit: pull Q up, build the LEFT JOIN, choose() + GROUP BY + count.
11274 * R stays at R_idx, Q is appended (Q_idx), join RTE appended (join_idx). ---*/
11275 oj_collect_cols(constants, R_rte, &Rc);
11276 oj_collect_cols(constants, Q_rte_orig, &Qc);
11277
11278 Q_copy = copyObject(Q_rte_orig);
11279#if PG_VERSION_NUM >= 160000
11280 if (Q_rte_orig->perminfoindex != 0) {
11281 RTEPermissionInfo *pi =
11282 getRTEPermissionInfo(sub->rteperminfos, Q_rte_orig);
11283 q->rteperminfos = lappend(q->rteperminfos, copyObject(pi));
11284 Q_copy->perminfoindex = list_length(q->rteperminfos);
11285 }
11286#endif
11287 q->rtable = lappend(q->rtable, Q_copy);
11288 Q_idx = list_length(q->rtable);
11289
11290 /* Move the body's Vars to the outer level: Q(level0,1) -> (level0, Q_idx);
11291 * correlated R(level1) -> (level0). */
11292 dctx.q_old = 1;
11293 dctx.q_new = Q_idx;
11294 theta = oj_decorr_var_mut(copyObject(sub->jointree->quals), &dctx);
11295 valexpr = (Expr *)oj_decorr_var_mut(
11296 copyObject((Node *)((TargetEntry *)linitial(sub->targetList))->expr),
11297 &dctx);
11298
11299 /* Build the synthetic join RTE (eref / joinaliasvars / left/right cols), the
11300 * same bookkeeping the deparser needs as in oj_build_join_query. */
11301 {
11302 List *av = NIL, *lcols = NIL, *rcols = NIL, *cn = NIL;
11303 jrte = makeNode(RangeTblEntry);
11304 for (i = 0; i < Rc.n; ++i) {
11305 av = lappend(av, makeVar(R_idx, Rc.attno[i], Rc.type[i], Rc.typmod[i],
11306 Rc.coll[i], 0));
11307 lcols = lappend_int(lcols, Rc.attno[i]);
11308 rcols = lappend_int(rcols, 0);
11309 cn = lappend(cn, makeString(pstrdup(Rc.name[i])));
11310 }
11311 for (i = 0; i < Qc.n; ++i) {
11312 av = lappend(av, makeVar(Q_idx, Qc.attno[i], Qc.type[i], Qc.typmod[i],
11313 Qc.coll[i], 0));
11314 lcols = lappend_int(lcols, 0);
11315 rcols = lappend_int(rcols, Qc.attno[i]);
11316 cn = lappend(cn, makeString(pstrdup(Qc.name[i])));
11317 }
11318 jrte->rtekind = RTE_JOIN;
11319 jrte->jointype = JOIN_LEFT;
11320 jrte->alias = NULL;
11321 jrte->eref = makeAlias(PROVSQL_JOIN_ALIAS, cn);
11322 jrte->joinaliasvars = av;
11323#if PG_VERSION_NUM >= 130000
11324 jrte->joinleftcols = lcols;
11325 jrte->joinrightcols = rcols;
11326 jrte->joinmergedcols = 0;
11327#endif
11328 jrte->inFromCl = true;
11329 q->rtable = lappend(q->rtable, jrte);
11330 join_idx = list_length(q->rtable);
11331 }
11332
11333 {
11334 JoinExpr *je = makeNode(JoinExpr);
11335 RangeTblRef *lr = makeNode(RangeTblRef), *rr = makeNode(RangeTblRef);
11336 lr->rtindex = R_idx;
11337 rr->rtindex = Q_idx;
11338 je->jointype = JOIN_LEFT;
11339 je->larg = (Node *)lr;
11340 je->rarg = (Node *)rr;
11341 je->quals = theta;
11342 je->isNatural = false;
11343 je->usingClause = NIL;
11344 je->rtindex = join_idx;
11345 q->jointree->fromlist = list_make1(je);
11346 /* Any pre-existing outer WHERE (over R, level 0) stays in jointree->quals.*/
11347 }
11348
11349 /* What replaces the SubLink, over the LEFT-JOIN group:
11350 * - value body -> choose(val) picks the single matched value;
11351 * - aggregate body -> the aggregate itself. count(*) is rewritten to
11352 * count(Q.key) so the null-padded antijoin row (Q.key IS NULL) is not
11353 * counted -- an empty correlated group must give 0, not 1.
11354 * For a target-list SubLink it replaces the entry directly; for a WHERE
11355 * SubLink it is substituted into the conjunct, which moves to HAVING. */
11356 if (is_agg_body) {
11357 Aggref *agg = (Aggref *)valexpr; /* the remapped body aggregate */
11358 if (agg->aggstar) {
11359 Var *qkey = NULL;
11360 /* Prefer the constant match indicator projected by
11361 * oj_wrap_body_with_match_ind: under the NULL-guarded antijoin
11362 * correlation a matched row can be NULL in every data column, so
11363 * only the indicator keys matched vs null-padded reliably. */
11364 if (Q_rte_orig->rtekind == RTE_SUBQUERY) {
11365 ListCell *klc;
11366 foreach (klc, Q_rte_orig->subquery->targetList) {
11367 TargetEntry *kte = (TargetEntry *)lfirst(klc);
11368 if (!kte->resjunk && kte->resname &&
11369 !strcmp(kte->resname, PROVSQL_MATCH_IND_COLNAME)) {
11370 qkey = makeVar(Q_idx, kte->resno, BOOLOID, -1, InvalidOid, 0);
11371 break;
11372 }
11373 }
11374 }
11375 if (qkey == NULL) {
11376 qkey = (Var *)copyObject(scan.found_var);
11377 qkey->varno = Q_idx;
11378#if PG_VERSION_NUM >= 130000
11379 qkey->varnosyn = 0;
11380 qkey->varattnosyn = 0;
11381#endif
11382 }
11383 repl_expr = (Expr *)oj_make_aggref(F_COUNT_ANY, INT8OID, qkey->vartype,
11384 (Expr *)qkey);
11385 } else {
11386 repl_expr = valexpr;
11387 }
11388 } else if (is_limit1) {
11389 /* ORDER BY … LIMIT 1 = argmax: choose(val ORDER BY key). The subselect's
11390 * targetList (the value plus junk sort-key entries) and its sortClause map
11391 * directly onto the ordered Aggref's args / aggorder; remap each arg's Q
11392 * vars to the pulled-up Q. No count gate -- LIMIT 1 always takes one row. */
11393 Aggref *agg = makeNode(Aggref);
11394 List *new_args = NIL;
11395 ListCell *alc;
11396 foreach (alc, sub->targetList) {
11397 TargetEntry *te = (TargetEntry *)copyObject(lfirst(alc));
11398 te->expr = (Expr *)oj_decorr_var_mut((Node *)te->expr, &dctx);
11399 new_args = lappend(new_args, te);
11400 }
11401 agg->aggfnoid = constants->OID_FUNCTION_CHOOSE;
11402 agg->aggtype = exprType((Node *)valexpr);
11403 agg->aggtranstype = InvalidOid;
11404 agg->aggargtypes = list_make1_oid(exprType((Node *)valexpr));
11405 agg->args = new_args;
11406 agg->aggorder = (List *)copyObject((Node *)sub->sortClause);
11407 agg->aggkind = AGGKIND_NORMAL;
11408 agg->aggsplit = AGGSPLIT_SIMPLE;
11409 agg->location = -1;
11410#if PG_VERSION_NUM >= 140000
11411 agg->aggno = agg->aggtransno = -1;
11412#endif
11413 repl_expr = (Expr *)agg;
11414 } else {
11415 repl_expr = (Expr *)oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
11416 exprType((Node *)valexpr),
11417 exprType((Node *)valexpr), valexpr);
11418 }
11419 if (coalesce) {
11420 /* Each coalesced sublink gets its own choose() over the shared group: remap
11421 * its body's value to the pulled-up Q and replace its target entry. (The
11422 * representative's repl_expr above is recomputed here as the first item, so
11423 * the bodies all use the identical Q/correlation remap.) */
11424 ListCell *la, *lb;
11425 forboth(la, co_sls, lb, co_tes) {
11426 SubLink *sli = (SubLink *)lfirst(la);
11427 TargetEntry *tei = (TargetEntry *)lfirst(lb);
11428 Expr *vi = (Expr *)oj_decorr_var_mut(
11429 copyObject(
11430 (Node *)((TargetEntry *)linitial(((Query *)sli->subselect)->targetList))
11431 ->expr),
11432 &dctx);
11433 tei->expr = (Expr *)oj_make_aggref(constants->OID_FUNCTION_CHOOSE,
11434 exprType((Node *)vi),
11435 exprType((Node *)vi), vi);
11436 }
11437 } else if (nested_in_tl) {
11438 /* Swap just the SubLink node inside the target expression for choose(),
11439 * leaving the surrounding arithmetic; the agg_token operator overloads
11440 * (try_swap_agg_arith) later carry the lifted token through +,-,*,/. */
11442 rc.target = sl;
11443 rc.repl = (Node *)repl_expr;
11444 sl_te->expr = (Expr *)oj_replace_sublink_mut((Node *)sl_te->expr, &rc);
11445 } else if (!in_where)
11446 sl_te->expr = repl_expr;
11447
11448 /* GROUP BY every R user column: each needs a target-list entry carrying a
11449 * ressortgroupref plus a SortGroupClause. */
11450 {
11451 int sgref = 0;
11452 /* Highest existing ressortgroupref, so new ones do not collide. */
11453 foreach (lc, q->targetList) {
11454 TargetEntry *te = (TargetEntry *)lfirst(lc);
11455 if (te->ressortgroupref > sgref)
11456 sgref = te->ressortgroupref;
11457 }
11458 for (i = 0; i < Rc.n; ++i) {
11459 TargetEntry *gte = NULL;
11460 SortGroupClause *sgc;
11461 ListCell *lc2;
11462
11463 /* Reuse an existing target entry that already projects this R column. */
11464 foreach (lc2, q->targetList) {
11465 TargetEntry *te = (TargetEntry *)lfirst(lc2);
11466 if (IsA(te->expr, Var)) {
11467 Var *v = (Var *)te->expr;
11468 if (v->varlevelsup == 0 && v->varno == R_idx &&
11469 v->varattno == Rc.attno[i]) {
11470 gte = te;
11471 break;
11472 }
11473 }
11474 }
11475 if (gte == NULL) {
11476 Var *v = makeVar(R_idx, Rc.attno[i], Rc.type[i], Rc.typmod[i],
11477 Rc.coll[i], 0);
11478 gte = makeTargetEntry((Expr *)v, list_length(q->targetList) + 1,
11479 pstrdup(Rc.name[i]), true /* resjunk */);
11480 q->targetList = lappend(q->targetList, gte);
11481 }
11482 if (gte->ressortgroupref == 0)
11483 gte->ressortgroupref = ++sgref;
11484 sgc = makeNode(SortGroupClause);
11485 sgc->tleSortGroupRef = gte->ressortgroupref;
11486 get_sort_group_operators(Rc.type[i], false, true, false, &sgc->sortop,
11487 &sgc->eqop, NULL, &sgc->hashable);
11488 q->groupClause = lappend(q->groupClause, sgc);
11489 }
11490 }
11491
11492 /* HAVING. A value body adds count(Q.key) <= 1 (Q.key NULL on the null-padded
11493 * antijoin rows), enforcing the scalar subquery's at-most-one-row rule; an
11494 * aggregate body -- and an ORDER BY … LIMIT 1 (argmax) body, which legally
11495 * takes the top of many rows -- needs no such gate. When the SubLink came from
11496 * a WHERE comparison, that conjunct (with the SubLink replaced) is ANDed in --
11497 * a comparison on the aggregated value belongs in HAVING. */
11498 {
11499 List *having_conjuncts = NIL;
11500
11501 if (!is_agg_body && !is_limit1) {
11502 /* SELECT DISTINCT v counts distinct VALUES (count(DISTINCT v) <= 1); a
11503 * plain value body counts matching rows (count(Q.key) <= 1). */
11504 having_conjuncts = list_make1(
11505 is_distinct ? oj_count_distinct_cmp(valexpr, "<=", 1)
11506 : oj_count_cmp((Var *)scan.found_var, Q_idx, "<=", 1));
11507 /* A WHERE comparison must test an actual subquery value, so the correlated
11508 * group has to be non-empty: count(…) = 1, not merely <= 1. An empty
11509 * group would give a NULL comparison (the row is excluded), but the value
11510 * gate over the all-NULL aggregate does not encode that, so the >= 1 gate
11511 * supplies it. (A target-list subquery keeps <= 1 only: zero matches is a
11512 * legal NULL value, and the row still exists.) */
11513 if (in_where)
11514 having_conjuncts = lappend(
11515 having_conjuncts,
11516 is_distinct ? oj_count_distinct_cmp(valexpr, ">=", 1)
11517 : oj_count_cmp((Var *)scan.found_var, Q_idx, ">=", 1));
11518 }
11519
11520 if (in_where) {
11521 /* Split the WHERE AND-list: the conjunct holding the SubLink (with the
11522 * SubLink -> repl_expr substitution) moves to HAVING; the rest stay. */
11524 Node *quals = q->jointree->quals;
11525 List *conjs =
11526 (quals && IsA(quals, BoolExpr) &&
11527 ((BoolExpr *)quals)->boolop == AND_EXPR)
11528 ? ((BoolExpr *)quals)->args
11529 : (quals ? list_make1(quals) : NIL);
11530 List *kept = NIL;
11531 ListCell *lc2;
11532
11533 rc.target = sl;
11534 rc.replacement = (Node *)repl_expr;
11535 foreach (lc2, conjs) {
11536 Node *c = (Node *)lfirst(lc2);
11537 if (oj_contains_sublink_walker(c, sl))
11538 having_conjuncts =
11539 lappend(having_conjuncts, oj_sl_replace_mut(c, &rc));
11540 else
11541 kept = lappend(kept, c);
11542 }
11543 q->jointree->quals =
11544 (kept == NIL) ? NULL
11545 : (list_length(kept) == 1 ? (Node *)linitial(kept)
11546 : (Node *)makeBoolExpr(
11547 AND_EXPR, kept, -1));
11548 }
11549
11550 q->havingQual =
11551 (having_conjuncts == NIL)
11552 ? NULL
11553 : (list_length(having_conjuncts) == 1
11554 ? (Node *)linitial(having_conjuncts)
11555 : (Node *)makeBoolExpr(AND_EXPR, having_conjuncts, -1));
11556 }
11557
11558 q->hasAggs = true;
11559 q->hasSubLinks = false;
11560 return true;
11561}
11562
11563/**
11564 * @brief Group the right-hand arm of a set difference by all its columns so
11565 * the per-tuple right provenances ⊕-combine before the monus.
11566 *
11567 * ProvSQL's multiset difference implements the NOT-IN semantics of the ICDE
11568 * 2026 paper (§IV-B):
11569 * @code
11570 * ⟪q₁ − q₂⟫ = {{ (u, α ⊖ ⊕_{β : (u,β)∈q₂} β) | (u,α) ∈ q₁ }}
11571 * @endcode
11572 * The sum @c ⊕β ranges over ALL right tuples equal to @c u, so the right arm
11573 * must be grouped by its columns first. Without that,
11574 * @c transform_except_into_join's bare @c LEFT @c JOIN emits one monus per
11575 * matching right tuple (yielding @c ⊕(α⊖βᵢ) instead of @c α⊖⊕β) and inflates
11576 * the result multiplicity -- the long-standing "add group by in the right-side
11577 * table" gap. Wrapping the still-raw right arm in
11578 * @code
11579 * SELECT cols FROM (rarg) GROUP BY cols
11580 * @endcode
11581 * makes the later @c get_provenance_attributes / group-by pass build @c ⊕β per
11582 * group and gives the right arm exactly one row per distinct @c u.
11583 *
11584 * Runs before provenance discovery, on the @c SETOP_EXCEPT query (for the
11585 * non-ALL case, on the @c all=true inner set operation that
11586 * @c rewrite_non_all_into_external_group_by leaves behind). It applies equally
11587 * to @c EXCEPT (@c ε(q₁−q₂)) and @c EXCEPT @c ALL (@c q₁−q₂): the only
11588 * difference between them, duplicate elimination of the left arm, is handled
11589 * separately by the non-ALL outer GROUP BY.
11590 */
11591static void group_set_difference_right_arm(const constants_t *constants,
11592 Query *q) {
11593 SetOperationStmt *so;
11594 RangeTblRef *rarg_ref;
11595 RangeTblEntry *rarg_rte;
11596 Query *origB, *G;
11597 RangeTblEntry *w_rte;
11598 RangeTblRef *rtr;
11599 FromExpr *fe;
11600 List *tl = NIL;
11601 ListCell *lc;
11602 int colno = 0, sgref = 0;
11603 bool any_group = false;
11604
11605 (void)constants;
11606
11607 if (q->setOperations == NULL || !IsA(q->setOperations, SetOperationStmt))
11608 return;
11609 so = (SetOperationStmt *)q->setOperations;
11610 if (so->op != SETOP_EXCEPT)
11611 return;
11612 /* Chained difference (rarg is itself a SetOperationStmt) is rejected later
11613 * by transform_except_into_join; leave it untouched here. */
11614 if (!IsA(so->rarg, RangeTblRef))
11615 return;
11616 rarg_ref = (RangeTblRef *)so->rarg;
11617 rarg_rte = list_nth_node(RangeTblEntry, q->rtable, rarg_ref->rtindex - 1);
11618 if (rarg_rte->rtekind != RTE_SUBQUERY || rarg_rte->subquery == NULL)
11619 return;
11620 origB = rarg_rte->subquery;
11621
11622 /* Already a single-row-per-group shape? (Our own outer-join antijoin builds
11623 * the right arm pre-grouped.) Re-grouping is harmless but pointless, so skip
11624 * when the arm already carries a groupClause. */
11625 if (origB->groupClause != NIL || origB->groupingSets != NIL)
11626 return;
11627
11628 G = makeNode(Query);
11629 G->commandType = CMD_SELECT;
11630 G->canSetTag = true;
11631 w_rte = oj_make_subquery_rte(origB);
11632 G->rtable = list_make1(w_rte);
11633 rtr = makeNode(RangeTblRef);
11634 rtr->rtindex = 1;
11635 fe = makeNode(FromExpr);
11636 fe->fromlist = list_make1(rtr);
11637 G->jointree = fe;
11638
11639 colno = 0;
11640 foreach (lc, origB->targetList) {
11641 TargetEntry *te = (TargetEntry *)lfirst(lc);
11642 Var *v;
11643 TargetEntry *nte;
11644 SortGroupClause *sgc;
11645 Oid coltype;
11646
11647 ++colno;
11648 if (te->resjunk)
11649 continue;
11650
11651 coltype = exprType((Node *)te->expr);
11652 v = makeVar(1, colno, coltype, exprTypmod((Node *)te->expr),
11653 exprCollation((Node *)te->expr), 0);
11654 nte = makeTargetEntry((Expr *)v, list_length(tl) + 1,
11655 te->resname ? pstrdup(te->resname) : NULL, false);
11656
11657 /* Group by every column (a UUID provsql column would already have been
11658 * rejected upstream; raw arms expose only value columns here). */
11659 sgc = makeNode(SortGroupClause);
11660 sgc->tleSortGroupRef = nte->ressortgroupref = ++sgref;
11661 get_sort_group_operators(coltype, false, true, false, &sgc->sortop,
11662 &sgc->eqop, NULL, &sgc->hashable);
11663 G->groupClause = lappend(G->groupClause, sgc);
11664 any_group = true;
11665
11666 tl = lappend(tl, nte);
11667 }
11668 G->targetList = tl;
11669
11670 if (!any_group)
11671 return; /* nothing to group on; leave the arm unchanged */
11672
11673 rarg_rte->subquery = G;
11674}
11675
11676/**
11677 * @brief Recursively annotate a UNION tree with the provenance UUID type.
11678 *
11679 * Walks the @c SetOperationStmt tree of a UNION and appends the UUID type
11680 * to @c colTypes / @c colTypmods / @c colCollations on every node, and sets
11681 * @c all = true so that PostgreSQL does not deduplicate the combined stream.
11682 * The non-ALL deduplication has already been moved to an outer GROUP BY by
11683 * @c rewrite_non_all_into_external_group_by before this is called.
11684 *
11685 * @param constants Extension OID cache.
11686 * @param stmt Root (or subtree) of the UNION @c SetOperationStmt.
11687 * @param q Outer query (to look up subquery RTEs for agg_token type updates).
11688 */
11689static void process_set_operation_union(const constants_t *constants,
11690 SetOperationStmt *stmt,
11691 Query *q) {
11692 if (stmt->op != SETOP_UNION) {
11693 provsql_error("Unsupported mixed set operations");
11694 }
11695 if (IsA(stmt->larg, SetOperationStmt)) {
11696 process_set_operation_union(constants, (SetOperationStmt *)(stmt->larg), q);
11697 }
11698 if (IsA(stmt->rarg, SetOperationStmt)) {
11699 process_set_operation_union(constants, (SetOperationStmt *)(stmt->rarg), q);
11700 }
11701
11702 /* Update colTypes for columns that became agg_token after rewriting.
11703 * Use the left branch's subquery to detect agg_token columns. */
11704 if (IsA(stmt->larg, RangeTblRef)) {
11705 Index rtindex = ((RangeTblRef *)stmt->larg)->rtindex;
11706 RangeTblEntry *rte = list_nth_node(RangeTblEntry, q->rtable, rtindex - 1);
11707 if (rte->rtekind == RTE_SUBQUERY && rte->subquery != NULL) {
11708 ListCell *lc_type = list_head(stmt->colTypes);
11709 ListCell *lc_te = list_head(rte->subquery->targetList);
11710 while (lc_type != NULL && lc_te != NULL) {
11711 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
11712 if (exprType((Node *)te->expr) == constants->OID_TYPE_AGG_TOKEN) {
11713 lfirst_oid(lc_type) = constants->OID_TYPE_AGG_TOKEN;
11714 }
11715 lc_type = my_lnext(stmt->colTypes, lc_type);
11716 lc_te = my_lnext(rte->subquery->targetList, lc_te);
11717 }
11718 }
11719 }
11720
11721 stmt->colTypes = lappend_oid(stmt->colTypes, constants->OID_TYPE_UUID);
11722 stmt->colTypmods = lappend_int(stmt->colTypmods, -1);
11723 stmt->colCollations = lappend_int(stmt->colCollations, 0);
11724 stmt->all = true;
11725}
11726
11727/**
11728 * @brief Add a WHERE condition filtering out zero-provenance tuples.
11729 *
11730 * For EXCEPT queries, tuples whose provenance evaluates to zero (i.e., the
11731 * right-hand side fully subsumes the left-hand side) must be excluded from
11732 * the result. This function appends @c provsql <> gate_zero() to
11733 * @p q->jointree->quals, ANDing with any existing WHERE condition.
11734 *
11735 * @param constants Extension OID cache.
11736 * @param q Query to modify in place.
11737 * @param provsql Provenance expression that was added to the SELECT list.
11738 */
11739static void add_select_non_zero(const constants_t *constants, Query *q,
11740 Expr *provsql) {
11741 FuncExpr *gate_zero = makeNode(FuncExpr);
11742 OpExpr *oe = makeNode(OpExpr);
11743
11744 gate_zero->funcid = constants->OID_FUNCTION_GATE_ZERO;
11745 gate_zero->funcresulttype = constants->OID_TYPE_UUID;
11746
11747 oe->opno = constants->OID_OPERATOR_NOT_EQUAL_UUID;
11748 oe->opfuncid = constants->OID_FUNCTION_NOT_EQUAL_UUID;
11749 oe->opresulttype = BOOLOID;
11750 oe->args = list_make2(provsql, gate_zero);
11751 oe->location = -1;
11752
11753 if (q->jointree->quals != NULL) {
11754 BoolExpr *be = makeNode(BoolExpr);
11755
11756 be->boolop = AND_EXPR;
11757 be->args = list_make2(oe, q->jointree->quals);
11758 be->location = -1;
11759
11760 q->jointree->quals = (Node *)be;
11761 } else
11762 q->jointree->quals = (Node *)oe;
11763}
11764
11765/**
11766 * @brief Append @p expr to @p havingQual with an AND, creating one if needed.
11767 *
11768 * If @p havingQual is NULL, returns @p expr directly. If it is already an
11769 * AND @c BoolExpr, appends to its argument list. Otherwise wraps both in a
11770 * new AND node.
11771 *
11772 * @param havingQual Existing HAVING qualifier, or NULL.
11773 * @param expr Expression to conjoin.
11774 * @return The updated HAVING qualifier.
11775 */
11776static Node *add_to_havingQual(Node *havingQual, Expr *expr)
11777{
11778 if(!havingQual) {
11779 havingQual = (Node*) expr;
11780 } else if(IsA(havingQual, BoolExpr) && ((BoolExpr*)havingQual)->boolop==AND_EXPR) {
11781 BoolExpr *be = (BoolExpr*)havingQual;
11782 be->args = lappend(be->args, expr);
11783 } else if(IsA(havingQual, OpExpr) || IsA(havingQual, BoolExpr)) {
11784 /* BoolExpr that is not an AND (OR/NOT): wrap with a new AND node. */
11785 BoolExpr *be = makeNode(BoolExpr);
11786 be->boolop=AND_EXPR;
11787 be->location=-1;
11788 be->args = list_make2(havingQual, expr);
11789 havingQual = (Node*) be;
11790 } else
11791 provsql_error("Unknown structure within Boolean expression");
11792
11793 return havingQual;
11794}
11795
11796/**
11797 * @brief Check whether @p op is a supported comparison on an aggregate result.
11798 *
11799 * Returns true iff @p op is a two-argument operator where at least one
11800 * argument is a @c Var of type @c agg_token (or an implicit-cast wrapper
11801 * thereof) and the other is a @c Const (possibly cast). This is the set
11802 * of WHERE-on-aggregate patterns that ProvSQL can safely move to a HAVING
11803 * clause.
11804 *
11805 * @param op The @c OpExpr to inspect.
11806 * @param constants Extension OID cache.
11807 * @return True if the pattern is supported, false otherwise.
11808 */
11809static bool check_selection_on_aggregate(OpExpr *op, const constants_t *constants)
11810{
11811 int agg_sides = 0;
11812
11813 if(op->args->length != 2)
11814 return false;
11815
11816 for(unsigned i=0; i<2; ++i) {
11817 Node *arg = lfirst(list_nth_cell(op->args, i));
11818 if(expr_contains_agg(arg, constants))
11819 agg_sides++;
11820 /* The other side may be any aggregate-free expression: the threshold. */
11821 }
11822
11823 /* At least one aggregate side. Constant arithmetic over a single aggregate
11824 * is folded into the threshold (normalize_agg_comparison); anything else
11825 * (agg-vs-agg, products of aggregates, c/agg, ...) is resolved by the
11826 * possible-worlds enumeration in having_semantics, which leaves the gate
11827 * unresolved -- a clean error -- if it cannot handle the shape. */
11828 return agg_sides >= 1;
11829}
11830
11831/**
11832 * @brief Check whether every leaf of a Boolean expression is a supported
11833 * comparison on an aggregate result.
11834 *
11835 * Recursively validates @c OpExpr leaves via @c check_selection_on_aggregate
11836 * and descends into nested @c BoolExpr nodes.
11837 *
11838 * @param be The Boolean expression to validate.
11839 * @param constants Extension OID cache.
11840 * @return True if all leaves are supported, false if any is not.
11841 */
11842static bool check_boolexpr_on_aggregate(BoolExpr *be, const constants_t *constants)
11843{
11844 ListCell *lc;
11845
11846 foreach (lc, be->args) {
11847 Node *n=lfirst(lc);
11848 /* An agg-free child is an ordinary (regular) condition mixed into the
11849 * HAVING predicate -- supported as a deterministic indicator (the χ
11850 * case). Only children that actually involve an aggregate must match a
11851 * supported aggregate-comparison shape. */
11852 if(!expr_contains_agg(n, constants))
11853 continue;
11854 if(IsA(n, OpExpr)) {
11855 if(!check_selection_on_aggregate((OpExpr*) n, constants))
11856 return false;
11857 } else if(IsA(n, BoolExpr)) {
11858 if(!check_boolexpr_on_aggregate((BoolExpr*) n, constants))
11859 return false;
11860 } else
11861 return false;
11862 }
11863
11864 return true;
11865}
11866
11867/**
11868 * @brief Top-level dispatcher for supported WHERE-on-aggregate patterns.
11869 *
11870 * @param expr Expression to validate (@c OpExpr or @c BoolExpr).
11871 * @param constants Extension OID cache.
11872 * @return True if ProvSQL can handle this expression.
11873 */
11874static bool check_expr_on_aggregate(Expr *expr, const constants_t *constants) {
11875 switch(expr->type) {
11876 case T_BoolExpr:
11877 return check_boolexpr_on_aggregate((BoolExpr*) expr, constants);
11878 case T_OpExpr:
11879 return check_selection_on_aggregate((OpExpr*) expr, constants);
11880 case T_NullTest:
11881 /* A pushable IS [NOT] NULL never reaches here: it was moved into the
11882 * subquery that owns the aggregate before provenance discovery (see
11883 * push_agg_nulltest_into_subquery). One that arrives is a shape the
11884 * pushdown declined -- under an OR or a NOT, say -- so it is reported as
11885 * unsupported rather than as an unrecognised node. */
11886 return false;
11887 default:
11888 provsql_error("Unknown structure within Boolean expression");
11889 }
11890}
11891
11892/* -------------------------------------------------------------------------
11893 * Main query transformation
11894 * ------------------------------------------------------------------------- */
11895
11896/**
11897 * @brief Build the per-RTE column-numbering map used by where-provenance.
11898 *
11899 * Assigns a sequential position (1, 2, 3, …) to every non-provenance,
11900 * non-join, non-empty column across all RTEs in @p q->rtable. The
11901 * @c provsql column is assigned -1 so callers can detect provenance-tracked
11902 * RTEs. Join-RTE columns and empty-named columns (used for anonymous GROUP
11903 * BY keys) are assigned 0.
11904 *
11905 * @note For @c RTE_RELATION entries that are provenance-tracked, the
11906 * sequential numbers produced here must @b not be used as PROJECT gate
11907 * positions. Because numbering is query-order-dependent, the sequential
11908 * number for a column of a provenance table that is not the first RTE
11909 * will exceed @c nb_columns of that table's IN gate, causing
11910 * @c WhereCircuit::evaluate() to return an empty locator set. Instead,
11911 * callers should use @c varattno directly (see
11912 * @c make_provenance_expression()). The -1 sentinel is the reliable
11913 * way to identify a provenance-tracked RTE.
11914 *
11915 * @param q Query whose range table is mapped.
11916 * @param columns Pre-allocated array of length @p q->rtable->length.
11917 * Each element is allocated and filled by this function.
11918 * @param nbcols Out-param: total number of non-provenance output columns.
11919 */
11920static void build_column_map(Query *q, int **columns, int *nbcols) {
11921 unsigned i = 0;
11922 ListCell *l;
11923
11924 *nbcols = 0;
11925
11926 foreach (l, q->rtable) {
11927 RangeTblEntry *r = (RangeTblEntry *)lfirst(l);
11928 ListCell *lc;
11929
11930 columns[i] = 0;
11931 if (r->eref && r->eref->colnames != NIL) {
11932 unsigned j = 0;
11933
11934 columns[i] = (int *)palloc(list_length(r->eref->colnames) * sizeof(int));
11935
11936 foreach (lc, r->eref->colnames) {
11937 if (!lfirst(lc)) {
11938 /* Column without name – used e.g. when grouping by a discarded column */
11939 columns[i][j] = ++(*nbcols);
11940 } else {
11941 const char *v = strVal(lfirst(lc));
11942
11943 if (strcmp(v, "") && r->rtekind != RTE_JOIN) { /* join RTE columns ignored */
11944 if (!strcmp(v, PROVSQL_COLUMN_NAME))
11945 columns[i][j] = -1;
11946 else
11947 columns[i][j] = ++(*nbcols);
11948 } else {
11949 columns[i][j] = 0;
11950 }
11951 }
11952
11953 ++j;
11954 }
11955 }
11956
11957 ++i;
11958 }
11959}
11960
11961/**
11962 * @brief Categorisation of a top-level WHERE conjunct.
11963 *
11964 * Drives the unified WHERE classifier.
11965 * Both probabilistic flavours (agg_token's "moved to HAVING" world and
11966 * random_variable's "lifted to provenance" world) are special cases of
11967 * "this conjunct involves a probabilistic value the executor cannot
11968 * evaluate as a Boolean directly, so the planner has to route it to a
11969 * different evaluation site". The classifier reports which site, or
11970 * (for unsupported mixes) errors.
11971 */
11972typedef enum {
11973 QUAL_DETERMINISTIC, /**< no probabilistic value; stays in WHERE */
11974 QUAL_PURE_AGG, /**< pure agg_token expression; route to HAVING */
11975 QUAL_PURE_RV, /**< pure random_variable expression; lift to provenance */
11976 QUAL_MIXED_AGG_DET, /**< agg_token mixed with non-agg leaves; error */
11977 QUAL_MIXED_RV_DET, /**< random_variable mixed with non-RV leaves; error */
11978 QUAL_MIXED_AGG_RV /**< agg_token and random_variable in the same expr; error */
11979} qual_class;
11980
11981/**
11982 * @brief Classify @p expr along the @c qual_class axis.
11983 *
11984 * Decision table (the predicates @c has_aggtoken,
11985 * @c expr_contains_rv_cmp, @c check_expr_on_aggregate, and
11986 * @c check_expr_on_rv each return whether the expression "contains" or
11987 * "is purely" the corresponding flavour):
11988 *
11989 * | aggtoken | rv_cmp | check_agg | check_rv | classification |
11990 * |----------|--------|-----------|----------|-----------------------|
11991 * | yes | yes | - | - | QUAL_MIXED_AGG_RV |
11992 * | yes | no | true | - | QUAL_PURE_AGG |
11993 * | yes | no | false | - | QUAL_MIXED_AGG_DET |
11994 * | no | yes | - | true | QUAL_PURE_RV |
11995 * | no | yes | - | false | QUAL_MIXED_RV_DET |
11996 * | no | no | - | - | QUAL_DETERMINISTIC |
11997 */
11998static qual_class classify_qual(Expr *expr, const constants_t *constants)
11999{
12000 bool has_agg = has_aggtoken((Node *)expr, constants);
12001 bool has_rv = expr_contains_rv_cmp((Node *)expr, constants);
12002
12003 if (has_agg && has_rv)
12004 return QUAL_MIXED_AGG_RV;
12005 if (has_agg) {
12006 if (check_expr_on_aggregate(expr, constants))
12007 return QUAL_PURE_AGG;
12008 return QUAL_MIXED_AGG_DET;
12009 }
12010 if (has_rv) {
12011 if (check_expr_on_rv(expr, constants))
12012 return QUAL_PURE_RV;
12013 return QUAL_MIXED_RV_DET;
12014 }
12015 return QUAL_DETERMINISTIC;
12016}
12017
12018/** @brief Raise the user-facing error appropriate to a mixed @p c.
12019 *
12020 * Each @c provsql_error call is @c ereport(ERROR), which does not
12021 * return; the explicit @c break statements below are present only to
12022 * keep @c -Wimplicit-fallthrough happy (PostgreSQL's @c elog macro is
12023 * not marked @c noreturn for the compiler's flow analysis). */
12025{
12026 switch (c) {
12027 case QUAL_MIXED_AGG_DET:
12028 /* An ordinary comparison mixed with an aggregate one is now supported
12029 * (the regular leaf becomes a deterministic indicator); this fires only
12030 * when an aggregate comparison itself has an unsupported shape. */
12031 provsql_error("Unsupported aggregate comparison shape in the selection "
12032 "predicate");
12033 break;
12034 case QUAL_MIXED_RV_DET:
12035 /* Likewise: a random_variable comparison mixed with ordinary ones is
12036 * supported; this fires only on an unsupported random_variable
12037 * comparison shape. */
12038 provsql_error("Unsupported random_variable comparison shape in the "
12039 "WHERE clause");
12040 break;
12041 case QUAL_MIXED_AGG_RV:
12042 provsql_error("WHERE clause mixes agg_token (HAVING-style) and "
12043 "random_variable (per-tuple) comparisons inside the "
12044 "same Boolean expression; this combination is not "
12045 "supported");
12046 break;
12047 default:
12048 /* QUAL_DETERMINISTIC / QUAL_PURE_AGG / QUAL_PURE_RV: not a mixed case. */
12049 break;
12050 }
12051}
12052
12053/**
12054 * @brief Unified WHERE classifier &ndash; routes each top-level conjunct
12055 * to the right evaluation site in a single pass.
12056 *
12057 * Walks the WHERE clause, classifies each top-level conjunct, and
12058 * routes pure-agg_token conjuncts to HAVING and pure-random_variable
12059 * conjuncts to the returned rv_cmps list, leaving the deterministic
12060 * conjuncts in WHERE. Doing it in one pass means the rare conjunct
12061 * that mixes agg_token and random_variable gets a deterministic, useful
12062 * error message.
12063 *
12064 * Supported shapes:
12065 * - Whole WHERE is a single conjunct: classify and route or error.
12066 * - Top-level AND of conjuncts: classify each, route, and (after
12067 * walking) collapse the AND if it has zero or one remaining children
12068 * so downstream code does not see a degenerate Boolean node.
12069 * - Top-level OR / NOT containing both deterministic and probabilistic
12070 * leaves: error.
12071 *
12072 * @param constants Extension OID cache.
12073 * @param q Query whose @c jointree->quals and @c havingQual
12074 * may both be mutated in place.
12075 * @return List of @c FuncExpr nodes (one per lifted RV conjunct), each
12076 * producing a @c UUID. The caller conjoins these into
12077 * @c prov_atts before @c make_provenance_expression.
12078 */
12079static List *
12080migrate_probabilistic_quals(const constants_t *constants, Query *q)
12081{
12082 List *rv_cmps = NIL;
12083 Node *quals;
12084
12085 if (!q->jointree || !q->jointree->quals)
12086 return NIL;
12087
12088 quals = q->jointree->quals;
12089
12090 /* Whole WHERE is one conjunct (single OpExpr, or non-AND BoolExpr
12091 * which we treat opaquely &ndash; the per-flavour pure checks
12092 * @c check_expr_on_aggregate / @c check_expr_on_rv recurse through
12093 * the BoolExpr structure themselves). */
12094 if (!IsA(quals, BoolExpr) || ((BoolExpr *)quals)->boolop != AND_EXPR) {
12095 qual_class c = classify_qual((Expr *)quals, constants);
12097
12098 switch (c) {
12099 case QUAL_PURE_AGG:
12100 q->havingQual = add_to_havingQual(q->havingQual, (Expr *)quals);
12101 q->jointree->quals = NULL;
12102 break;
12103 case QUAL_PURE_RV:
12104 rv_cmps = lappend(rv_cmps,
12105 rv_Expr_to_provenance((Expr *)quals,
12106 constants, false));
12107 q->jointree->quals = NULL;
12108 break;
12109 case QUAL_DETERMINISTIC:
12110 /* Leave WHERE alone. */
12111 break;
12112 default:
12113 /* Errors handled by error_for_mixed_qual. */
12114 break;
12115 }
12116 return rv_cmps;
12117 }
12118
12119 /* Top-level AND: walk conjuncts. */
12120 {
12121 BoolExpr *be = (BoolExpr *)quals;
12122 ListCell *cell, *prev;
12123
12124 for (cell = list_head(be->args), prev = NULL; cell != NULL;) {
12125 Expr *conjunct = (Expr *)lfirst(cell);
12126 qual_class c = classify_qual(conjunct, constants);
12127
12129
12130 switch (c) {
12131 case QUAL_PURE_AGG:
12132 q->havingQual = add_to_havingQual(q->havingQual, conjunct);
12133 be->args = my_list_delete_cell(be->args, cell, prev);
12134 if (prev)
12135 cell = my_lnext(be->args, prev);
12136 else
12137 cell = list_head(be->args);
12138 break;
12139 case QUAL_PURE_RV:
12140 rv_cmps = lappend(rv_cmps,
12141 rv_Expr_to_provenance(conjunct,
12142 constants, false));
12143 be->args = my_list_delete_cell(be->args, cell, prev);
12144 if (prev)
12145 cell = my_lnext(be->args, prev);
12146 else
12147 cell = list_head(be->args);
12148 break;
12149 case QUAL_DETERMINISTIC:
12150 prev = cell;
12151 cell = my_lnext(be->args, cell);
12152 break;
12153 default:
12154 /* Errors handled by error_for_mixed_qual. */
12155 break;
12156 }
12157 }
12158
12159 /* Collapse degenerate ANDs so downstream code sees a tidy WHERE. */
12160 if (be->args == NIL)
12161 q->jointree->quals = NULL;
12162 else if (list_length(be->args) == 1)
12163 q->jointree->quals = (Node *)linitial(be->args);
12164 }
12165
12166 return rv_cmps;
12167}
12168
12169/** @brief Context for the @c insert_agg_token_casts_mutator. */
12171 Query *query; ///< Outer query (to look up subquery RTEs)
12172 const constants_t *constants; ///< Extension OID cache
12174
12175/**
12176 * @brief Look up the original aggregate return type for an agg_token Var.
12177 *
12178 * Navigates from the Var's varno/varattno to the subquery's target list,
12179 * finds the provenance_aggregate() FuncExpr, and extracts the type OID
12180 * from its second argument (aggtype).
12181 */
12183 RangeTblEntry *rte;
12184 TargetEntry *te;
12185
12186 if (v->varno < 1 || v->varno > list_length(ctx->query->rtable))
12187 return InvalidOid;
12188
12189 rte = list_nth_node(RangeTblEntry, ctx->query->rtable, v->varno - 1);
12190 if (rte->rtekind != RTE_SUBQUERY || rte->subquery == NULL)
12191 return InvalidOid;
12192
12193 if (v->varattno < 1 || v->varattno > list_length(rte->subquery->targetList))
12194 return InvalidOid;
12195
12196 te = list_nth_node(TargetEntry, rte->subquery->targetList, v->varattno - 1);
12197 if (IsA(te->expr, FuncExpr)) {
12198 FuncExpr *f = (FuncExpr *)te->expr;
12199 if (f->funcid == ctx->constants->OID_FUNCTION_PROVENANCE_AGGREGATE) {
12200 Const *aggtype_const = (Const *)lsecond(f->args);
12201 return DatumGetObjectId(aggtype_const->constvalue);
12202 }
12203 }
12204 return InvalidOid;
12205}
12206
12207/**
12208 * @brief Wrap an agg_token Var in a cast to its original type, in place.
12209 */
12210static void cast_agg_token_in_list(ListCell *lc,
12212 Var *v = (Var *)lfirst(lc);
12213 Oid target = get_agg_token_orig_type(v, ctx);
12214 HeapTuple castTuple;
12215
12216 if (!OidIsValid(target))
12217 return;
12218
12219 castTuple = SearchSysCache2(CASTSOURCETARGET,
12220 ObjectIdGetDatum(ctx->constants->OID_TYPE_AGG_TOKEN),
12221 ObjectIdGetDatum(target));
12222 if (HeapTupleIsValid(castTuple)) {
12223 Form_pg_cast castForm = (Form_pg_cast)GETSTRUCT(castTuple);
12224 if (OidIsValid(castForm->castfunc)) {
12225 FuncExpr *fc = makeNode(FuncExpr);
12226 fc->funcid = castForm->castfunc;
12227 fc->funcresulttype = target;
12228 fc->funcretset = false;
12229 fc->funcvariadic = false;
12230 fc->funcformat = COERCE_IMPLICIT_CAST;
12231 fc->funccollid = InvalidOid;
12232 fc->inputcollid = InvalidOid;
12233 fc->args = list_make1(v);
12234 fc->location = -1;
12235 lfirst(lc) = fc;
12236 }
12237 ReleaseSysCache(castTuple);
12238 }
12239}
12240
12241/**
12242 * @brief Wrap any agg_token Vars in an argument list.
12243 */
12244static void cast_agg_token_args(List *args,
12246 ListCell *lc;
12247 foreach (lc, args) {
12248 if (IsA(lfirst(lc), Var) &&
12249 ((Var *)lfirst(lc))->vartype == ctx->constants->OID_TYPE_AGG_TOKEN)
12250 cast_agg_token_in_list(lc, ctx);
12251 }
12252}
12253
12254/**
12255 * @brief Insert agg_token casts for Vars used in expressions.
12256 *
12257 * After the WHERE-to-HAVING migration, agg_token Vars remaining in
12258 * expression nodes (OpExpr, WindowFunc, CoalesceExpr, MinMaxExpr, etc.)
12259 * need explicit casts to their original type so that operators and
12260 * functions receive correct values. The original type is looked up
12261 * from the provenance_aggregate() call in the subquery.
12262 */
12263static Node *
12264insert_agg_token_casts_mutator(Node *node, void *data) {
12266
12267 if (node == NULL)
12268 return NULL;
12269
12270 if (IsA(node, OpExpr)) {
12271 /* Arithmetic over an agg_token Var (e.g. cnt+1 where cnt comes from a
12272 * subquery aggregate) is kept as an agg_token (gate_arith) rather than
12273 * cast to scalar, preserving provenance. */
12274 Node *swapped = try_swap_agg_arith((OpExpr *)node, ctx->constants);
12275 if (swapped != NULL)
12276 return swapped;
12277 cast_agg_token_args(((OpExpr *)node)->args, ctx);
12278 return (Node *)node;
12279 }
12280 if (IsA(node, WindowFunc)) {
12281 cast_agg_token_args(((WindowFunc *)node)->args, ctx);
12282 return (Node *)node;
12283 }
12284 if (IsA(node, CoalesceExpr)) {
12285 cast_agg_token_args(((CoalesceExpr *)node)->args, ctx);
12286 return (Node *)node;
12287 }
12288 if (IsA(node, MinMaxExpr)) {
12289 cast_agg_token_args(((MinMaxExpr *)node)->args, ctx);
12290 return (Node *)node;
12291 }
12292 if (IsA(node, NullIfExpr)) {
12293 cast_agg_token_args(((NullIfExpr *)node)->args, ctx);
12294 return (Node *)node;
12295 }
12296
12297 return expression_tree_mutator(node, insert_agg_token_casts_mutator, data);
12298}
12299
12300/**
12301 * @brief Walk query and insert agg_token casts where needed.
12302 */
12303static void insert_agg_token_casts(const constants_t *constants, Query *q) {
12304 insert_agg_token_casts_context ctx = {q, constants};
12305 query_tree_mutator(q, insert_agg_token_casts_mutator, &ctx,
12306 QTW_DONT_COPY_QUERY | QTW_IGNORE_RC_SUBQUERIES);
12307}
12308
12309/** @brief Context for @c join_qual_has_agg_token_walker. */
12311 const constants_t *constants; ///< Extension OID cache
12312 Index *rteid; ///< Out: varno of the agg_token Var
12313 AttrNumber *join_attno; ///< Out: attno of the agg_token Var
12315
12316static bool join_qual_has_agg_token_walker(Node *node,
12318{
12319 if (node == NULL)
12320 return false;
12321 if (IsA(node, OpExpr)) {
12322 OpExpr *oe = (OpExpr *) node;
12323 Node *left = (Node *) linitial(oe->args);
12324 Node *right = (Node *) lsecond(oe->args);
12325
12326 /* Unwrap casts */
12327 if (IsA(left, FuncExpr) &&
12328 (((FuncExpr *)left)->funcformat == COERCE_IMPLICIT_CAST ||
12329 ((FuncExpr *)left)->funcformat == COERCE_EXPLICIT_CAST) &&
12330 list_length(((FuncExpr *)left)->args) == 1)
12331 left = linitial(((FuncExpr *)left)->args);
12332 if (IsA(right, FuncExpr) &&
12333 (((FuncExpr *)right)->funcformat == COERCE_IMPLICIT_CAST ||
12334 ((FuncExpr *)right)->funcformat == COERCE_EXPLICIT_CAST) &&
12335 list_length(((FuncExpr *)right)->args) == 1)
12336 right = linitial(((FuncExpr *)right)->args);
12337
12338 if (IsA(left, Var) && IsA(right, Var)) {
12339 Var *left_var = (Var *)left;
12340 Var *right_var = (Var *)right;
12341 if (left_var->vartype == ctx->constants->OID_TYPE_AGG_TOKEN &&
12342 right_var->vartype != ctx->constants->OID_TYPE_AGG_TOKEN) {
12343 *ctx->rteid = left_var->varno;
12344 *ctx->join_attno = left_var->varattno;
12345 return true;
12346 }
12347 if (right_var->vartype == ctx->constants->OID_TYPE_AGG_TOKEN &&
12348 left_var->vartype != ctx->constants->OID_TYPE_AGG_TOKEN) {
12349 *ctx->rteid = right_var->varno;
12350 *ctx->join_attno = right_var->varattno;
12351 return true;
12352 }
12353 }
12354 }
12355 return expression_tree_walker(node, join_qual_has_agg_token_walker,
12356 (void *) ctx);
12357}
12358
12359/**
12360 * @brief Return true if @p node contains an @c OpExpr that equates an
12361 * @c agg_token @c Var with a non-@c agg_token @c Var.
12362 *
12363 * On a match, writes the agg_token Var's @c varno and @c varattno to
12364 * @p *rteid and @p *join_attno. Used to detect JOIN conditions that
12365 * require the @c rewrite_join_agg_token rewrite.
12366 *
12367 * @param node Expression tree to inspect.
12368 * @param constants Extension OID cache.
12369 * @param rteid Out: varno of the agg_token Var (unchanged on miss).
12370 * @param join_attno Out: attno of the agg_token Var (unchanged on miss).
12371 * @return True iff such an @c OpExpr was found.
12372 */
12373static bool join_qual_has_agg_token(Node *node, const constants_t *constants,
12374 Index *rteid, AttrNumber *join_attno)
12375{
12377 ctx.constants = constants;
12378 ctx.rteid = rteid;
12379 ctx.join_attno = join_attno;
12380 return join_qual_has_agg_token_walker(node, &ctx);
12381}
12382
12383/**
12384 * @brief Build an AST node for <tt>arr[idx]</tt> on a uuid[] expression.
12385 *
12386 * Wraps the version rename between @c ArrayRef (PG < 12) and
12387 * @c SubscriptingRef (PG 12+), and the addition of @c refrestype (PG 14+).
12388 *
12389 * @param arr_expr Expression evaluating to @c uuid[].
12390 * @param index 1-based element position.
12391 * @param constants Extension OID cache.
12392 * @return Subscripting node with result type @c uuid.
12393 */
12394static Node *make_uuid_array_subscript(Node *arr_expr, int index,
12395 const constants_t *constants)
12396{
12397 Const *idx = makeConst(INT4OID, -1, InvalidOid, sizeof(int32),
12398 Int32GetDatum(index), false, true);
12399#if PG_VERSION_NUM >= 120000
12400 SubscriptingRef *sub = makeNode(SubscriptingRef);
12401 sub->refcontainertype = constants->OID_TYPE_UUID_ARRAY;
12402 sub->refelemtype = constants->OID_TYPE_UUID;
12403#if PG_VERSION_NUM >= 140000
12404 sub->refrestype = constants->OID_TYPE_UUID;
12405#endif
12406 sub->reftypmod = -1;
12407 sub->refcollid = InvalidOid;
12408 sub->refupperindexpr = list_make1(idx);
12409 sub->reflowerindexpr = NIL;
12410 sub->refexpr = (Expr *)arr_expr;
12411 sub->refassgnexpr = NULL;
12412 return (Node *)sub;
12413#else
12414 ArrayRef *sub = makeNode(ArrayRef);
12415 sub->refarraytype = constants->OID_TYPE_UUID_ARRAY;
12416 sub->refelemtype = constants->OID_TYPE_UUID;
12417 sub->reftypmod = -1;
12418 sub->refcollid = InvalidOid;
12419 sub->refupperindexpr = list_make1(idx);
12420 sub->reflowerindexpr = NIL;
12421 sub->refexpr = (Expr *)arr_expr;
12422 sub->refassgnexpr = NULL;
12423 return (Node *)sub;
12424#endif
12425}
12426
12427/**
12428 * @brief Context for @c retype_agg_var_walker.
12429 *
12430 * Identifies the Var location whose type must flip from @c agg_token
12431 * to @c text after the source relation has been replaced by an
12432 * explode-style subquery.
12433 */
12434typedef struct retype_agg_var_ctx {
12435 Index rteid; ///< Varno of the replaced RTE
12436 AttrNumber join_attno; ///< Attno of the former agg_token column
12437 const constants_t *constants;///< Extension OID cache
12439
12440/**
12441 * @brief Walker that retypes agg_token Vars to text and rewrites the
12442 * equality OpExpr to @c text = text with the non-agg side cast via I/O.
12443 *
12444 * Only affects Vars with @c varlevelsup == 0 matching @c (rteid, join_attno).
12445 * Sibling-query subqueries are left untouched via @c QTW_IGNORE_RT_SUBQUERIES
12446 * at the top-level call.
12447 */
12448static bool retype_agg_var_walker(Node *node, retype_agg_var_ctx *ctx)
12449{
12450 if (node == NULL)
12451 return false;
12452
12453 if (IsA(node, OpExpr)) {
12454 OpExpr *oe = (OpExpr *)node;
12455 if (list_length(oe->args) == 2) {
12456 Node *left = (Node *)linitial(oe->args);
12457 Node *right = (Node *)lsecond(oe->args);
12458 Var *agg_v = NULL;
12459 bool agg_on_left = false;
12460
12461 if (IsA(left, Var)) {
12462 Var *v = (Var *)left;
12463 if (v->varlevelsup == 0 && v->varno == ctx->rteid &&
12464 v->varattno == ctx->join_attno &&
12465 v->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
12466 agg_v = v;
12467 agg_on_left = true;
12468 }
12469 }
12470 if (agg_v == NULL && IsA(right, Var)) {
12471 Var *v = (Var *)right;
12472 if (v->varlevelsup == 0 && v->varno == ctx->rteid &&
12473 v->varattno == ctx->join_attno &&
12474 v->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
12475 agg_v = v;
12476 }
12477 }
12478
12479 if (agg_v != NULL) {
12480 Node *other = agg_on_left ? right : left;
12481 Oid text_eq;
12482 Operator opInfo;
12483 Form_pg_operator opform;
12484
12485 agg_v->vartype = TEXTOID;
12486 agg_v->varcollid = DEFAULT_COLLATION_OID;
12487
12488 if (exprType(other) != TEXTOID) {
12489 CoerceViaIO *c = makeNode(CoerceViaIO);
12490 c->arg = (Expr *)other;
12491 c->resulttype = TEXTOID;
12492 c->resultcollid = DEFAULT_COLLATION_OID;
12493 c->coerceformat = COERCE_EXPLICIT_CAST;
12494 c->location = -1;
12495 other = (Node *)c;
12496 }
12497
12498 if (agg_on_left)
12499 oe->args = list_make2(agg_v, other);
12500 else
12501 oe->args = list_make2(other, agg_v);
12502
12503 text_eq = find_equality_operator(TEXTOID, TEXTOID);
12504 if (!OidIsValid(text_eq))
12505 provsql_error("rewrite_join_agg_token: text = text operator "
12506 "not found");
12507 opInfo = SearchSysCache1(OPEROID, ObjectIdGetDatum(text_eq));
12508 if (!HeapTupleIsValid(opInfo))
12509 provsql_error("rewrite_join_agg_token: could not look up "
12510 "text equality operator");
12511 opform = (Form_pg_operator)GETSTRUCT(opInfo);
12512 oe->opno = text_eq;
12513 oe->opfuncid = opform->oprcode;
12514 oe->opresulttype = opform->oprresult;
12515 oe->inputcollid = DEFAULT_COLLATION_OID;
12516 ReleaseSysCache(opInfo);
12517
12518 /* Args handled; skip their subtree walk */
12519 return false;
12520 }
12521 }
12522 }
12523
12524 if (IsA(node, Var)) {
12525 Var *v = (Var *)node;
12526 if (v->varlevelsup == 0 && v->varno == ctx->rteid &&
12527 v->varattno == ctx->join_attno &&
12528 v->vartype == ctx->constants->OID_TYPE_AGG_TOKEN) {
12529 v->vartype = TEXTOID;
12530 v->varcollid = DEFAULT_COLLATION_OID;
12531 }
12532 return false;
12533 }
12534
12535 if (IsA(node, Query)) {
12536 /* Nested queries address a different rtable; do not descend. */
12537 return false;
12538 }
12539
12540 return expression_tree_walker(node, retype_agg_var_walker, (void *)ctx);
12541}
12542
12543/** @brief Context for @c push_agg_nulltest_walker. */
12544typedef struct {
12545 Query *q; /* query whose WHERE is being scanned */
12547 bool pushed; /* at least one qual was moved down */
12549
12550/** @brief Walker for @c expr_contains_aggref. */
12551static bool contains_aggref_walker(Node *node, void *found)
12552{
12553 if (node == NULL)
12554 return false;
12555 if (IsA(node, Aggref)) {
12556 *(bool *) found = true;
12557 return true;
12558 }
12559 if (IsA(node, Query))
12560 return false;
12561 return expression_tree_walker(node, contains_aggref_walker, found);
12562}
12563
12564/**
12565 * @brief Whether an expression contains a plain @c Aggref.
12566 *
12567 * @c expr_contains_agg recognises the shapes the rewriting has already
12568 * produced (an @c agg_token @c Var, a @c provenance_aggregate call); this one
12569 * runs before that, when the aggregate is still the parser's own node.
12570 */
12571static bool expr_contains_aggref(Node *node)
12572{
12573 bool found = false;
12574 contains_aggref_walker(node, &found);
12575 return found;
12576}
12577
12578/**
12579 * @brief The subquery target entry an @c IS @c [NOT] @c NULL is testing, if it
12580 * is an aggregate of a subquery in @p q.
12581 *
12582 * @return The subquery's @c TargetEntry for the tested column, or @c NULL when
12583 * @p nt is not of that shape. Writes the owning subquery to
12584 * @p *sub_out on success.
12585 */
12586static TargetEntry *agg_nulltest_target(Query *q, NullTest *nt,
12587 const constants_t *constants,
12588 Query **sub_out)
12589{
12590 Node *arg = (Node *) nt->arg;
12591 Var *v;
12592 RangeTblEntry *rte;
12593 TargetEntry *te;
12594
12595 /* Unwrap a single-argument cast, as the HAVING converter does. */
12596 if (IsA(arg, FuncExpr)) {
12597 FuncExpr *fe = (FuncExpr *) arg;
12598 if ((fe->funcformat == COERCE_IMPLICIT_CAST ||
12599 fe->funcformat == COERCE_EXPLICIT_CAST) &&
12600 list_length(fe->args) == 1)
12601 arg = (Node *) linitial(fe->args);
12602 }
12603
12604 if (!IsA(arg, Var))
12605 return NULL;
12606 v = (Var *) arg;
12607 if (v->varlevelsup != 0 || v->varno < 1 ||
12608 v->varno > (Index) list_length(q->rtable))
12609 return NULL;
12610
12611 /* Follow the column through however many subqueries merely forward it,
12612 * down to the level that actually computes the aggregate. */
12613 for (;;) {
12614 Query *sub;
12615
12616 if (v->varlevelsup != 0 || v->varno < 1 ||
12617 v->varno > (Index) list_length(q->rtable))
12618 return NULL;
12619 rte = (RangeTblEntry *) list_nth(q->rtable, v->varno - 1);
12620 if (rte->rtekind != RTE_SUBQUERY || rte->subquery == NULL)
12621 return NULL;
12622 sub = rte->subquery;
12623 if (v->varattno < 1 || v->varattno > list_length(sub->targetList))
12624 return NULL;
12625
12626 te = (TargetEntry *) list_nth(sub->targetList, v->varattno - 1);
12627 if (te->resjunk)
12628 return NULL;
12629
12630 if (IsA(te->expr, Var)) { /* a pass-through level: descend */
12631 q = sub;
12632 v = (Var *) te->expr;
12633 continue;
12634 }
12635
12636 if (!sub->hasAggs || !expr_contains_aggref((Node *) te->expr))
12637 return NULL;
12638
12639 *sub_out = sub;
12640 return te;
12641 }
12642}
12643
12644/**
12645 * @brief Move one @c IS @c [NOT] @c NULL conjunct into its subquery's HAVING.
12646 *
12647 * @return True when @p nt tested a subquery's aggregate and was moved.
12648 */
12649static bool push_one_agg_nulltest(NullTest *nt, agg_nulltest_ctx *ctx)
12650{
12651 Query *sub = NULL;
12652 TargetEntry *te = agg_nulltest_target(ctx->q, nt, ctx->constants, &sub);
12653 NullTest *down;
12654
12655 if (te == NULL)
12656 return false;
12657
12658 down = (NullTest *) copyObject(nt);
12659 down->arg = (Expr *) copyObject(te->expr);
12660 sub->havingQual = add_to_havingQual(sub->havingQual, (Expr *) down);
12661 ctx->pushed = true;
12662 return true;
12663}
12664
12665/**
12666 * @brief Push @c IS @c [NOT] @c NULL on a subquery's aggregate down into that
12667 * subquery's HAVING.
12668 *
12669 * The HAVING lowering of @c IS @c [NOT] @c NULL is built from the aggregate's
12670 * per-row (value, token) pairs, which live in the @c provenance_aggregate call
12671 * itself -- so, unlike a comparison (lowered to a @c gate_cmp over the finished
12672 * @c gate_agg, and therefore computable from the token alone), it can only be
12673 * built in the query level that owns the aggregate. Moving the predicate to
12674 * that level is what lets @c WHERE @c c @c IS @c NULL over a grouped subquery
12675 * mean what the fused @c HAVING @c sum(v) @c IS @c NULL means. Filtering the
12676 * subquery's rows is also the predicate's ordinary SQL reading, so the data
12677 * part is unchanged.
12678 *
12679 * Runs before provenance discovery, so the subquery is rewritten with the
12680 * HAVING already in place.
12681 *
12682 * @param q Query to rewrite (modified in place).
12683 * @param constants Extension OID cache.
12684 * @return @p q when something moved, @c NULL when nothing matched.
12685 */
12686static Query *push_agg_nulltest_into_subquery(Query *q,
12687 const constants_t *constants)
12688{
12689 agg_nulltest_ctx ctx;
12690 Node *quals;
12691
12692 if (q->jointree == NULL || q->jointree->quals == NULL)
12693 return NULL;
12694
12695 ctx.q = q;
12696 ctx.constants = constants;
12697 ctx.pushed = false;
12698
12699 quals = q->jointree->quals;
12700
12701 /* The whole WHERE is the predicate. */
12702 if (IsA(quals, NullTest)) {
12703 if (!push_one_agg_nulltest((NullTest *) quals, &ctx))
12704 return NULL;
12705 q->jointree->quals = NULL;
12706 return q;
12707 }
12708
12709 /* Top-level AND: move out the conjuncts that match, leave the rest. Only a
12710 * conjunction may be split -- under an OR or a NOT the predicate does not
12711 * hold of the subquery's rows on its own. */
12712 if (IsA(quals, BoolExpr) && ((BoolExpr *) quals)->boolop == AND_EXPR) {
12713 BoolExpr *be = (BoolExpr *) quals;
12714 ListCell *cell, *prev;
12715
12716 for (cell = list_head(be->args), prev = NULL; cell != NULL;) {
12717 Node *conj = (Node *) lfirst(cell);
12718
12719 if (IsA(conj, NullTest) &&
12720 push_one_agg_nulltest((NullTest *) conj, &ctx)) {
12721 be->args = my_list_delete_cell(be->args, cell, prev);
12722 cell = prev ? my_lnext(be->args, prev) : list_head(be->args);
12723 } else {
12724 prev = cell;
12725 cell = my_lnext(be->args, cell);
12726 }
12727 }
12728
12729 if (!ctx.pushed)
12730 return NULL;
12731
12732 /* Do not leave a degenerate AND behind. */
12733 if (be->args == NIL)
12734 q->jointree->quals = NULL;
12735 else if (list_length(be->args) == 1)
12736 q->jointree->quals = (Node *) linitial(be->args);
12737
12738 return q;
12739 }
12740
12741 return NULL;
12742}
12743
12744/**
12745 * @brief Replace the source relation of an agg_token JOIN with an
12746 * explode-style subquery.
12747 *
12748 * Given a JOIN qual of the form @c rteid.join_attno = other where
12749 * @c rteid.join_attno is of type @c agg_token, replaces the RTE at @p rteid
12750 * in place with a subquery:
12751 *
12752 * @code{.sql}
12753 * SELECT t.col_1, ..., t.col_{join_attno-1},
12754 * get_extra(get_children(sm)[2]) AS <agg_col>,
12755 * ...,
12756 * provenance_times(get_children(sm)[1], t.provsql) AS provsql
12757 * FROM <t>, LATERAL unnest(get_children(t.<agg_col>)) AS sm
12758 * @endcode
12759 *
12760 * The subquery preserves the original column order, so outer Vars still
12761 * address the same attnos. The outer query is then walked to retype Vars
12762 * at (@p rteid, @p join_attno) from @c agg_token to @c text and rewrite the
12763 * equality @c OpExpr to @c text = text (casting the other side via I/O).
12764 *
12765 * The copy of the source RTE inside the subquery has its @c provsql column
12766 * renamed so the recursive @c process_query pass does not re-detect it as a
12767 * provenance source – the combined provenance is already captured by the
12768 * subquery's exposed @c provsql target entry.
12769 *
12770 * @param q Query to rewrite (modified in place).
12771 * @param constants Extension OID cache.
12772 * @param rteid 1-based varno of the RTE owning the agg_token column.
12773 * @param join_attno 1-based attno of the agg_token column in that RTE.
12774 * @return The modified query.
12775 */
12776static Query *rewrite_join_agg_token(Query *q, const constants_t *constants,
12777 Index rteid, AttrNumber join_attno)
12778{
12779 RangeTblEntry *src_rte = (RangeTblEntry *)list_nth(q->rtable, rteid - 1);
12780 AttrNumber provsql_attno = 0;
12781 AttrNumber attno;
12782 ListCell *lc;
12783 Query *inner;
12784 RangeTblEntry *inner_src, *sm_rte;
12785 RangeTblFunction *rtfunc;
12786 FuncExpr *unnest_call, *get_children_of_agg, *agg_to_uuid;
12787 Var *agg_var_in_inner;
12788 Alias *sm_alias, *sm_eref;
12789 RangeTblRef *inner_rtr1, *inner_rtr2;
12790 FromExpr *inner_jt;
12791 List *inner_tl = NIL;
12792
12793 if (src_rte->rtekind != RTE_RELATION && src_rte->rtekind != RTE_SUBQUERY)
12794 provsql_error("rewrite_join_agg_token: source RTE kind %d not supported",
12795 (int)src_rte->rtekind);
12796
12797 /* Locate the provsql column of the source RTE. */
12798 attno = 1;
12799 foreach (lc, src_rte->eref->colnames) {
12800 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME)) {
12801 provsql_attno = attno;
12802 break;
12803 }
12804 ++attno;
12805 }
12806 if (provsql_attno == 0)
12807 provsql_error("rewrite_join_agg_token: source relation has no "
12808 "provsql column");
12809
12810 /* --- Build the lateral RTE: unnest(get_children(agg_token_uuid(agg_var))) --- */
12811
12812 agg_var_in_inner = makeNode(Var);
12813 agg_var_in_inner->varno = 1;
12814 agg_var_in_inner->varattno = join_attno;
12815 agg_var_in_inner->vartype = constants->OID_TYPE_AGG_TOKEN;
12816 agg_var_in_inner->varcollid = InvalidOid;
12817 agg_var_in_inner->vartypmod = -1;
12818 agg_var_in_inner->location = -1;
12819
12820 agg_to_uuid = makeNode(FuncExpr);
12821 agg_to_uuid->funcid = constants->OID_FUNCTION_AGG_TOKEN_UUID;
12822 agg_to_uuid->funcresulttype = constants->OID_TYPE_UUID;
12823 agg_to_uuid->funcretset = false;
12824 agg_to_uuid->funcvariadic = false;
12825 agg_to_uuid->funcformat = COERCE_IMPLICIT_CAST;
12826 agg_to_uuid->funccollid = InvalidOid;
12827 agg_to_uuid->inputcollid = InvalidOid;
12828 agg_to_uuid->args = list_make1(agg_var_in_inner);
12829 agg_to_uuid->location = -1;
12830
12831 get_children_of_agg = makeNode(FuncExpr);
12832 get_children_of_agg->funcid = constants->OID_FUNCTION_GET_CHILDREN;
12833 get_children_of_agg->funcresulttype = constants->OID_TYPE_UUID_ARRAY;
12834 get_children_of_agg->funcretset = false;
12835 get_children_of_agg->funcvariadic = false;
12836 get_children_of_agg->funcformat = COERCE_EXPLICIT_CALL;
12837 get_children_of_agg->funccollid = InvalidOid;
12838 get_children_of_agg->inputcollid = InvalidOid;
12839 get_children_of_agg->args = list_make1(agg_to_uuid);
12840 get_children_of_agg->location = -1;
12841
12842 unnest_call = makeNode(FuncExpr);
12843 unnest_call->funcid = constants->OID_UNNEST;
12844 unnest_call->funcresulttype = constants->OID_TYPE_UUID;
12845 unnest_call->funcretset = true;
12846 unnest_call->funcvariadic = false;
12847 unnest_call->funcformat = COERCE_EXPLICIT_CALL;
12848 unnest_call->funccollid = InvalidOid;
12849 unnest_call->inputcollid = InvalidOid;
12850 unnest_call->args = list_make1(get_children_of_agg);
12851 unnest_call->location = -1;
12852
12853 rtfunc = makeNode(RangeTblFunction);
12854 rtfunc->funcexpr = (Node *)unnest_call;
12855 rtfunc->funccolcount = 1;
12856 rtfunc->funccolnames = NIL;
12857 rtfunc->funccoltypes = NIL;
12858 rtfunc->funccoltypmods = NIL;
12859 rtfunc->funccolcollations = NIL;
12860 rtfunc->funcparams = NULL;
12861
12862 sm_alias = makeNode(Alias);
12863 sm_eref = makeNode(Alias);
12864 sm_alias->aliasname = "sm";
12865 sm_eref->aliasname = "sm";
12866 sm_eref->colnames = list_make1(makeString("sm"));
12867
12868 sm_rte = makeNode(RangeTblEntry);
12869 sm_rte->rtekind = RTE_FUNCTION;
12870 sm_rte->functions = list_make1(rtfunc);
12871 sm_rte->funcordinality = false;
12872 sm_rte->alias = sm_alias;
12873 sm_rte->eref = sm_eref;
12874 sm_rte->lateral = true;
12875 sm_rte->inFromCl = true;
12876#if PG_VERSION_NUM < 160000
12877 sm_rte->requiredPerms = 0;
12878#endif
12879
12880 /* --- Inner rtable RTE 1: the source relation (deep copy). --- */
12881
12882 inner_src = copyObject(src_rte);
12883
12884 /* Rename the provsql column in the inner RTE's eref so the recursive
12885 * process_query pass does not re-detect it as a provenance source. The
12886 * combined provenance is already captured by the subquery's exposed
12887 * provsql TargetEntry below. */
12888 hide_provsql_colname(inner_src);
12889
12890 inner_rtr1 = makeNode(RangeTblRef);
12891 inner_rtr1->rtindex = 1;
12892 inner_rtr2 = makeNode(RangeTblRef);
12893 inner_rtr2->rtindex = 2;
12894 inner_jt = makeNode(FromExpr);
12895 inner_jt->fromlist = list_make2(inner_rtr1, inner_rtr2);
12896 inner_jt->quals = NULL;
12897
12898 /* --- Target list of the inner subquery, preserving original column order. --- */
12899
12900 attno = 1;
12901 foreach (lc, src_rte->eref->colnames) {
12902 const char *colname = strVal(lfirst(lc));
12903 TargetEntry *te = makeNode(TargetEntry);
12904 te->resno = attno;
12905 te->resname = pstrdup(colname);
12906 te->resjunk = false;
12907
12908 if (attno == join_attno) {
12909 /* get_extra(get_children(sm)[2]) */
12910 Var *sm_var = makeNode(Var);
12911 FuncExpr *gch, *ge;
12912 Node *subscript;
12913
12914 sm_var->varno = 2;
12915 sm_var->varattno = 1;
12916 sm_var->vartype = constants->OID_TYPE_UUID;
12917 sm_var->varcollid = InvalidOid;
12918 sm_var->vartypmod = -1;
12919 sm_var->location = -1;
12920
12921 gch = makeNode(FuncExpr);
12922 gch->funcid = constants->OID_FUNCTION_GET_CHILDREN;
12923 gch->funcresulttype = constants->OID_TYPE_UUID_ARRAY;
12924 gch->funcretset = false;
12925 gch->funcvariadic = false;
12926 gch->funcformat = COERCE_EXPLICIT_CALL;
12927 gch->funccollid = InvalidOid;
12928 gch->inputcollid = InvalidOid;
12929 gch->args = list_make1(sm_var);
12930 gch->location = -1;
12931
12932 subscript = make_uuid_array_subscript((Node *)gch, 2, constants);
12933
12934 ge = makeNode(FuncExpr);
12935 ge->funcid = constants->OID_FUNCTION_GET_EXTRA;
12936 ge->funcresulttype = TEXTOID;
12937 ge->funcretset = false;
12938 ge->funcvariadic = false;
12939 ge->funcformat = COERCE_EXPLICIT_CALL;
12940 ge->funccollid = DEFAULT_COLLATION_OID;
12941 ge->inputcollid = InvalidOid;
12942 ge->args = list_make1(subscript);
12943 ge->location = -1;
12944
12945 te->expr = (Expr *)ge;
12946 } else if (attno == provsql_attno) {
12947 /* provenance_times(get_children(sm)[1], t.provsql) – VARIADIC uuid[] */
12948 Var *sm_var = makeNode(Var);
12949 Var *prov_var = makeNode(Var);
12950 FuncExpr *gch, *pt;
12951 ArrayExpr *arr;
12952 Node *subscript;
12953
12954 sm_var->varno = 2;
12955 sm_var->varattno = 1;
12956 sm_var->vartype = constants->OID_TYPE_UUID;
12957 sm_var->varcollid = InvalidOid;
12958 sm_var->vartypmod = -1;
12959 sm_var->location = -1;
12960
12961 gch = makeNode(FuncExpr);
12962 gch->funcid = constants->OID_FUNCTION_GET_CHILDREN;
12963 gch->funcresulttype = constants->OID_TYPE_UUID_ARRAY;
12964 gch->funcretset = false;
12965 gch->funcvariadic = false;
12966 gch->funcformat = COERCE_EXPLICIT_CALL;
12967 gch->funccollid = InvalidOid;
12968 gch->inputcollid = InvalidOid;
12969 gch->args = list_make1(sm_var);
12970 gch->location = -1;
12971
12972 subscript = make_uuid_array_subscript((Node *)gch, 1, constants);
12973
12974 prov_var->varno = 1;
12975 prov_var->varattno = provsql_attno;
12976 prov_var->vartype = constants->OID_TYPE_UUID;
12977 prov_var->varcollid = InvalidOid;
12978 prov_var->vartypmod = -1;
12979 prov_var->location = -1;
12980
12981 arr = makeNode(ArrayExpr);
12982 arr->array_typeid = constants->OID_TYPE_UUID_ARRAY;
12983 arr->element_typeid = constants->OID_TYPE_UUID;
12984 arr->elements = list_make2(subscript, prov_var);
12985 arr->location = -1;
12986
12987 pt = makeNode(FuncExpr);
12988 pt->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
12989 pt->funcresulttype = constants->OID_TYPE_UUID;
12990 pt->funcretset = false;
12991 pt->funcvariadic = true;
12992 pt->funcformat = COERCE_EXPLICIT_CALL;
12993 pt->funccollid = InvalidOid;
12994 pt->inputcollid = InvalidOid;
12995 pt->args = list_make1(arr);
12996 pt->location = -1;
12997
12998 te->expr = (Expr *)pt;
12999 } else {
13000 /* Passthrough Var(1, attno). */
13001 Var *v = makeNode(Var);
13002 Oid vtype = InvalidOid;
13003 int32 vtypmod = -1;
13004 Oid vcoll = InvalidOid;
13005
13006 if (src_rte->rtekind == RTE_RELATION) {
13007 get_atttypetypmodcoll(src_rte->relid, attno, &vtype, &vtypmod, &vcoll);
13008 } else { /* RTE_SUBQUERY */
13009 TargetEntry *sub_te =
13010 (TargetEntry *)list_nth(src_rte->subquery->targetList, attno - 1);
13011 vtype = exprType((Node *)sub_te->expr);
13012 vtypmod = exprTypmod((Node *)sub_te->expr);
13013 vcoll = exprCollation((Node *)sub_te->expr);
13014 }
13015
13016 v->varno = 1;
13017 v->varattno = attno;
13018 v->vartype = vtype;
13019 v->varcollid = vcoll;
13020 v->vartypmod = vtypmod;
13021 v->location = -1;
13022 te->expr = (Expr *)v;
13023 }
13024
13025 inner_tl = lappend(inner_tl, te);
13026 ++attno;
13027 }
13028
13029 inner = makeNode(Query);
13030 inner->commandType = CMD_SELECT;
13031 inner->canSetTag = true;
13032 inner->rtable = list_make2(inner_src, sm_rte);
13033 inner->jointree = inner_jt;
13034 inner->targetList = inner_tl;
13035 inner->hasAggs = false;
13036 inner->hasSubLinks = false;
13037
13038#if PG_VERSION_NUM >= 160000
13039 /* PG 16+ moved permission info from RangeTblEntry into a separate
13040 * Query.rteperminfos list, indexed by RangeTblEntry.perminfoindex.
13041 * Our copy of src_rte kept its original perminfoindex, so the inner
13042 * query needs a matching rteperminfos entry – without it, perminfoindex
13043 * dangles and the planner short-circuits the subquery. */
13044 if (inner_src->perminfoindex != 0) {
13045 RTEPermissionInfo *perminfo =
13046 getRTEPermissionInfo(q->rteperminfos, src_rte);
13047 inner->rteperminfos = list_make1(copyObject(perminfo));
13048 inner_src->perminfoindex = 1;
13049 }
13050#endif
13051
13052 /* --- Replace src_rte in place with the subquery; outer varnos unchanged. --- */
13053
13054 src_rte->rtekind = RTE_SUBQUERY;
13055 src_rte->subquery = inner;
13056 src_rte->relid = InvalidOid;
13057 src_rte->relkind = 0;
13058#if PG_VERSION_NUM >= 120000
13059 src_rte->rellockmode = 0; /* field added in PG 12 */
13060#endif
13061 src_rte->inh = false;
13062 src_rte->lateral = false;
13063#if PG_VERSION_NUM >= 160000
13064 src_rte->perminfoindex = 0;
13065#else
13066 src_rte->selectedCols = NULL;
13067 src_rte->insertedCols = NULL;
13068 src_rte->updatedCols = NULL;
13069 src_rte->requiredPerms = ACL_SELECT;
13070#endif
13071
13072 /* Drop the "provsql" entry from the outer RTE's eref->colnames.
13073 * get_provenance_attributes will scan the subquery's target list for
13074 * a "provsql" TE and reinsert the colname at the matching position,
13075 * keeping eref->colnames length in sync with the subquery's target
13076 * list. Without this, the pre-existing "provsql" entry (inherited
13077 * from the original relation) plus the reinsertion would produce a
13078 * 5-colname list for a 4-column subquery, which PostgreSQL rejects. */
13079 {
13080 ListCell *cell, *prev;
13081 AttrNumber i;
13082
13083 prev = NULL;
13084 i = 1;
13085 for (cell = list_head(src_rte->eref->colnames); cell != NULL; ) {
13086 if (i == provsql_attno) {
13087 src_rte->eref->colnames =
13088 my_list_delete_cell(src_rte->eref->colnames, cell, prev);
13089 break;
13090 }
13091 prev = cell;
13092 cell = my_lnext(src_rte->eref->colnames, cell);
13093 ++i;
13094 }
13095 }
13096
13097 /* --- Retype outer Vars (rteid, join_attno) from agg_token to text and
13098 * rewrite the equality OpExpr to text = text. --- */
13099 {
13101 ctx.rteid = rteid;
13102 ctx.join_attno = join_attno;
13103 ctx.constants = constants;
13104 query_tree_walker(q, retype_agg_var_walker, (void *)&ctx,
13105 QTW_IGNORE_RT_SUBQUERIES);
13106 }
13107
13108 return q;
13109}
13110
13111/**
13112 * @brief Wrap @p expr in a @c provsql.assume_boolean FuncExpr.
13113 *
13114 * Used by @c make_provenance_expression when its caller (the
13115 * safe-query rewrite path in @c process_query) flagged the result
13116 * as needing the @c gate_assumed structural marker.
13117 * Wrapping at expression-build time rather than at splice time
13118 * means @c add_to_select and
13119 * @c replace_provenance_function_by_expression both consume the
13120 * already-wrapped expression, so every per-row root occurrence in
13121 * the final target list -- the auto-added @c provsql column and
13122 * every substituted user-side @c provenance() call -- carries the
13123 * wrapper uniformly.
13124 *
13125 * @param constants Extension OID cache.
13126 * @param expr Provenance expression to wrap.
13127 * @return A @c FuncExpr applying @c provsql.assume_boolean to @p expr.
13128 */
13129static Expr *wrap_in_assume_boolean(const constants_t *constants,
13130 Expr *expr) {
13131 FuncExpr *wrap = makeNode(FuncExpr);
13132 wrap->funcid = constants->OID_FUNCTION_ASSUME_BOOLEAN;
13133 wrap->funcresulttype = constants->OID_TYPE_UUID;
13134 wrap->funcretset = false;
13135 wrap->funcvariadic = false;
13136 wrap->funcformat = COERCE_EXPLICIT_CALL;
13137 wrap->funccollid = InvalidOid;
13138 wrap->inputcollid = InvalidOid;
13139 wrap->args = list_make1(expr);
13140 wrap->location = -1;
13141 return (Expr *) wrap;
13142}
13143
13144/**
13145 * @brief Wrap @p expr in a @c provsql.annotate(uuid, text) FuncExpr carrying
13146 * @p cert.
13147 *
13148 * Used by @c make_provenance_expression to attach the inversion-free
13149 * tractability certificate to the per-row provenance root: the resulting
13150 * annotation gate is transparent for every evaluator and carries @p cert in
13151 * its @c extra (and folded into its UUID). @p cert is copied into a text
13152 * @c Const.
13153 */
13154static Expr *wrap_in_annotate(const constants_t *constants, Expr *expr,
13155 const char *cert) {
13156 FuncExpr *wrap = makeNode(FuncExpr);
13157 Const *ce = makeConst(TEXTOID, -1, DEFAULT_COLLATION_OID, -1,
13158 CStringGetTextDatum(cert), false, false);
13159 wrap->funcid = constants->OID_FUNCTION_ANNOTATE;
13160 wrap->funcresulttype = constants->OID_TYPE_UUID;
13161 wrap->funcretset = false;
13162 wrap->funcvariadic = false;
13163 wrap->funcformat = COERCE_EXPLICIT_CALL;
13164 wrap->funccollid = InvalidOid;
13165 wrap->inputcollid = DEFAULT_COLLATION_OID;
13166 wrap->args = list_make2(expr, (Expr *) ce);
13167 wrap->location = -1;
13168 return (Expr *) wrap;
13169}
13170
13171/**
13172 * @brief Wrap @p target in a @c provsql.cond(uuid, uuid) FuncExpr conditioning
13173 * it on @p evidence.
13174 *
13175 * Used by @c process_query when the query carries a @c given(...) marker: the
13176 * per-row output provenance @p target is conditioned on the marker's evidence
13177 * expression, so each output row's provenance becomes
13178 * @c "cond(row_provenance, evidence)". @p evidence is the (per-row, possibly
13179 * correlated) argument captured from the stripped @c given() term.
13180 */
13181static Expr *wrap_in_cond(const constants_t *constants, Expr *target,
13182 Expr *evidence) {
13183 FuncExpr *wrap = makeNode(FuncExpr);
13184 wrap->funcid = constants->OID_FUNCTION_COND;
13185 wrap->funcresulttype = constants->OID_TYPE_UUID;
13186 wrap->funcretset = false;
13187 wrap->funcvariadic = false;
13188 wrap->funcformat = COERCE_EXPLICIT_CALL;
13189 wrap->funccollid = InvalidOid;
13190 wrap->inputcollid = InvalidOid;
13191 wrap->args = list_make2(target, evidence);
13192 wrap->location = -1;
13193 return (Expr *) wrap;
13194}
13195
13196/** @brief Mark column @p attno of RTE @p r as selected (read permission). */
13197static void mark_col_selected(Query *q, RangeTblEntry *r, AttrNumber attno) {
13198#if PG_VERSION_NUM >= 160000
13199 if (r->perminfoindex != 0) {
13200 RTEPermissionInfo *rpi =
13201 list_nth_node(RTEPermissionInfo, q->rteperminfos, r->perminfoindex - 1);
13202 rpi->selectedCols = bms_add_member(
13203 rpi->selectedCols, attno - FirstLowInvalidHeapAttributeNumber);
13204 }
13205#else
13206 r->selectedCols = bms_add_member(r->selectedCols,
13207 attno - FirstLowInvalidHeapAttributeNumber);
13208#endif
13209}
13210
13211/** @brief A @c Var for column @p attno of RTE @p relid, with the column's
13212 * actual type/typmod/collation, marking the column selected. */
13213static Var *make_column_var(Query *q, RangeTblEntry *r, Index relid,
13214 AttrNumber attno) {
13215 Oid typid; int32 typmod; Oid coll;
13216 Var *v;
13217 get_atttypetypmodcoll(r->relid, attno, &typid, &typmod, &coll);
13218 v = makeVar(relid, attno, typid, typmod, coll, 0);
13219 v->location = -1;
13220 mark_col_selected(q, r, attno);
13221 return v;
13222}
13223
13224/** @brief Coerce @p arg to @c text via its output function (any type -> text). */
13225static Expr *coerce_via_io_to_text(Expr *arg) {
13226 CoerceViaIO *c = makeNode(CoerceViaIO);
13227 c->arg = arg;
13228 c->resulttype = TEXTOID;
13229 c->resultcollid = DEFAULT_COLLATION_OID;
13230 c->coerceformat = COERCE_IMPLICIT_CAST;
13231 c->location = -1;
13232 return (Expr *) c;
13233}
13234
13235/**
13236 * @brief Wrap an atom's provenance @c Var in the inversion-free per-input
13237 * order marker: @c annotate(prov, inversion_free_key(root, sec, factor)).
13238 *
13239 * @p prov_var is a @c Var on the atom's provsql column (its @c varno is the
13240 * range-table index of the atom); @p m gives the root- and secondary-class
13241 * columns and the factor for that atom.
13242 */
13243static Expr *build_inversion_free_marker(const constants_t *constants, Query *q,
13244 Var *prov_var, const InvFreeMarker *m) {
13245 Index relid = prov_var->varno;
13246 RangeTblEntry *r = list_nth_node(RangeTblEntry, q->rtable, relid - 1);
13247 Var *rootv = make_column_var(q, r, relid, m->root_col);
13248 Const *factorc = makeConst(INT4OID, -1, InvalidOid, sizeof(int32),
13249 Int32GetDatum(m->factor), false, true);
13250 FuncExpr *keyf = makeNode(FuncExpr);
13251 FuncExpr *ann = makeNode(FuncExpr);
13252 Expr *secarg;
13253
13254 /* A root-only atom (no secondary class, e.g. a self-join-free hierarchical
13255 * query's atoms all binding only the head variable) carries a constant
13256 * secondary key: every such input shares the single tile of its block. */
13257 if (m->sec_col == 0)
13258 secarg = (Expr *) makeConst(TEXTOID, -1, DEFAULT_COLLATION_OID, -1,
13259 CStringGetTextDatum("0"), false, false);
13260 else
13261 secarg = coerce_via_io_to_text(
13262 (Expr *) make_column_var(q, r, relid, m->sec_col));
13263
13264 keyf->funcid = constants->OID_FUNCTION_INVERSION_FREE_KEY;
13265 keyf->funcresulttype = TEXTOID;
13266 keyf->funcretset = false;
13267 keyf->funcvariadic = false;
13268 keyf->funcformat = COERCE_EXPLICIT_CALL;
13269 keyf->funccollid = DEFAULT_COLLATION_OID;
13270 keyf->inputcollid = DEFAULT_COLLATION_OID;
13271 keyf->args = list_make3(coerce_via_io_to_text((Expr *) rootv),
13272 secarg,
13273 (Expr *) factorc);
13274 keyf->location = -1;
13275
13276 ann->funcid = constants->OID_FUNCTION_ANNOTATE;
13277 ann->funcresulttype = constants->OID_TYPE_UUID;
13278 ann->funcretset = false;
13279 ann->funcvariadic = false;
13280 ann->funcformat = COERCE_EXPLICIT_CALL;
13281 ann->funccollid = InvalidOid;
13282 ann->inputcollid = DEFAULT_COLLATION_OID;
13283 ann->args = list_make2((Expr *) prov_var, (Expr *) keyf);
13284 ann->location = -1;
13285 return (Expr *) ann;
13286}
13287
13288/**
13289 * @brief Replace each certified atom's provenance @c Var in @p prov_atts with
13290 * its per-input-marker-wrapped form (in place).
13291 */
13292static void wrap_inversion_free_markers(const constants_t *constants, Query *q,
13293 List *prov_atts,
13294 const InvFreeMarker *markers,
13295 int natoms) {
13296 ListCell *lc;
13297 foreach (lc, prov_atts) {
13298 Node *n = (Node *) lfirst(lc);
13299 if (IsA(n, Var)) {
13300 Var *pv = (Var *) n;
13301 if (pv->varno >= 1 && (int) pv->varno <= natoms
13302 && markers[pv->varno - 1].valid)
13303 lfirst(lc) = build_inversion_free_marker(constants, q, pv,
13304 &markers[pv->varno - 1]);
13305 }
13306 }
13307}
13308
13309/* -------------------------------------------------------------------------
13310 * Inversion-free: conjunctive flattening of SPJ subqueries/views
13311 * ------------------------------------------------------------------------- */
13312
13313/**
13314 * @brief Where a flattened base atom came from, for mapping markers back.
13315 *
13316 * A slot @em path from the top lineage query down to the base relation: each
13317 * element is a 1-based range-table slot, and the last element is the base's
13318 * position within the innermost subquery. @c depth @c == @c 1 (@c path @c ==
13319 * @c [s]) is a base/kept relation directly at top slot @c s; deeper paths step
13320 * through one nested SPJ subquery per element, so views-over-views map back to
13321 * the right input through the recursive subquery rewrite.
13322 */
13323typedef struct FlatAtomOrigin {
13325 int *path; /* palloc'd, length depth (1-based slot indices) */
13327
13328/** @brief Context for @c flatten_mut (a multi-relation conjunctive inliner). */
13329typedef struct flatten_ctx {
13330 int N; /* original parent range-table length */
13331 bool *slot_flat; /* [1..N]: parent slot is an inlined subquery */
13332 int *parent_newpos; /* [1..N]: new varno of a kept (non-inlined) slot */
13333 int **sub_newpos; /* [1..N] -> [1..sub_rtlen]: new varno of a subquery base */
13334 int *sub_rtlen; /* [1..N]: that subquery's range-table length */
13335 Var ***sub_tl; /* [1..N] -> [1..sub_tl_n]: subquery TL base Var by resno */
13336 int *sub_tl_n; /* [1..N] */
13337 bool quals_mode; /* true while remapping a subquery's pulled-up WHERE */
13338 int quals_slot; /* the inlined parent slot whose WHERE is being remapped */
13339} flatten_ctx;
13340
13341/**
13342 * @brief Tree mutator implementing the conjunctive inlining of SPJ subqueries.
13343 *
13344 * Parent mode (@c quals_mode false): a @c Var on an inlined subquery slot is
13345 * replaced by the base @c Var its target list maps the column to, renumbered to
13346 * that base's new flat position; a @c Var on a kept slot is renumbered to the
13347 * slot's new position. Subquery-WHERE mode (@c quals_mode true): a base @c Var
13348 * inside subquery @c quals_slot is renumbered to its new flat position. Outer
13349 * references (@c varlevelsup > 0) are never touched.
13350 */
13351static Node *flatten_mut(Node *node, void *cp) {
13352 flatten_ctx *c = (flatten_ctx *) cp;
13353 if (node == NULL)
13354 return NULL;
13355 if (IsA(node, Var)) {
13356 Var *v = (Var *) node;
13357 if (v->varlevelsup == 0) {
13358 if (c->quals_mode) {
13359 int i = c->quals_slot;
13360 if ((int) v->varno >= 1 && (int) v->varno <= c->sub_rtlen[i]
13361 && c->sub_newpos[i][v->varno] > 0) {
13362 Var *nv = (Var *) copyObject(v);
13363 nv->varno = c->sub_newpos[i][v->varno];
13364 return (Node *) nv;
13365 }
13366 } else if ((int) v->varno >= 1 && (int) v->varno <= c->N) {
13367 int i = (int) v->varno;
13368 if (c->slot_flat[i]) {
13369 if (v->varattno >= 1 && v->varattno <= c->sub_tl_n[i]
13370 && c->sub_tl[i][v->varattno] != NULL) {
13371 Var *base = c->sub_tl[i][v->varattno];
13372 Var *nv = (Var *) copyObject(base);
13373 nv->varno = c->sub_newpos[i][base->varno];
13374 nv->varlevelsup = 0;
13375 return (Node *) nv;
13376 }
13377 } else {
13378 Var *nv = (Var *) copyObject(v);
13379 nv->varno = c->parent_newpos[i];
13380 return (Node *) nv;
13381 }
13382 }
13383 }
13384 return (Node *) copyObject(v);
13385 }
13386 return expression_tree_mutator(node, flatten_mut, cp);
13387}
13388
13389/** @brief A depth-1 origin path @c [slot]. */
13390static FlatAtomOrigin *flat_origin1(int slot) {
13391 FlatAtomOrigin *o = (FlatAtomOrigin *) palloc(sizeof(FlatAtomOrigin));
13392 o->depth = 1;
13393 o->path = (int *) palloc(sizeof(int));
13394 o->path[0] = slot;
13395 return o;
13396}
13397
13398/** @brief Prepend @p slot to @p sub's path, for an atom inlined one level up. */
13400 FlatAtomOrigin *o = (FlatAtomOrigin *) palloc(sizeof(FlatAtomOrigin));
13401 int d;
13402 o->depth = sub->depth + 1;
13403 o->path = (int *) palloc(o->depth * sizeof(int));
13404 o->path[0] = slot;
13405 for (d = 0; d < sub->depth; d++)
13406 o->path[d + 1] = sub->path[d];
13407 return o;
13408}
13409
13410/* Forward declaration: the flattener recurses into nested subqueries. */
13411static FlatAtomOrigin *flatten_spj_subqueries(Query *probe, int *nflat_out);
13412
13413/**
13414 * @brief In place, inline every SPJ subquery/view of @p probe into its base
13415 * relations, flattening to one conjunction of base atoms.
13416 *
13417 * A range-table slot is inlined when it is a non-lateral @c RTE_SUBQUERY whose
13418 * subquery is a plain SELECT (no aggregation, grouping, DISTINCT, set
13419 * operation, sublink, CTE or LIMIT), whose @c FROM is flat @c RangeTblRefs over
13420 * base @c RTE_RELATIONs (PG 14/15 view OLD/NEW placeholders ignored; one or
13421 * more bases -- a view with a join inside is fine), and whose non-junk target
13422 * list entries are all plain @c Vars on those bases. Such a subquery is a pure
13423 * SPJ over base relations: its bases are appended in place of the slot, the
13424 * parent's column references are substituted by the corresponding base columns,
13425 * and the subquery's WHERE is pulled up, yielding an equivalent flat
13426 * conjunction. The parent's own @c FROM must already be flat @c RangeTblRefs
13427 * (the detector requires this too); an explicit @c JoinExpr there carries
13428 * ON-conditions a fromlist rebuild would drop, so flattening is declined.
13429 *
13430 * @param probe the (throwaway) query copy to flatten in place.
13431 * @param nflat_out set to the flattened range-table length.
13432 * @return a palloc'd @c FlatAtomOrigin per flattened position, mapping it back
13433 * to the parent slot (and, for an inlined subquery, the base position within
13434 * it) so the detector's per-atom markers can be threaded to the right input.
13435 */
13436static FlatAtomOrigin *flatten_spj_subqueries(Query *probe, int *nflat_out) {
13437 int N = list_length(probe->rtable);
13438 flatten_ctx c;
13439 List *new_rtable = NIL;
13440 List *origins_l = NIL;
13441 List *merged_quals = NIL;
13442 bool any_flat = false, parent_flat = (probe->jointree != NULL);
13443 int i, newpos = 0;
13444 ListCell *lc;
13445 FlatAtomOrigin *origins;
13446
13447 c.N = N;
13448 c.slot_flat = (bool *) palloc0((N + 1) * sizeof(bool));
13449 c.parent_newpos = (int *) palloc0((N + 1) * sizeof(int));
13450 c.sub_newpos = (int **) palloc0((N + 1) * sizeof(int *));
13451 c.sub_rtlen = (int *) palloc0((N + 1) * sizeof(int));
13452 c.sub_tl = (Var ***) palloc0((N + 1) * sizeof(Var **));
13453 c.sub_tl_n = (int *) palloc0((N + 1) * sizeof(int));
13454 c.quals_mode = false;
13455 c.quals_slot = 0;
13456
13457 if (parent_flat)
13458 foreach (lc, probe->jointree->fromlist)
13459 if (!IsA((Node *) lfirst(lc), RangeTblRef)) { parent_flat = false; break; }
13460
13461 /* Layout pass: decide which slots inline, append base atoms / kept slots to
13462 * new_rtable, and record each new position's origin. */
13463 for (i = 1; parent_flat && i <= N; i++) {
13464 RangeTblEntry *rte = list_nth_node(RangeTblEntry, probe->rtable, i - 1);
13465 Query *sq;
13466 bool ok;
13467 int maxres = 0, b;
13468 ListCell *lc2;
13469 Var **tl;
13470 FlatAtomOrigin *sub_origins = NULL;
13471 int sub_n = 0;
13472
13473 if (!(rte->rtekind == RTE_SUBQUERY && rte->subquery != NULL && !rte->lateral)) {
13474 newpos++;
13475 new_rtable = lappend(new_rtable, rte);
13476 c.parent_newpos[i] = newpos;
13477 origins_l = lappend(origins_l, flat_origin1(i));
13478 continue;
13479 }
13480 sq = rte->subquery;
13481 ok = !(sq->commandType != CMD_SELECT
13482 || sq->setOperations || sq->hasAggs || sq->hasWindowFuncs
13483 || sq->groupingSets || sq->groupClause || sq->havingQual
13484 || sq->distinctClause || sq->hasDistinctOn || sq->hasSubLinks
13485 || sq->limitCount || sq->limitOffset || sq->cteList
13486 || sq->jointree == NULL);
13487 if (ok)
13488 foreach (lc2, sq->jointree->fromlist)
13489 if (!IsA((Node *) lfirst(lc2), RangeTblRef)) { ok = false; break; }
13490 /* Recursively flatten this subquery's own SPJ subqueries first, so a
13491 * view-over-views collapses to base atoms before we inline it. Mutates
13492 * sq (a node in the throwaway probe) in place; sub_origins maps sq's
13493 * flattened positions back to paths within sq, which we prepend our slot to
13494 * so the marker reaches the right base input through the nested rewrite. */
13495 if (ok)
13496 sub_origins = flatten_spj_subqueries(sq, &sub_n);
13497 /* every range-table entry a real base relation or a view artifact */
13498 if (ok) {
13499 int realbase = 0;
13500 foreach (lc2, sq->rtable) {
13501 RangeTblEntry *br = (RangeTblEntry *) lfirst(lc2);
13502 if (br->rtekind == RTE_RELATION && br->relkind == RELKIND_VIEW)
13503 continue; /* OLD/NEW placeholder */
13504 else if (br->rtekind == RTE_RELATION) realbase++;
13505 else { ok = false; break; } /* join / nested subquery inside */
13506 }
13507 if (realbase < 1) ok = false;
13508 }
13509 /* non-junk target list entries all plain Vars on a base relation */
13510 if (ok)
13511 foreach (lc2, sq->targetList) {
13512 TargetEntry *te = (TargetEntry *) lfirst(lc2);
13513 if (!te->resjunk && te->resno > maxres) maxres = te->resno;
13514 }
13515 tl = ok ? (Var **) palloc0((maxres + 1) * sizeof(Var *)) : NULL;
13516 if (ok) {
13517 foreach (lc2, sq->targetList) {
13518 TargetEntry *te = (TargetEntry *) lfirst(lc2);
13519 Var *v;
13520 RangeTblEntry *br;
13521 if (te->resjunk) continue;
13522 if (!IsA(te->expr, Var)) { ok = false; break; }
13523 v = (Var *) te->expr;
13524 if (v->varlevelsup != 0
13525 || (int) v->varno < 1 || (int) v->varno > list_length(sq->rtable)) {
13526 ok = false; break;
13527 }
13528 br = list_nth_node(RangeTblEntry, sq->rtable, v->varno - 1);
13529 if (!(br->rtekind == RTE_RELATION && br->relkind != RELKIND_VIEW)) {
13530 ok = false; break;
13531 }
13532 tl[te->resno] = v;
13533 }
13534 }
13535
13536 if (!ok) {
13537 /* not flattenable: keep the slot as-is (detector will reject it) */
13538 if (tl) pfree(tl);
13539 newpos++;
13540 new_rtable = lappend(new_rtable, rte);
13541 c.parent_newpos[i] = newpos;
13542 origins_l = lappend(origins_l, flat_origin1(i));
13543 continue;
13544 }
13545
13546 /* inline: append each real base, assigning it a new flat position */
13547 c.slot_flat[i] = true;
13548 c.sub_rtlen[i] = list_length(sq->rtable);
13549 c.sub_newpos[i] = (int *) palloc0((c.sub_rtlen[i] + 1) * sizeof(int));
13550 c.sub_tl[i] = tl;
13551 c.sub_tl_n[i] = maxres;
13552 b = 0;
13553 foreach (lc2, sq->rtable) {
13554 RangeTblEntry *br = (RangeTblEntry *) lfirst(lc2);
13555 ++b;
13556 if (br->rtekind == RTE_RELATION && br->relkind != RELKIND_VIEW) {
13557 newpos++;
13558 new_rtable = lappend(new_rtable, copyObject(br));
13559 c.sub_newpos[i][b] = newpos;
13560 /* compose: our slot, then the base's path within the (already
13561 * recursively flattened) subquery -- so nested views map all the way
13562 * down to the base input. */
13563 origins_l = lappend(origins_l,
13564 (sub_origins != NULL && b - 1 < sub_n)
13565 ? flat_origin_prepend(i, &sub_origins[b - 1])
13566 : flat_origin1(i));
13567 }
13568 }
13569 any_flat = true;
13570 }
13571
13572 if (parent_flat && any_flat) {
13573 /* (1) remap the parent's target list and WHERE */
13574 probe->targetList = (List *) flatten_mut((Node *) probe->targetList, &c);
13575 if (probe->jointree->quals)
13576 merged_quals = lappend(merged_quals, flatten_mut(probe->jointree->quals, &c));
13577 /* (2) pull every inlined subquery's WHERE up, remapping base varnos */
13578 for (i = 1; i <= N; i++) {
13579 RangeTblEntry *rte;
13580 if (!c.slot_flat[i]) continue;
13581 rte = list_nth_node(RangeTblEntry, probe->rtable, i - 1);
13582 if (rte->subquery->jointree && rte->subquery->jointree->quals) {
13583 c.quals_mode = true; c.quals_slot = i;
13584 merged_quals =
13585 lappend(merged_quals,
13586 flatten_mut((Node *) copyObject(rte->subquery->jointree->quals),
13587 &c));
13588 c.quals_mode = false;
13589 }
13590 }
13591 /* (3) commit the flattened range table, a flat fromlist and combined WHERE */
13592 probe->rtable = new_rtable;
13593 {
13594 List *fl = NIL;
13595 for (i = 1; i <= newpos; i++) {
13596 RangeTblRef *r = makeNode(RangeTblRef);
13597 r->rtindex = i;
13598 fl = lappend(fl, r);
13599 }
13600 probe->jointree->fromlist = fl;
13601 }
13602 probe->jointree->quals =
13603 (merged_quals == NIL) ? NULL
13604 : (list_length(merged_quals) == 1) ? (Node *) linitial(merged_quals)
13605 : (Node *) makeBoolExpr(AND_EXPR, merged_quals, -1);
13606
13607 *nflat_out = newpos;
13608 origins = (FlatAtomOrigin *) palloc(newpos * sizeof(FlatAtomOrigin));
13609 i = 0;
13610 foreach (lc, origins_l)
13611 origins[i++] = *(FlatAtomOrigin *) lfirst(lc);
13612 return origins;
13613 }
13614
13615 /* Nothing flattened (no flattenable subquery, or a non-flat parent FROM):
13616 * leave probe untouched and return an identity map (one depth-1 path per slot). */
13617 *nflat_out = N;
13618 origins = (FlatAtomOrigin *) palloc(N * sizeof(FlatAtomOrigin));
13619 for (i = 0; i < N; i++) {
13620 origins[i].depth = 1;
13621 origins[i].path = (int *) palloc(sizeof(int));
13622 origins[i].path[0] = i + 1;
13623 }
13624 return origins;
13625}
13626
13627/**
13628 * @brief Build the inversion-free marker context for top-level query @p q.
13629 *
13630 * Runs the detector on a flattened, group-RTE-stripped copy of @p q so that
13631 * single-base SPJ subqueries/views are recognised as base atoms. On success
13632 * sets @p *cert_out to the serialised root certificate and returns a context
13633 * tree mirroring @p q's range table: a direct base atom's marker at its slot,
13634 * a flattened subquery's marker in a one-entry child context at its slot.
13635 * Returns NULL (declining) when @p q is not certified or carries no markers;
13636 * @p *cert_out may still be set (the cert attaches even without markers, and
13637 * the path then declines at evaluation and falls back).
13638 */
13640 Query *q, char **cert_out) {
13641 bool has_subq = false, has_group = false;
13642 Query *probe;
13643 FlatAtomOrigin *origins = NULL;
13644 InvFreeMarker *flat = NULL;
13645 int nflat = 0, norigins = 0, N, p;
13646 char *cert = NULL;
13647 InvFreeMarkerCtx *ctx;
13648 ListCell *lc;
13649
13650 foreach (lc, q->rtable)
13651 if (((RangeTblEntry *) lfirst(lc))->rtekind == RTE_SUBQUERY) has_subq = true;
13652#if PG_VERSION_NUM >= 180000
13653 has_group = q->hasGroupRTE;
13654#endif
13655
13656 /* No subqueries and no synthetic group RTE: analyse q in place (read-only),
13657 * positions equal q's slots. Otherwise work on a copy: strip the PG 18 group
13658 * RTE, then flatten SPJ subqueries (origins map flattened positions back). */
13659 if (!has_subq && !has_group) {
13660 probe = q;
13661 } else {
13662 probe = (Query *) copyObject(q);
13663#if PG_VERSION_NUM >= 180000
13664 if (has_group)
13665 strip_group_rte_pg18(probe);
13666#endif
13667 if (has_subq)
13668 origins = flatten_spj_subqueries(probe, &norigins);
13669 }
13670
13671 if (!inversion_free_analyze(constants, probe, &cert, &flat, &nflat))
13672 return NULL;
13673 if (cert_out)
13674 *cert_out = cert;
13675 if (flat == NULL) /* certified but no marker model: decline */
13676 return NULL;
13677
13678 N = list_length(q->rtable);
13679 ctx = (InvFreeMarkerCtx *) palloc0(sizeof(InvFreeMarkerCtx));
13680 ctx->natoms = N;
13681 ctx->markers = (InvFreeMarker *) palloc0((size_t) N * sizeof(InvFreeMarker));
13682 ctx->sub = (InvFreeMarkerCtx **) palloc0((size_t) N * sizeof(InvFreeMarkerCtx *));
13683 /* Map each flattened atom's marker back to q by walking its origin slot path
13684 * down the *original* (un-flattened) query tree, creating/sizing a nested
13685 * child context at each subquery hop, so the recursive subquery rewrite later
13686 * threads the marker to the right base input. With no flattening, position
13687 * == q slot (the synthetic group RTE, if any, sits after the base atoms, so
13688 * the prefix aligns), i.e. an implicit depth-1 path. */
13689 for (p = 0; p < nflat; p++) {
13690 int tmp_path[1];
13691 int *path;
13692 int depth, d, base;
13693 InvFreeMarkerCtx *cur = ctx;
13694 Query *qcur = q;
13695 if (!flat[p].valid)
13696 continue;
13697 if (origins != NULL) {
13698 if (p >= norigins) continue;
13699 path = origins[p].path;
13700 depth = origins[p].depth;
13701 } else {
13702 tmp_path[0] = p + 1;
13703 path = tmp_path;
13704 depth = 1;
13705 }
13706 /* descend all but the last path element (the nested subquery slots) */
13707 for (d = 0; d + 1 < depth && cur != NULL; d++) {
13708 int slot = path[d];
13709 RangeTblEntry *rte;
13710 InvFreeMarkerCtx *child;
13711 int sublen;
13712 if (slot < 1 || slot > qcur->rtable->length) { cur = NULL; break; }
13713 rte = list_nth_node(RangeTblEntry, qcur->rtable, slot - 1);
13714 if (rte->rtekind != RTE_SUBQUERY || rte->subquery == NULL) { cur = NULL; break; }
13715 sublen = list_length(rte->subquery->rtable);
13716 child = cur->sub[slot - 1];
13717 if (child == NULL) {
13718 child = (InvFreeMarkerCtx *) palloc0(sizeof(InvFreeMarkerCtx));
13719 child->natoms = sublen;
13720 child->markers = (InvFreeMarker *) palloc0((size_t) sublen * sizeof(InvFreeMarker));
13721 child->sub = (InvFreeMarkerCtx **) palloc0((size_t) sublen * sizeof(InvFreeMarkerCtx *));
13722 cur->sub[slot - 1] = child;
13723 }
13724 cur = child;
13725 qcur = rte->subquery;
13726 }
13727 if (cur == NULL)
13728 continue;
13729 base = path[depth - 1]; /* base slot at the leaf */
13730 if (base >= 1 && base - 1 < cur->natoms)
13731 cur->markers[base - 1] = flat[p];
13732 }
13733 return ctx;
13734}
13735
13736/**
13737 * @brief The output (head) columns of a UNION arm as plain base @c Var\ s.
13738 *
13739 * Returns a list of the @c Var behind each non-junk target entry (stripping
13740 * @c RelabelType), or @c NIL if any output column is not a bare @c Var -- the
13741 * UCQ head classes can only be aligned across arms through column @c Var\ s.
13742 */
13743static List *inv_free_arm_head_vars(Query *arm) {
13744 List *heads = NIL;
13745 ListCell *lc;
13746 foreach (lc, arm->targetList) {
13747 TargetEntry *te = (TargetEntry *) lfirst(lc);
13748 Node *e;
13749 if (te->resjunk)
13750 continue;
13751 e = (Node *) te->expr;
13752 while (e != NULL && IsA(e, RelabelType))
13753 e = (Node *) ((RelabelType *) e)->arg;
13754 if (e == NULL || !IsA(e, Var) || ((Var *) e)->varlevelsup != 0)
13755 return NIL;
13756 heads = lappend(heads, e);
13757 }
13758 return heads;
13759}
13760
13761/** @brief Build the equality qual @p v1 @c = @p v2, or NULL if the types have
13762 * no @c = operator. */
13763static OpExpr *inv_free_make_eq(Var *v1, Var *v2) {
13764 Oid eqop = find_equality_operator(v1->vartype, v2->vartype);
13765 OpExpr *op;
13766 if (!OidIsValid(eqop))
13767 return NULL;
13768 op = makeNode(OpExpr);
13769 op->opno = eqop;
13770 op->opfuncid = get_opcode(eqop);
13771 op->opresulttype = BOOLOID;
13772 op->opretset = false;
13773 op->opcollid = InvalidOid;
13774 op->inputcollid = v1->varcollid;
13775 op->args = list_make2((Var *) copyObject(v1), (Var *) copyObject(v2));
13776 op->location = -1;
13777 return op;
13778}
13779
13780/**
13781 * @brief Build the inversion-free marker context for a set-semantics @c UNION of
13782 * inversion-free branches (the full Jha & Suciu UCQ(OBDD) case).
13783 *
13784 * A deduplicating @c UNION is lowered by @c rewrite_non_all_into_external_group_by
13785 * to an outer @c GROUP @c BY over an inner @c UNION @c ALL subquery, whose
13786 * per-group provenance root is @c provenance_plus(array_agg(...)) -- the OR of
13787 * the contributing branch tokens (a user @c GROUP @c BY over a @c UNION @c ALL
13788 * derived table has the same shape).
13789 *
13790 * Inversion-freeness of a @c UNION is a *joint* property of the whole UCQ (a
13791 * relation shared between two branches can introduce a cross-branch inversion),
13792 * so a per-arm analysis does not suffice. This builds one synthetic SPJ query
13793 * merging every arm's base atoms into a single range table (arm variables offset
13794 * into one numbering, the arms' head columns equated, each arm's @c WHERE pulled
13795 * up) and runs the existing detector on it via @c inversion_free_analyze: shared
13796 * relations become one relation symbol, so positional consistency and the
13797 * precedence graph span the whole UCQ, exactly Thm 4.2's condition. The
13798 * resulting per-atom markers are mapped back to each arm (base relations keep
13799 * positions @c 1..n_i since PG 18's synthetic group RTE is appended last and
13800 * stripped), threaded into the inner arms, and the recipe lands on the outer
13801 * plus root; the structured d-DNNF then Shannon-decomposes the OR over the
13802 * joint order (branch-disjoint arms collapse via @c orDecompose).
13803 *
13804 * Returns @c NULL -- declining to the generic / joint-width / Möbius chain -- when
13805 * @p q is not a group-over-@c UNION-ALL of flat inversion-free arms (only the
13806 * jointly inversion-free class is certified). Sets @p *cert_out to a serialised
13807 * recipe (the evaluator routes on its presence; the order comes from the keys).
13808 */
13810 Query *q, char **cert_out) {
13811 int N = list_length(q->rtable), inner_slot = -1, i, narms, natoms, off;
13812 Query *inner = NULL, *merged;
13813 InvFreeMarkerCtx *ctx, *ctx_inner;
13814 List *merged_quals = NIL, *fromlist = NIL, *head0 = NIL;
13815 int *arm_off, *arm_base, *arm_real_len;
13816 InvFreeMarker *mm = NULL;
13817 char *cert_str = NULL;
13818 int mm_natoms = 0, p;
13819 ListCell *lc;
13820
13821 /* Only a deduplicating group has a provenance_plus OR root: the lowered
13822 * UNION-distinct, or a user GROUP BY over a UNION ALL derived table. A plain
13823 * projection / join over a UNION ALL passes a single branch token through
13824 * (no OR), so certifying it would wrongly mark its inputs. */
13825 if (q->groupClause == NIL)
13826 return NULL;
13827
13828 /* A pure deduplicating group only: every output column is a grouping key, so
13829 * equating them across arms aligns the UCQ heads. A user aggregate / HAVING
13830 * (GROUP BY a key while projecting/aggregating other columns) is not a plain
13831 * plus-of-branches OR -- it is the HAVING / count-PMF / joint-width path's
13832 * shape, where aligning the non-key columns would be wrong. */
13833 if (q->hasAggs || q->havingQual != NULL)
13834 return NULL;
13835
13836 /* The outer FROM must be exactly one inner UNION (ALL) subquery (plus, on
13837 * PG 18+, the synthetic group RTE): any other real RTE means the plus root
13838 * also factors in a join, which this per-arm marker model does not cover. */
13839 for (i = 0; i < N; i++) {
13840 RangeTblEntry *r = list_nth_node(RangeTblEntry, q->rtable, i);
13841#if PG_VERSION_NUM >= 180000
13842 if (r->rtekind == RTE_GROUP)
13843 continue;
13844#endif
13845 if (r->rtekind == RTE_SUBQUERY && r->subquery != NULL
13846 && r->subquery->setOperations != NULL
13847 && IsA(r->subquery->setOperations, SetOperationStmt)
13848 && ((SetOperationStmt *) r->subquery->setOperations)->op == SETOP_UNION
13849 && ((SetOperationStmt *) r->subquery->setOperations)->all) {
13850 /* UNION ALL only: a non-ALL UNION nested here has not been lowered yet
13851 * (rewrite_non_all_into_external_group_by runs when this subquery is
13852 * processed, restructuring it), so the arm positions we analyse now would
13853 * not match the post-lowering circuit. A top-level deduplicating UNION is
13854 * already lowered to GROUP-BY-over-UNION-ALL before this runs, so it
13855 * arrives here with all = true; an un-lowered nested one declines. */
13856 if (inner_slot >= 0)
13857 return NULL; /* more than one union subquery: out of scope */
13858 inner_slot = i;
13859 inner = r->subquery;
13860 } else {
13861 return NULL; /* a non-union real RTE: the root is not a pure union OR */
13862 }
13863 }
13864 if (inner == NULL)
13865 return NULL;
13866 narms = list_length(inner->rtable);
13867 if (narms < 2)
13868 return NULL;
13869
13870 arm_off = (int *) palloc((size_t) narms * sizeof(int));
13871 arm_base = (int *) palloc((size_t) narms * sizeof(int));
13872 arm_real_len = (int *) palloc((size_t) narms * sizeof(int));
13873
13874 merged = makeNode(Query);
13875 merged->commandType = CMD_SELECT;
13876 merged->rtable = NIL;
13877 off = 0;
13878 i = 0;
13879 foreach (lc, inner->rtable) {
13880 RangeTblEntry *r = (RangeTblEntry *) lfirst(lc);
13881 Query *arm;
13882 List *heads;
13883 ListCell *lcr;
13884 if (r->rtekind != RTE_SUBQUERY || r->subquery == NULL)
13885 return NULL; /* a non-subquery arm: out of scope */
13886 arm_real_len[i] = list_length(r->subquery->rtable);
13887 arm = (Query *) copyObject(r->subquery);
13888#if PG_VERSION_NUM >= 180000
13889 if (arm->hasGroupRTE)
13891#endif
13892 foreach (lcr, arm->rtable)
13893 if (((RangeTblEntry *) lfirst(lcr))->rtekind != RTE_RELATION)
13894 return NULL; /* arm is not a flat base-relation SPJ */
13895 arm_off[i] = off;
13896 arm_base[i] = list_length(arm->rtable);
13897 if (arm->jointree && arm->jointree->quals) {
13898 OffsetVarNodes(arm->jointree->quals, off, 0);
13899 merged_quals = lappend(merged_quals, arm->jointree->quals);
13900 }
13901 OffsetVarNodes((Node *) arm->targetList, off, 0);
13902 merged->rtable = list_concat(merged->rtable, arm->rtable);
13903 heads = inv_free_arm_head_vars(arm);
13904 if (heads == NIL)
13905 return NULL; /* non-Var head column: cannot align the UCQ */
13906 if (i == 0) {
13907 head0 = heads;
13908 } else {
13909 ListCell *l0, *li;
13910 if (list_length(heads) != list_length(head0))
13911 return NULL;
13912 forboth (l0, head0, li, heads) {
13913 OpExpr *eq = inv_free_make_eq((Var *) lfirst(l0), (Var *) lfirst(li));
13914 if (eq == NULL)
13915 return NULL;
13916 merged_quals = lappend(merged_quals, eq);
13917 }
13918 }
13919 off += arm_base[i];
13920 i++;
13921 }
13922 natoms = off;
13923 if (natoms < 2)
13924 return NULL;
13925
13926 for (p = 1; p <= natoms; p++) {
13927 RangeTblRef *rr = makeNode(RangeTblRef);
13928 rr->rtindex = p;
13929 fromlist = lappend(fromlist, rr);
13930 }
13931 {
13932 Node *quals = NULL;
13933 if (list_length(merged_quals) == 1)
13934 quals = (Node *) linitial(merged_quals);
13935 else if (merged_quals != NIL)
13936 quals = (Node *) makeBoolExpr(AND_EXPR, merged_quals, -1);
13937 merged->jointree = makeFromExpr(fromlist, quals);
13938 }
13939 {
13940 List *tl = NIL;
13941 int resno = 1;
13942 ListCell *lh;
13943 foreach (lh, head0)
13944 tl = lappend(tl, makeTargetEntry((Expr *) copyObject(lfirst(lh)),
13945 (AttrNumber) resno++, NULL, false));
13946 merged->targetList = tl;
13947 }
13948
13949 if (!inversion_free_analyze(constants, merged, &cert_str, &mm, &mm_natoms))
13950 return NULL; /* not jointly inversion-free: fall back */
13951 if (mm == NULL || mm_natoms != natoms)
13952 return NULL;
13953
13954 ctx_inner = (InvFreeMarkerCtx *) palloc0(sizeof(InvFreeMarkerCtx));
13955 ctx_inner->natoms = narms;
13956 ctx_inner->markers = (InvFreeMarker *) palloc0((size_t) narms * sizeof(InvFreeMarker));
13957 ctx_inner->sub = (InvFreeMarkerCtx **) palloc0((size_t) narms * sizeof(InvFreeMarkerCtx *));
13958 for (i = 0; i < narms; i++) {
13959 InvFreeMarkerCtx *arm_ctx = (InvFreeMarkerCtx *) palloc0(sizeof(InvFreeMarkerCtx));
13960 int loc;
13961 arm_ctx->natoms = arm_real_len[i];
13962 arm_ctx->markers = (InvFreeMarker *) palloc0((size_t) arm_real_len[i] * sizeof(InvFreeMarker));
13963 arm_ctx->sub = (InvFreeMarkerCtx **) palloc0((size_t) arm_real_len[i] * sizeof(InvFreeMarkerCtx *));
13964 for (loc = 0; loc < arm_base[i]; loc++)
13965 arm_ctx->markers[loc] = mm[arm_off[i] + loc];
13966 ctx_inner->sub[i] = arm_ctx;
13967 }
13968
13969 ctx = (InvFreeMarkerCtx *) palloc0(sizeof(InvFreeMarkerCtx));
13970 ctx->natoms = N;
13971 ctx->markers = (InvFreeMarker *) palloc0((size_t) N * sizeof(InvFreeMarker));
13972 ctx->sub = (InvFreeMarkerCtx **) palloc0((size_t) N * sizeof(InvFreeMarkerCtx *));
13973 ctx->sub[inner_slot] = ctx_inner;
13974
13975 if (cert_out)
13976 *cert_out = cert_str;
13977 return ctx;
13978}
13979
13980/**
13981 * @brief Process the inert provenance() fetches in one query's own clauses.
13982 *
13983 * Walks @p q's target list, jointree and HAVING for scalar SubLinks whose
13984 * subselect's sole output is provenance() (@c subselect_is_pure_provenance_fetch),
13985 * runs @c process_query on each such subselect (resolving its provenance()
13986 * to that scope's token, with no provsql column appended), and records the
13987 * processed subselect so later coupling-time detectors treat it as
13988 * untracked. Does not descend into nested queries: a FROM subquery, or a
13989 * non-inert sublink's subselect, runs its own pass when @c process_query
13990 * reaches it.
13991 */
13992typedef struct { const constants_t *constants; } inert_walk_ctx;
13993
13994/**
13995 * @brief Make a processed inert subselect return exactly its provenance
13996 * token as a single column.
13997 *
13998 * After @c process_query (with @c wrap_root false, so the token is the
13999 * plain provenance expression, not @c assume_boolean-wrapped) the subselect
14000 * carries the resolved provenance() value, an auto-appended @c provsql
14001 * column, and possibly resjunk grouping / ordering keys. An inert scalar
14002 * fetch must return exactly one non-junk column: keep the resolved
14003 * provenance() value (the first non-junk entry that is not the appended
14004 * @c provsql), drop the @c provsql duplicate, and retain the resjunk
14005 * entries that GROUP BY / ORDER BY still reference.
14006 */
14007static void keep_only_provenance_output(Query *sub) {
14008 ListCell *lc;
14009 TargetEntry *value = NULL;
14010 List *kept;
14011 int n = 0;
14012 foreach (lc, sub->targetList) {
14013 TargetEntry *te = (TargetEntry *)lfirst(lc);
14014 if (te->resjunk)
14015 continue;
14016 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME))
14017 continue; /* the appended duplicate */
14018 value = te; /* the resolved provenance() value */
14019 break;
14020 }
14021 if (value == NULL)
14022 return; /* defensive */
14023 kept = list_make1(value);
14024 foreach (lc, sub->targetList) {
14025 TargetEntry *te = (TargetEntry *)lfirst(lc);
14026 if (te != value && te->resjunk)
14027 kept = lappend(kept, te); /* grouping / ordering keys */
14028 }
14029 foreach (lc, kept)
14030 ((TargetEntry *)lfirst(lc))->resno = ++n;
14031 sub->targetList = kept;
14032}
14033
14034static bool process_inert_fetches_walker(Node *node, void *cx) {
14035 inert_walk_ctx *ctx = (inert_walk_ctx *)cx;
14036 if (node == NULL)
14037 return false;
14038 if (IsA(node, SubLink)) {
14039 SubLink *sl = (SubLink *)node;
14040 if (sl->subLinkType == EXPR_SUBLINK && sl->subselect &&
14041 IsA(sl->subselect, Query) &&
14043 (Query *)sl->subselect)) {
14044 bool *removed = NULL;
14045 Query *processed = process_query(ctx->constants, (Query *)sl->subselect,
14046 &removed, false, false, false, NULL);
14047 keep_only_provenance_output(processed);
14048 sl->subselect = (Node *)processed;
14050 return false; /* handled; do not descend into it */
14051 }
14052 /* A non-inert sublink: descend into its testexpr only, not its
14053 * subselect (a different scope, handled on its own). */
14054 return expression_tree_walker((Node *) sl->testexpr,
14056 }
14057 if (IsA(node, Query))
14058 return false; /* a nested query scope: not this pass's job */
14059 return expression_tree_walker(node, process_inert_fetches_walker, cx);
14060}
14061
14062static void process_inert_fetches(const constants_t *constants, Query *q) {
14063 inert_walk_ctx ctx = { constants };
14064 process_inert_fetches_walker((Node *)q->targetList, &ctx);
14065 if (q->jointree)
14066 process_inert_fetches_walker((Node *)q->jointree, &ctx);
14067 if (q->havingQual)
14068 process_inert_fetches_walker(q->havingQual, &ctx);
14069}
14070
14071/**
14072 * @brief Rewrite a single SELECT query to carry provenance.
14073 *
14074 * This is the recursive entry point for the provenance rewriter. It is
14075 * called from @c provsql_planner for top-level queries and re-entered from
14076 * @c get_provenance_attributes for subqueries in FROM.
14077 *
14078 * High-level steps:
14079 * 1. Strip any @c provsql column propagated into this query's target list.
14080 * 2. Detect and rewrite structural forms requiring pre-processing:
14081 * non-ALL set operations (wrap in outer GROUP BY), AGG DISTINCT (push
14082 * into a subquery), DISTINCT (convert to GROUP BY).
14083 * 3. Collect provenance attributes via @c get_provenance_attributes.
14084 * 4. Build a column-numbering map for where-provenance (@c build_column_map).
14085 * 5. Handle aggregates, migrate WHERE-on-aggregate to HAVING, and set ops.
14086 * 6. Build and splice the combined provenance expression.
14087 *
14088 * @param constants Extension OID cache.
14089 * @param q Query to rewrite (modified in place).
14090 * @param removed Out-param: boolean array indicating which original target
14091 * list entries were provenance columns and were removed.
14092 * May be @c NULL if the caller does not need this info.
14093 * @param wrap_root If true, mark this query's provenance expression as a
14094 * safe-query root that must be wrapped in
14095 * @c provsql.assume_boolean before splicing.
14096 * @param top_level True for the outermost query the user evaluates; gates the
14097 * inversion-free analysis (run only at the top).
14098 * @param in_boolean_rewrite True once a safe-query (boolean) rewrite has fired
14099 * above; propagated through every recursion (including into
14100 * subqueries, where @c wrap_root is otherwise lost) so the
14101 * joint-width recogniser defers to the safe rewrite everywhere
14102 * in its subtree.
14103 * @param inv_ctx Inversion-free marker context supplied by a parent that
14104 * flattened this query as a subquery, or @c NULL; when set,
14105 * this query applies the supplied per-input markers instead of
14106 * running its own analysis or read-once rewrite.
14107 * @return The (possibly restructured) rewritten query, or @c NULL if the
14108 * query has no FROM clause and can be skipped.
14109 */
14110/* ----------------------------------------------------------------------------
14111 * Inner-join canonicalisation.
14112 *
14113 * PostgreSQL hands the planner hook two different Query shapes for the same
14114 * inner-join semantics: "FROM a, b WHERE c" is a flat fromlist of
14115 * RangeTblRefs with the condition in the WHERE quals, while
14116 * "FROM a JOIN b ON c" is a JoinExpr tree plus a synthetic RTE_JOIN entry.
14117 * Rewrite passes that pattern-match the flat shape have repeatedly missed
14118 * the JOIN one, so normalize_inner_joins canonicalises every tracked query
14119 * level to the flat form before any shape-sensitive pass runs. Rewrite
14120 * passes downstream (and new ones) may therefore assume the comma-join form;
14121 * only outer joins still appear as JoinExprs, for lower_outer_joins.
14122 * ------------------------------------------------------------------------- */
14123
14124/** @brief Context for the join-alias walker/mutator of
14125 * @c normalize_inner_joins. */
14126typedef struct join_alias_ctx {
14127 List *rtable; ///< range table owning the joinaliasvars
14128 Bitmapset *flattened; ///< rtindexes of the RTE_JOIN entries being dissolved
14129 int sublevels_up; ///< current query nesting depth
14130 bool wholerow; ///< a whole-row Var references a dissolved join
14132
14133/** @brief Walker: does any Var reference a dissolved join RTE as a whole row
14134 * (@c varattno @c <= @c 0)? Such a reference cannot be resolved through
14135 * @c joinaliasvars, so the normalization declines. */
14136static bool join_wholerow_walker(Node *node, void *cx) {
14137 join_alias_ctx *c = (join_alias_ctx *)cx;
14138 if (node == NULL)
14139 return false;
14140 if (IsA(node, Var)) {
14141 Var *v = (Var *)node;
14142 if ((int)v->varlevelsup == c->sublevels_up && v->varattno <= 0 &&
14143 bms_is_member(v->varno, c->flattened))
14144 c->wholerow = true;
14145 return false;
14146 }
14147 if (IsA(node, Query)) {
14148 bool res;
14149 c->sublevels_up++;
14150 res = query_tree_walker((Query *)node, join_wholerow_walker, cx, 0);
14151 c->sublevels_up--;
14152 return res;
14153 }
14154 return expression_tree_walker(node, join_wholerow_walker, cx);
14155}
14156
14157/** @brief Mutator: replace every Var referencing a dissolved join RTE by its
14158 * @c joinaliasvars expression -- resolved recursively, since chained joins
14159 * alias through each other -- adjusted to the Var's level. This covers
14160 * USING / NATURAL merged columns and aliased ON-join columns alike. */
14161static Node *join_alias_resolve_mut(Node *node, void *cx) {
14162 join_alias_ctx *c = (join_alias_ctx *)cx;
14163 if (node == NULL)
14164 return NULL;
14165 if (IsA(node, Var)) {
14166 Var *v = (Var *)node;
14167 if ((int)v->varlevelsup == c->sublevels_up && v->varattno > 0 &&
14168 bms_is_member(v->varno, c->flattened)) {
14169 RangeTblEntry *rte = rt_fetch(v->varno, c->rtable);
14170 Node *expr =
14171 (Node *)copyObject(list_nth(rte->joinaliasvars, v->varattno - 1));
14172 if (c->sublevels_up > 0)
14173 IncrementVarSublevelsUp(expr, c->sublevels_up, 0);
14174 return join_alias_resolve_mut(expr, cx);
14175 }
14176 return (Node *)copyObject(v);
14177 }
14178 if (IsA(node, Query)) {
14179 Query *res;
14180 c->sublevels_up++;
14181 res = query_tree_mutator((Query *)node, join_alias_resolve_mut, cx, 0);
14182 c->sublevels_up--;
14183 return (Node *)res;
14184 }
14185 return expression_tree_mutator(node, join_alias_resolve_mut, cx);
14186}
14187
14188/** @brief Recursively collect an all-inner join tree's leaf RangeTblRefs,
14189 * ON quals, and dissolved RTE_JOIN rtindexes. Returns @c false -- leaving
14190 * the outputs unusable -- on any outer join, aliased join
14191 * (@c JOIN @c ... @c AS, whose column renaming the flat form cannot carry),
14192 * or unexpected node. */
14193static bool inner_join_collect(Node *jt, List **refs, List **quals,
14194 Bitmapset **joins) {
14195 if (jt == NULL)
14196 return false;
14197 if (IsA(jt, RangeTblRef)) {
14198 *refs = lappend(*refs, jt);
14199 return true;
14200 }
14201 if (IsA(jt, JoinExpr)) {
14202 JoinExpr *j = (JoinExpr *)jt;
14203 if (j->jointype != JOIN_INNER || j->alias != NULL)
14204 return false;
14205 if (!inner_join_collect(j->larg, refs, quals, joins) ||
14206 !inner_join_collect(j->rarg, refs, quals, joins))
14207 return false;
14208 if (j->quals)
14209 *quals = lappend(*quals, j->quals);
14210 *joins = bms_add_member(*joins, j->rtindex);
14211 return true;
14212 }
14213 return false;
14214}
14215
14216/** @brief Context for the rtindex-renumbering mutator of
14217 * @c normalize_inner_joins. */
14218typedef struct renumber_rte_ctx {
14219 int old_size; ///< range-table length before compaction
14220 int *old_to_new; ///< 1-based rtindex map; dissolved slots map to 0
14221 int sublevels_up; ///< current query nesting depth
14223
14224/** @brief Mutator: renumber every Var / RangeTblRef / JoinExpr rtindex of
14225 * the compacted level through @c old_to_new, at any nesting depth (a
14226 * nested subquery reaches the level via @c varlevelsup). A @c varnosyn
14227 * pointing at a dropped slot is cleared (the deparse hint has no
14228 * surviving target). */
14229static Node *renumber_rte_mut(Node *node, void *cx) {
14231 if (node == NULL)
14232 return NULL;
14233 if (IsA(node, Var)) {
14234 Var *v = (Var *)copyObject(node);
14235 if ((int)v->varlevelsup == c->sublevels_up) {
14236 if ((int)v->varno >= 1 && (int)v->varno <= c->old_size &&
14237 c->old_to_new[v->varno] > 0)
14238 v->varno = (Index)c->old_to_new[v->varno];
14239#if PG_VERSION_NUM >= 130000
14240 if ((int)v->varnosyn >= 1 && (int)v->varnosyn <= c->old_size) {
14241 if (c->old_to_new[v->varnosyn] > 0) {
14242 v->varnosyn = (Index)c->old_to_new[v->varnosyn];
14243 } else {
14244 v->varnosyn = 0;
14245 v->varattnosyn = 0;
14246 }
14247 }
14248#endif
14249 }
14250 return (Node *)v;
14251 }
14252 if (IsA(node, RangeTblRef)) {
14253 RangeTblRef *r = (RangeTblRef *)copyObject(node);
14254 if (c->sublevels_up == 0 && r->rtindex >= 1 &&
14255 r->rtindex <= c->old_size && c->old_to_new[r->rtindex] > 0)
14256 r->rtindex = c->old_to_new[r->rtindex];
14257 return (Node *)r;
14258 }
14259 if (IsA(node, JoinExpr)) {
14260 JoinExpr *j = (JoinExpr *)expression_tree_mutator(node, renumber_rte_mut, cx);
14261 if (c->sublevels_up == 0 && j->rtindex >= 1 &&
14262 j->rtindex <= c->old_size && c->old_to_new[j->rtindex] > 0)
14263 j->rtindex = c->old_to_new[j->rtindex];
14264 return (Node *)j;
14265 }
14266 if (IsA(node, Query)) {
14267 Query *res;
14268 c->sublevels_up++;
14269 res = query_tree_mutator((Query *)node, renumber_rte_mut, cx, 0);
14270 c->sublevels_up--;
14271 return (Node *)res;
14272 }
14273 return expression_tree_mutator(node, renumber_rte_mut, cx);
14274}
14275
14276/**
14277 * @brief Canonicalise explicit inner joins in @p q's FROM to the comma-join
14278 * form: each all-inner JoinExpr fromlist item becomes its leaf
14279 * RangeTblRefs, the ON conditions are splayed into one flat WHERE
14280 * conjunction, every reference to the dissolved joins' alias columns
14281 * (USING / NATURAL merged columns included) is resolved to base
14282 * expressions, and the dissolved RTE_JOIN entries are dropped from
14283 * the range table with every surviving rtindex renumbered.
14284 *
14285 * A fromlist item containing an outer join is kept intact for
14286 * @c lower_outer_joins, as is the whole query when a whole-row Var
14287 * references a dissolved join (unresolvable through @c joinaliasvars).
14288 * Runs on every tracked query level and -- via
14289 * @c normalize_inner_joins_walker -- on every nested Query (sublink
14290 * bodies, subquery RTEs, CTE bodies), before any shape-sensitive pass.
14291 */
14292static void normalize_inner_joins(Query *q) {
14293 Bitmapset *flattened = NULL;
14294 bool changed = false;
14295 ListCell *lc;
14296 join_alias_ctx actx;
14297
14298 if (q->commandType != CMD_SELECT || q->jointree == NULL)
14299 return;
14300
14301 /* Probe: which fromlist items are all-inner join trees? (The actual
14302 * collection re-runs below, on the alias-resolved tree.) */
14303 foreach (lc, q->jointree->fromlist) {
14304 Node *item = (Node *)lfirst(lc);
14305 List *refs = NIL, *jquals = NIL;
14306 Bitmapset *joins = NULL;
14307 if (IsA(item, JoinExpr) &&
14308 inner_join_collect(item, &refs, &jquals, &joins)) {
14309 flattened = bms_union(flattened, joins);
14310 changed = true;
14311 }
14312 }
14313 if (!changed)
14314 return;
14315
14316 actx.rtable = q->rtable;
14317 actx.flattened = flattened;
14318 actx.sublevels_up = 0;
14319 actx.wholerow = false;
14320 query_tree_walker(q, join_wholerow_walker, &actx, 0);
14321 if (actx.wholerow)
14322 return;
14323
14324 /* 1) Resolve every reference to a dissolved join's alias columns, over
14325 * the whole level: target list, quals, HAVING, window clauses,
14326 * RTE-embedded expressions (LATERAL subqueries / functions, kept joins'
14327 * joinaliasvars), and nested queries reaching this level through
14328 * varlevelsup. */
14329 actx.sublevels_up = 0;
14330 query_tree_mutator(q, join_alias_resolve_mut, &actx, QTW_DONT_COPY_QUERY);
14331
14332 /* 2) Flatten the fromlist; splay nested ANDs with make_ands_implicit so
14333 * downstream passes that split only a top-level AND (e.g.
14334 * rewrite_predicate_sublinks) see every conjunct. */
14335 {
14336 List *newfrom = NIL, *conjs = NIL;
14337 foreach (lc, q->jointree->fromlist) {
14338 Node *item = (Node *)lfirst(lc);
14339 List *refs = NIL, *jquals = NIL;
14340 Bitmapset *joins = NULL;
14341 ListCell *qc;
14342 if (IsA(item, JoinExpr) &&
14343 inner_join_collect(item, &refs, &jquals, &joins)) {
14344 newfrom = list_concat(newfrom, refs);
14345 foreach (qc, jquals)
14346 conjs =
14347 list_concat(conjs, make_ands_implicit((Expr *)lfirst(qc)));
14348 } else {
14349 newfrom = lappend(newfrom, item);
14350 }
14351 }
14352 if (q->jointree->quals)
14353 conjs =
14354 list_concat(conjs, make_ands_implicit((Expr *)q->jointree->quals));
14355 q->jointree->fromlist = newfrom;
14356 q->jointree->quals =
14357 (conjs == NIL) ? NULL : (Node *)make_ands_explicit(conjs);
14358 }
14359
14360 /* 3) Compact: drop the dissolved RTE_JOIN entries (now unreferenced)
14361 * and renumber every surviving rtindex, so downstream passes -- e.g.
14362 * the safe-query candidate gate -- see a clean range table. */
14363 {
14364 renumber_rte_ctx rctx;
14365 int old_size = list_length(q->rtable);
14366 int next = 1, i = 1;
14367 List *new_rtable = NIL;
14368
14369 rctx.old_to_new = (int *)palloc0((old_size + 1) * sizeof(int));
14370 foreach (lc, q->rtable) {
14371 if (!bms_is_member(i, flattened)) {
14372 rctx.old_to_new[i] = next++;
14373 new_rtable = lappend(new_rtable, lfirst(lc));
14374 }
14375 i++;
14376 }
14377 q->rtable = new_rtable;
14378 rctx.old_size = old_size;
14379 rctx.sublevels_up = 0;
14380 query_tree_mutator(q, renumber_rte_mut, &rctx, QTW_DONT_COPY_QUERY);
14381 pfree(rctx.old_to_new);
14382 }
14383}
14384
14385/** @brief Walker: apply @c normalize_inner_joins to every nested Query --
14386 * sublink subselects, subquery RTEs, CTE bodies -- so that e.g. a sublink
14387 * body is already canonical when the sublink pre-passes inspect it. */
14388static bool normalize_inner_joins_walker(Node *node, void *cx) {
14389 if (node == NULL)
14390 return false;
14391 if (IsA(node, Query)) {
14392 Query *sub = (Query *)node;
14394 return query_tree_walker(sub, normalize_inner_joins_walker, cx, 0);
14395 }
14396 return expression_tree_walker(node, normalize_inner_joins_walker, cx);
14397}
14398
14399static Query *process_query(const constants_t *constants, Query *q,
14400 bool **removed, bool wrap_root, bool top_level,
14401 bool in_boolean_rewrite,
14402 const InvFreeMarkerCtx *inv_ctx) {
14403 List *prov_atts;
14404 bool has_union = false;
14405 bool has_difference = false;
14406 bool supported = true;
14407 bool group_by_rewrite = false;
14408 int nbcols = 0;
14409 int **columns = NULL;
14410 int columns_len = 0;
14411 unsigned i = 0;
14412 char *inv_cert = NULL; /* serialised inversion-free certificate (root) */
14413 const InvFreeMarkerCtx *local_inv_ctx = NULL; /* this query's marker context */
14414 List *given_evidence = NIL; /* captured given(...) whole-tuple evidence */
14415 if (provsql_verbose >= 50)
14416 elog_node_display(NOTICE, "ProvSQL: Before query rewriting", q, true);
14417
14418 /* Canonicalise explicit inner joins to the comma-join form -- for this
14419 * level and, via the walker, for every nested Query (sublink bodies,
14420 * subquery RTEs, CTE bodies) -- before any shape-sensitive pass runs;
14421 * downstream passes may assume the flat fromlist, and only outer joins
14422 * still appear as JoinExprs (for lower_outer_joins). */
14423 if (provsql_active) {
14425 query_tree_walker(q, normalize_inner_joins_walker, NULL, 0);
14426 }
14427
14428 /* Inert provenance() fetches: resolve a scalar `(SELECT provenance()
14429 * FROM R ...)` to that subquery's token in place and record it as
14430 * untracked. Runs *before* the FROM-less early return below (a
14431 * `SELECT (SELECT provenance() ...)` has no outer rtable but still
14432 * carries the fetch) and before the decorrelation passes that would
14433 * otherwise couple it into the outer lineage. */
14434 if (provsql_active)
14435 process_inert_fetches(constants, q);
14436
14437 /* Natural Boolean-predicate conditioning: rewrite "X | (predicate)" into
14438 * the carrier's conditioning constructor over the converted condition gate
14439 * (cond / random_variable_cond / agg_token_cond, and given(...) for the
14440 * prefix whole-tuple form). Runs before the FROM-less early return (an
14441 * rv-conditioning query commonly has no FROM), before the given()-marker
14442 * strip (which then sees the emitted given() call), and before
14443 * migrate_probabilistic_quals (so a comparison inside the predicate is
14444 * consumed here, not lifted as a WHERE qual). */
14445 if (provsql_active)
14446 rewrite_cond_predicates(constants, q);
14447
14448 /* Comparison-event surface: lift RV comparisons appearing in the SELECT
14449 * target list -- a projected "x > y" and the probability(<predicate>)
14450 * Boolean overloads -- into their gate_cmp event tokens. Runs alongside
14451 * rewrite_cond_predicates and, like it, only over the target list (WHERE /
14452 * HAVING quals stay with migrate_probabilistic_quals). */
14453 if (provsql_active)
14454 rewrite_probability_events(constants, q);
14455
14456 /* Normalise a bare boolean aggregate used as a HAVING condition (HAVING
14457 * bool_or(x), HAVING NOT(every(x))) to "agg = true", while the aggregate is
14458 * still a raw Aggref -- before it is replaced by a provenance_aggregate.
14459 * The existing aggregate-comparison recognition and the boolean-domain
14460 * HAVING evaluator then handle it. */
14461 if (provsql_active && q->havingQual != NULL)
14462 q->havingQual = normalize_bool_agg_having((Node *) q->havingQual);
14463
14464 if (q->rtable == NULL) {
14465 /* FROM-less SELECT: the rest of the rewriter indexes into
14466 * q->rtable, so it can't process anything tied to a base relation.
14467 * But a WHERE-on-RV is still meaningful in this shape (e.g.
14468 * SELECT 1 WHERE normal(0,1) > 2)
14469 * since the comparison produces a pure-rv gate that's lifted into
14470 * a synthesised provsql column on the single result row. Run only
14471 * the qual migration + targetList splice and return; everything
14472 * else this function does (column mapping, set-ops, aggregation
14473 * rewriting, ...) assumes a non-empty rtable. */
14474 List *rv_cmps = migrate_probabilistic_quals(constants, q);
14475 if (rv_cmps != NIL) {
14476 Expr *provenance;
14477 RangeTblEntry *values_rte;
14478 RangeTblRef *rtr;
14479 Var *v;
14480
14481 if (list_length(rv_cmps) == 1) {
14482 provenance = (Expr *)linitial(rv_cmps);
14483 } else {
14484 /* Multiple rv conjuncts: combine via provenance_times. */
14485 FuncExpr *times = makeNode(FuncExpr);
14486 ArrayExpr *array = makeNode(ArrayExpr);
14487 times->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
14488 times->funcresulttype = constants->OID_TYPE_UUID;
14489 times->funcvariadic = true;
14490 times->location = -1;
14491 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
14492 array->element_typeid = constants->OID_TYPE_UUID;
14493 array->elements = rv_cmps;
14494 array->location = -1;
14495 times->args = list_make1(array);
14496 provenance = (Expr *)times;
14497 }
14498
14499 /* Bind the lifted expression to a single evaluation by wrapping
14500 * it in a synthesized FROM (VALUES (<expr>)) AS _prov_(provsql).
14501 * Without this, multiple references to the same provenance
14502 * expression in the outer targetList (the user's provenance()
14503 * call, plus the auto-added provsql column) each re-invoke any
14504 * rv constructor inside, producing distinct UUIDs per call
14505 * because uniform / normal / ... mint a fresh leaf gate each
14506 * time. Wrapping in VALUES gives one evaluation site that all
14507 * outer references read from. */
14508 values_rte = makeNode(RangeTblEntry);
14509 values_rte->rtekind = RTE_VALUES;
14510 values_rte->values_lists = list_make1(list_make1(provenance));
14511 values_rte->coltypes = list_make1_oid(constants->OID_TYPE_UUID);
14512 values_rte->coltypmods = list_make1_int(-1);
14513 values_rte->colcollations = list_make1_oid(InvalidOid);
14514 values_rte->eref = makeAlias(
14515 "_prov_",
14516 list_make1(makeString(pstrdup(PROVSQL_COLUMN_NAME))));
14517 values_rte->inh = false;
14518 values_rte->inFromCl = true;
14519#if PG_VERSION_NUM < 160000
14520 values_rte->requiredPerms = 0;
14521#endif
14522 q->rtable = list_make1(values_rte);
14523
14524 rtr = makeNode(RangeTblRef);
14525 rtr->rtindex = 1;
14526 if (q->jointree == NULL) {
14527 q->jointree = makeNode(FromExpr);
14528 }
14529 q->jointree->fromlist = list_make1(rtr);
14530
14531 v = makeVar(1, 1, constants->OID_TYPE_UUID, -1, InvalidOid, 0);
14532
14533 /* Substitute any provenance() FuncExpr in the targetList with
14534 * a reference to the bound expression. */
14535 replace_provenance_function_by_expression(constants, q, (Expr *)v);
14536
14537 /* Append a provsql column reading the same Var so callers that
14538 * expect the auto-added column find it. */
14539 {
14540 TargetEntry *te = makeTargetEntry(
14541 (Expr *)copyObject(v),
14542 list_length(q->targetList) + 1,
14543 pstrdup(PROVSQL_COLUMN_NAME),
14544 false);
14545 q->targetList = lappend(q->targetList, te);
14546 }
14547 }
14548 return q;
14549 }
14550
14551 /* Normalise SELECT DISTINCT into the equivalent GROUP BY *before*
14552 * inlining: the recursive-reachability aggregation detectors
14553 * (detect_reach_aggregations / detect_reach_conjunctions, run inside
14554 * inline_ctes) key on groupClause, and a DISTINCT aggregation is
14555 * provenance-identical to its GROUP BY twin -- normalising here lets
14556 * them recognise it with no DISTINCT-specific arm. Idempotent with
14557 * the late site below. */
14558 if (provsql_active)
14560
14561 /* Inline non-recursive CTE references as subqueries so we can track
14562 * provenance through them. Must happen before set operation handling
14563 * since UNION/EXCEPT branches may reference CTEs. Gated on
14564 * provsql.active: when provenance tracking is off the hook must stand
14565 * back and let the query plan as ordinary SQL -- it must not, in
14566 * particular, drive the recursive-CTE fixpoint (eval_recursive), which
14567 * runs SPI and creates temp tables at plan time. */
14568 if (provsql_active)
14569 inline_ctes(constants, q);
14570
14571 /* Decorrelate a top-level scalar subquery into a LEFT JOIN + choose() +
14572 * GROUP BY + count<=1 HAVING. Runs before lower_outer_joins so the LEFT JOIN
14573 * it produces is lowered with correct outer-join provenance, and before the
14574 * "Subqueries not supported" guard further down. */
14575 if (provsql_active) {
14576 rewrite_array_sublinks(constants, q);
14578 rewrite_uncorrelated_antijoin(constants, q);
14579 rewrite_predicate_sublinks(constants, q);
14582 decorrelate_scalar_sublinks(constants, q);
14583 }
14584
14585 /* Lower a top-level outer JOIN (LEFT / RIGHT / FULL) of two base relations
14586 * into the UNION-ALL of its matched and null-padded antijoin arms, so the
14587 * non-monotone outer-join provenance (the 0-match world) is captured. No-op
14588 * on every other shape. Runs before provenance discovery / set-op handling
14589 * so the constructed UNION / EXCEPT subqueries are processed by the recursive
14590 * passes. */
14591 if (provsql_active)
14592 lower_outer_joins(constants, q);
14593
14594 {
14595 Bitmapset *removed_sortgrouprefs = NULL;
14596
14597 if (q->targetList) {
14598 removed_sortgrouprefs =
14599 remove_provenance_attributes_select(constants, q, removed);
14600 if (removed_sortgrouprefs != NULL)
14601 remove_provenance_attribute_groupref(q, removed_sortgrouprefs);
14602 if (q->setOperations)
14604 }
14605 }
14606
14607 /* Whole-tuple output conditioning: strip any given(...) marker now (before
14608 * the aggregation / set-operation restructuring below, which would
14609 * renumber the Vars in the captured per-row evidence), and condition this
14610 * query's output provenance on each captured evidence at splice time. The
14611 * marker is meaningful only for a per-row projection: an aggregated /
14612 * grouped / set-operation / DISTINCT query has no single output row to
14613 * condition, so reject it with a clear message rather than silently
14614 * conditioning an aggregate. */
14615 if (provsql_active && q->targetList) {
14616 given_evidence = strip_given_markers(constants, q);
14617 if (given_evidence != NIL &&
14618 (q->hasAggs || q->groupClause || q->groupingSets || q->havingQual ||
14619 q->distinctClause || q->setOperations || q->hasWindowFuncs))
14621 "provsql.given (whole-tuple output conditioning) is supported only in "
14622 "a plain per-row SELECT, not in an aggregated / grouped / DISTINCT / "
14623 "set-operation query; condition the individual tokens with the binary "
14624 "| operator instead");
14625 }
14626
14627 if(provsql_active) {
14628 if (q->setOperations) {
14629 // TODO: Nest set operations as subqueries in FROM,
14630 // so that we only do set operations on base tables
14631
14632 SetOperationStmt *stmt = (SetOperationStmt *)q->setOperations;
14633 if (!stmt->all) {
14634 /* Check if any branch has aggregates – non-ALL set operations
14635 * on aggregate results are not supported because agg_token
14636 * lacks comparison operators for deduplication */
14637 ListCell *lc_rte;
14638 foreach (lc_rte, q->rtable) {
14639 RangeTblEntry *rte = (RangeTblEntry *)lfirst(lc_rte);
14640 if (rte->rtekind == RTE_SUBQUERY && rte->subquery &&
14641 rte->subquery->hasAggs)
14642 provsql_error("Non-ALL set operations (UNION, EXCEPT) on "
14643 "aggregate results not supported");
14644 }
14646 return process_query(constants, q, removed, wrap_root, top_level,
14647 in_boolean_rewrite, inv_ctx);
14648 }
14649 }
14650
14651 if (q->hasAggs) {
14652 Query *rewritten = rewrite_agg_distinct(q, constants);
14653 if (rewritten)
14654 return process_query(constants, rewritten, removed, wrap_root, top_level,
14655 in_boolean_rewrite, inv_ctx);
14656 }
14657
14658 /* An IS [NOT] NULL on a subquery's aggregate has to be evaluated in the
14659 * level that owns the aggregate; move it there before that subquery is
14660 * rewritten. */
14661 {
14662 Query *rewritten = push_agg_nulltest_into_subquery(q, constants);
14663 if (rewritten)
14664 return process_query(constants, rewritten, removed, wrap_root,
14665 top_level, in_boolean_rewrite, inv_ctx);
14666 }
14667
14668 /* Rewrite any JOIN on an agg_token column before provenance
14669 * discovery, so get_provenance_attributes sees the already-correct
14670 * subquery with a proper provsql column. */
14671 {
14672 Index rteid;
14673 AttrNumber join_attno;
14674
14675 if (join_qual_has_agg_token((Node *)q->jointree, constants, &rteid,
14676 &join_attno))
14677 {
14678 Query *rewritten = rewrite_join_agg_token(q, constants, rteid, join_attno);
14679 if (rewritten)
14680 return process_query(constants, rewritten, removed, wrap_root,
14681 top_level, in_boolean_rewrite, inv_ctx);
14682 }
14683 }
14684
14685 /* Opt-in safe-query optimisation slot: when on, try to rewrite
14686 * hierarchical conjunctive queries to a read-once form whose
14687 * probability is computable in linear time via independent
14688 * evaluation. See try_safe_query_rewrite().
14689 *
14690 * The rewriter is gated on the presence of the assume_boolean()
14691 * helper (installed by the 1.6.0 upgrade script). Without it we
14692 * cannot wrap the per-row root in a gate_assumed, which is
14693 * what downstream evaluators inspect to refuse unsound evaluation,
14694 * so we refuse to rewrite on schemas that still predate the
14695 * helper. */
14696 if (provsql_boolean_provenance && inv_ctx == NULL &&
14697 OidIsValid(constants->OID_FUNCTION_ASSUME_BOOLEAN)) {
14698 /* Read-once rewrite (an operation-mode change: it rewrites the query and
14699 * changes the produced circuit), so it is gated on boolean_provenance.
14700 * Skipped when @c inv_ctx is supplied: this query is an inlined subquery
14701 * whose base inputs must receive the parent's transparent order markers
14702 * (a no-op rewrite for the single-base projection it then is), not a
14703 * circuit-changing read-once rewrite that would bypass them. */
14704 Query *rewritten = try_safe_query_rewrite(constants, q);
14705 if (rewritten)
14706 /* The whole rewritten subtree is a boolean safe-query rewrite: flag it
14707 * so the joint-width recogniser defers to it everywhere below (the
14708 * signal would otherwise be lost at the subquery boundary). */
14709 return process_query(constants, rewritten, removed, true, top_level,
14710 true, inv_ctx);
14711
14712 }
14713
14714 /* Inversion-free analysis is *not* an operation-mode change: it leaves the
14715 * lineage intact and only attaches a transparent certificate + per-input
14716 * order markers, read back at probability evaluation. So it is decoupled
14717 * from boolean_provenance and gated on its own knob (provsql.inversion_free,
14718 * default on), run on THIS query – the one whose lineage we build – so the
14719 * certificate and markers align with the lineage by construction. Only at
14720 * the outermost (top-level) root the user evaluates; never when the
14721 * read-once rewrite above already fired (that path returns early). */
14722 if (inv_ctx != NULL) {
14723 /* This query is an inlined subquery: the parent's flattened analysis
14724 * already produced our base-atom markers. Apply them as-is; attach no
14725 * certificate here (the cert lives on the parent's per-row root). */
14726 local_inv_ctx = inv_ctx;
14727 } else if (top_level && provsql_inversion_free
14728 && OidIsValid(constants->OID_FUNCTION_ANNOTATE)) {
14729 /* Build the inversion-free marker context tree. The detector runs on a
14730 * flattened copy (single-base SPJ subqueries / views inlined to their
14731 * base relation in place; on PG 18 the synthetic RTE_GROUP is stripped),
14732 * so the certificate and per-input order markers align with the lineage
14733 * by construction; the original q is left intact (only transparent
14734 * markers + a root certificate are added, read back at probability
14735 * evaluation). The evaluator's size-bounded mismatch backstop declines
14736 * if any marker fails to land on its input. */
14737 local_inv_ctx = build_inversion_free_ctx(constants, q, &inv_cert);
14738 /* When q is not a single hierarchical CQ but a deduplicating UNION of
14739 * inversion-free arms (lowered to a GROUP BY over an inner UNION ALL, its
14740 * per-group root the provenance_plus OR), certify the whole UCQ jointly:
14741 * the recipe goes on the plus root and the cross-branch order markers are
14742 * threaded into the inner arms. */
14743 if (local_inv_ctx == NULL)
14744 local_inv_ctx = build_inversion_free_union_ctx(constants, q, &inv_cert);
14745 }
14746
14747 /* Set difference (EXCEPT / EXCEPT ALL): group the right arm so the per-row
14748 * right provenances ⊕-combine before the monus, giving the paper's NOT-IN
14749 * semantics α ⊖ ⊕β. Must run before get_provenance_attributes processes
14750 * the arms. */
14751 group_set_difference_right_arm(constants, q);
14752
14753 /* Every rewrite that can absorb an outer join has run; what survives
14754 * with a tracked null-padded side would be silently mis-tracked as an
14755 * inner join. */
14756 if (q->jointree)
14757 check_unlowered_outer_joins(constants, q, (Node *)q->jointree);
14758
14759 // get_provenance_attributes will also recursively process subqueries
14760 // by calling process_query (threading each subquery's marker sub-context)
14761 prov_atts = get_provenance_attributes(constants, q, in_boolean_rewrite,
14762 top_level, local_inv_ctx);
14763
14764 /* Inversion-free path: wrap each certified atom's provenance token in its
14765 * per-input order marker. prov_atts are base-relation Vars (the certified
14766 * class has only RTE_RELATION atoms and no agg/distinct/set-op restructuring,
14767 * so each Var's varno is still the atom's range-table index). */
14768 if (local_inv_ctx != NULL && local_inv_ctx->markers != NULL)
14769 wrap_inversion_free_markers(constants, q, prov_atts,
14770 local_inv_ctx->markers, local_inv_ctx->natoms);
14771
14772 if (prov_atts == NIL) {
14773 /* If the WHERE clause contains a random_variable comparison, we
14774 * still need to take the rewriting path so the result tuple
14775 * carries the comparator's gate_cmp UUID as its provenance.
14776 * Synthesize a single gate_one() prov_att; the combination
14777 * provenance_times(one, rv_cmp) collapses to rv_cmp downstream
14778 * because gate_one is the multiplicative identity. */
14779 if (q->jointree && q->jointree->quals &&
14780 expr_contains_rv_cmp(q->jointree->quals, constants)) {
14781 FuncExpr *one_expr = makeNode(FuncExpr);
14782 one_expr->funcid = constants->OID_FUNCTION_GATE_ONE;
14783 one_expr->funcresulttype = constants->OID_TYPE_UUID;
14784 one_expr->args = NIL;
14785 one_expr->location = -1;
14786 prov_atts = list_make1(one_expr);
14787 } else {
14788 return q;
14789 }
14790 }
14791
14792 if (q->hasSubLinks && query_has_tracked_sublink(constants, q)) {
14793 /* Only sublinks over a provenance-tracked relation are unsupported; one
14794 * whose body touches no tracked relation is a deterministic filter/value
14795 * and is left for Postgres to evaluate (the row keeps R's provenance).
14796 *
14797 * Of the tracked ones, a scalar subquery nested inside a larger expression
14798 * (arithmetic, a function argument) is not decorrelatable by the current
14799 * rewrites, but rather than rejecting it we let it through with a warning:
14800 * Postgres evaluates the sublink (the value is correct), the row keeps the
14801 * outer relation's provenance, and the subquery's data is treated as
14802 * certain. A tracked sublink still in a direct position (a GROUP BY body, a
14803 * multi-relation EXISTS…) is a genuinely unsupported form and still errors. */
14804 bool has_direct = false;
14805 List *nested = classify_remaining_sublinks(constants, q, &has_direct);
14806 if (has_direct || nested == NIL) {
14807 provsql_error("Subqueries (EXISTS, IN, scalar subquery) not supported");
14808 supported = false;
14809 } else {
14811 "scalar subquery nested in an expression is not tracked; its data is "
14812 "treated as certain and the result keeps only the outer provenance");
14813 }
14814 }
14815
14816 /* Normally already normalised before inline_ctes; this late call
14817 * catches any DISTINCT introduced by the intervening rewrites (set
14818 * operations, sublink decorrelation) and is a no-op otherwise. */
14819 if (supported && q->distinctClause)
14821
14822 if (supported && q->setOperations) {
14823 SetOperationStmt *stmt = (SetOperationStmt *)q->setOperations;
14824
14825 if (stmt->op == SETOP_UNION) {
14826 process_set_operation_union(constants, stmt, q);
14827 has_union = true;
14828 } else if (stmt->op == SETOP_EXCEPT) {
14829 if (!transform_except_into_join(constants, q))
14830 supported = false;
14831 has_difference = true;
14832 } else {
14833 provsql_error("Set operations other than UNION and EXCEPT not "
14834 "supported");
14835 supported = false;
14836 }
14837 }
14838
14839 if (supported && q->groupClause &&
14840 !provenance_function_in_group_by(constants, q)) {
14841 group_by_rewrite = true;
14842 }
14843
14844 if (supported && q->groupingSets) {
14845 if (q->groupClause || list_length(q->groupingSets) > 1 ||
14846 ((GroupingSet *)linitial(q->groupingSets))->kind !=
14847 GROUPING_SET_EMPTY) {
14848 provsql_error("GROUPING SETS, CUBE, and ROLLUP not supported");
14849 supported = false;
14850 } else {
14851 // Simple GROUP BY ()
14852 group_by_rewrite = true;
14853 }
14854 }
14855
14856 if (supported) {
14857 /* Sized here, after every rewrite that can grow q->rtable (scalar-
14858 * subquery decorrelation, ARRAY() lowering, outer-join lowering,
14859 * EXCEPT / set-operation transforms…). Sizing it at the top of the
14860 * provsql_active block under-allocated once those rewrites added RTEs,
14861 * and build_column_map then wrote past the end of the array. */
14862 columns_len = q->rtable->length;
14863 columns = (int **)palloc0(columns_len * sizeof(int *));
14864 build_column_map(q, columns, &nbcols);
14865 }
14866
14867 if (supported) {
14868 Expr *provenance;
14869 List *rv_cmps;
14870
14871 /* Window functions are not supported: their per-row result has no
14872 * aggregate-provenance semantics. The query still executes and each
14873 * output row carries its input row's tuple provenance, but the
14874 * windowed computation itself (e.g. SUM() OVER ...) is an opaque
14875 * scalar, not an agg_token. Warn once per rewritten query level that
14876 * actually involves provenance-tracked relations. */
14877 if (q->hasWindowFuncs)
14878 provsql_warning("window functions are not supported; provenance is "
14879 "tracked per input row only, and the windowed "
14880 "computation is treated as an opaque scalar");
14881
14882 /* Single unified pass over WHERE: each top-level conjunct is
14883 * routed to the right evaluation site (HAVING for agg_token,
14884 * the returned rv_cmps list for random_variable, left in WHERE
14885 * otherwise). Mixed shapes raise a clear error. See the
14886 * qual_class doc above for the routing matrix.
14887 *
14888 * Must run before replace_aggregations_by_provenance_aggregate
14889 * so the lifted RV cmps factor into each row's contribution to
14890 * any surrounding agg_token: otherwise the cmp lands at group
14891 * level with row-typed Vars the executor cannot resolve, or
14892 * gets discarded by the HAVING-replaces-result branch of
14893 * make_provenance_expression.
14894 *
14895 * Skipped for SR_PLUS / SR_MONUS (UNION / EXCEPT outer level):
14896 * each branch is rewritten by its own recursive process_query
14897 * call, so an outer-level WHERE on RV here is exotic; the
14898 * fallback after make_provenance_expression handles it. */
14899 rv_cmps = migrate_probabilistic_quals(constants, q);
14900 if (rv_cmps != NIL && !has_union && !has_difference) {
14901 prov_atts = list_concat(prov_atts, rv_cmps);
14902 rv_cmps = NIL;
14903 }
14904
14905 if (q->hasAggs) {
14906 ListCell *lc_sort;
14907
14908 // Compute aggregation expressions
14910 constants, q, prov_atts,
14911 has_union ? SR_PLUS : (has_difference ? SR_MONUS : SR_TIMES));
14912
14913 /* Lower a searched CASE whose branches are aggregates into an
14914 * agg_case gate_case, now that the branch aggregates are agg_tokens
14915 * (the guards compare agg_tokens, which having_Expr_to_provenance_cmp
14916 * lowers exactly as it does a HAVING comparison). Must run here, after
14917 * the aggregate pass and before insert_agg_token_casts, so the result
14918 * stays an agg_token instead of being cast to numeric (which would
14919 * discard the provenance and corrupt the CASE). */
14920 rewrite_agg_cases(constants, q);
14921
14922 // If there are any sort clauses on something whose type is now
14923 // aggregate token, we throw an error: sorting aggregation values
14924 // when provenance is captured is ill-defined
14925 foreach (lc_sort, q->sortClause) {
14926 SortGroupClause *sort = (SortGroupClause *)lfirst(lc_sort);
14927 ListCell *lc_te;
14928 foreach (lc_te, q->targetList) {
14929 TargetEntry *te = (TargetEntry *)lfirst(lc_te);
14930 if (sort->tleSortGroupRef == te->ressortgroupref) {
14931 if (exprType((Node *)te->expr) == constants->OID_TYPE_AGG_TOKEN)
14932 provsql_error("ORDER BY on the result of an aggregate function is "
14933 "not supported");
14934 break;
14935 }
14936 }
14937 }
14938 }
14939
14940 /* Insert casts for agg_token Vars used in arithmetic or window
14941 * functions, now that WHERE-to-HAVING migration is done */
14942 insert_agg_token_casts(constants, q);
14943
14945 constants, q, prov_atts, q->hasAggs, group_by_rewrite,
14946 has_union ? SR_PLUS : (has_difference ? SR_MONUS : SR_TIMES), columns,
14947 nbcols, wrap_root, in_boolean_rewrite, inv_cert);
14948
14949 /* Fallback for the rare set-op outer WHERE case: conjoin via
14950 * provenance_times after the aggregation wrappers. Correct only
14951 * when no aggregation collapses rows above this point. */
14952 if (rv_cmps != NIL) {
14953 FuncExpr *times = makeNode(FuncExpr);
14954 ArrayExpr *array = makeNode(ArrayExpr);
14955 times->funcid = constants->OID_FUNCTION_PROVENANCE_TIMES;
14956 times->funcresulttype = constants->OID_TYPE_UUID;
14957 times->funcvariadic = true;
14958 times->location = -1;
14959 array->array_typeid = constants->OID_TYPE_UUID_ARRAY;
14960 array->element_typeid = constants->OID_TYPE_UUID;
14961 array->elements = lcons(provenance, rv_cmps);
14962 array->location = -1;
14963 times->args = list_make1(array);
14964 provenance = (Expr *)times;
14965 }
14966
14967 /* Whole-tuple output conditioning: wrap the per-row provenance in
14968 * cond(row_provenance, evidence) for each captured given(...) marker.
14969 * Done before add_to_select / replace_provenance_function_by_expression
14970 * so the auto-added provsql column AND any user-side provenance() call
14971 * uniformly carry the conditioning (mirrors the assume_boolean wrap).
14972 * Multiple markers accumulate as a conjunction of evidence (cond folds
14973 * (X|A)|B = X|(A∧B)). */
14974 if (given_evidence != NIL && OidIsValid(constants->OID_FUNCTION_COND)) {
14975 ListCell *lc_ev;
14976 foreach (lc_ev, given_evidence)
14977 provenance = wrap_in_cond(constants, provenance, (Expr *)lfirst(lc_ev));
14978 }
14979
14982
14983 if (has_difference)
14984 add_select_non_zero(constants, q, provenance);
14985 }
14986
14987 /* columns is NULL when the query was not supported (build_column_map
14988 * never ran); columns_len is its allocation-time length, in case a later
14989 * step grew q->rtable again. */
14990 for (i = 0; columns != NULL && i < (unsigned)columns_len; ++i) {
14991 if (columns[i])
14992 pfree(columns[i]);
14993 }
14994 }
14995
14996 if (provsql_verbose >= 50)
14997 elog_node_display(NOTICE, "ProvSQL: After query rewriting", q, true);
14998
14999 return q;
15000}
15001
15002/* -------------------------------------------------------------------------
15003 * INSERT ... SELECT provenance propagation
15004 * ------------------------------------------------------------------------- */
15005
15006/** @brief Walker context for @c collect_source_var_types. */
15007typedef struct {
15008 Index src_rteid; /**< Range-table index of the source subquery. */
15009 int natts; /**< Length of the @c types / @c typmods arrays. */
15010 Oid *types; /**< Expected column type, indexed by varattno - 1. */
15011 int32 *typmods; /**< Matching typmod, indexed by varattno - 1. */
15013
15014/**
15015 * @brief Walker: record the column types the INSERT expects from its source.
15016 *
15017 * Every @c Var of the INSERT's target list that references the source
15018 * subquery carries the type parse analysis resolved for that output column,
15019 * i.e. the type the target column was matched against. Collecting them by
15020 * attribute number gives @c restore_insert_source_types the contract the
15021 * rewritten subquery has to keep.
15022 */
15023static bool collect_source_var_types(Node *node, void *cx) {
15025 if (node == NULL)
15026 return false;
15027 if (IsA(node, Var)) {
15028 Var *v = (Var *)node;
15029 if (v->varno == ctx->src_rteid && v->varlevelsup == 0 &&
15030 v->varattno >= 1 && v->varattno <= ctx->natts) {
15031 ctx->types[v->varattno - 1] = v->vartype;
15032 ctx->typmods[v->varattno - 1] = v->vartypmod;
15033 }
15034 return false;
15035 }
15036 return expression_tree_walker(node, collect_source_var_types, cx);
15037}
15038
15039/**
15040 * @brief Coerce the rewritten source SELECT back to the types the INSERT expects.
15041 *
15042 * The aggregate-provenance rewrite retypes an aggregate over a tracked
15043 * relation to @c agg_token, but an INSERT's target row type was fixed by parse
15044 * analysis long before the planner hook ran, so the two stages disagree and the
15045 * executor rejects the row ("table row type and query-specified row type do not
15046 * match"). The rewrite is still what we want -- for a provenance-tracked
15047 * target it is what fills the @c provsql column -- so rather than suppressing
15048 * it we cast each retyped output column back to its declared type through the
15049 * assignment casts @c agg_token exposes (@c bigint, @c integer, @c numeric,
15050 * @c double @c precision, @c text), which extract the aggregate's running
15051 * value. The provenance the cast drops is exactly the provenance the target
15052 * column cannot store.
15053 *
15054 * A no-op whenever the rewrite left the column types alone, which is the
15055 * common (non-aggregate) case. A column with no reachable cast is left as it
15056 * is, so the executor's own error still surfaces.
15057 */
15058static void restore_insert_source_types(Query *q, Index src_rteid,
15059 Query *subquery) {
15060 src_var_type_ctx ctx;
15061 ListCell *lc;
15062 int i;
15063
15064 ctx.src_rteid = src_rteid;
15065 ctx.natts = list_length(subquery->targetList);
15066 if (ctx.natts == 0)
15067 return;
15068 ctx.types = (Oid *)palloc0(sizeof(Oid) * ctx.natts);
15069 ctx.typmods = (int32 *)palloc(sizeof(int32) * ctx.natts);
15070 for (i = 0; i < ctx.natts; ++i)
15071 ctx.typmods[i] = -1;
15072
15073 collect_source_var_types((Node *)q->targetList, &ctx);
15074
15075 foreach (lc, subquery->targetList) {
15076 TargetEntry *te = (TargetEntry *)lfirst(lc);
15077 Oid want, have;
15078 Node *coerced;
15079
15080 if (te->resjunk || te->resno < 1 || te->resno > ctx.natts)
15081 continue;
15082 want = ctx.types[te->resno - 1];
15083 if (!OidIsValid(want))
15084 continue; /* no Var refers to it (e.g. the added provsql) */
15085 have = exprType((Node *)te->expr);
15086 if (have == want)
15087 continue;
15088 coerced = coerce_to_target_type(NULL, (Node *)te->expr, have, want,
15089 ctx.typmods[te->resno - 1],
15090 COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST,
15091 -1);
15092 if (coerced != NULL)
15093 te->expr = (Expr *)coerced;
15094 }
15095
15096 pfree(ctx.types);
15097 pfree(ctx.typmods);
15098}
15099
15100/**
15101 * @brief Propagate provenance through INSERT ... SELECT.
15102 *
15103 * If the source SELECT involves provenance-tracked tables and the target
15104 * table has a provsql column, rewrites the source SELECT to carry
15105 * provenance and maps its provsql output to the target's provsql column,
15106 * replacing the default uuid_generate_v4().
15107 *
15108 * If the target has no provsql column, emits a warning instead.
15109 */
15110static void process_insert_select(const constants_t *constants, Query *q) {
15111 ListCell *lc;
15112 Index src_rteid = 0;
15113 RangeTblEntry *src_rte = NULL;
15114 RangeTblEntry *tgt_rte;
15115 AttrNumber provsql_attno = 0;
15116 TargetEntry *provsql_te = NULL;
15117 bool provsql_te_is_new = false;
15118
15119 /* Find the source SELECT subquery with provenance */
15120 foreach (lc, q->rtable) {
15121 RangeTblEntry *r = (RangeTblEntry *)lfirst(lc);
15122 ++src_rteid;
15123 if (r->rtekind == RTE_SUBQUERY && r->subquery &&
15124 has_provenance(constants, r->subquery)) {
15125 src_rte = r;
15126 break;
15127 }
15128 }
15129
15130 if (src_rte == NULL)
15131 return;
15132
15133 /* Rewrite the source SELECT so its own provenance semantics -- HAVING
15134 * lifting, provenance() resolution -- take effect. This must run whether or
15135 * not the target table is provenance-tracked: the old code returned early
15136 * (warning, below) when the target had no provsql column, which left the
15137 * SELECT's HAVING on the physical rows and provenance() unresolved, so the
15138 * INSERT saw zero rows. */
15139 {
15140 bool *removed = NULL;
15141 Query *new_subquery =
15142 process_query(constants, src_rte->subquery, &removed, false, false, false,
15143 NULL);
15144 if (new_subquery == NULL)
15145 return;
15146 src_rte->subquery = new_subquery;
15147 }
15148
15149 /* The rewrite may have retyped an output column (an aggregate becomes an
15150 * agg_token); the INSERT's target row type is already fixed, so cast back. */
15151 restore_insert_source_types(q, src_rteid, src_rte->subquery);
15152
15153 /* Check if the target table has a provsql column */
15154 tgt_rte = list_nth_node(RangeTblEntry, q->rtable, q->resultRelation - 1);
15155 if (tgt_rte->rtekind == RTE_RELATION) {
15156 AttrNumber attid = 1;
15157 foreach (lc, tgt_rte->eref->colnames) {
15158 if (!strcmp(strVal(lfirst(lc)), PROVSQL_COLUMN_NAME) &&
15159 get_atttype(tgt_rte->relid, attid) == constants->OID_TYPE_UUID)
15160 provsql_attno = attid;
15161 ++attid;
15162 }
15163 }
15164
15165 if (provsql_attno == 0) {
15166 /* The target cannot store provenance. The source SELECT was rewritten
15167 * above (so it returns the right rows), but its auto-added provsql column
15168 * has no target column to land in -- drop it so the INSERT's column
15169 * mapping stays consistent, and warn that provenance is not propagated. */
15170 remove_provsql_from_select(src_rte->subquery);
15171 provsql_warning("INSERT ... SELECT on provenance-tracked "
15172 "tables: source provenance is not propagated "
15173 "to inserted rows");
15174 return;
15175 }
15176
15177 /* Find the provsql target entry and verify it's a UUID default */
15178 foreach (lc, q->targetList) {
15179 TargetEntry *te = (TargetEntry *)lfirst(lc);
15180 if (te->resno == provsql_attno &&
15181 exprType((Node *)te->expr) == constants->OID_TYPE_UUID) {
15182 provsql_te = te;
15183 break;
15184 }
15185 }
15186
15187 if (provsql_te == NULL) {
15188 /* The target's provsql column is not in the INSERT's targetList
15189 * (no DEFAULT on the column since 1.6.0; the user did not name
15190 * the column either). Synthesise a TE so we have something to
15191 * substitute the source provsql Var into below. */
15192 provsql_te = makeNode(TargetEntry);
15193 provsql_te->resno = provsql_attno;
15194 provsql_te->resname = pstrdup(PROVSQL_COLUMN_NAME);
15195 provsql_te_is_new = true;
15196 }
15197
15198 /* Map the source's provsql column into the target's provsql column. */
15199 {
15200 AttrNumber src_provsql_attno = 0;
15201
15202 foreach (lc, src_rte->subquery->targetList) {
15203 TargetEntry *te = (TargetEntry *)lfirst(lc);
15204 if (te->resname && !strcmp(te->resname, PROVSQL_COLUMN_NAME) &&
15205 exprType((Node *)te->expr) == constants->OID_TYPE_UUID) {
15206 src_provsql_attno = te->resno;
15207 break;
15208 }
15209 }
15210
15211 if (src_provsql_attno == 0)
15212 return;
15213
15214 /* Replace the target's provsql default with a Var from the source */
15215 {
15216 Var *v = makeNode(Var);
15217 v->varno = src_rteid;
15218 v->varattno = src_provsql_attno;
15219 v->vartype = constants->OID_TYPE_UUID;
15220 v->vartypmod = -1;
15221 v->varcollid = InvalidOid;
15222 v->location = -1;
15223 provsql_te->expr = (Expr *)v;
15224 }
15225
15226 /* Now that its expr is set, splice a freshly synthesised provsql
15227 * target entry into the INSERT's targetList. */
15228 if (provsql_te_is_new)
15229 q->targetList = lappend(q->targetList, provsql_te);
15230
15231 /* Update the subquery RTE's column names to include provsql */
15232 src_rte->eref->colnames = lappend(src_rte->eref->colnames,
15233 makeString(pstrdup(PROVSQL_COLUMN_NAME)));
15234 }
15235}
15236
15237/* -------------------------------------------------------------------------
15238 * Planner hook & extension lifecycle
15239 * ------------------------------------------------------------------------- */
15240
15241/**
15242 * @brief Walker: true if any @c Query in the tree defines a @c provsql column
15243 * by hand.
15244 *
15245 * A non-junk target entry resnamed @c provsql whose expression is not a
15246 * legitimate uuid-typed @c Var (the passthrough of a tracked relation's
15247 * provsql column, which @c remove_provenance_attributes_select strips) is a
15248 * hand-made provenance column -- e.g. @c "provenance() AS provsql" or
15249 * @c "expr AS provsql". It collides with the provenance column ProvSQL adds
15250 * itself: the output column count desyncs and a later @c Var mis-binds to a
15251 * non-uuid column, crashing @c get_gate_type when it dereferences the value as
15252 * a pointer.
15253 *
15254 * Run once on the user's ORIGINAL query in the planner hook, before any
15255 * rewriting, so the intermediate queries ProvSQL builds (which legitimately
15256 * carry a provsql column) are never visited.
15257 */
15258static bool query_defines_handmade_provsql(Node *node, void *cx) {
15259 const constants_t *constants = (const constants_t *)cx;
15260 if (node == NULL)
15261 return false;
15262 if (IsA(node, Query)) {
15263 Query *q = (Query *)node;
15264 ListCell *lc;
15265 foreach (lc, q->targetList) {
15266 TargetEntry *te = (TargetEntry *)lfirst(lc);
15267 if (te->resjunk || te->resname == NULL ||
15268 strcmp(te->resname, PROVSQL_COLUMN_NAME))
15269 continue;
15270 if (IsA(te->expr, Var) &&
15271 ((Var *)te->expr)->vartype == constants->OID_TYPE_UUID)
15272 continue; /* legitimate passthrough of a real provsql column */
15273 return true;
15274 }
15275 return query_tree_walker(q, query_defines_handmade_provsql, cx, 0);
15276 }
15277 return expression_tree_walker(node, query_defines_handmade_provsql, cx);
15278}
15279
15280/** @brief Executor nesting depth.
15281 *
15282 * Tracks how deep we are inside @c Executor invocations. Incremented
15283 * in @c provsql_executor_start, decremented in @c provsql_executor_end.
15284 * The classifier @c NOTICE only fires when this is zero, which
15285 * corresponds to the user's outermost statement being planned (before
15286 * any executor entry). Plans built for PL/pgSQL function bodies that
15287 * the rewriter inserts -- @c provenance_times, @c provenance_plus,
15288 * @c provenance_aggregate, ... -- happen during execution of the
15289 * user's plan, so they see depth >= 1 and skip the NOTICE. */
15291
15292/**
15293 * @brief PostgreSQL planner hook – entry point for provenance rewriting.
15294 *
15295 * Replaces (or chains after) the standard planner. For every CMD_SELECT
15296 * that involves at least one provenance-bearing relation or an explicit
15297 * @c provenance() call, rewrites the query via @c process_query before
15298 * handing the result to the standard planner. Non-SELECT commands and
15299 * queries without provenance are passed through unchanged.
15300 * @param q The query to plan.
15301 * @param cursorOptions Cursor options bitmask.
15302 * @param boundParams Pre-bound parameter values.
15303 * @return The planned statement.
15304 */
15305static PlannedStmt *provsql_planner(Query *q,
15306#if PG_VERSION_NUM >= 130000
15307 const char *query_string,
15308#endif
15309 int cursorOptions,
15310 ParamListInfo boundParams
15311#if PG_VERSION_NUM >= 190000
15312 , ExplainState *es
15313#endif
15314 ) {
15315 /* Scope the inert-fetch record to this rewrite (re-entrant: nested
15316 * planner invocations save and restore their own). */
15317 List *saved_inert_subselects = provsql_inert_subselects;
15319
15320 if (q->commandType == CMD_INSERT && q->rtable && provsql_active) {
15321 const constants_t constants = get_constants(false);
15322 if (constants.ok) {
15323 if (provenance_in_sublink_walker((Node *)q, (void *)&constants))
15324 provsql_error("a subquery over a provenance-tracked relation cannot be "
15325 "used as a scalar subquery / IN / EXISTS expression; put "
15326 "it in the FROM clause instead");
15327 rewrite_dml_rv_surface(&constants, q);
15328 process_insert_select(&constants, q);
15329 }
15330 } else if (q->commandType == CMD_UPDATE && q->rtable && provsql_active) {
15331 /* An UPDATE gets no provenance rewriting (data-modification tracking is
15332 * done by the statement triggers), but its SET expressions are a value
15333 * position like an INSERT's, so the RV surface there needs the same
15334 * lowering. */
15335 const constants_t constants = get_constants(false);
15336 if (constants.ok)
15337 rewrite_dml_rv_surface(&constants, q);
15338 } else if (q->commandType == CMD_SELECT) {
15339 /* No rtable check here: a FROM-less SELECT (e.g.
15340 * SELECT 1 WHERE normal(0,1) > 2)
15341 * still needs the hook to engage when the WHERE contains an
15342 * rv_cmp. has_provenance walks the tree and returns false fast
15343 * on FROM-less queries that have neither rv_cmp nor provenance(),
15344 * so widening the gate costs nothing in the common case. */
15345 const constants_t constants = get_constants(false);
15346
15347 /* A subquery over a provenance-tracked relation used in an expression
15348 * context (scalar subquery / IN / EXISTS) is not supported -- and would
15349 * otherwise slip past has_provenance() (which does not descend into
15350 * SubLinks) and leave provenance() to fail at runtime. Flag it clearly. */
15351 if (provsql_active && constants.ok &&
15352 provenance_in_sublink_walker((Node *)q, (void *)&constants))
15353 provsql_error("a subquery over a provenance-tracked relation cannot be "
15354 "used as a scalar subquery / IN / EXISTS expression; put "
15355 "it in the FROM clause instead");
15356
15357 /* Query-time TID / BID / OPAQUE classifier. Emits a NOTICE for
15358 * the user's outermost SELECT when the GUC is on. Runs on the
15359 * user's original Query before any provsql rewriting so the
15360 * reported kind reflects the SQL the user wrote. Gating on
15361 * @c provsql_executor_depth @c == @c 0 skips the spurious extra
15362 * planning calls triggered by PL/pgSQL function bodies the
15363 * rewriter inserts (@c provenance_times, ...), whose internal
15364 * SELECTs go through the planner hook during execution of the
15365 * user's plan. */
15366 if (provsql_executor_depth == 0
15368 && q->rtable != NIL) {
15370 provsql_classify_query(q, &cls);
15372 list_free(cls.source_relids);
15373 }
15374
15375 if (constants.ok && has_provenance(&constants, q)) {
15376 bool *removed = NULL;
15377 Query *new_query;
15378 clock_t begin = 0;
15379
15380 /* A user query may not define its own provsql column by hand; ProvSQL
15381 * manages the provenance column itself. Checked here, once, on the
15382 * original query before any rewriting -- so the intermediate queries the
15383 * rewriter builds (which legitimately carry a provsql column) are not
15384 * flagged. */
15385 if (provsql_active &&
15386 query_defines_handmade_provsql((Node *)q, (void *)&constants))
15387 provsql_error("a query may not define a column named \"%s\" by hand; "
15388 "ProvSQL manages the provenance column itself",
15390
15391#if PG_VERSION_NUM >= 150000
15392 if (provsql_verbose >= 20)
15393 provsql_notice("Main query before query rewriting:\n%s\n",
15394 pg_get_querydef(q, true));
15395#endif
15396
15397 if (provsql_verbose >= 40)
15398 begin = clock();
15399
15400 new_query = process_query(&constants, q, &removed, false, true, false,
15401 NULL);
15402
15403 if (provsql_verbose >= 40)
15404 provsql_notice("planner time spent=%f",
15405 (double)(clock() - begin) / CLOCKS_PER_SEC);
15406
15407 if (new_query != NULL)
15408 q = new_query;
15409
15410#if PG_VERSION_NUM >= 150000
15411 if (provsql_verbose >= 20)
15412 provsql_notice("Main query after query rewriting:\n%s\n",
15413 pg_get_querydef(q, true));
15414#endif
15415 }
15416 }
15417
15418 provsql_inert_subselects = saved_inert_subselects;
15419
15420 if (prev_planner)
15421 return prev_planner(q,
15422#if PG_VERSION_NUM >= 130000
15423 query_string,
15424#endif
15425 cursorOptions, boundParams
15426#if PG_VERSION_NUM >= 190000
15427 , es
15428#endif
15429 );
15430 else
15431 return standard_planner(q,
15432#if PG_VERSION_NUM >= 130000
15433 query_string,
15434#endif
15435 cursorOptions, boundParams
15436#if PG_VERSION_NUM >= 190000
15437 , es
15438#endif
15439 );
15440}
15441
15442/* -------------------------------------------------------------------------
15443 * Executor hooks (depth tracking only)
15444 *
15445 * We install ExecutorStart / ExecutorEnd hooks solely to maintain
15446 * @c provsql_executor_depth, which the classifier in @c provsql_planner
15447 * consults to distinguish the user's outermost statement from nested
15448 * PL/pgSQL bodies the rewriter calls into. No other behaviour changes.
15449 * ------------------------------------------------------------------------- */
15450static ExecutorStart_hook_type prev_ExecutorStart = NULL;
15451static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
15452
15453static void provsql_executor_start(QueryDesc *queryDesc, int eflags) {
15455 PG_TRY();
15456 {
15458 prev_ExecutorStart(queryDesc, eflags);
15459 else
15460 standard_ExecutorStart(queryDesc, eflags);
15461 }
15462 PG_CATCH();
15463 {
15465 PG_RE_THROW();
15466 }
15467 PG_END_TRY();
15468}
15469
15470static void provsql_executor_end(QueryDesc *queryDesc) {
15471#if PG_VERSION_NUM >= 130000
15472 PG_TRY();
15473 {
15474 if (prev_ExecutorEnd)
15475 prev_ExecutorEnd(queryDesc);
15476 else
15477 standard_ExecutorEnd(queryDesc);
15478 }
15479 PG_FINALLY();
15480 {
15482 }
15483 PG_END_TRY();
15484#else
15485 /* PG < 13 lacks PG_FINALLY: emulate by running the cleanup on the
15486 * error path (via PG_CATCH + PG_RE_THROW) and on the success path
15487 * (after PG_END_TRY). Functionally equivalent. */
15488 PG_TRY();
15489 {
15490 if (prev_ExecutorEnd)
15491 prev_ExecutorEnd(queryDesc);
15492 else
15493 standard_ExecutorEnd(queryDesc);
15494 }
15495 PG_CATCH();
15496 {
15498 PG_RE_THROW();
15499 }
15500 PG_END_TRY();
15502#endif
15503}
15504
15505/* -------------------------------------------------------------------------
15506 * ProcessUtility hook: CTAS lineage inheritance.
15507 *
15508 * When a @c CREATE @c TABLE @c AS (or @c CREATE @c MATERIALIZED @c VIEW,
15509 * or @c SELECT @c INTO -- PG's parser transforms all three into
15510 * @c CreateTableAsStmt) projects a @c provsql column lifted verbatim
15511 * from a tracked source, the resulting relation's atoms are not freshly
15512 * minted UUIDs but lineage tokens of one or more base @c
15513 * add_provenance / @c repair_key relations. The hook intercepts the
15514 * utility statement, classifies the inner @c SELECT via
15515 * @c provsql_classify_query, lets PG run the CTAS, then populates
15516 * @c provsql_table_info (with the inherited @c kind / BID @c block_key)
15517 * and the ancestor registry (with the transitive union of source
15518 * ancestor sets) on the just-created relation. A @c provenance_guard
15519 * trigger is installed on the new table so any subsequent INSERT /
15520 * UPDATE that supplies a non-NULL @c provsql still flips the table to
15521 * OPAQUE the standard way.
15522 *
15523 * The hook deliberately fires only when the inner @c SELECT projects
15524 * a @c provsql column from a tracked source -- otherwise the new
15525 * relation has no @c provsql column and the lineage metadata would be
15526 * operationally pointless. Users who want a tracked CTAS-derived
15527 * table without inherited lineage still call @c add_provenance on it
15528 * afterwards (that path seeds @c {self} and overrides whatever this
15529 * hook may have recorded).
15530 * ------------------------------------------------------------------------- */
15531
15532static ProcessUtility_hook_type prev_ProcessUtility = NULL;
15533
15534/** @brief State captured by the pre-execution pass for the post-execution one. */
15535typedef struct ProvSQLCtasCapture {
15536 bool fire; ///< true when the post-pass should run
15537 Query *inner_query; ///< cloned for safety; freed by pfree on completion
15541 Oid source_relid; ///< Single source whose block_key we want to align (BID only)
15545
15546/**
15547 * @brief Decide whether @p parsetree is a CTAS that should trigger
15548 * the ancestry hook, and if so populate @p cap with the inner
15549 * classification, the (single) source's block-key columns, and
15550 * the transitive ancestor union.
15551 *
15552 * Fires only when the inner @c SELECT's target list projects a base-
15553 * level @c Var (possibly through @c RelabelType wrappers) that
15554 * resolves to the @c provsql column of an @c RTE_RELATION whose
15555 * metadata is non-OPAQUE. Anything else (no @c provsql in the
15556 * projection, classifier says OPAQUE, the projected source is itself
15557 * OPAQUE) leaves @c cap->fire false and the post-pass becomes a
15558 * no-op.
15559 */
15560static void provsql_ProcessUtility_capture(Node *parsetree,
15561 ProvSQLCtasCapture *cap) {
15562 CreateTableAsStmt *stmt;
15563 Query *qry;
15565 ListCell *lc;
15566 AttrNumber prov_resno = InvalidAttrNumber;
15567 Oid source_relid = InvalidOid;
15568 ProvenanceTableInfo source_info;
15569 Bitmapset *ancestor_bms = NULL;
15570 int bms_member;
15571 uint16 ancestor_n;
15572
15573 cap->fire = false;
15574 if (!provsql_active)
15575 return;
15576 if (parsetree == NULL || !IsA(parsetree, CreateTableAsStmt))
15577 return;
15578 stmt = (CreateTableAsStmt *) parsetree;
15579 if (stmt->query == NULL || !IsA(stmt->query, Query))
15580 return;
15581 qry = (Query *) stmt->query;
15582 if (qry->commandType != CMD_SELECT)
15583 return;
15584
15585 provsql_classify_query(qry, &cls);
15586 if (cls.kind == PROVSQL_TABLE_OPAQUE) {
15587 list_free(cls.source_relids);
15588 return;
15589 }
15590
15591 /* Walk the inner target list for a TLE whose Var resolves to the
15592 * provsql column of a tracked, non-OPAQUE source. First match wins
15593 * (CTAS preserves the TLE's column name verbatim in the new
15594 * table, so a single provsql TLE is the normal case). */
15595 foreach (lc, qry->targetList) {
15596 TargetEntry *te = (TargetEntry *) lfirst(lc);
15597 Node *e = (Node *) te->expr;
15598 Var *v;
15599 RangeTblEntry *rte;
15600 AttrNumber prov_attno;
15601
15602 if (te->resjunk)
15603 continue;
15604 while (e != NULL && IsA(e, RelabelType))
15605 e = (Node *) ((RelabelType *) e)->arg;
15606 if (e == NULL || !IsA(e, Var))
15607 continue;
15608 v = (Var *) e;
15609 if (v->varlevelsup != 0)
15610 continue;
15611 if (v->varno < 1 || (int) v->varno > list_length(qry->rtable))
15612 continue;
15613 rte = (RangeTblEntry *) list_nth(qry->rtable, v->varno - 1);
15614 if (rte->rtekind != RTE_RELATION)
15615 continue;
15616 prov_attno = get_attnum(rte->relid, PROVSQL_COLUMN_NAME);
15617 if (prov_attno == InvalidAttrNumber || v->varattno != prov_attno)
15618 continue;
15619 if (!provsql_lookup_table_info(rte->relid, &source_info))
15620 continue;
15621 if (source_info.kind == PROVSQL_TABLE_OPAQUE)
15622 continue;
15623 prov_resno = te->resno;
15624 source_relid = rte->relid;
15625 break;
15626 }
15627 if (prov_resno == InvalidAttrNumber) {
15628 list_free(cls.source_relids);
15629 return;
15630 }
15631
15632 /* Transitive ancestor union: lookup each classifier-reported source's
15633 * registered ancestry, fall back to {source} when none recorded
15634 * (defensive: the SQL add_provenance / repair_key seed should always
15635 * give us a non-empty set). Bitmapset dedupes; we then walk it in
15636 * ascending order to get the sorted Oid array the registry stores. */
15637 foreach (lc, cls.source_relids) {
15638 Oid src_relid = lfirst_oid(lc);
15639 uint16 src_n;
15640 Oid src_ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS];
15641 if (provsql_lookup_ancestry(src_relid, &src_n, src_ancestors)) {
15642 for (uint16 i = 0; i < src_n; ++i)
15643 ancestor_bms = bms_add_member(ancestor_bms, (int) src_ancestors[i]);
15644 } else {
15645 ancestor_bms = bms_add_member(ancestor_bms, (int) src_relid);
15646 }
15647 }
15648 list_free(cls.source_relids);
15649
15650 ancestor_n = 0;
15651 bms_member = -1;
15652 while ((bms_member = bms_next_member(ancestor_bms, bms_member)) >= 0) {
15653 if (ancestor_n >= PROVSQL_TABLE_INFO_MAX_ANCESTORS) {
15654 /* Cap exceeded: refuse to fire rather than truncate the ancestor
15655 * set silently (a partial set would let the safe-query
15656 * disjointness check accept a join that shouldn't be safe). */
15657 bms_free(ancestor_bms);
15658 return;
15659 }
15660 cap->ancestors[ancestor_n++] = (Oid) bms_member;
15661 }
15662 bms_free(ancestor_bms);
15663
15664 cap->fire = true;
15665 cap->inner_query = qry;
15667 cap->ancestor_n = ancestor_n;
15668 cap->source_relid = source_relid;
15669 cap->source_block_key_n = source_info.block_key_n;
15670 memcpy(cap->source_block_key, source_info.block_key,
15671 source_info.block_key_n * sizeof(AttrNumber));
15672}
15673
15674/** @brief Map @c provsql_table_kind to its textual label
15675 * (@c set_table_info accepts text). */
15677 switch (k) {
15678 case PROVSQL_TABLE_TID: return "tid";
15679 case PROVSQL_TABLE_BID: return "bid";
15680 case PROVSQL_TABLE_OPAQUE: return "opaque";
15681 }
15682 return "opaque";
15683}
15684
15685/** @brief Forward declaration of the C SQL entry points. */
15686extern Datum set_table_info(PG_FUNCTION_ARGS);
15687extern Datum set_ancestors(PG_FUNCTION_ARGS);
15688
15689/**
15690 * @brief Apply @p cap to the freshly-created relation @c stmt->into->rel.
15691 *
15692 * For BID sources: walks the inner query's target list to align each
15693 * source block-key column to its output @c resno. If any block-key
15694 * column is missing from the projection (the CTAS dropped it), the
15695 * new relation cannot honour the BID invariant under that column --
15696 * the hook demotes to TID rather than asserting a now-stale block
15697 * key.
15698 *
15699 * Installs @c provenance_guard via SPI so subsequent INSERT /
15700 * UPDATE OF provsql on the new relation flip its kind to OPAQUE
15701 * through the standard guard path.
15702 */
15703static void provsql_ProcessUtility_apply(Node *parsetree,
15704 ProvSQLCtasCapture *cap) {
15705 CreateTableAsStmt *stmt;
15706 Oid new_relid;
15707 AttrNumber prov_attno;
15708 provsql_table_kind eff_kind;
15709 uint16 eff_block_key_n = 0;
15710 AttrNumber eff_block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY];
15711 Datum kind_datum;
15712 Datum block_key_datum;
15713 Datum ancestors_datum;
15714 Datum *block_key_elems;
15715 Datum *ancestor_elems;
15716 ArrayType *block_key_arr;
15717 ArrayType *ancestors_arr;
15718 const char *nspname;
15719 const char *relname;
15720 StringInfoData trigger_sql;
15721
15722 if (!cap->fire)
15723 return;
15724 stmt = (CreateTableAsStmt *) parsetree;
15725
15726 new_relid = RangeVarGetRelid(stmt->into->rel, NoLock, true);
15727 if (new_relid == InvalidOid)
15728 return;
15729
15730 /* Confirm the new relation actually has a @c provsql @c uuid column.
15731 * CTAS preserves TLE column names, so this is essentially the
15732 * post-execution verification of what the pre-pass already
15733 * required. */
15734 prov_attno = get_attnum(new_relid, PROVSQL_COLUMN_NAME);
15735 if (prov_attno == InvalidAttrNumber)
15736 return;
15737 if (get_atttype(new_relid, prov_attno) != UUIDOID)
15738 return;
15739
15740 /* BID block-key alignment: each source block-key column must
15741 * survive in the inner-query target list. When all do, the new
15742 * relation's effective block key is the corresponding output
15743 * resno. When any is missing, demote to TID (a partial block
15744 * key would falsely advertise mutual exclusion the rows no
15745 * longer have). */
15746 eff_kind = cap->inherited_kind;
15747 if (eff_kind == PROVSQL_TABLE_BID) {
15748 bool ok = true;
15749 for (uint16 i = 0; i < cap->source_block_key_n; ++i) {
15750 AttrNumber src_attno = cap->source_block_key[i];
15751 ListCell *lc;
15752 bool found = false;
15753 foreach (lc, cap->inner_query->targetList) {
15754 TargetEntry *te = (TargetEntry *) lfirst(lc);
15755 Node *e = (Node *) te->expr;
15756 Var *v;
15757 RangeTblEntry *rte;
15758 if (te->resjunk)
15759 continue;
15760 while (e != NULL && IsA(e, RelabelType))
15761 e = (Node *) ((RelabelType *) e)->arg;
15762 if (e == NULL || !IsA(e, Var))
15763 continue;
15764 v = (Var *) e;
15765 if (v->varlevelsup != 0)
15766 continue;
15767 if (v->varno < 1
15768 || (int) v->varno > list_length(cap->inner_query->rtable))
15769 continue;
15770 rte = (RangeTblEntry *)
15771 list_nth(cap->inner_query->rtable, v->varno - 1);
15772 if (rte->rtekind != RTE_RELATION)
15773 continue;
15774 if (rte->relid == cap->source_relid
15775 && v->varattno == src_attno) {
15776 if (eff_block_key_n >= PROVSQL_TABLE_INFO_MAX_BLOCK_KEY) {
15777 ok = false;
15778 break;
15779 }
15780 eff_block_key[eff_block_key_n++] = te->resno;
15781 found = true;
15782 break;
15783 }
15784 }
15785 if (!found) {
15786 ok = false;
15787 break;
15788 }
15789 }
15790 if (!ok) {
15791 eff_kind = PROVSQL_TABLE_TID;
15792 eff_block_key_n = 0;
15793 }
15794 }
15795
15796 /* Marshal arguments and invoke the SQL-level helpers via
15797 * DirectFunctionCall: this reaches the worker through the same IPC
15798 * path the user-facing SQL functions use, including the relcache
15799 * invalidation broadcast on the way out. */
15800 kind_datum = CStringGetTextDatum(provsql_ctas_kind_label(eff_kind));
15801 if (eff_block_key_n == 0) {
15802 block_key_arr = construct_empty_array(INT2OID);
15803 } else {
15804 block_key_elems = palloc(eff_block_key_n * sizeof(Datum));
15805 for (uint16 i = 0; i < eff_block_key_n; ++i)
15806 block_key_elems[i] = Int16GetDatum(eff_block_key[i]);
15807 block_key_arr = construct_array(block_key_elems, eff_block_key_n,
15808 INT2OID, 2, true, 's');
15809 pfree(block_key_elems);
15810 }
15811 block_key_datum = PointerGetDatum(block_key_arr);
15812 DirectFunctionCall3(set_table_info,
15813 ObjectIdGetDatum(new_relid),
15814 kind_datum,
15815 block_key_datum);
15816
15817 if (cap->ancestor_n == 0) {
15818 ancestors_arr = construct_empty_array(OIDOID);
15819 } else {
15820 ancestor_elems = palloc(cap->ancestor_n * sizeof(Datum));
15821 for (uint16 i = 0; i < cap->ancestor_n; ++i)
15822 ancestor_elems[i] = ObjectIdGetDatum(cap->ancestors[i]);
15823 ancestors_arr = construct_array(ancestor_elems, cap->ancestor_n,
15824 OIDOID, sizeof(Oid), true, 'i');
15825 pfree(ancestor_elems);
15826 }
15827 ancestors_datum = PointerGetDatum(ancestors_arr);
15828 DirectFunctionCall2(set_ancestors,
15829 ObjectIdGetDatum(new_relid),
15830 ancestors_datum);
15831
15832 /* Install the provenance_guard trigger via SPI. Users who later
15833 * INSERT / UPDATE OF provsql with a non-NULL value will then
15834 * trigger the standard kind flip to OPAQUE; users who omit the
15835 * column on INSERT get a fresh @c uuid_generate_v4 leaf (which
15836 * already disconnects the row from the inherited lineage, but the
15837 * guard prevents the more dangerous shared-UUID aliasing path).
15838 *
15839 * Materialized views are exempt: PG forbids triggers on them, and
15840 * they cannot be modified through DML anyway (only @c REFRESH @c
15841 * MATERIALIZED @c VIEW changes the contents -- which re-runs the
15842 * inner SELECT and the freshly-projected rows continue to carry
15843 * lineage from the same sources). */
15844 /* PG 14 renamed CreateTableAsStmt.relkind -> objtype (same ObjectType,
15845 * same OBJECT_* values; pure field rename). */
15846#if PG_VERSION_NUM >= 140000
15847 if (stmt->objtype == OBJECT_MATVIEW)
15848#else
15849 if (stmt->relkind == OBJECT_MATVIEW)
15850#endif
15851 return;
15852
15853 nspname = get_namespace_name(get_rel_namespace(new_relid));
15854 relname = get_rel_name(new_relid);
15855 if (nspname == NULL || relname == NULL)
15856 return;
15857 initStringInfo(&trigger_sql);
15858 appendStringInfo(&trigger_sql,
15859 "CREATE TRIGGER provenance_guard "
15860 "BEFORE INSERT OR UPDATE OF provsql ON %s.%s "
15861 /* "EXECUTE PROCEDURE" is the legacy form, kept as a valid synonym
15862 * of "EXECUTE FUNCTION" through PG 18 -- matches the rest of the
15863 * codebase and stays PG 10-compatible. Promote when PG 10 drops
15864 * out of the support floor. */
15865 "FOR EACH ROW EXECUTE PROCEDURE provsql.provenance_guard()",
15866 quote_identifier(nspname), quote_identifier(relname));
15867 if (SPI_connect() != SPI_OK_CONNECT)
15868 provsql_error("CTAS lineage hook: SPI_connect failed");
15869 if (SPI_exec(trigger_sql.data, 0) != SPI_OK_UTILITY)
15870 provsql_error("CTAS lineage hook: failed to install provenance_guard "
15871 "on %s.%s", nspname, relname);
15872 SPI_finish();
15873 pfree(trigger_sql.data);
15874}
15875
15877 PlannedStmt *pstmt,
15878 const char *queryString,
15879#if PG_VERSION_NUM >= 140000
15880 bool readOnlyTree,
15881#endif
15882 ProcessUtilityContext context,
15883 ParamListInfo params,
15884 QueryEnvironment *queryEnv,
15885 DestReceiver *dest,
15886#if PG_VERSION_NUM >= 130000
15887 QueryCompletion *qc
15888#else
15889 char *completionTag
15890#endif
15891 ) {
15892 Node *parsetree = pstmt ? pstmt->utilityStmt : NULL;
15893 ProvSQLCtasCapture cap = {0};
15894
15895 provsql_ProcessUtility_capture(parsetree, &cap);
15896
15898 prev_ProcessUtility(pstmt, queryString,
15899#if PG_VERSION_NUM >= 140000
15900 readOnlyTree,
15901#endif
15902 context, params, queryEnv, dest,
15903#if PG_VERSION_NUM >= 130000
15904 qc
15905#else
15906 completionTag
15907#endif
15908 );
15909 else
15910 standard_ProcessUtility(pstmt, queryString,
15911#if PG_VERSION_NUM >= 140000
15912 readOnlyTree,
15913#endif
15914 context, params, queryEnv, dest,
15915#if PG_VERSION_NUM >= 130000
15916 qc
15917#else
15918 completionTag
15919#endif
15920 );
15921
15922 provsql_ProcessUtility_apply(parsetree, &cap);
15923}
15924
15925/**
15926 * @brief Extension initialization – called once when the shared library is loaded.
15927 *
15928 * Registers the GUC variables (@c provsql.active, @c where_provenance,
15929 * @c update_provenance, @c verbose_level, @c aggtoken_text_as_uuid,
15930 * @c tool_search_path), installs the planner hook and shared-memory hooks,
15931 * and launches the background MMap worker.
15932 *
15933 * Must be loaded via @c shared_preload_libraries; raises an error otherwise.
15934 */
15935void _PG_init(void) {
15936#ifndef PROVSQL_INPROCESS_STORE
15937 /* The multi-process build registers background workers and a shared
15938 memory segment, which only works when loaded at postmaster start via
15939 shared_preload_libraries. The single-process build has neither: the
15940 planner hook is installed here at CREATE EXTENSION / dlopen time and
15941 covers every subsequent query in the one backend, so no preload (and
15942 no shared_preload_libraries, which the WASM host does not support) is
15943 required. */
15944 if (!process_shared_preload_libraries_in_progress)
15945 provsql_error("provsql needs to be added to the shared_preload_libraries "
15946 "configuration variable");
15947#endif
15948
15949 DefineCustomBoolVariable("provsql.active",
15950 "Should ProvSQL track provenance?",
15951 "1 is standard ProvSQL behavior, 0 means provsql attributes will be dropped.",
15953 true,
15954 PGC_USERSET,
15955 0,
15956 NULL,
15957 NULL,
15958 NULL);
15959 DefineCustomEnumVariable("provsql.provenance",
15960 "Provenance class tracked and assumed by the rewriter.",
15961 "Declares, for the session, the most specific "
15962 "class of provenance semantics the circuits "
15963 "must remain faithful for; constructions are "
15964 "licensed accordingly, from the most general "
15965 "to the most specialised: 'where' adds "
15966 "where-provenance tracking (equality and "
15967 "projection gates) on top of universal "
15968 "semiring provenance; 'semiring' (the "
15969 "default) tracks universal semiring "
15970 "provenance; 'absorptive' additionally lets "
15971 "recursive queries on cyclic data stop at "
15972 "the absorptive value fixpoint, tagging "
15973 "their tokens so non-absorptive semirings "
15974 "refuse them; 'boolean' (which implies "
15975 "'absorptive') additionally enables the "
15976 "Boolean-only machinery -- the safe-query "
15977 "read-once rewrite, the bounded-treewidth "
15978 "reachability route, Boolean circuit "
15979 "simplifications -- whose outputs only "
15980 "preserve the Boolean function of the "
15981 "lineage and are tagged as such.",
15985 PGC_USERSET,
15986 0,
15987 NULL,
15989 NULL);
15990 DefineCustomBoolVariable("provsql.update_provenance",
15991 "Should ProvSQL track update provenance?",
15992 "1 turns update provenance on, 0 off.",
15994 false,
15995 PGC_USERSET,
15996 0,
15997 NULL,
15998 NULL,
15999 NULL);
16000 DefineCustomBoolVariable("provsql.aggtoken_text_as_uuid",
16001 "Output agg_token cells as the underlying UUID "
16002 "instead of \"value (*)\".",
16003 "Off by default for psql-friendly output. UI "
16004 "layers (notably ProvSQL Studio) flip this on "
16005 "per session so aggregate cells expose the "
16006 "circuit root UUID for click-through; the "
16007 "display value is recovered via "
16008 "provsql.agg_token_value_text(uuid).",
16010 false,
16011 PGC_USERSET,
16012 0,
16013 NULL,
16014 NULL,
16015 NULL);
16016 DefineCustomIntVariable("provsql.verbose_level",
16017 "Level of verbosity for ProvSQL informational and debug messages",
16018 "0 for quiet (default), 1-9 for informational messages, 10-100 for debug information.",
16020 0,
16021 0,
16022 100,
16023 PGC_USERSET,
16024 1,
16025 NULL,
16026 NULL,
16027 NULL);
16028 DefineCustomStringVariable("provsql.tool_search_path",
16029 "Directories prepended to PATH when ProvSQL spawns external tools (superuser-only).",
16030 "Colon-separated list of directories searched before the server's PATH "
16031 "when locating d4, c2d, minic2d, dsharp, weightmc, or graph-easy. "
16032 "Empty (default) means rely on the server's PATH alone. "
16033 "Restricted to superusers (PGC_SUSET): it controls which directories the "
16034 "postgres OS user searches for executables, so a non-privileged role must "
16035 "not be able to redirect it to an attacker-controlled binary.",
16037 "",
16038 PGC_SUSET,
16039 0,
16040 NULL,
16041 NULL,
16042 NULL);
16043 DefineCustomStringVariable("provsql.fallback_compiler",
16044 "Compiler used by makeDD's final fallback when both "
16045 "interpretAsDD and tree-decomposition fail.",
16046 "Name of the external compiler invoked by "
16047 "BooleanCircuit::makeDD after interpretAsDD raises "
16048 "(non-independent or non-NNF circuit) and the "
16049 "tree-decomposition builder raises (treewidth above "
16050 "the supported bound). Accepts any value supported "
16051 "by BooleanCircuit::compilation: d4, d4v2, c2d, "
16052 "minic2d, dsharp, panini-obdd, panini-obdd-and, "
16053 "panini-decdnnf. Default: d4.",
16055 "d4",
16056 PGC_USERSET,
16057 0,
16058 NULL,
16059 NULL,
16060 NULL);
16061 DefineCustomStringVariable("provsql.kcmcp_server",
16062 "Launch command for the managed KCMCP knowledge-compiler server.",
16063 "Shell command the supervisor background worker runs to start a "
16064 "warm KCMCP server (see the KC server protocol). The literal "
16065 "{endpoint} is replaced by a Unix-socket path the worker picks "
16066 "and publishes for the in-extension client to reach (a registry "
16067 "record of kind 'kcmcp' with endpoint 'managed' uses it). {endpoint} "
16068 "already carries the scheme (e.g. unix:/path). Empty (default) "
16069 "launches no server. Example: 'tdkc --kcmcp {endpoint}'. "
16070 "PGC_SIGHUP (config file / ALTER SYSTEM + reload): it runs an "
16071 "arbitrary command as the postgres OS user, so like "
16072 "provsql.tool_search_path it is not settable per session.",
16074 "",
16075 PGC_SIGHUP,
16076 0,
16077 NULL,
16078 NULL,
16079 NULL);
16080 DefineCustomStringVariable("provsql.last_eval_method",
16081 "Probability evaluation method(s) used by the most "
16082 "recent probability_evaluate call.",
16083 "Set automatically after each probability_evaluate "
16084 "call to the method that produced the result "
16085 "(comma-separated and deduplicated across calls in "
16086 "the session). Useful to see which strategy the "
16087 "default auto-selection settled on.",
16089 "",
16090 PGC_USERSET,
16091 0,
16092 NULL,
16093 NULL,
16094 NULL);
16095 DefineCustomBoolVariable("provsql.simplify_on_load",
16096 "Apply universal cmp-resolution passes when "
16097 "loading a provenance circuit.",
16098 "When on (default), every GenericCircuit returned "
16099 "by getGenericCircuit goes through RangeCheck "
16100 "(and any future universal pass): comparators "
16101 "decidable to certain Boolean values become "
16102 "Bernoulli gate_input gates with probability 0 "
16103 "or 1, transparent to every downstream consumer "
16104 "(semiring evaluators, MC, view_circuit, PROV "
16105 "export). Set off to inspect raw circuit "
16106 "structure (e.g. when debugging gate-creation "
16107 "paths).",
16109 true,
16110 PGC_USERSET,
16111 0,
16112 NULL,
16113 NULL,
16114 NULL);
16115 /* Debug-only: hidden from SHOW ALL and postgresql.conf.sample.
16116 * On is strictly better for end users (analytic answers where
16117 * possible, lower MC variance, more methods usable on continuous
16118 * circuits); off only serves developer A/B against pure MC and as
16119 * a bisection escape valve if a closure rule misbehaves. */
16120 DefineCustomBoolVariable("provsql.hybrid_evaluation",
16121 "Run the hybrid-evaluator simplifier and "
16122 "island decomposer inside probability_evaluate. "
16123 "Debug only.",
16124 "When on (default), probability_evaluate runs "
16125 "the HybridEvaluator peephole simplifier "
16126 "between RangeCheck and AnalyticEvaluator and "
16127 "the per-cmp MC island decomposer after "
16128 "AnalyticEvaluator. Off bypasses both and lets "
16129 "unresolved comparators fall through to "
16130 "whole-circuit MC. End users have no reason "
16131 "to flip this; it exists for developer A/B "
16132 "testing against the unfolded path and as a "
16133 "bisection knob if a closure rule turns out "
16134 "to be unsound on some workload.",
16136 true,
16137 PGC_USERSET,
16138 GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
16139 NULL,
16140 NULL,
16141 NULL);
16142 /* Debug-only: hidden from SHOW ALL and postgresql.conf.sample.
16143 * Umbrella for closed-form / analytic resolution of gate_cmp
16144 * probabilities in probability_evaluate (currently the
16145 * Poisson-binomial HAVING-COUNT pre-pass; future MIN / MAX / SUM
16146 * pre-passes gate on the same flag). On is strictly better for
16147 * end users (each resolver replaces an exponential DNF
16148 * construction with O(N) or O(N x C) arithmetic); off only serves
16149 * developer A/B against the unoptimised enumerate_valid_worlds
16150 * path and as a bisection escape valve. */
16151 DefineCustomBoolVariable("provsql.cmp_probability_evaluation",
16152 "Run closed-form / analytic probability "
16153 "evaluators for gate_cmps inside "
16154 "probability_evaluate. Debug only.",
16155 "When on (default), probability_evaluate "
16156 "runs pre-passes that recognise specific "
16157 "gate_cmp shapes (currently HAVING COUNT(*) "
16158 "op C over distinct gate_input leaves) and "
16159 "replace each cmp with a Bernoulli "
16160 "gate_input carrying the closed-form "
16161 "probability, bypassing the DNF that "
16162 "provsql_having's enumerate_valid_worlds "
16163 "would otherwise emit. Off forces the cmp "
16164 "to fall through to that enumeration path. "
16165 "Future MIN / MAX / SUM probability "
16166 "evaluators will gate on the same flag. "
16167 "End users have no reason to flip this; it "
16168 "exists for developer A/B testing and as a "
16169 "bisection escape valve.",
16171 true,
16172 PGC_USERSET,
16173 GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
16174 NULL,
16175 NULL,
16176 NULL);
16177 /* Kill-switch for the automatic inversion-free path in the default
16178 * probability chain. The path only fires when the query carries an
16179 * inversion-free certificate (attached by the planner only to certified
16180 * queries), so it is self-gating and safe on by default; off is for A/B
16181 * testing against the tree-decomposition / d4 fallback. The explicit
16182 * 'inversion-free' method bypasses this flag. */
16183 DefineCustomBoolVariable("provsql.inversion_free",
16184 "Use the inversion-free structured-d-DNNF "
16185 "probability path when available.",
16186 "When on (default), probability_evaluate, on a "
16187 "query whose provenance root carries an "
16188 "inversion-free tractability certificate, tries the "
16189 "structured-d-DNNF builder after the read-once "
16190 "independent evaluator and before the "
16191 "tree-decomposition / external-compiler fallback. "
16192 "Off disables only this automatic insertion; the "
16193 "explicit probability_evaluate(token, "
16194 "'inversion-free') method always runs and errors "
16195 "without a certificate. The path is gated on the "
16196 "certificate, attached only to certified queries, "
16197 "so on is safe; off serves developer A/B testing.",
16199 true,
16200 PGC_USERSET,
16201 GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
16202 NULL,
16203 NULL,
16204 NULL);
16205 DefineCustomBoolVariable("provsql.classify_top_level",
16206 "Emit a NOTICE classifying each top-level SELECT.",
16207 "When on, every top-level SELECT that "
16208 "touches a relation triggers a NOTICE of "
16209 "the form `ProvSQL: query result is "
16210 "<KIND> (sources: ...)` where <KIND> is "
16211 "TID, BID, or OPAQUE under the existing "
16212 "provsql_table_kind taxonomy and the "
16213 "sources list names the provenance-"
16214 "tracked base relations the query touches. "
16215 "Read-only : the classifier does not "
16216 "rewrite the query. Studio reads the "
16217 "NOTICE to label query results with their "
16218 "certified kind.",
16220 false,
16221 PGC_USERSET,
16222 0,
16223 NULL,
16224 NULL,
16225 NULL);
16226 DefineCustomIntVariable("provsql.monte_carlo_seed",
16227 "Seed for the Monte Carlo sampler.",
16228 "-1 (default) seeds from std::random_device for "
16229 "non-deterministic sampling. Any other value "
16230 "(including 0) is used as a literal seed for "
16231 "std::mt19937_64, making "
16232 "probability_evaluate(..., 'monte-carlo', n) "
16233 "reproducible across runs and across the Bernoulli "
16234 "and continuous (gate_rv) sampling paths.",
16236 -1,
16237 -1,
16238 INT_MAX,
16239 PGC_USERSET,
16240 0,
16241 NULL,
16242 NULL,
16243 NULL);
16244 DefineCustomIntVariable("provsql.rv_mc_samples",
16245 "Default sample count for analytical-evaluator MC fallbacks.",
16246 "Used when an analytical evaluator (Expectation, "
16247 "future hybrid evaluator, etc.) cannot decompose a "
16248 "sub-circuit and needs to fall back to Monte Carlo. "
16249 "Default 10000. Set to 0 to disable the fallback "
16250 "entirely: callers raise an exception rather than "
16251 "sampling, which is useful when only analytical "
16252 "answers are acceptable. Unrelated to "
16253 "probability_evaluate(..., 'monte-carlo', n) where "
16254 "the sample count is an explicit argument.",
16256 10000,
16257 0,
16258 INT_MAX,
16259 PGC_USERSET,
16260 0,
16261 NULL,
16262 NULL,
16263 NULL);
16264
16265 DefineCustomRealVariable("provsql.ess_warn_fraction",
16266 "Effective-sample-size warning threshold for likelihood weighting.",
16267 "Latent-variable posterior inference draws latents from "
16268 "the prior and weights them by the observed leaves' "
16269 "densities. When the posterior effective sample size "
16270 "(Sum(w)^2 / Sum(w^2)) falls below this fraction of the "
16271 "accepted draws, a warning is emitted: the weights are "
16272 "degenerating (raise provsql.rv_mc_samples, or the model "
16273 "has many observations per latent). Default 0.1; set to 0 "
16274 "to silence the warning.",
16276 0.1,
16277 0.0,
16278 1.0,
16279 PGC_USERSET,
16280 0,
16281 NULL,
16282 NULL,
16283 NULL);
16284
16285 DefineCustomIntVariable("provsql.dtree_max_subproblems",
16286 "Hard cap on d-tree subproblems before it bails (0 = off).",
16287 "Debug / safety knob for the d-tree speculative-execution "
16288 "budget. The cost chooser already budgets the d-tree at the "
16289 "next-best method's estimated cost; when this is > 0 it adds "
16290 "a fixed hard cap on the number of d-tree subproblems, after "
16291 "which the method throws and the chooser escalates to the "
16292 "next method. 0 leaves only the automatic budget.",
16294 0,
16295 0,
16296 INT_MAX,
16297 PGC_USERSET,
16298 GUC_NO_SHOW_ALL,
16299 NULL,
16300 NULL,
16301 NULL);
16302
16303 DefineCustomIntVariable("provsql.joint_max_treewidth",
16304 "Maximum joint treewidth the joint-width UCQ "
16305 "compiler attempts.",
16306 "The joint-width UCQ compiler "
16307 "(ucq_joint_evaluate) evaluates an arbitrary UCQ -- "
16308 "including queries that are #P-hard under the "
16309 "Dalvi-Suciu dichotomy -- exactly, tractably when "
16310 "the joint treewidth of the data and its "
16311 "correlation structure is bounded. Above this "
16312 "bound the path declines (the degeneracy screen or "
16313 "the min-fill build raises), and the caller falls "
16314 "back to the standard probability ladder. Default "
16315 "10 (the tree-decomposition compiler's own cap).",
16317 10,
16318 0,
16319 10,
16320 PGC_USERSET,
16321 0,
16322 NULL,
16323 NULL,
16324 NULL);
16325 DefineCustomIntVariable("provsql.joint_max_states",
16326 "Per-bag DP state-count cap of the joint-width UCQ "
16327 "compiler.",
16328 "The joint-width UCQ compiler caps the number of "
16329 "dynamic-programming states at any decomposition "
16330 "node; exceeding it raises and the caller falls "
16331 "back to the ladder. This cap, not the static "
16332 "enumerating-variable count, is the true safety "
16333 "net: the realised state count is governed by data "
16334 "sparsity and the absorbing satisfied-state "
16335 "collapse, typically far below the a-priori bound. "
16336 "Default 65536.",
16338 65536,
16339 1,
16340 INT_MAX,
16341 PGC_USERSET,
16342 0,
16343 NULL,
16344 NULL,
16345 NULL);
16346
16347 DefineCustomBoolVariable("provsql.joint_width",
16348 "Recognise unsafe UCQs at planner time and route "
16349 "their existence provenance through the "
16350 "joint-width compiler (debug-only switch).",
16351 "On by default. When provsql.provenance = "
16352 "'boolean', a conjunctive query the safe-query "
16353 "rewriter declined -- an unsafe / #P-hard UCQ -- "
16354 "whose existence is being formed (DISTINCT / GROUP "
16355 "BY) is recognised and its provenance replaced by "
16356 "the joint-width compiler's certified d-D (exact "
16357 "and tractable when the joint treewidth of the data "
16358 "and its correlation structure is bounded). Turn "
16359 "off only to compare against the general lineage "
16360 "for debugging.",
16362 true,
16363 PGC_USERSET,
16364 GUC_NO_SHOW_ALL,
16365 NULL,
16366 NULL,
16367 NULL);
16368
16369 DefineCustomBoolVariable("provsql.mobius",
16370 "Try the safe-UCQ Möbius-inversion route before the "
16371 "joint-width compiler (debug-only switch).",
16372 "On by default. The last missing exact route of the "
16373 "Dalvi-Suciu dichotomy: a UCQ safe only because the "
16374 "#P-hard terms of its inclusion-exclusion expansion "
16375 "carry a zero Möbius value on the CNF lattice and "
16376 "cancel (canonical witness QW / q9). Shares the "
16377 "joint-width descriptor but has PRECEDENCE over it: a "
16378 "guaranteed-PTIME exact route for its class (TID, "
16379 "self-join-free, safe), it is tried first and "
16380 "short-circuits past the joint-width compiler on "
16381 "success. The joint-width compiler runs only on a "
16382 "Möbius decline (correlated inputs, self-joins, "
16383 "unsafe shape); on its decline too the normal "
16384 "provenance is the fallback, so the query never "
16385 "fails. Turn off only to compare against the "
16386 "joint-width / general lineage for debugging.",
16388 true,
16389 PGC_USERSET,
16390 GUC_NO_SHOW_ALL,
16391 NULL,
16392 NULL,
16393 NULL);
16394
16395 DefineCustomIntVariable("provsql.mobius_max_gates",
16396 "Data-cost cap of the safe-UCQ Möbius-inversion "
16397 "route.",
16398 "The Möbius route is PTIME in its class but its "
16399 "lifted-inference recursion is O(|D|^k) in the data, "
16400 "k the safe query's level (number of nested "
16401 "independent-projections) -- supra-linear, and the "
16402 "degree grows with the query. To keep it safe to "
16403 "leave on by default with precedence, it declines "
16404 "(falling through to the joint-width compiler / the "
16405 "ladder) once its compile has built more than this "
16406 "many gates, so a high-level safe query on large data "
16407 "never out-costs the general pipeline. Default "
16408 "4000000.",
16410 4000000,
16411 0,
16412 INT_MAX,
16413 PGC_USERSET,
16414 0,
16415 NULL,
16416 NULL,
16417 NULL);
16418
16419 DefineCustomIntVariable("provsql.mobius_max_cnf",
16420 "Query-cost cap of the safe-UCQ Möbius-inversion "
16421 "route.",
16422 "The route walks the inclusion-exclusion lattice of "
16423 "the CNF of each sentence it meets, which has 2^M "
16424 "elements for M conjuncts, and declines above this "
16425 "cap. The bound is on the QUERY, not the data: only "
16426 "a very large union, or the ranking / shattering "
16427 "normalisation of a self-join, pushes M up. "
16428 "Default 8; 0 disables the cap.",
16430 8,
16431 0,
16432 24,
16433 PGC_USERSET,
16434 0,
16435 NULL,
16436 NULL,
16437 NULL);
16438
16439 // Emit warnings for undeclared provsql.* configuration parameters
16440 EmitWarningsOnPlaceholders("provsql");
16441
16442 prev_planner = planner_hook;
16443 prev_shmem_startup = shmem_startup_hook;
16444 prev_ExecutorStart = ExecutorStart_hook;
16445 prev_ExecutorEnd = ExecutorEnd_hook;
16446 prev_ProcessUtility = ProcessUtility_hook;
16447#ifdef PROVSQL_INPROCESS_STORE
16448 /* Single-process store: no shared memory to request and no background
16449 worker; the circuit lives in this process behind an in-memory
16450 dispatch. */
16451 provsql_inproc_init();
16452#elif (PG_VERSION_NUM >= 150000)
16453 prev_shmem_request = shmem_request_hook;
16454 shmem_request_hook = provsql_shmem_request;
16455#else
16457#endif
16458
16459 planner_hook = provsql_planner;
16460#ifndef PROVSQL_INPROCESS_STORE
16461 shmem_startup_hook = provsql_shmem_startup;
16462#endif
16463 ExecutorStart_hook = provsql_executor_start;
16464 ExecutorEnd_hook = provsql_executor_end;
16465 ProcessUtility_hook = provsql_ProcessUtility;
16466
16467#ifndef PROVSQL_INPROCESS_STORE
16470#endif
16471}
16472
16473/**
16474 * @brief Extension teardown – restores the planner and shmem hooks.
16475 */
16476void _PG_fini(void) {
16477 planner_hook = prev_planner;
16478 shmem_startup_hook = prev_shmem_startup;
16479 ExecutorStart_hook = prev_ExecutorStart;
16480 ExecutorEnd_hook = prev_ExecutorEnd;
16481 ProcessUtility_hook = prev_ProcessUtility;
16482}
#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_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).
static FuncCandidateList FuncnameGetCandidatesCompat(List *names, int nargs, List *argnames, bool expand_variadic, bool expand_defaults, bool include_out_arguments, bool missing_ok)
Version-agnostic wrapper around FuncnameGetCandidates().
#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:107
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:6967
static void check_unlowered_outer_joins(const constants_t *constants, Query *q, Node *n)
Refuse outer joins that survived lower_outer_joins with a provenance-tracked relation on a null-padde...
Definition provsql.c:8964
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:2959
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:7704
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:7650
static bool contains_agg_walker(Node *node, contains_agg_ctx *ctx)
Definition provsql.c:3186
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:15676
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:6396
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:7033
static Node * rewrite_probability_event_mutator(Node *node, void *data)
Mutator: lift the RV surface that can appear in the target list.
Definition provsql.c:4684
static bool contains_aggref_walker(Node *node, void *found)
Walker for expr_contains_aggref.
Definition provsql.c:12551
static bool normalize_inner_joins_walker(Node *node, void *cx)
Walker: apply normalize_inner_joins to every nested Query – sublink subselects, subquery RTEs,...
Definition provsql.c:14388
static qual_class classify_qual(Expr *expr, const constants_t *constants)
Classify expr along the qual_class axis.
Definition provsql.c:11998
static Node * lift_rv_event_mutator(Node *node, void *data)
Mutator: lift any random_variable comparison event to its token.
Definition provsql.c:4783
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:3786
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:6434
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:8068
bool provsql_where_provenance
Global variable that indicates if where-provenance support has been activated through the provsql....
Definition provsql.c:91
bool provsql_absorptive_provenance
Derived flag: the session's provenance class is 'absorptive' or 'boolean' – licenses constructions so...
Definition provsql.c:114
static void normalize_inner_joins(Query *q)
Canonicalise explicit inner joins in q's FROM to the comma-join form: each all-inner JoinExpr fromlis...
Definition provsql.c:14292
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:8458
static bool oj_tl_sublink_in_arith(Node *node, SubLink *sl)
Is SubLink sl reachable from node through arithmetic only?
Definition provsql.c:10978
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:9717
static bool has_provenance_walker(Node *node, void *data)
Definition provsql.c:7458
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:13181
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:7265
int provsql_verbose
Verbosity level; controlled by the provsql.verbose_level GUC.
Definition provsql.c:93
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:8863
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:5042
static Node * extract_quantified_corr(SubLink *sl, bool *antijoin, bool neg, const Query *outerq, bool *guarded)
Build the per-row correlation for a quantified sublink (IN / op ANY / op ALL), setting *antijoin.
Definition provsql.c:9855
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:7593
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:10067
static Aggref * oj_make_count_star(void)
A fresh count(*) Aggref (returns int8).
Definition provsql.c:10440
static bool expr_has_probabilistic_cmp(Node *node, void *data)
Walker: does node contain a probabilistic (random_variable or aggregate) comparison?
Definition provsql.c:4034
static Node * normalize_bool_agg_having(Node *n)
Definition provsql.c:8021
static bool aggtoken_walker(Node *node, void *data)
Tree walker that detects any Var of type agg_token.
Definition provsql.c:7858
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:8644
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:3703
static Node * rewrite_cond_predicate_mutator(Node *node, void *data)
Mutator: rewrite "X | (predicate)" into the carrier's cond.
Definition provsql.c:4181
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:7336
static bool collect_source_var_types(Node *node, void *cx)
Walker: record the column types the INSERT expects from its source.
Definition provsql.c:15023
static bool oj_is_arith_opexpr(Node *node)
Is node a binary/unary +,-,*,/ operator expression?
Definition provsql.c:10946
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:11809
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:4890
void _PG_init(void)
Extension initialization – called once when the shared library is loaded.
Definition provsql.c:15935
bool provsql_simplify_on_load
Run universal cmp-resolution passes when getGenericCircuit returns; controlled by the provsql....
Definition provsql.c:109
static List * inv_free_arm_head_vars(Query *arm)
The output (head) columns of a UNION arm as plain base Var\ s.
Definition provsql.c:13743
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:8373
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:3736
static Node * oj_renum_mut(Node *node, void *cx)
Definition provsql.c:8489
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:278
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:8407
static bool expr_provably_not_null(Node *e, const Query *q, Index levelsup)
Conservative provably-not-NULL test for the sublink lift.
Definition provsql.c:9797
static bool has_aggtoken(Node *node, const constants_t *constants)
Return true if node contains a Var of type agg_token.
Definition provsql.c:7882
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:7730
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:3832
static void process_inert_fetches(const constants_t *constants, Query *q)
Definition provsql.c:14062
int provsql_mobius_max_cnf
Query-cost cap of the Möbius route: it declines when a sentence's CNF has more than this many conjunc...
Definition provsql.c:108
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:3582
static bool cte_reference_walker(Node *node, void *context)
Walker: does the tree contain an RTE_CTE reference to a CTE of the given name?
Definition provsql.c:1367
static bool oj_limit_count_is_one(Node *limitCount)
Is limitCount the literal 1?
Definition provsql.c:10866
#define PROVSQL_JOIN_ALIAS
Sentinel eref alias marking join RTEs that ProvSQL itself constructs (the EXCEPT antijoin,...
Definition provsql.c:7686
static void rewrite_probability_events(const constants_t *constants, Query *q)
Lift RV-comparison events in q's target list into their tokens.
Definition provsql.c:4811
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:6943
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:3515
static Node * reduce_varattno_mutator(Node *node, void *ctx)
Tree-mutator callback that adjusts Var attribute numbers.
Definition provsql.c:229
static Node * oj_outer_remap(Node *node, void *cx)
Definition provsql.c:8884
bool provsql_inversion_free
Insert the inversion-free structured-d-DNNF path into the default probability chain (after independen...
Definition provsql.c:112
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:15560
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:7385
double provsql_ess_warn_fraction
Effective-sample-size warning threshold for likelihood weighting: warn when the posterior ESS falls b...
Definition provsql.c:101
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:10328
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:4972
static Node * oj_replace_sublink_mut(Node *node, void *cx)
Replace one specific SubLink node with repl, in place.
Definition provsql.c:11006
static Node * oj_param_repl_mut(Node *node, void *cx)
Replace every PARAM_SUBLINK with paramid by replacement.
Definition provsql.c:9673
static bool case_is_agg_carrier(CaseExpr *ce, const constants_t *constants)
Definition provsql.c:4543
static Node * build_rv_case(CaseExpr *ce, const constants_t *constants)
Lower an RV-typed searched CASE into a rv_case(...) call.
Definition provsql.c:4419
char * provsql_last_eval_method
Last probability evaluation method(s) used; exposed via provsql.last_eval_method.
Definition provsql.c:94
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:4355
PG_MODULE_MAGIC
Required PostgreSQL extension magic block.
Definition provsql.c:83
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:13292
static void insert_agg_token_casts(const constants_t *constants, Query *q)
Walk query and insert agg_token casts where needed.
Definition provsql.c:12303
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:5833
static bool oj_rtables_coalescible(List *rta, List *rtb)
Can two scalar-subquery bodies share a single decorrelating LEFT JOIN?
Definition provsql.c:10906
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:3853
static bool having_entails_group_existence(Expr *expr, const constants_t *constants, bool negated)
Whether a lifted HAVING predicate already entails that the group exists.
Definition provsql.c:7964
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:106
int provsql_rv_mc_samples
Default sample count for analytical-evaluator MC fallbacks; 0 disables fallback (callers raise instea...
Definition provsql.c:100
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:102
static bool oj_sublink_scan_walker(Node *node, void *cx)
Definition provsql.c:9221
static Node * add_to_havingQual(Node *havingQual, Expr *expr)
Append expr to havingQual with an AND, creating one if needed.
Definition provsql.c:11776
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:370
static Aggref * build_rv_sum_aggref(const constants_t *constants, Oid aggfnoid, Expr *arg)
Build an Aggref for an RV-summing aggregate over arg.
Definition provsql.c:2686
static Expr * wrap_in_assume_boolean(const constants_t *constants, Expr *expr)
Wrap expr in a provsql.assume_boolean FuncExpr.
Definition provsql.c:13129
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:8289
static void provsql_executor_end(QueryDesc *queryDesc)
Definition provsql.c:15470
char * provsql_kcmcp_server
Launch command for the managed KCMCP server (with a {endpoint} placeholder); controlled by the provsq...
Definition provsql.c:98
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:13436
static bool is_inert_subselect(Query *q)
Is q a recorded inert provenance()-fetch subselect?
Definition provsql.c:7339
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:11689
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:12776
static bool oj_joinref_walker(Node *node, void *cx)
Definition provsql.c:8849
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:7902
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:6361
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:8751
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:13639
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:6762
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:177
static void inline_ctes_in_rtable(List *rtable, List *cteList, List **lowered, List *kept)
Inline CTE references as subqueries within a query.
Definition provsql.c:1409
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:15258
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:9563
bool provsql_joint_width
Recognise unsafe UCQs at planner time and route their existence provenance through the joint-width co...
Definition provsql.c:105
static void rewrite_dml_rv_surface(const constants_t *constants, Query *q)
Lower the RV surface in the values a data-modifying statement supplies directly.
Definition provsql.c:4856
static bool oj_wrap_body_with_match_ind(const constants_t *constants, Query *sub)
Wrap a NULL-guarded antijoin body into a derived subquery D carrying a constant match-indicator colum...
Definition provsql.c:10293
static void provsql_executor_start(QueryDesc *queryDesc, int eflags)
Definition provsql.c:15453
static bool query_references_cte(Query *q, const char *name)
Does q (at any depth) reference a CTE named name?
Definition provsql.c:1386
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:7070
static void add_to_select(Query *q, Expr *provenance)
Append the provenance expression to q's target list.
Definition provsql.c:6810
void _PG_fini(void)
Extension teardown – restores the planner and shmem hooks.
Definition provsql.c:16476
static Node * provenance_mutator(Node *node, void *ctx)
Tree-mutator that replaces provenance() calls with the actual provenance expression.
Definition provsql.c:6888
provsql_provenance_class_t
Values of the provsql.provenance enum GUC, from most general to most specialised.
Definition provsql.c:117
@ PROVSQL_PROVENANCE_WHERE
Universal semiring provenance plus where-provenance gates.
Definition provsql.c:118
@ PROVSQL_PROVENANCE_BOOLEAN
Boolean-only machinery licensed (tagged); implies absorptive.
Definition provsql.c:121
@ PROVSQL_PROVENANCE_SEMIRING
Universal semiring provenance (default).
Definition provsql.c:119
@ PROVSQL_PROVENANCE_ABSORPTIVE
Absorptive-semiring constructions licensed (tagged).
Definition provsql.c:120
static Node * oj_sl_replace_mut(Node *node, void *cx)
Definition provsql.c:9246
static bool join_wholerow_walker(Node *node, void *cx)
Walker: does any Var reference a dissolved join RTE as a whole row (varattno <= 0)?
Definition provsql.c:14136
static void restore_insert_source_types(Query *q, Index src_rteid, Query *subquery)
Coerce the rewritten source SELECT back to the types the INSERT expects.
Definition provsql.c:15058
static Node * rewrite_agg_case_mutator(Node *node, void *context)
Definition provsql.c:4612
static Node * aggregation_type_mutator(Node *node, void *ctx)
Tree-mutator that retypes a specific Var to agg_token.
Definition provsql.c:303
int provsql_monte_carlo_seed
Seed for the Monte Carlo sampler; -1 means non-deterministic (std::random_device); controlled by the ...
Definition provsql.c:99
static bool oj_sub_bodies_coalescible(Query *a, Query *b)
Definition provsql.c:10924
static void provsql_ProcessUtility_apply(Node *parsetree, ProvSQLCtasCapture *cap)
Apply cap to the freshly-created relation stmt->into->rel.
Definition provsql.c:15703
static bool inner_join_collect(Node *jt, List **refs, List **quals, Bitmapset **joins)
Recursively collect an all-inner join tree's leaf RangeTblRefs, ON quals, and dissolved RTE_JOIN rtin...
Definition provsql.c:14193
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:6034
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:12080
static bool collect_having_distinct_walker(Node *node, void *ctx)
Walker that collects AGG(DISTINCT) Aggrefs from an expression.
Definition provsql.c:6008
static bool decorr_value_sublink_walker(Node *node, void *data)
Definition provsql.c:7418
static List * get_provenance_attributes(const constants_t *constants, Query *q, bool in_boolean_rewrite, bool top_level, const InvFreeMarkerCtx *inv_ctx)
Collect all provenance Var nodes reachable from q's range table.
Definition provsql.c:2156
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:12210
bool provsql_cmp_probability_evaluation
Run closed-form / analytic probability evaluators for gate_cmps inside probability_evaluate (currentl...
Definition provsql.c:111
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:8703
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:13243
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:7406
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:13154
static const struct config_enum_entry provsql_provenance_options[]
Option table of the provsql.provenance GUC.
Definition provsql.c:127
static void rewrite_cond_predicates(const constants_t *constants, Query *q)
Rewrite every "X | (predicate)" in q's own clauses.
Definition provsql.c:4296
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:6584
char * provsql_tool_search_path
Colon-separated directory list prepended to PATH when invoking external tools (d4,...
Definition provsql.c:96
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:10807
static OpExpr * normalize_agg_comparison(OpExpr *cmp, const constants_t *constants)
Fold constant arithmetic over an aggregate into the comparison threshold.
Definition provsql.c:3250
static Node * push_arith_into_agg_mutator(Node *node, void *ctx)
Tree-mutator applying try_push_into_aggref bottom-up.
Definition provsql.c:6736
static Query * push_agg_nulltest_into_subquery(Query *q, const constants_t *constants)
Push IS [NOT] NULL on a subquery's aggregate down into that subquery's HAVING.
Definition provsql.c:12686
static void mark_col_selected(Query *q, RangeTblEntry *r, AttrNumber attno)
Mark column attno of RTE r as selected (read permission).
Definition provsql.c:13197
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)
Definition provsql.c:14399
static Query * rewrite_agg_distinct(Query *q, const constants_t *constants)
Rewrite every AGG(DISTINCT key) in q using independent subqueries.
Definition provsql.c:6076
static bool node_is_agg_token(Node *n, const constants_t *constants)
Definition provsql.c:4461
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:10667
bool provsql_interrupted
Global variable that becomes true if this particular backend received an interrupt signal.
Definition provsql.c:89
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:14007
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:7767
#define PROVSQL_MATCH_IND_COLNAME
Column name of the constant match indicator added by oj_wrap_body_with_match_ind.
Definition provsql.c:10279
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:2649
static Node * try_push_into_aggref(OpExpr *op, const constants_t *constants)
Push distributive constant arithmetic into an aggregate's argument.
Definition provsql.c:6650
bool provsql_boolean_provenance
Derived flag: the session's provenance class is 'boolean' – enables the Boolean-only machinery (safe-...
Definition provsql.c:113
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:104
static Node * agg_arm_to_uuid(Node *arm, const constants_t *constants)
Definition provsql.c:4480
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:3955
static Node * build_agg_case(CaseExpr *ce, const constants_t *constants)
Definition provsql.c:4564
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:4064
static bool provenance_in_sublink_walker(Node *node, void *data)
Walker: true if a SubLink subselect calls provenance().
Definition provsql.c:7795
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:11025
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:8436
static Node * join_alias_resolve_mut(Node *node, void *cx)
Mutator: replace every Var referencing a dissolved join RTE by its joinaliasvars expression – resolve...
Definition provsql.c:14161
static bool oj_zero_satisfies(Oid opno, Const *c)
Does 0 satisfy the int8 comparison "0 <opno> c"?
Definition provsql.c:10641
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:2606
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:7111
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:8774
char * provsql_fallback_compiler
Compiler used by BooleanCircuit::makeDD as the final fallback after interpretAsDD and tree-decomposit...
Definition provsql.c:97
static PlannedStmt * provsql_planner(Query *q, int cursorOptions, ParamListInfo boundParams)
PostgreSQL planner hook – entry point for provenance rewriting.
Definition provsql.c:15305
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:10194
static ExecutorStart_hook_type prev_ExecutorStart
Definition provsql.c:15450
static bool provsql_active
true while ProvSQL query rewriting is enabled
Definition provsql.c:90
static bool oj_contains_sublink_walker(Node *node, void *cx)
Walker: true if the subtree contains the specific SubLink cx.
Definition provsql.c:9256
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:7208
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:5145
static void provsql_ProcessUtility(PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, char *completionTag)
Definition provsql.c:15876
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:10484
static OpExpr * inv_free_make_eq(Var *v1, Var *v2)
Build the equality qual v1 = v2, or NULL if the types have no = operator.
Definition provsql.c:13763
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:2367
static Node * oj_decorr_var_mut(Node *node, void *cx)
Definition provsql.c:9186
static void provsql_provenance_assign_hook(int newval, void *extra)
Assign hook of provsql.provenance: refresh the derived per-class flags.
Definition provsql.c:136
static bool is_supported_bool_agg(Oid aggfnoid)
Definition provsql.c:8004
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:10413
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:5105
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:9019
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:9689
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:2530
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:9615
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:9410
static InvFreeMarkerCtx * build_inversion_free_union_ctx(const constants_t *constants, Query *q, char **cert_out)
Build the inversion-free marker context for a set-semantics UNION of inversion-free branches (the ful...
Definition provsql.c:13809
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:9322
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:4005
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:8523
static planner_hook_type prev_planner
Previous planner hook (chained).
Definition provsql.c:147
static void normalize_distinct_into_group_by(Query *q)
Normalise a supported SELECT DISTINCT into a GROUP BY.
Definition provsql.c:7008
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:11842
static bool jointree_arm_has_tracked(const constants_t *constants, Query *q, Node *n)
Walker: does the jointree fragment n reference a provenance-tracked RTE of q?
Definition provsql.c:8922
static void process_insert_select(const constants_t *constants, Query *q)
Propagate provenance through INSERT ... SELECT.
Definition provsql.c:15110
static FlatAtomOrigin * flat_origin1(int slot)
A depth-1 origin path [slot].
Definition provsql.c:13390
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:7365
static bool join_qual_has_agg_token_walker(Node *node, join_qual_agg_token_ctx *ctx)
Definition provsql.c:12316
static bool sublink_is_inert(SubLink *sl)
Does sl wrap a recorded inert provenance()-fetch subselect?
Definition provsql.c:7348
static bool push_one_agg_nulltest(NullTest *nt, agg_nulltest_ctx *ctx)
Move one IS [NOT] NULL conjunct into its subquery's HAVING.
Definition provsql.c:12649
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:4318
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:12182
static Aggref * oj_make_aggref(Oid aggfnoid, Oid aggtype, Oid argtype, Expr *arg)
Build an Aggref for a single-argument aggregate.
Definition provsql.c:9265
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:12244
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:8313
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:3213
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:12448
static void rewrite_agg_cases(const constants_t *constants, Query *q)
Definition provsql.c:4630
static bool is_projected_rv_event(Node *node, const constants_t *constants)
Is node a projected random_variable comparison event?
Definition provsql.c:4655
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:5903
static int provsql_executor_depth
Executor nesting depth.
Definition provsql.c:15290
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:4146
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:10460
static ProcessUtility_hook_type prev_ProcessUtility
Definition provsql.c:15532
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:5215
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:11920
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:2442
static Node * insert_agg_token_casts_mutator(Node *node, void *data)
Insert agg_token casts for Vars used in expressions.
Definition provsql.c:12264
bool provsql_hybrid_evaluation
Run the hybrid-evaluator simplifier inside probability_evaluate; controlled by the provsql....
Definition provsql.c:110
static bool is_null_constant_operand(Node *node)
True when node is a NULL constant (through a coercion).
Definition provsql.c:3879
static bool provsql_update_provenance
true when provenance tracking for DML is enabled
Definition provsql.c:92
static int provsql_provenance_class
Backing variable of the provsql.provenance GUC.
Definition provsql.c:124
static bool check_expr_on_aggregate(Expr *expr, const constants_t *constants)
Top-level dispatcher for supported WHERE-on-aggregate patterns.
Definition provsql.c:11874
static bool provenance_function_walker(Node *node, void *data)
Tree walker that returns true if any provenance() call is found.
Definition provsql.c:7183
semiring_operation
Semiring operation used to combine provenance tokens.
Definition provsql.c:2497
@ SR_PLUS
Semiring addition (UNION, SELECT DISTINCT).
Definition provsql.c:2498
@ SR_TIMES
Semiring multiplication (JOIN, Cartesian product).
Definition provsql.c:2500
@ SR_MONUS
Semiring monus / set difference (EXCEPT).
Definition provsql.c:2499
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:11591
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:7935
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:12373
static Node * aggregation_mutator(Node *node, void *ctx)
Tree-mutator that replaces Aggrefs with provenance-aware aggregates.
Definition provsql.c:6338
static void inline_ctes(const constants_t *constants, Query *q)
Inline CTE references in q as subqueries where the rewrite needs them, preserving CTEs whose bodies n...
Definition provsql.c:2001
int provsql_joint_max_treewidth
Maximum joint treewidth the joint-width UCQ compiler attempts before declining (caller falls back to ...
Definition provsql.c:103
static bool rewrite_predicate_sublinks(const constants_t *constants, Query *q)
Rewrite top-level EXISTS / IN WHERE conjuncts (optionally negated) over tracked relations into correl...
Definition provsql.c:9981
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:3205
static Node * oj_wrap_remap_mut(Node *node, void *cx)
Definition provsql.c:9373
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:10625
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:6873
static void remove_provsql_from_select(Query *q)
Remove the auto-added provsql output column from a rewritten query.
Definition provsql.c:7827
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:9293
static Expr * coerce_via_io_to_text(Expr *arg)
Coerce arg to text via its output function (any type -> text).
Definition provsql.c:13225
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:4933
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:13213
static Node * build_binop(const char *op, Node *l, Node *r)
Build l <op> r, resolving the operator by name.
Definition provsql.c:3231
static Node * flatten_mut(Node *node, void *cp)
Tree mutator implementing the conjunctive inlining of SPJ subqueries.
Definition provsql.c:13351
static bool expr_contains_aggref(Node *node)
Whether an expression contains a plain Aggref.
Definition provsql.c:12571
static ExecutorEnd_hook_type prev_ExecutorEnd
Definition provsql.c:15451
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:7624
static bool process_inert_fetches_walker(Node *node, void *cx)
Definition provsql.c:14034
bool provsql_aggtoken_text_as_uuid
When true, agg_token::text emits the underlying provenance UUID instead of "value (*)".
Definition provsql.c:95
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:3399
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:11739
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:257
qual_class
Categorisation of a top-level WHERE conjunct.
Definition provsql.c:11972
@ QUAL_MIXED_RV_DET
random_variable mixed with non-RV leaves; error
Definition provsql.c:11977
@ QUAL_PURE_AGG
pure agg_token expression; route to HAVING
Definition provsql.c:11974
@ QUAL_DETERMINISTIC
no probabilistic value; stays in WHERE
Definition provsql.c:11973
@ QUAL_MIXED_AGG_DET
agg_token mixed with non-agg leaves; error
Definition provsql.c:11976
@ QUAL_MIXED_AGG_RV
agg_token and random_variable in the same expr; error
Definition provsql.c:11978
@ QUAL_PURE_RV
pure random_variable expression; lift to provenance
Definition provsql.c:11975
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:12394
static TargetEntry * agg_nulltest_target(Query *q, NullTest *nt, const constants_t *constants, Query **sub_out)
The subquery target entry an IS [NOT] NULL is testing, if it is an aggregate of a subquery in q.
Definition provsql.c:12586
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:3902
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:6516
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, baking each aggregate's identity element into the per-ro...
Definition provsql.c:2735
static void error_for_mixed_qual(qual_class c)
Raise the user-facing error appropriate to a mixed c.
Definition provsql.c:12024
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:7575
static bool case_has_rv_cmp(CaseExpr *ce, const constants_t *constants)
Does a searched CASE have at least one RV-comparison guard?
Definition provsql.c:4394
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:13399
static Node * renumber_rte_mut(Node *node, void *cx)
Mutator: renumber every Var / RangeTblRef / JoinExpr rtindex of the compacted level through old_to_ne...
Definition provsql.c:14229
static Node * peel_agg_casts(Node *n)
Peel implicit/explicit cast FuncExprs and RelabelTypes that wrap a single argument,...
Definition provsql.c:6484
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:8610
#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...
Context for cte_reference_walker.
Definition provsql.c:1353
const char * name
CTE name searched for.
Definition provsql.c:1354
Where a flattened base atom came from, for mapping markers back.
Definition provsql.c:13323
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:428
const char * name
Definition provsql.c:429
Query * subquery
Definition provsql.c:430
Result of provsql_classify_query.
provsql_table_kind kind
State captured by the pre-execution pass for the post-execution one.
Definition provsql.c:15535
provsql_table_kind inherited_kind
Definition provsql.c:15538
bool fire
true when the post-pass should run
Definition provsql.c:15536
uint16 source_block_key_n
Definition provsql.c:15542
Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS]
Definition provsql.c:15540
AttrNumber source_block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]
Definition provsql.c:15543
Query * inner_query
cloned for safety; freed by pfree on completion
Definition provsql.c:15537
Oid source_relid
Single source whose block_key we want to align (BID only).
Definition provsql.c:15541
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 push_agg_nulltest_walker.
Definition provsql.c:12544
const constants_t * constants
Definition provsql.c:12546
Context for the aggregation_mutator tree walker.
Definition provsql.c:6324
semiring_operation op
Semiring operation for combining tokens.
Definition provsql.c:6326
const constants_t * constants
Extension OID cache.
Definition provsql.c:6327
bool is_scalar
Aggregation has no GROUP BY (single always-present row).
Definition provsql.c:6328
List * prov_atts
List of provenance Var nodes.
Definition provsql.c:6325
Context for the aggregation_type_mutator tree walker.
Definition provsql.c:269
const constants_t * constants
Extension OID cache.
Definition provsql.c:272
Index varattno
Attribute number of the aggregate column.
Definition provsql.c:271
Index varno
Range-table entry index of the aggregate var.
Definition provsql.c:270
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_RV_DIV
OID of random_variable_div(rv, rv) -> rv: builds the avg num/denom division gate.
Oid OID_AGG_AVG_RV
provsql.avg(random_variable)
Oid OID_AGG_RV_CORR_IMPL
provsql.rv_corr_impl(ind rv, x rv, y rv)
Oid OID_AGG_RV_SUM_OR_NULL
provsql.rv_sum_or_null(random_variable): the avg-numerator sum, NULL on an empty group (so avg is NUL...
Oid OID_AGG_SUM_RV
OIDs of the RV-returning aggregates, keyed for the per-aggregate identity dispatch in make_rv_aggrega...
Oid OID_FUNCTION_CHOOSE
OID of the choose(anyelement) aggregate (keeps the first non-NULL value); used to decorrelate scalar ...
Oid OID_AGG_RV_STDDEV_SAMP_IMPL
provsql.rv_stddev_samp_impl(ind rv, x rv)
Oid OID_AGG_STDDEV_SAMP_RV
provsql.stddev_samp(rv)
Oid OID_FUNCTION_ANNOTATE
OID of provsql.annotate(uuid,text)->uuid.
Oid OID_FUNCTION_PROVENANCE
OID of the provenance FUNCTION.
Oid OID_FUNCTION_RV_LEAST
provsql.least(VARIADIC random_variable[])
Oid OID_FUNCTION_RV_CASE
OID of provsql.rv_case(uuid[])->random_variable.
Oid OID_FUNCTION_INVERSION_FREE_KEY
OID of provsql.inversion_free_key(text,text,int)->text.
Oid OID_FUNCTION_AGG_VALUE_GATE
agg_value_gate(numeric) -> uuid
Oid OID_FUNCTION_AGG_TOKEN_UUID
OID of the agg_token_uuid FUNCTION.
Oid OID_AGG_STDDEV_POP_RV
provsql.stddev_pop(rv)
Oid OID_FUNCTION_RV_AGGREGATE_SEMIMOD
OID of rv_aggregate_semimod(uuid, rv) -> rv: wraps a per-row argument as mixture(prov,...
Oid OID_FUNCTION_GATE_ZERO
OID of the provenance_zero FUNCTION.
Oid OID_TYPE_RANDOM_VARIABLE_ARRAY
OID of the random_variable[] TYPE.
Oid OID_AGG_PRODUCT_RV
provsql.product(random_variable)
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_RV_AGGREGATE_INDICATOR
OID of rv_aggregate_indicator(uuid) -> rv: the avg denominator wrap mixture(prov, 1,...
Oid OID_FUNCTION_GET_CHILDREN
OID of the get_children FUNCTION.
Oid OID_FUNCTION_RV_AGGREGATE_SEMIMOD_ID
OID of the 3-arg rv_aggregate_semimod(uuid, rv, float8): identity-parameterised wrap mixture(prov,...
Oid OID_UNNEST
OID of the unnest(anyarray) FUNCTION.
Oid OID_FUNCTION_COND_PREDICATE
cond_predicate(uuid,boolean)
Oid OID_AGG_MIN_RV
provsql.min(random_variable)
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_AGG_RV_PERCENTILE_IMPL
provsql.rv_percentile_impl(fraction float8, ind rv, x rv)
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_AGG_CASE
OID of agg_case(uuid[]), the agg_token constructor the planner hook lowers an aggregate-carrier CASE ...
Oid OID_AGG_CORR_RV
provsql.corr(rv, rv)
Oid OID_FUNCTION_GIVEN
OID of provsql.given(uuid)->uuid.
Oid OID_FUNCTION_GATE_ONE
OID of the provenance_one FUNCTION.
Oid OID_AGG_COVAR_SAMP_RV
provsql.covar_samp(rv, rv)
Oid OID_FUNCTION_RV_AGGREGATE_INDICATOR_VALUED
OID of rv_aggregate_indicator(uuid, rv) -> rv: NULL when the row's value is NULL (SQL NULL-skip for a...
Oid OID_FUNCTION_PROBABILITY_EVALUATE
OID of the real provsql.probability_evaluate(uuid,text,text).
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_PREDICATE_COND_PREDICATE
predicate_cond_predicate(boolean,boolean) – (A)|(B), both events
Oid OID_AGG_MAX_RV
provsql.max(random_variable)
Oid OID_FUNCTION_AGG_COND
OID of provsql.agg_token_cond(agg_token,uuid): the conditioning constructor for the agg_token carrier...
Oid OID_FUNCTION_PROBABILITY_PREDICATE
OID of the probability(boolean,text,text) placeholder.
Oid OID_TYPE_RANDOM_VARIABLE
OID of the random_variable TYPE.
Oid OID_AGG_COVAR_POP_RV
SQL-standard statistic aggregates over random_variable rows and their internal indicator-carrying rew...
Oid OID_FUNCTION_PROVENANCE_CMP
OID of the provenance_cmp FUNCTION.
Oid OID_AGG_PERCENTILE_CONT_RV
provsql.percentile_cont(float8) WITHIN GROUP (ORDER BY rv)
Oid OID_FUNCTION_RV_GREATEST
provsql.greatest(VARIADIC random_variable[])
Oid OID_AGG_RV_COVAR_POP_IMPL
provsql.rv_covar_pop_impl(ind rv, x rv, y rv)
Oid OID_AGG_RV_STDDEV_POP_IMPL
provsql.rv_stddev_pop_impl(ind rv, x rv)
Oid OID_AGG_RV_COVAR_SAMP_IMPL
provsql.rv_covar_samp_impl(ind rv, x rv, y rv)
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...
Oid OID_FUNCTION_PROVENANCE_CMP_TIMES
OID of the provenance_cmp_times FUNCTION.
Context for contains_agg_walker.
Definition provsql.c:3181
const constants_t * constants
Definition provsql.c:3182
Context for flatten_mut (a multi-relation conjunctive inliner).
Definition provsql.c:13329
Var *** sub_tl
Definition provsql.c:13335
bool * slot_flat
Definition provsql.c:13331
int ** sub_newpos
Definition provsql.c:13333
int * sub_tl_n
Definition provsql.c:13336
bool quals_mode
Definition provsql.c:13337
int * parent_newpos
Definition provsql.c:13332
int * sub_rtlen
Definition provsql.c:13334
Collector for AGG(DISTINCT) Aggrefs inside a HAVING clause.
Definition provsql.c:5997
List * aggs
Aggref* nodes carrying aggdistinct, in traversal order.
Definition provsql.c:5998
Context for replace_having_distinct_mutator: next outer RT index.
Definition provsql.c:6022
Process the inert provenance() fetches in one query's own clauses.
Definition provsql.c:13992
const constants_t * constants
Definition provsql.c:13992
Context for the insert_agg_token_casts_mutator.
Definition provsql.c:12170
const constants_t * constants
Extension OID cache.
Definition provsql.c:12172
Query * query
Outer query (to look up subquery RTEs).
Definition provsql.c:12171
Rewrite a single SELECT query to carry provenance.
Definition provsql.c:14126
List * rtable
range table owning the joinaliasvars
Definition provsql.c:14127
int sublevels_up
current query nesting depth
Definition provsql.c:14129
bool wholerow
a whole-row Var references a dissolved join
Definition provsql.c:14130
Bitmapset * flattened
rtindexes of the RTE_JOIN entries being dissolved
Definition provsql.c:14128
Context for join_qual_has_agg_token_walker.
Definition provsql.c:12310
const constants_t * constants
Extension OID cache.
Definition provsql.c:12311
Index * rteid
Out: varno of the agg_token Var.
Definition provsql.c:12312
AttrNumber * join_attno
Out: attno of the agg_token Var.
Definition provsql.c:12313
Per-relation user-column descriptor for the outer-join lowering.
Definition provsql.c:8300
Oid * coll
column collation OID
Definition provsql.c:8305
int n
number of user (non-provsql, non-dropped) columns
Definition provsql.c:8301
int32 * typmod
column typmod
Definition provsql.c:8304
Oid * type
column type OID
Definition provsql.c:8303
AttrNumber * attno
original attribute number in the base relation
Definition provsql.c:8302
char ** name
column name
Definition provsql.c:8306
Mutator: lift a scalar subquery's body into the outer query level.
Definition provsql.c:9181
Walker context: detect a Var referencing the join RTE index.
Definition provsql.c:8845
Outer Var remap context for the LEFT-join lowering: base-relation Vars (R_idx / S_idx) are retargeted...
Definition provsql.c:8879
AttrNumber * S_map
Definition provsql.c:8881
Index new_idx
Definition provsql.c:8880
AttrNumber * R_map
Definition provsql.c:8881
Index S_idx
Definition provsql.c:8880
Index R_idx
Definition provsql.c:8880
Context for oj_param_repl_mut.
Definition provsql.c:9667
Var-renumber context: map varno from[i] → to[i].
Definition provsql.c:8483
Index from[2]
Definition provsql.c:8485
Index to[2]
Definition provsql.c:8486
Mutator: replace the specific SubLink node target (by pointer) with replacement.
Definition provsql.c:9241
SubLink * target
Definition provsql.c:9242
Var-remap context for the FROM-wrapping pre-step: a Var at target_level on relation varno / attribute...
Definition provsql.c:9365
int ** pos
Definition provsql.c:9369
int target_level
Definition provsql.c:9366
Index newidx
Definition provsql.c:9367
SubLink * skip
Definition provsql.c:9370
Context for the provenance_mutator tree walker.
Definition provsql.c:6857
bool provsql_has_aggref
true when provsql contains an Aggref (set once by replace_provenance_function_by_expression)....
Definition provsql.c:6860
bool inside_aggref
true while descending the argument tree of an Aggref node.
Definition provsql.c:6861
const constants_t * constants
Extension OID cache.
Definition provsql.c:6859
Expr * provsql
Provenance expression to substitute for provenance() calls.
Definition provsql.c:6858
Context for the reduce_varattno_mutator tree walker.
Definition provsql.c:218
Index varno
Range-table entry whose attribute numbers are being adjusted.
Definition provsql.c:219
int * offset
Per-attribute cumulative shift to apply.
Definition provsql.c:220
Context for the rtindex-renumbering mutator of normalize_inner_joins.
Definition provsql.c:14218
int sublevels_up
current query nesting depth
Definition provsql.c:14221
int old_size
range-table length before compaction
Definition provsql.c:14219
int * old_to_new
1-based rtindex map; dissolved slots map to 0
Definition provsql.c:14220
Context for retype_agg_var_walker.
Definition provsql.c:12434
Index rteid
Varno of the replaced RTE.
Definition provsql.c:12435
const constants_t * constants
Extension OID cache.
Definition provsql.c:12437
AttrNumber join_attno
Attno of the former agg_token column.
Definition provsql.c:12436
Walker context for collect_source_var_types.
Definition provsql.c:15007
int32 * typmods
Matching typmod, indexed by varattno - 1.
Definition provsql.c:15011
Oid * types
Expected column type, indexed by varattno - 1.
Definition provsql.c:15010
int natts
Length of the types / typmods arrays.
Definition provsql.c:15009
Index src_rteid
Range-table index of the source subquery.
Definition provsql.c:15008