ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
ucq_joint_evaluate.cpp
Go to the documentation of this file.
1/**
2 * @file ucq_joint_evaluate.cpp
3 * @brief SQL entry points for the joint-width UCQ compiler.
4 *
5 * Exposes @c UCQJointCompiler (see @c UCQJointCompiler.h) to SQL. The
6 * compiler's job ends at the certified d-D circuit; probability / Shapley /
7 * expectation are the standard evaluation on the materialised token, so the
8 * SQL surface is materialisation, not probability:
9 *
10 * - @c ucq_joint_provenance_answer(): the planner-substituted per-answer
11 * entry point. On the first call of a query it gathers the facts once
12 * (@c ucq_joint_gather), runs the single top-down DP, and materialises every
13 * answer's d-D into the store, caching head -> token in @c fn_extra; each
14 * output group is then an O(1) lookup -- one gather + one decomposition +
15 * one sweep for the whole GROUP BY.
16 * - @c ucq_joint_materialize_tracked(): materialise the Boolean (existence)
17 * d-D of a UCQ over real provenance tokens, returning its root token.
18 * - @c ucq_joint_compile_stats(): the same compilation returning the
19 * probability together with the three width columns (joint width, data-only
20 * and circuit-only degeneracy lower bounds) that substantiate thesis
21 * Prop. 4.2.11 empirically, and the structural statistics; the columnar
22 * form takes the query and facts as flat parallel arrays.
23 *
24 * Element ids are dense integers assigned by the gather with a dictionary
25 * shared across relations (so join-compatible values match).
26 */
27extern "C" {
28#include "postgres.h"
29#include "fmgr.h"
30#include "funcapi.h"
31#include "miscadmin.h"
32#include "access/htup_details.h"
33#include "access/xact.h"
34#include "catalog/pg_type.h"
35#include "executor/spi.h"
36#include "utils/array.h"
37#include "utils/builtins.h"
38#include "utils/resowner.h"
39#include "utils/uuid.h"
40
41#include "compatibility.h" /* TYPALIGN_INT fallback for PG < 13 */
42#include "provsql_utils.h"
43
44PG_FUNCTION_INFO_V1(ucq_joint_compile_stats);
45PG_FUNCTION_INFO_V1(ucq_joint_compile_stats_tracked);
46PG_FUNCTION_INFO_V1(ucq_joint_materialize_tracked);
47PG_FUNCTION_INFO_V1(ucq_joint_provenance_answer);
48}
49
50#include "c_cpp_compatibility.h"
51#include "JointEncoding.h"
52#include "UCQJointCompiler.h"
53#include "CircuitFromMMap.h"
55#include "GenericCircuit.h"
56#include "provsql_utils_cpp.h"
57
58#include <algorithm>
59#include <cmath>
60#include <limits>
61#include <map>
62#include <string>
63#include <vector>
64
65namespace {
66
67/** @brief Validate a 1-D, NULL-free array argument and return its length. */
68int checkedArrayLength(ArrayType *arr, const char *what)
69{
70 if (arr == NULL)
71 return 0;
72 if (ARR_NDIM(arr) > 1)
73 provsql_error("ucq_joint: %s must be a one-dimensional array", what);
74 if (ARR_HASNULL(arr))
75 provsql_error("ucq_joint: %s must not contain NULLs", what);
76 return ARR_NDIM(arr) == 0 ? 0 : ARR_DIMS(arr)[0];
77}
78
79/** @brief Fetch an int[] argument's data pointer (NULL when absent). */
80const int32 *intArray(FunctionCallInfo fcinfo, int argno, const char *what,
81 int &len)
82{
83 ArrayType *a = PG_ARGISNULL(argno) ? NULL : PG_GETARG_ARRAYTYPE_P(argno);
84 len = checkedArrayLength(a, what);
85 return (a == NULL || len == 0) ? NULL : (const int32 *) ARR_DATA_PTR(a);
86}
87
88/**
89 * @brief Decode the ten columnar arguments into a @c UCQ and the facts.
90 *
91 * Argument layout (all NULL-free 1-D arrays):
92 * 0 disjunct_nvars int[] per disjunct: number of query variables
93 * 1 atom_disjunct int[] per atom: owning disjunct index
94 * 2 atom_rel int[] per atom: relation id
95 * 3 atom_vars int[] flattened atom variable lists
96 * 4 atom_arity int[] per atom: number of variables
97 * 5 fact_rel int[] per fact: relation id
98 * 6 fact_elems int[] flattened fact element lists
99 * 7 fact_arity int[] per fact: arity
100 * 8 fact_tokens uuid[] per fact: provenance token (nil = certain)
101 * 9 fact_probs float8[] per fact: probability
102 */
103void decodeArgs(FunctionCallInfo fcinfo, UCQ &ucq,
104 std::vector<FactRow> &rows)
105{
106 int n_disj, n_ad, n_ar, n_av, n_aa;
107 const int32 *d_nvars = intArray(fcinfo, 0, "disjunct_nvars", n_disj);
108 const int32 *a_disj = intArray(fcinfo, 1, "atom_disjunct", n_ad);
109 const int32 *a_rel = intArray(fcinfo, 2, "atom_rel", n_ar);
110 const int32 *a_vars = intArray(fcinfo, 3, "atom_vars", n_av);
111 const int32 *a_arity = intArray(fcinfo, 4, "atom_arity", n_aa);
112
113 if (n_disj == 0)
114 provsql_error("ucq_joint: the UCQ has no disjuncts");
115 if (n_ad != n_ar || n_ad != n_aa)
116 provsql_error("ucq_joint: atom arrays must have the same length");
117
118 ucq.disjuncts.resize(n_disj);
119 for (int d = 0; d < n_disj; ++d)
120 ucq.disjuncts[d].n_vars = static_cast<unsigned>(d_nvars[d]);
121
122 int voff = 0;
123 for (int i = 0; i < n_ad; ++i) {
124 const int d = a_disj[i];
125 if (d < 0 || d >= n_disj)
126 provsql_error("ucq_joint: atom disjunct index out of range");
127 const int ar = a_arity[i];
128 if (ar < 0 || voff + ar > n_av)
129 provsql_error("ucq_joint: atom_vars shorter than declared arities");
130 Atom atom;
131 atom.relation_id = static_cast<unsigned>(a_rel[i]);
132 atom.vars.reserve(ar);
133 for (int k = 0; k < ar; ++k)
134 atom.vars.push_back(static_cast<unsigned>(a_vars[voff + k]));
135 voff += ar;
136 ucq.disjuncts[d].atoms.push_back(std::move(atom));
137 }
138
139 int n_fr, n_fe, n_fa, n_fp, n_ft;
140 const int32 *f_rel = intArray(fcinfo, 5, "fact_rel", n_fr);
141 const int32 *f_elems = intArray(fcinfo, 6, "fact_elems", n_fe);
142 const int32 *f_arity = intArray(fcinfo, 7, "fact_arity", n_fa);
143 ArrayType *toks = PG_ARGISNULL(8) ? NULL : PG_GETARG_ARRAYTYPE_P(8);
144 ArrayType *prbs = PG_ARGISNULL(9) ? NULL : PG_GETARG_ARRAYTYPE_P(9);
145 n_ft = checkedArrayLength(toks, "fact_tokens");
146 n_fp = checkedArrayLength(prbs, "fact_probs");
147
148 if (n_fr != n_fa || n_fr != n_ft || n_fr != n_fp)
149 provsql_error("ucq_joint: fact arrays must have the same length");
150 const pg_uuid_t *tok_data =
151 (toks && n_ft) ? (const pg_uuid_t *) ARR_DATA_PTR(toks) : NULL;
152 const float8 *prob_data =
153 (prbs && n_fp) ? (const float8 *) ARR_DATA_PTR(prbs) : NULL;
154
155 int eoff = 0;
156 rows.reserve(n_fr);
157 for (int i = 0; i < n_fr; ++i) {
158 const int ar = f_arity[i];
159 if (ar < 0 || eoff + ar > n_fe)
160 provsql_error("ucq_joint: fact_elems shorter than declared arities");
161 FactRow row;
162 row.relation_id = static_cast<unsigned>(f_rel[i]);
163 row.elements.reserve(ar);
164 for (int k = 0; k < ar; ++k)
165 row.elements.push_back(static_cast<unsigned long>(f_elems[eoff + k]));
166 eoff += ar;
167 bool nil = true;
168 for (int b = 0; b < 16; ++b)
169 if (tok_data[i].data[b] != 0)
170 nil = false;
171 if (!nil)
172 row.token = uuid2string(tok_data[i]);
173 row.prob = prob_data[i];
174 rows.push_back(std::move(row));
175 }
176}
177
178/** @brief Run the compiler over the decoded arguments. */
179UCQJointCompiler::Result runFromArgs(FunctionCallInfo fcinfo)
180{
181 UCQ ucq;
182 std::vector<FactRow> rows;
183 decodeArgs(fcinfo, ucq, rows);
184
185 const JointEncoding enc = JointEncoding::fromFacts(rows);
186 const unsigned max_tw =
187 static_cast<unsigned>(provsql_joint_max_treewidth);
188 const std::size_t max_states =
189 static_cast<std::size_t>(provsql_joint_max_states);
190 try {
191 return UCQJointCompiler::compile(enc, ucq, max_tw, max_states);
192 } catch (TreeDecompositionException &) {
194 "ucq_joint: joint treewidth exceeds the configured maximum (%d); "
195 "fall back to the standard probability ladder",
197 throw; /* unreachable; placate the compiler */
198 }
199}
200
201// ---------------------------------------------------------------------
202// Tracked (correlated) path: walk the mmap circuit slice from the fact
203// tokens, then run the merged DP.
204// ---------------------------------------------------------------------
205
206/** @brief Decode the five query arrays (arguments 0..4) into a @c UCQ. */
207void decodeQuery(FunctionCallInfo fcinfo, UCQ &ucq)
208{
209 int n_disj, n_ad, n_ar, n_av, n_aa;
210 const int32 *d_nvars = intArray(fcinfo, 0, "disjunct_nvars", n_disj);
211 const int32 *a_disj = intArray(fcinfo, 1, "atom_disjunct", n_ad);
212 const int32 *a_rel = intArray(fcinfo, 2, "atom_rel", n_ar);
213 const int32 *a_vars = intArray(fcinfo, 3, "atom_vars", n_av);
214 const int32 *a_arity = intArray(fcinfo, 4, "atom_arity", n_aa);
215
216 if (n_disj == 0)
217 provsql_error("ucq_joint: the UCQ has no disjuncts");
218 if (n_ad != n_ar || n_ad != n_aa)
219 provsql_error("ucq_joint: atom arrays must have the same length");
220
221 ucq.disjuncts.resize(n_disj);
222 for (int d = 0; d < n_disj; ++d)
223 ucq.disjuncts[d].n_vars = static_cast<unsigned>(d_nvars[d]);
224
225 int voff = 0;
226 for (int i = 0; i < n_ad; ++i) {
227 const int d = a_disj[i];
228 if (d < 0 || d >= n_disj)
229 provsql_error("ucq_joint: atom disjunct index out of range");
230 const int ar = a_arity[i];
231 if (ar < 0 || voff + ar > n_av)
232 provsql_error("ucq_joint: atom_vars shorter than declared arities");
233 Atom atom;
234 atom.relation_id = static_cast<unsigned>(a_rel[i]);
235 atom.vars.reserve(ar);
236 for (int k = 0; k < ar; ++k)
237 atom.vars.push_back(static_cast<unsigned>(a_vars[voff + k]));
238 voff += ar;
239 ucq.disjuncts[d].atoms.push_back(std::move(atom));
240 }
241}
242
243/**
244 * @brief Build the circuit slice reachable from the fact tokens.
245 *
246 * Walks the mmap circuit (through @c getGenericCircuit) from each
247 * distinct token down to @c gate_input leaves, keying slice nodes by
248 * UUID so facts whose tokens share an internal gate or an event share
249 * the slice node (the correlation the joint screen must see). Stores
250 * are normalised to arity ≤ 2 (a fan-in-@e k @c gate_times / @c gate_plus
251 * becomes a left-deep binary tree). @c gate_monus(one, x) is the
252 * Boolean @c NOT x; constants fold; @c gate_mulinput / @c gate_mixture
253 * and other non-Boolean gate types are rejected (the caller falls back
254 * to the ladder).
255 *
256 * The returned per-node code is @c -2 for constant true, @c -1 for
257 * constant false, and otherwise the slice index.
258 */
259struct SliceBuilder {
260 std::vector<SliceGate> slice;
261 std::map<std::string, int> uuid2node; ///< UUID -> node code (cached).
262
263 /// A @c gate_mulinput leaf deferred until @c expandMulBlocks() can see
264 /// the whole categorical block it belongs to. @c block is the UUID of
265 /// the shared key gate (all values of one repair_key block share it);
266 /// @c value_index orders the values; @c prob is the value's mass.
267 struct MulRef { std::string block; unsigned value_index; double prob; };
268 std::vector<MulRef> mulrefs; ///< Deferred mulinput leaves.
269 std::vector<int> mul_resolved; ///< MulRef index -> slice node.
270 unsigned mulsb_counter = 0; ///< Fresh stick-breaking event ids.
271
272 /// Node codes below this are deferred mulinput references; the actual
273 /// MulRef index is @c MULREF_BASE - code (so distinct from the -1/-2
274 /// constants and from any non-negative slice index).
275 static constexpr int MULREF_BASE = -1000000;
276
277 int binarize(SliceGateType t, std::vector<int> &children) {
278 if (children.empty())
279 return t == SliceGateType::AND ? -2 : -1; // empty AND=true, OR=false
280 int acc = children[0];
281 for (std::size_t i = 1; i < children.size(); ++i) {
282 SliceGate s;
283 s.type = t;
284 s.children = {static_cast<unsigned>(acc),
285 static_cast<unsigned>(children[i])};
286 slice.push_back(std::move(s));
287 acc = static_cast<int>(slice.size()) - 1;
288 }
289 return acc;
290 }
291
292 int walk(const GenericCircuit &gc, gate_t g) {
293 const std::string u = gc.getUUID(g);
294 if (!u.empty()) {
295 auto it = uuid2node.find(u);
296 if (it != uuid2node.end())
297 return it->second;
298 }
299 int res;
300 switch (gc.getGateType(g)) {
301 case gate_input: {
302 SliceGate s;
304 s.prob = gc.getProb(g);
305 s.token = u;
306 slice.push_back(std::move(s));
307 res = static_cast<int>(slice.size()) - 1;
308 break;
309 }
310 case gate_one:
311 res = -2;
312 break;
313 case gate_zero:
314 res = -1;
315 break;
316 case gate_times: {
317 std::vector<int> ch;
318 bool cfalse = false;
319 for (gate_t c : gc.getWires(g)) {
320 int r = walk(gc, c);
321 if (r == -1) { cfalse = true; break; }
322 if (r == -2) continue; // one: identity for AND
323 ch.push_back(r);
324 }
325 res = cfalse ? -1 : binarize(SliceGateType::AND, ch);
326 break;
327 }
328 case gate_plus: {
329 std::vector<int> ch;
330 bool ctrue = false;
331 for (gate_t c : gc.getWires(g)) {
332 int r = walk(gc, c);
333 if (r == -2) { ctrue = true; break; }
334 if (r == -1) continue; // zero: identity for OR
335 ch.push_back(r);
336 }
337 res = ctrue ? -2 : binarize(SliceGateType::OR, ch);
338 break;
339 }
340 case gate_monus: {
341 const auto &w = gc.getWires(g);
342 if (w.size() != 2)
343 throw JointCompilerException("malformed monus gate in circuit slice");
344 const int a = walk(gc, w[0]);
345 const int b = walk(gc, w[1]);
346 if (a != -2)
347 throw JointCompilerException(
348 "non-Boolean monus in circuit slice (only the negation "
349 "monus(one, x) is supported)");
350 if (b == -2) res = -1; // NOT true = false
351 else if (b == -1) res = -2; // NOT false = true
352 else {
353 SliceGate s;
355 s.children = {static_cast<unsigned>(b)};
356 slice.push_back(std::move(s));
357 res = static_cast<int>(slice.size()) - 1;
358 }
359 break;
360 }
361 case gate_mulinput: {
362 // A BID / repair_key categorical value. We cannot stick-break it
363 // here -- that needs the cumulative mass of every value in its block,
364 // which is scattered across the facts. Defer it: record the value
365 // and return a placeholder code; expandMulBlocks() resolves the whole
366 // block at once (after every fact has been walked) into shared
367 // independent IN/NOT/AND slice nodes that enforce the mutual
368 // exclusivity (the block's values share stick-breaking events -- the
369 // correlation the joint screen must see).
370 const auto &w = gc.getWires(g);
371 if (w.empty())
372 throw JointCompilerException("malformed mulinput gate (no key wire)");
373 const std::string key = gc.getUUID(w[0]);
374 if (key.empty())
375 throw JointCompilerException(
376 "mulinput key gate has no UUID (cannot group the block)");
377 MulRef m;
378 m.block = key;
379 m.value_index = gc.getInfos(g).first;
380 m.prob = gc.getProb(g);
381 mulrefs.push_back(std::move(m));
382 res = MULREF_BASE - static_cast<int>(mulrefs.size() - 1);
383 break;
384 }
385 default:
386 throw JointCompilerException(
387 "unsupported gate type in circuit slice (joint-width "
388 "compilation handles input/and/or/not and mulinput only)");
389 }
390 if (!u.empty())
391 uuid2node[u] = res;
392 return res;
393 }
394
395 /// Append a fresh independent IN (INPUT) slice node of mass @p p. Its
396 /// token is synthetic and unique so JointEncoding treats it as a brand
397 /// new event (a stick-breaking coin), never colliding with a real input.
398 int emitInput(double p) {
399 SliceGate s;
401 s.prob = p;
402 s.token = "\x01mulsb:" + std::to_string(mulsb_counter++);
403 slice.push_back(std::move(s));
404 return static_cast<int>(slice.size()) - 1;
405 }
406
407 /// Append a NOT slice node over the existing node @p child (>= 0).
408 int emitNot(int child) {
409 SliceGate s;
411 s.children = {static_cast<unsigned>(child)};
412 slice.push_back(std::move(s));
413 return static_cast<int>(slice.size()) - 1;
414 }
415
416 /// Stick-break the value range [@p start, @p end] of one block (balanced
417 /// bisection, mirroring BooleanCircuit::rewriteMultivaluedGatesRec): the
418 /// IN coin at each split is SHARED by both halves (so the values are
419 /// mutually exclusive), and each value's slice node is the AND of the
420 /// path of coins (positive on the left, negated on the right) that
421 /// selects it. @p prefix carries that path of already-chosen coins.
422 void expandRec(const std::vector<int> &idxs, const std::vector<double> &cum,
423 unsigned start, unsigned end, std::vector<int> &prefix) {
424 if (start == end) {
425 mul_resolved[idxs[start]] =
426 prefix.empty() ? emitInput(1.0) // sole value, mass 1
427 : binarize(SliceGateType::AND, prefix);
428 return;
429 }
430 const unsigned mid = (start + end) / 2;
431 const double prev_start = (start == 0) ? 0. : cum[start - 1];
432 const int g = emitInput((cum[mid] - prev_start) / (cum[end] - prev_start));
433 const int ng = emitNot(g);
434 prefix.push_back(g);
435 expandRec(idxs, cum, start, mid, prefix);
436 prefix.pop_back();
437 prefix.push_back(ng);
438 expandRec(idxs, cum, mid + 1, end, prefix);
439 prefix.pop_back();
440 }
441
442 /// Resolve every deferred mulinput leaf. Group the MulRefs by block,
443 /// order each block's values, stick-break it into shared IN/NOT/AND slice
444 /// nodes, then rewrite every deferred reference (in slice children) to the
445 /// resolved node. Call once, after every fact token has been walked.
446 void expandMulBlocks() {
447 if (mulrefs.empty())
448 return;
449 mul_resolved.assign(mulrefs.size(), -1);
450 std::map<std::string, std::vector<int>> by_block;
451 for (int i = 0; i < static_cast<int>(mulrefs.size()); ++i)
452 by_block[mulrefs[i].block].push_back(i);
453
454 for (auto &kv : by_block) {
455 std::vector<int> &idxs = kv.second;
456 std::sort(idxs.begin(), idxs.end(), [&](int a, int b) {
457 return mulrefs[a].value_index < mulrefs[b].value_index;
458 });
459 const unsigned n = static_cast<unsigned>(idxs.size());
460 std::vector<double> cum(n);
461 double c = 0.;
462 for (unsigned i = 0; i < n; ++i) {
463 c += mulrefs[idxs[i]].prob;
464 cum[i] = c;
465 }
466 std::vector<int> prefix;
467 // When the present values do not exhaust the block (their masses sum
468 // to less than 1, the rest being "key absent"), gate the whole block
469 // behind one block-active coin of that total mass.
470 constexpr double eps = std::numeric_limits<double>::epsilon() * 10;
471 if (cum[n - 1] < 1. - eps)
472 prefix.push_back(emitInput(cum[n - 1]));
473 expandRec(idxs, cum, 0, n - 1, prefix);
474 }
475
476 // Rewrite deferred references buried as children of AND/OR/NOT nodes.
477 for (SliceGate &sg : slice)
478 for (unsigned &ch : sg.children) {
479 const int ci = static_cast<int>(ch);
480 if (ci <= MULREF_BASE)
481 ch = static_cast<unsigned>(mul_resolved[MULREF_BASE - ci]);
482 }
483 }
484
485 /// Map a raw @c walk() code to a final slice node (resolving any deferred
486 /// mulinput reference). Returns -2 (true), -1 (false), or a slice index.
487 int resolveCode(int code) const {
488 if (code <= MULREF_BASE)
489 return mul_resolved[MULREF_BASE - code];
490 return code;
491 }
492};
493
494/**
495 * @brief Walk the fact tokens at arguments @p base..base+3 into a fact list
496 * and its circuit slice.
497 *
498 * @p base+0 fact_rel, @p base+1 fact_elems, @p base+2 fact_arity,
499 * @p base+3 fact_tokens (uuid[]). Each token is walked through the mmap
500 * circuit (shared slice nodes for shared gates -- the correlation the joint
501 * screen must see). On return @p facts holds the present facts (their gate
502 * indices into @p slice), @p n_elements is the element-id bound, and
503 * @p has_internal is true iff the slice has a non-INPUT gate (correlated).
504 * Shared by the Boolean tracked compile and the per-answer tracked sweep.
505 */
506void buildTrackedFactsArrays(const int32 *f_rel, int n_fr,
507 const int32 *f_elems, int n_fe,
508 const int32 *f_arity, int n_fa,
509 const pg_uuid_t *tok_data, int n_ft,
510 std::vector<Fact> &facts,
511 std::vector<SliceGate> &slice,
512 unsigned long &n_elements, bool &has_internal)
513{
514 if (n_fr != n_fa || n_fr != n_ft)
515 provsql_error("ucq_joint: fact arrays must have the same length");
516
517 SliceBuilder sb;
518 // Walk every fact's token first, keeping its raw slice code; mulinput
519 // (BID) leaves resolve only after the whole batch is seen (a value's
520 // block-mates live in other facts), so we finalise the facts in a second
521 // pass once expandMulBlocks() has stick-broken the blocks.
522 std::vector<std::pair<Fact, int>> pending;
523 pending.reserve(n_fr);
524 n_elements = 0;
525 int eoff = 0;
526 for (int i = 0; i < n_fr; ++i) {
527 const int ar = f_arity[i];
528 if (ar < 0 || eoff + ar > n_fe)
529 provsql_error("ucq_joint: fact_elems shorter than declared arities");
530 Fact f;
531 f.relation_id = static_cast<unsigned>(f_rel[i]);
532 for (int k = 0; k < ar; ++k) {
533 unsigned long e = static_cast<unsigned long>(f_elems[eoff + k]);
534 f.elements.push_back(e);
535 n_elements = std::max(n_elements, e + 1);
536 }
537 eoff += ar;
538 // Walk the token's slice.
539 GenericCircuit gc = getGenericCircuit(tok_data[i]);
540 const int node = sb.walk(gc, gc.getGate(uuid2string(tok_data[i])));
541 pending.emplace_back(std::move(f), node);
542 }
543
544 sb.expandMulBlocks();
545
546 facts.clear();
547 facts.reserve(pending.size());
548 for (auto &pf : pending) {
549 const int node = sb.resolveCode(pf.second);
550 if (node == -1)
551 continue; // constant-false token: the fact is never present
552 Fact &f = pf.first;
553 if (node == -2)
555 else {
557 f.gate = static_cast<std::size_t>(node);
558 }
559 facts.push_back(std::move(f));
560 }
561
562 has_internal = false;
563 for (const auto &sg : sb.slice)
564 if (sg.type != SliceGateType::INPUT) {
565 has_internal = true;
566 break;
567 }
568 slice = std::move(sb.slice);
569}
570
571/** @brief @c buildTrackedFactsArrays over the fact arrays at @p base..base+3. */
572void buildTrackedFacts(FunctionCallInfo fcinfo, int base,
573 std::vector<Fact> &facts, std::vector<SliceGate> &slice,
574 unsigned long &n_elements, bool &has_internal)
575{
576 int n_fr, n_fe, n_fa, n_ft;
577 const int32 *f_rel = intArray(fcinfo, base, "fact_rel", n_fr);
578 const int32 *f_elems = intArray(fcinfo, base + 1, "fact_elems", n_fe);
579 const int32 *f_arity = intArray(fcinfo, base + 2, "fact_arity", n_fa);
580 ArrayType *toks =
581 PG_ARGISNULL(base + 3) ? NULL : PG_GETARG_ARRAYTYPE_P(base + 3);
582 n_ft = checkedArrayLength(toks, "fact_tokens");
583 const pg_uuid_t *tok_data =
584 (toks && n_ft) ? (const pg_uuid_t *) ARR_DATA_PTR(toks) : NULL;
585 buildTrackedFactsArrays(f_rel, n_fr, f_elems, n_fe, f_arity, n_fa,
586 tok_data, n_ft, facts, slice, n_elements, has_internal);
587}
588
589/**
590 * @brief Single top-down DP materialisation: from the columnar fact arrays,
591 * walk the tokens once, run the single DP, and materialise every
592 * answer's certified d-D into the store.
593 *
594 * Returns a map from each answer's head (element-id tuple) to its root token.
595 * Throws @c TreeDecompositionException when the joint width is too large (the
596 * caller falls back). This is the engine behind the transparent per-answer
597 * route: one walk + one decomposition + one sweep for ALL answers.
598 */
599std::map<std::vector<unsigned long>, pg_uuid_t> materializeAnswersSingleDP(
600 const UCQ &ucq, const std::vector<unsigned> &head_vars,
601 const int32 *f_rel, int n_fr, const int32 *f_elems, int n_fe,
602 const int32 *f_arity, int n_fa, const pg_uuid_t *tok_data, int n_ft)
603{
604 std::vector<Fact> facts;
605 std::vector<SliceGate> slice;
606 unsigned long n_elements;
607 bool has_internal;
608 buildTrackedFactsArrays(f_rel, n_fr, f_elems, n_fe, f_arity, n_fa,
609 tok_data, n_ft, facts, slice, n_elements, has_internal);
610
611 const JointEncoding enc =
612 JointEncoding::fromCorrelated(std::move(facts), std::move(slice), n_elements);
613 const unsigned max_tw = static_cast<unsigned>(provsql_joint_max_treewidth);
614 const std::size_t max_states =
615 static_cast<std::size_t>(provsql_joint_max_states);
616
618 UCQJointCompiler::compileAnswersOneDP(enc, ucq, head_vars, max_tw, max_states);
619
620 std::vector<gate_t> roots;
621 roots.reserve(circ.answers.size());
622 for (const auto &a : circ.answers)
623 roots.push_back(a.root);
624 const auto uuid_of = materializeCertifiedDD(circ.dd, roots);
625
626 std::map<std::vector<unsigned long>, pg_uuid_t> out;
627 for (const auto &a : circ.answers)
628 out[a.head] = uuid_of.at(a.root);
629 return out;
630}
631
632/** @brief Run the correlated (tracked) compilation from the arguments. */
633UCQJointCompiler::Result runTrackedFromArgs(FunctionCallInfo fcinfo)
634{
635 UCQ ucq;
636 decodeQuery(fcinfo, ucq);
637
638 std::vector<Fact> facts;
639 std::vector<SliceGate> slice;
640 unsigned long n_elements;
641 bool has_internal;
642 buildTrackedFacts(fcinfo, 5, facts, slice, n_elements, has_internal);
643
644 const unsigned max_tw = static_cast<unsigned>(provsql_joint_max_treewidth);
645 const std::size_t max_states =
646 static_cast<std::size_t>(provsql_joint_max_states);
647
648 // Dispatch by input class: when the slice has no internal gates (every
649 // fact gated by a bare gate_input leaf -- the non-correlated / TID
650 // regime), use the data-graph fast path, which decomposes only the
651 // data (no gate vertices); the joint screen there is the data
652 // treewidth. Internal gates (correlated inputs) need the full joint
653 // data+circuit decomposition. Both give the same probability; the
654 // fast path is just cheaper. A shared leaf across *different* element
655 // tuples is a real correlation that fromFacts rejects -- fall back to
656 // the joint path then.
657 if (!has_internal) {
658 std::vector<FactRow> rows;
659 rows.reserve(facts.size());
660 for (const auto &f : facts) {
661 FactRow row;
662 row.relation_id = f.relation_id;
663 row.elements = f.elements;
664 if (f.kind == FactGateKind::GATE) {
665 row.token = slice[f.gate].token;
666 row.prob = slice[f.gate].prob;
667 } // CERTAIN: empty token, prob 1
668 rows.push_back(std::move(row));
669 }
670 try {
671 const JointEncoding enc = JointEncoding::fromFacts(rows);
672 try {
673 return UCQJointCompiler::compile(enc, ucq, max_tw, max_states);
674 } catch (TreeDecompositionException &) {
676 "ucq_joint: data treewidth exceeds the configured maximum (%d); "
677 "fall back to the standard probability ladder",
679 }
680 } catch (const JointCompilerException &) {
681 // A gate_input shared across different element tuples: genuinely
682 // correlated; fall through to the joint path below.
683 }
684 }
685
686 const JointEncoding enc =
687 JointEncoding::fromCorrelated(std::move(facts), std::move(slice),
688 n_elements);
689 try {
690 return UCQJointCompiler::compile(enc, ucq, max_tw, max_states);
691 } catch (TreeDecompositionException &) {
693 "ucq_joint: joint treewidth exceeds the configured maximum (%d); "
694 "fall back to the standard probability ladder",
696 throw;
697 }
698}
699
700} // namespace
701
702
703/**
704 * @brief PostgreSQL-callable entry point: UCQ probability plus
705 * compilation statistics (the three width columns and the
706 * structural stats).
707 */
708Datum ucq_joint_compile_stats(PG_FUNCTION_ARGS)
709{
710 try {
711 auto result = runFromArgs(fcinfo);
712
713 TupleDesc tupdesc;
714 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
715 provsql_error("ucq_joint_compile_stats: expected composite return type");
716 tupdesc = BlessTupleDesc(tupdesc);
717
718 Datum values[8];
719 bool nulls[8] = {false, false, false, false, false, false, false, false};
720 values[0] = Float8GetDatum(result.dd.probabilityEvaluation());
721 values[1] = Int32GetDatum(static_cast<int32>(result.stats.joint_treewidth));
722 values[2] = Int32GetDatum(static_cast<int32>(result.stats.data_treewidth_lb));
723 values[3] = Int32GetDatum(static_cast<int32>(result.stats.circuit_treewidth_lb));
724 values[4] = Int64GetDatum(static_cast<int64>(result.stats.nb_bags));
725 values[5] = Int64GetDatum(static_cast<int64>(result.stats.max_states));
726 values[6] = Int64GetDatum(static_cast<int64>(result.stats.dd_size));
727 unsigned maxenum = 0;
728 for (unsigned ev : result.stats.n_enumerating)
729 if (ev > maxenum)
730 maxenum = ev;
731 values[7] = Int32GetDatum(static_cast<int32>(maxenum));
732
733 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
734 } catch (const std::exception &e) {
735 provsql_error("ucq_joint_compile_stats: %s", e.what());
736 } catch (...) {
737 provsql_error("ucq_joint_compile_stats: unknown exception");
738 }
739 PG_RETURN_NULL();
740}
741
742/**
743 * @brief PostgreSQL-callable entry point: compile the UCQ over correlated
744 * inputs and **materialise** its certified d-D into the store,
745 * returning the root provenance token.
746 *
747 * This is the architecturally-primary route: the joint-width compiler's
748 * job is to build the deterministic, decomposable circuit; the answer --
749 * probability, Shapley, expectation, any provenance-store evaluation --
750 * is then obtained through the single standard entry point on the
751 * returned token (e.g. @c probability_evaluate(token)), exploiting the
752 * d-D certificate for linear-time evaluation. Unlike the reachability
753 * route, the token is NOT wrapped in the @c 'absorptive' marker: the d-D
754 * is the exact Boolean provenance of the (non-recursive) UCQ.
755 */
756Datum ucq_joint_materialize_tracked(PG_FUNCTION_ARGS)
757{
758 try {
759 auto result = runTrackedFromArgs(fcinfo);
760 const auto uuid_of =
761 materializeCertifiedDD(result.dd, {result.dd.getRoot()});
762 pg_uuid_t *u = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
763 *u = uuid_of.at(result.dd.getRoot());
764 PG_RETURN_UUID_P(u);
765 } catch (const std::exception &e) {
766 provsql_error("ucq_joint_materialize: %s", e.what());
767 } catch (...) {
768 provsql_error("ucq_joint_materialize: unknown exception");
769 }
770 PG_RETURN_NULL();
771}
772
773
774/**
775 * @brief PostgreSQL-callable entry point: correlated UCQ probability plus
776 * compilation statistics (the three width columns substantiate
777 * Prop. 4.2.11: a correlated instance can have small data and
778 * circuit widths but large joint width).
779 */
780Datum ucq_joint_compile_stats_tracked(PG_FUNCTION_ARGS)
781{
782 try {
783 auto result = runTrackedFromArgs(fcinfo);
784
785 TupleDesc tupdesc;
786 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
787 provsql_error("ucq_joint_compile_stats: expected composite return type");
788 tupdesc = BlessTupleDesc(tupdesc);
789
790 Datum values[8];
791 bool nulls[8] = {false, false, false, false, false, false, false, false};
792 values[0] = Float8GetDatum(result.dd.probabilityEvaluation());
793 values[1] = Int32GetDatum(static_cast<int32>(result.stats.joint_treewidth));
794 values[2] = Int32GetDatum(static_cast<int32>(result.stats.data_treewidth_lb));
795 values[3] = Int32GetDatum(static_cast<int32>(result.stats.circuit_treewidth_lb));
796 values[4] = Int64GetDatum(static_cast<int64>(result.stats.nb_bags));
797 values[5] = Int64GetDatum(static_cast<int64>(result.stats.max_states));
798 values[6] = Int64GetDatum(static_cast<int64>(result.stats.dd_size));
799 unsigned maxenum = 0;
800 for (unsigned ev : result.stats.n_enumerating)
801 if (ev > maxenum)
802 maxenum = ev;
803 values[7] = Int32GetDatum(static_cast<int32>(maxenum));
804
805 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
806 } catch (const std::exception &e) {
807 provsql_error("ucq_joint_compile_stats: %s", e.what());
808 } catch (...) {
809 provsql_error("ucq_joint_compile_stats: unknown exception");
810 }
811 PG_RETURN_NULL();
812}
813
814
815
816
817
818// ---------------------------------------------------------------------
819// Transparent per-answer route (planner-substituted). One call per output
820// group; on the FIRST call we gather the facts once, run the single top-down
821// DP, materialise EVERY answer's certified d-D, and cache head -> token in
822// fn_extra. Each subsequent group is an O(1) lookup, so the whole GROUP BY
823// costs one gather + one decomposition + one sweep.
824// ---------------------------------------------------------------------
825
826namespace {
827
828/** @brief A query's cached answers: serialised head-text key -> token. */
829struct JwAnswerCache {
830 bool declined = false;
831 std::vector<std::string> keys;
832 std::vector<pg_uuid_t> tokens;
833};
834
835/** @brief Delete the cache when its memory context is reset (query end). */
836void jwAnswerCacheDelete(void *arg)
837{
838 delete reinterpret_cast<JwAnswerCache *>(arg);
839}
840
841/** @brief Serialise a head tuple (text values) to one lookup key. */
842std::string jwHeadKey(const std::vector<std::string> &vals)
843{
844 std::string k;
845 for (const auto &v : vals) { k += v; k.push_back('\x1f'); }
846 return k;
847}
848
849} // namespace
850
851/**
852 * @brief Gather + single-DP materialise all answers into @p cache.
853 *
854 * Runs inside a subtransaction (the caller wraps it): a SQL error from the
855 * gather (e.g. an unsupported token type for which the joint route declines)
856 * propagates as an @c ereport and is caught by the caller's @c PG_CATCH; a
857 * compiler decline (joint width too large) is a C++ exception caught here and
858 * signalled by returning @c false. Returns @c true and fills @p cache on
859 * success.
860 */
861static bool jwComputeCache(Datum descriptor,
862 const std::vector<unsigned> &head_vars,
863 JwAnswerCache *cache)
864{
865 SPI_connect();
866 Oid argt[1] = { JSONBOID };
867 Datum argv[1] = { descriptor };
868 char argn[1] = { ' ' };
869 const int rc = SPI_execute_with_args(
870 "SELECT * FROM provsql.ucq_joint_gather($1)", 1, argt, argv, argn, true, 1);
871 if (rc != SPI_OK_SELECT || SPI_processed != 1) {
872 SPI_finish();
873 return false;
874 }
875
876 std::vector<std::string> keys;
877 std::vector<pg_uuid_t> tokens;
878 bool ok = true;
879 try {
880 TupleDesc td = SPI_tuptable->tupdesc;
881 HeapTuple row = SPI_tuptable->vals[0];
882 bool isnull;
883 auto intArr = [&](int col, int &n) -> const int32 * {
884 Datum dd = SPI_getbinval(row, td, col, &isnull);
885 if (isnull) { n = 0; return nullptr; }
886 ArrayType *a = DatumGetArrayTypeP(dd);
887 n = ArrayGetNItems(ARR_NDIM(a), ARR_DIMS(a));
888 return (const int32 *) ARR_DATA_PTR(a);
889 };
890 int n_dnv, n_adisj, n_arel, n_avars, n_aarity, n_frel, n_felems, n_farity;
891 const int32 *dnv = intArr(1, n_dnv);
892 const int32 *adisj = intArr(2, n_adisj);
893 const int32 *arel = intArr(3, n_arel);
894 const int32 *avars = intArr(4, n_avars);
895 const int32 *aarity = intArr(5, n_aarity);
896 const int32 *frel = intArr(6, n_frel);
897 const int32 *felems = intArr(7, n_felems);
898 const int32 *farity = intArr(8, n_farity);
899 Datum dtok = SPI_getbinval(row, td, 9, &isnull);
900 ArrayType *atok = isnull ? nullptr : DatumGetArrayTypeP(dtok);
901 const int n_ftok = atok ? ArrayGetNItems(ARR_NDIM(atok), ARR_DIMS(atok)) : 0;
902 const pg_uuid_t *ftok =
903 atok ? (const pg_uuid_t *) ARR_DATA_PTR(atok) : nullptr;
904
905 std::vector<std::string> val_by_id;
906 Datum dval = SPI_getbinval(row, td, 10, &isnull);
907 if (!isnull) {
908 ArrayType *aval = DatumGetArrayTypeP(dval);
909 Datum *elems; bool *nulls; int nval;
910 deconstruct_array(aval, TEXTOID, -1, false, TYPALIGN_INT,
911 &elems, &nulls, &nval);
912 val_by_id.resize(nval);
913 for (int i = 0; i < nval; ++i)
914 val_by_id[i] = nulls[i] ? std::string() : TextDatumGetCString(elems[i]);
915 }
916
917 UCQ ucq;
918 ucq.disjuncts.resize(n_dnv);
919 for (int d = 0; d < n_dnv; ++d)
920 ucq.disjuncts[d].n_vars = static_cast<unsigned>(dnv[d]);
921 int voff = 0;
922 for (int i = 0; i < n_adisj; ++i) {
923 const int dd = adisj[i];
924 const int ar = aarity[i];
925 Atom atom;
926 atom.relation_id = static_cast<unsigned>(arel[i]);
927 for (int k = 0; k < ar; ++k)
928 atom.vars.push_back(static_cast<unsigned>(avars[voff + k]));
929 voff += ar;
930 ucq.disjuncts[dd].atoms.push_back(std::move(atom));
931 }
932
933 const auto answers = materializeAnswersSingleDP(
934 ucq, head_vars, frel, n_frel, felems, n_felems, farity, n_farity,
935 ftok, n_ftok);
936 keys.reserve(answers.size());
937 tokens.reserve(answers.size());
938 for (const auto &kv : answers) {
939 std::vector<std::string> txt;
940 txt.reserve(kv.first.size());
941 for (unsigned long id : kv.first)
942 txt.push_back(id < val_by_id.size() ? val_by_id[id] : std::string());
943 keys.push_back(jwHeadKey(txt));
944 tokens.push_back(kv.second);
945 }
946 } catch (...) {
947 ok = false; // compiler decline (joint width too large, ...)
948 }
949 SPI_finish();
950 if (ok) {
951 cache->keys = std::move(keys);
952 cache->tokens = std::move(tokens);
953 }
954 return ok;
955}
956
957/**
958 * @brief Per-answer joint-width provenance via the single top-down DP.
959 * See @c ucq_joint_provenance_answer in provsql.common.sql.
960 */
961Datum ucq_joint_provenance_answer(PG_FUNCTION_ARGS)
962{
963 JwAnswerCache *cache =
964 reinterpret_cast<JwAnswerCache *>(fcinfo->flinfo->fn_extra);
965
966 if (cache == nullptr) {
967 // First call: build and cache all answers in the (per-query) fn context.
968 MemoryContext fnctx = fcinfo->flinfo->fn_mcxt;
969 cache = new JwAnswerCache();
970 MemoryContextCallback *cb = (MemoryContextCallback *)
971 MemoryContextAllocZero(fnctx, sizeof(MemoryContextCallback));
972 cb->func = jwAnswerCacheDelete;
973 cb->arg = cache;
974 MemoryContextRegisterResetCallback(fnctx, cb);
975 fcinfo->flinfo->fn_extra = cache;
976
977 if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) {
978 cache->declined = true;
979 } else {
980 std::vector<unsigned> head_vars;
981 {
982 ArrayType *a = PG_GETARG_ARRAYTYPE_P(1);
983 const int32 *d = (const int32 *) ARR_DATA_PTR(a);
984 const int n = ArrayGetNItems(ARR_NDIM(a), ARR_DIMS(a));
985 for (int i = 0; i < n; ++i)
986 head_vars.push_back(static_cast<unsigned>(d[i]));
987 }
988 const Datum desc = PG_GETARG_DATUM(0);
989
990 // Catch any error from the gather / compiler (an unsupported token, a
991 // width too large, ...) and decline gracefully to the @p fallback, so a
992 // recognised query never fails.
993 MemoryContext oldcxt = CurrentMemoryContext;
994 ResourceOwner oldowner = CurrentResourceOwner;
995 BeginInternalSubTransaction(NULL);
996 PG_TRY();
997 {
998 if (!jwComputeCache(desc, head_vars, cache))
999 cache->declined = true;
1000 ReleaseCurrentSubTransaction();
1001 MemoryContextSwitchTo(oldcxt);
1002 CurrentResourceOwner = oldowner;
1003 }
1004 PG_CATCH();
1005 {
1006 MemoryContextSwitchTo(oldcxt);
1007 RollbackAndReleaseCurrentSubTransaction();
1008 MemoryContextSwitchTo(oldcxt);
1009 CurrentResourceOwner = oldowner;
1010 FlushErrorState();
1011 cache->declined = true;
1012 cache->keys.clear();
1013 cache->tokens.clear();
1014 }
1015 PG_END_TRY();
1016 }
1017 }
1018
1019 // Every call: look the group's head up in the cache.
1020 if (!cache->declined && !PG_ARGISNULL(2)) {
1021 std::vector<std::string> hv;
1022 ArrayType *a = PG_GETARG_ARRAYTYPE_P(2);
1023 Datum *elems; bool *nulls; int n;
1024 deconstruct_array(a, TEXTOID, -1, false, TYPALIGN_INT, &elems, &nulls, &n);
1025 for (int i = 0; i < n; ++i)
1026 hv.push_back(nulls[i] ? std::string() : TextDatumGetCString(elems[i]));
1027 const std::string key = jwHeadKey(hv);
1028 for (std::size_t i = 0; i < cache->keys.size(); ++i)
1029 if (cache->keys[i] == key) {
1030 pg_uuid_t *u = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
1031 *u = cache->tokens[i];
1032 PG_RETURN_UUID_P(u);
1033 }
1034 }
1035
1036 // Declined, or the group is not an answer of the joint compiler: fall back.
1037 if (PG_ARGISNULL(3))
1038 PG_RETURN_NULL();
1039 PG_RETURN_DATUM(PG_GETARG_DATUM(3));
1040}
@ AND
Boolean AND aggregate.
Definition Aggregation.h:57
std::unordered_map< gate_t, pg_uuid_t, hash_gate_t > materializeCertifiedDD(const dDNNF &dd, const std::vector< gate_t > &roots)
Materialise (the reachable part of) a certified d-D into the mmap store.
Content-addressed materialisation of a certified d-D into the mmap provenance store.
static CircuitCache cache
Process-local singleton circuit gate cache.
GenericCircuit getGenericCircuit(pg_uuid_t token)
Build a GenericCircuit from the mmap store rooted at token.
Build in-memory circuits from the mmap-backed persistent store.
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Semiring-agnostic in-memory provenance circuit.
Phase A of the joint-width UCQ compiler: assemble the joint graph of the data and its correlation str...
SliceGateType
Gate kind of a circuit-slice node (correlated regime).
@ GATE
Present iff its slice gate evaluates true (correlated regime).
@ CERTAIN
Always present: an untracked relation, constant-true token.
Phase C of the joint-width UCQ compiler: a UCQ-specialised homomorphism-type DP that runs directly ov...
Fix macro conflicts between PostgreSQL headers and the C++ STL/Boost.
std::vector< gate_t > & getWires(gate_t g)
Return a mutable reference to the child-wire list of gate g.
Definition Circuit.h:140
gateType getGateType(gate_t g) const
Return the type of gate g.
Definition Circuit.h:130
uuid getUUID(gate_t g) const
Return the UUID string associated with gate g.
Definition Circuit.hpp:46
gate_t getGate(const uuid &u)
Return (or create) the gate associated with UUID u.
Definition Circuit.hpp:33
In-memory provenance circuit with semiring-generic evaluation.
double getProb(gate_t g) const
Return the probability for gate g.
std::pair< unsigned, unsigned > getInfos(gate_t g) const
Return the integer annotation pair for gate g.
Exception thrown when joint-width compilation cannot proceed.
The joint encoding of an instance: facts, world events, and the joint graph the screen and the DP run...
static JointEncoding fromCorrelated(std::vector< Fact > facts, std::vector< SliceGate > slice, unsigned long n_elements)
Construct the correlated-regime encoding from facts and an already-extracted circuit slice.
static JointEncoding fromFacts(const std::vector< FactRow > &rows)
Build the data-graph (§3.5 fast path) encoding from raw rows.
Exception thrown when a tree decomposition cannot be constructed.
static AnswerCircuit compileAnswersOneDP(const JointEncoding &enc, const UCQ &ucq, const std::vector< unsigned > &head_vars, unsigned max_treewidth=TreeDecomposition::MAX_TREEWIDTH, std::size_t max_states=DEFAULT_MAX_STATES)
static Result compile(const JointEncoding &enc, const UCQ &ucq, unsigned max_treewidth=TreeDecomposition::MAX_TREEWIDTH, std::size_t max_states=DEFAULT_MAX_STATES)
Compile a Boolean UCQ over enc into a certified d-D.
PostgreSQL cross-version compatibility shims for ProvSQL.
#define TYPALIGN_INT
Alignment codes for the array routines (construct_array / deconstruct_array).
int provsql_joint_max_states
Per-bag DP state-count cap of the joint-width UCQ compiler (the true safety net); provsql....
Definition provsql.c:99
int provsql_joint_max_treewidth
Maximum joint treewidth the joint-width UCQ compiler attempts before declining (caller falls back to ...
Definition provsql.c:98
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
Core types, constants, and utilities shared across ProvSQL.
string uuid2string(pg_uuid_t uuid)
Format a pg_uuid_t as a std::string.
C++ utility functions for UUID manipulation.
One atom of a conjunctive query: a relation symbol applied to query variables.
std::vector< unsigned > vars
Query-variable indices, one per column.
unsigned relation_id
Dense id of the relation symbol.
One row of an atom's relation, as handed in by the SQL layer.
unsigned relation_id
Dense id of the relation symbol.
double prob
Tuple probability (for an independent gate_input token).
std::string token
Provenance gate (UUID); empty marks a certain (untracked) fact.
std::vector< unsigned long > elements
Dense ids of the row's domain elements.
A deduplicated fact participating in the DP.
std::size_t gate
Index into slice (when GATE).
std::vector< unsigned long > elements
Dense element ids (the fact's tuple).
FactGateKind kind
How the fact's presence is gated.
unsigned relation_id
Dense id of the relation symbol.
SliceGateType type
Node kind.
std::vector< unsigned > children
Child slice indices (≤ 2; empty for INPUT).
double prob
Marginal (INPUT only).
std::string token
Provenance token of the leaf (INPUT only; for the d-D IN gate UUID).
dDNNF dd
The shared certified d-D.
std::vector< AnswerRoot > answers
One root per discovered answer.
A compiled UCQ: the d-D and its statistics.
A union of conjunctive queries.
std::vector< CQ > disjuncts
The disjuncts (at least one).
UUID structure.
Datum ucq_joint_compile_stats(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: UCQ probability plus compilation statistics (the three width columns...
static bool jwComputeCache(Datum descriptor, const std::vector< unsigned > &head_vars, JwAnswerCache *cache)
Gather + single-DP materialise all answers into cache.
Datum ucq_joint_compile_stats_tracked(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: correlated UCQ probability plus compilation statistics (the three wi...
Datum ucq_joint_materialize_tracked(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: compile the UCQ over correlated inputs and materialise its certified...
Datum ucq_joint_provenance_answer(PG_FUNCTION_ARGS)
Per-answer joint-width provenance via the single top-down DP.