ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
reachability_evaluate.cpp
Go to the documentation of this file.
1/**
2 * @file reachability_evaluate.cpp
3 * @brief SQL entry points for decomposition-aligned reachability
4 * compilation over bounded-treewidth data.
5 *
6 * Exposes @c ReachabilityCompiler (see @c ReachabilityCompiler.h) to SQL:
7 *
8 * - @c reachability_evaluate(): exact probability that the target vertex
9 * is reachable from the source vertex (two-terminal network
10 * reliability), in time linear in the number of edges for data of
11 * bounded treewidth;
12 * - @c reachability_compile_stats(): same compilation, returning the
13 * probability together with structural statistics (data treewidth,
14 * number of bags, maximum DP state count, d-D size) that
15 * substantiate the linear-size claim in tests and benchmarks.
16 *
17 * Both take the edge relation in columnar form (parallel arrays of
18 * source vertices, target vertices, provenance tokens, and
19 * probabilities); the user-facing wrappers in @c provsql.sql gather
20 * those arrays from an arbitrary provenance-tracked edge relation.
21 * Vertices are dense integer IDs (the wrappers map arbitrary vertex
22 * values onto them).
23 */
24extern "C" {
25#include "postgres.h"
26#include "fmgr.h"
27#include "funcapi.h"
28#include "miscadmin.h"
29#include "access/htup_details.h"
30#include "catalog/pg_type.h"
31#include "utils/array.h"
32#include "utils/builtins.h"
33#include "utils/tuplestore.h"
34#include "utils/uuid.h"
35
36#include "provsql_utils.h"
37#include "provsql_mmap.h"
38#include "provsql_shmem.h"
39
40PG_FUNCTION_INFO_V1(reachability_evaluate);
41PG_FUNCTION_INFO_V1(reachability_compile_stats);
42PG_FUNCTION_INFO_V1(reachability_materialize);
43PG_FUNCTION_INFO_V1(reachability_materialize_hops);
44PG_FUNCTION_INFO_V1(reachability_materialize_any);
45PG_FUNCTION_INFO_V1(reachability_materialize_cover);
46}
47
48#include "c_cpp_compatibility.h"
51#include "provsql_utils_cpp.h"
52
53#include <map>
54#include <string>
55#include <unordered_map>
56#include <unordered_set>
57#include <vector>
58
59namespace {
60
61/**
62 * @brief Validate a 1-D, NULL-free array argument and return its length.
63 * @param arr The array (may be null for "no edges").
64 * @param what Argument name for error messages.
65 * @return Number of elements (0 when @p arr is null).
66 */
67int checkedArrayLength(ArrayType *arr, const char *what)
68{
69 if (arr == NULL)
70 return 0;
71 if (ARR_NDIM(arr) > 1)
72 provsql_error("reachability: %s must be a one-dimensional array", what);
73 if (ARR_HASNULL(arr))
74 provsql_error("reachability: %s must not contain NULLs", what);
75 return ARR_NDIM(arr) == 0 ? 0 : ARR_DIMS(arr)[0];
76}
77
78/**
79 * @brief Decode the four columnar edge arrays (arguments 0..3 of every
80 * entry point) into edge rows.
81 *
82 * @param fcinfo PostgreSQL function-call info.
83 * @return The decoded edge rows.
84 */
85std::vector<ReachabilityCompiler::EdgeRow> edgesFromArgs(
86 FunctionCallInfo fcinfo, int block_args_at = -1)
87{
88 ArrayType *srcs = PG_ARGISNULL(0) ? NULL : PG_GETARG_ARRAYTYPE_P(0);
89 ArrayType *dsts = PG_ARGISNULL(1) ? NULL : PG_GETARG_ARRAYTYPE_P(1);
90 ArrayType *tokens = PG_ARGISNULL(2) ? NULL : PG_GETARG_ARRAYTYPE_P(2);
91 ArrayType *probs = PG_ARGISNULL(3) ? NULL : PG_GETARG_ARRAYTYPE_P(3);
92 ArrayType *bkeys = NULL;
93 ArrayType *bidx = NULL;
94 if (block_args_at >= 0) {
95 bkeys = PG_ARGISNULL(block_args_at) ? NULL
96 : PG_GETARG_ARRAYTYPE_P(block_args_at);
97 bidx = PG_ARGISNULL(block_args_at+1) ? NULL
98 : PG_GETARG_ARRAYTYPE_P(block_args_at+1);
99 }
100
101 const int n = checkedArrayLength(srcs, "sources");
102 if (checkedArrayLength(dsts, "destinations") != n ||
103 checkedArrayLength(tokens, "tokens") != n ||
104 checkedArrayLength(probs, "probabilities") != n)
105 provsql_error("reachability: edge arrays must have the same length");
106 if (bkeys != NULL &&
107 (checkedArrayLength(bkeys, "block keys") != n ||
108 checkedArrayLength(bidx, "block indices") != n))
109 provsql_error("reachability: edge arrays must have the same length");
110
111 std::vector<ReachabilityCompiler::EdgeRow> rows;
112 rows.reserve(n);
113
114 if (n > 0) {
115 /* All element types are fixed-length and NULL-free (checked above),
116 * so the data areas are packed and can be read directly, as
117 * create_gate does for uuid[]. */
118 const int32 *src_data = (const int32 *) ARR_DATA_PTR(srcs);
119 const int32 *dst_data = (const int32 *) ARR_DATA_PTR(dsts);
120 const pg_uuid_t *token_data = (const pg_uuid_t *) ARR_DATA_PTR(tokens);
121 const float8 *prob_data = (const float8 *) ARR_DATA_PTR(probs);
122 const pg_uuid_t *bkey_data =
123 bkeys ? (const pg_uuid_t *) ARR_DATA_PTR(bkeys) : NULL;
124 const int32 *bidx_data = bidx ? (const int32 *) ARR_DATA_PTR(bidx) : NULL;
125
126 for (int i = 0; i < n; ++i) {
128 row.src = static_cast<unsigned long>(src_data[i]);
129 row.dst = static_cast<unsigned long>(dst_data[i]);
130 row.token = uuid2string(token_data[i]);
131 row.prob = prob_data[i];
132 if (row.prob < 0. || row.prob > 1.)
133 provsql_error("reachability: edge probability %f out of [0,1]",
134 row.prob);
135 if (bkey_data) {
136 bool nil = true;
137 for (int b = 0; b < 16; ++b)
138 if (bkey_data[i].data[b] != 0)
139 nil = false;
140 if (!nil) {
141 row.block_key = uuid2string(bkey_data[i]);
142 row.block_index = static_cast<unsigned>(bidx_data[i]);
143 }
144 }
145 rows.push_back(std::move(row));
146 }
147 }
148
149 return rows;
150}
151
152/**
153 * @brief Decode the source-arc arrays (three consecutive arguments
154 * starting at @p base): vertices, tokens (nil = certain) and
155 * probabilities.
156 *
157 * @param fcinfo PostgreSQL function-call info.
158 * @param base Index of the source-vertices argument.
159 * @return The decoded source arcs.
160 */
161std::vector<ReachabilityCompiler::SourceArc> sourcesFromArgs(
162 FunctionCallInfo fcinfo, int base)
163{
164 std::vector<ReachabilityCompiler::SourceArc> sources;
165 ArrayType *sv = PG_ARGISNULL(base) ? NULL : PG_GETARG_ARRAYTYPE_P(base);
166 ArrayType *st = PG_ARGISNULL(base+1) ? NULL : PG_GETARG_ARRAYTYPE_P(base+1);
167 ArrayType *sp = PG_ARGISNULL(base+2) ? NULL : PG_GETARG_ARRAYTYPE_P(base+2);
168 const int ns = checkedArrayLength(sv, "source vertices");
169 if (checkedArrayLength(st, "source tokens") != ns ||
170 checkedArrayLength(sp, "source probabilities") != ns)
171 provsql_error("reachability: source arrays must have the same length");
172 if (ns == 0)
173 provsql_error("reachability: at least one source is required");
174 const int32 *v_data = (const int32 *) ARR_DATA_PTR(sv);
175 const pg_uuid_t *t_data = (const pg_uuid_t *) ARR_DATA_PTR(st);
176 const float8 *p_data = (const float8 *) ARR_DATA_PTR(sp);
177 sources.reserve(ns);
178 for (int i = 0; i < ns; ++i) {
180 sa.vertex = static_cast<unsigned long>(v_data[i]);
181 bool nil = true;
182 for (int b = 0; b < 16; ++b)
183 if (t_data[i].data[b] != 0)
184 nil = false;
185 sa.certain = nil;
186 if (!nil)
187 sa.token = uuid2string(t_data[i]);
188 sa.prob = p_data[i];
189 if (sa.prob < 0. || sa.prob > 1.)
190 provsql_error("reachability: source probability %f out of [0,1]",
191 sa.prob);
192 sources.push_back(std::move(sa));
193 }
194 return sources;
195}
196
197/**
198 * @brief Decode the single-target argument layout and run the compilation.
199 *
200 * Argument layout: @c srcs @c int[], @c dsts @c int[], @c tokens
201 * @c uuid[], @c probs @c float8[], @c source @c int, @c target @c int,
202 * @c directed @c boolean.
203 *
204 * @param fcinfo PostgreSQL function-call info.
205 * @return The compiled d-D and statistics.
206 */
207ReachabilityCompiler::Result compileFromArgs(FunctionCallInfo fcinfo)
208{
209 for (int i = 4; i < 7; ++i)
210 if (PG_ARGISNULL(i))
211 provsql_error("reachability: source, target and directed must not be NULL");
212
213 auto rows = edgesFromArgs(fcinfo);
214 const unsigned long source = static_cast<unsigned long>(PG_GETARG_INT32(4));
215 const unsigned long target = static_cast<unsigned long>(PG_GETARG_INT32(5));
216 const bool directed = PG_GETARG_BOOL(6);
217
218 try {
219 return ReachabilityCompiler::compile(rows, source, target, directed);
220 } catch (TreeDecompositionException &) {
222 "reachability: data treewidth exceeds the supported limit (%d)",
224 throw; /* unreachable; placate the compiler */
225 }
226}
227
228
229} // namespace
230
231/**
232 * @brief PostgreSQL-callable entry point: exact reachability probability.
233 *
234 * Arguments: see @c compileFromArgs().
235 * Returns: the probability that @c target is reachable from @c source.
236 */
237Datum reachability_evaluate(PG_FUNCTION_ARGS)
238{
239 try {
240 auto result = compileFromArgs(fcinfo);
241 PG_RETURN_FLOAT8(result.dd.probabilityEvaluation());
242 } catch (const std::exception &e) {
243 provsql_error("reachability: %s", e.what());
244 } catch (...) {
245 provsql_error("reachability: unknown exception");
246 }
247 PG_RETURN_NULL();
248}
249
250/**
251 * @brief PostgreSQL-callable entry point: probability plus compilation
252 * statistics.
253 *
254 * Arguments: see @c compileFromArgs().
255 * Returns: composite @c (probability, data_treewidth, nb_bags,
256 * max_states, nb_gates, nb_variables).
257 */
258Datum reachability_compile_stats(PG_FUNCTION_ARGS)
259{
260 try {
261 auto result = compileFromArgs(fcinfo);
262
263 TupleDesc tupdesc;
264 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
265 provsql_error("reachability_compile_stats: expected composite return type");
266 tupdesc = BlessTupleDesc(tupdesc);
267
268 Datum values[6];
269 bool nulls[6] = {false, false, false, false, false, false};
270 values[0] = Float8GetDatum(result.dd.probabilityEvaluation());
271 values[1] = Int32GetDatum(static_cast<int32>(result.stats.data_treewidth));
272 values[2] = Int64GetDatum(static_cast<int64>(result.stats.nb_bags));
273 values[3] = Int64GetDatum(static_cast<int64>(result.stats.max_states));
274 values[4] = Int64GetDatum(static_cast<int64>(result.stats.nb_gates));
275 values[5] = Int64GetDatum(static_cast<int64>(result.stats.nb_variables));
276
277 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
278 } catch (const std::exception &e) {
279 provsql_error("reachability_compile_stats: %s", e.what());
280 } catch (...) {
281 provsql_error("reachability_compile_stats: unknown exception");
282 }
283 PG_RETURN_NULL();
284}
285
286/**
287 * @brief PostgreSQL-callable entry point: all-targets compilation and
288 * materialisation.
289 *
290 * Arguments: @c srcs @c int[], @c dsts @c int[], @c tokens @c uuid[],
291 * @c probs @c float8[], @c block_keys @c uuid[] (nil = independent
292 * tuple; otherwise the BID key variable grouping mutually exclusive
293 * alternatives) and @c block_indices @c int[], @c source_vertices
294 * @c int[], @c source_tokens @c uuid[] (the nil UUID marking a certain
295 * source), @c source_probs @c float8[], @c directed @c boolean.
296 * Returns: one @c (vertex, token) row per vertex reachable in the
297 * all-edges-present world, @c token being the materialised certified
298 * provenance circuit of "some present source reaches the vertex",
299 * wrapped in the @c 'absorptive' assumption marker (see
300 * @c wrapAssumedAbsorptive). This is the engine behind the rewriter's
301 * recursive-reachability route.
302 */
303Datum reachability_materialize(PG_FUNCTION_ARGS)
304{
305 try {
306 if (PG_ARGISNULL(9))
307 provsql_error("reachability: directed must not be NULL");
308
309 auto rows = edgesFromArgs(fcinfo, 4);
310 const bool directed = PG_GETARG_BOOL(9);
311
312 /* Sources: parallel arrays of vertices, tokens and probabilities; the
313 * nil UUID marks a *certain* source (an untracked source relation, or
314 * the constant base arm of the recursive shape). */
315 const auto sources = sourcesFromArgs(fcinfo, 6);
316
318 try {
319 all = ReachabilityCompiler::compileAll(rows, sources, directed);
320 } catch (TreeDecompositionException &) {
322 "reachability: data treewidth exceeds the supported limit (%d)",
324 }
325
326 std::vector<gate_t> roots;
327 roots.reserve(all.roots.size());
328 for (const auto &vr : all.roots)
329 roots.push_back(vr.root);
330 const auto uuid_of = materializeCertifiedDD(all.dd, roots);
331
332 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
333 MemoryContext per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
334 MemoryContext oldcontext = MemoryContextSwitchTo(per_query_ctx);
335
336 TupleDesc tupdesc;
337 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
338 MemoryContextSwitchTo(oldcontext);
339 provsql_error("reachability_materialize: function must return a row type");
340 }
341 tupdesc = BlessTupleDesc(tupdesc);
342
343 Tuplestorestate *tupstore = tuplestore_begin_heap(
344 rsinfo->allowedModes & SFRM_Materialize_Random, false, work_mem);
345 rsinfo->returnMode = SFRM_Materialize;
346 rsinfo->setResult = tupstore;
347 rsinfo->setDesc = tupdesc;
348
349 for (const auto &vr : all.roots) {
350 Datum values[2];
351 bool nulls[2] = {false, false};
352 values[0] = Int32GetDatum(static_cast<int32>(vr.vertex));
353 pg_uuid_t *u = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
354 *u = wrapAssumedAbsorptive(uuid_of.at(vr.root));
355 values[1] = UUIDPGetDatum(u);
356 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
357 }
358
359 MemoryContextSwitchTo(oldcontext);
360 return (Datum) 0;
361 } catch (const std::exception &e) {
362 provsql_error("reachability_materialize: %s", e.what());
363 } catch (...) {
364 provsql_error("reachability_materialize: unknown exception");
365 }
366 PG_RETURN_NULL();
367}
368
369/**
370 * @brief PostgreSQL-callable entry point: bounded-hop all-targets
371 * compilation and materialisation.
372 *
373 * Arguments 0..9 as @c reachability_materialize, then @c hop_bound
374 * @c int (maximum walk length) and @c hop_seed @c int (the recursive
375 * CTE's base-arm hop constant, added to the reported lengths).
376 * Returns: one @c (vertex, hops, token) row per (vertex, walk length)
377 * pair achievable in the all-edges-present world -- matching the rows
378 * the generic fixpoint derives for the hop-counting CTE shape, with
379 * @c token the materialised certified circuit of "some present source
380 * reaches the vertex by a walk of exactly this many edges", wrapped in
381 * the @c 'absorptive' assumption marker.
382 *
383 * Additionally pre-creates, for every vertex with at least two length
384 * rows, the gate a hop-discarding query's deduplication will mint --
385 * @c uuid5('plus{sorted tokens}') over the vertex's length tokens
386 * (multiset, as @c provenance_plus aggregates them) -- as a certified
387 * single-child @c plus over the DP's native within-bound root, which
388 * computes the same Boolean function as that OR but *deterministically
389 * by construction*. The natural "is the vertex within k hops" query
390 * thus evaluates through the linear certified route instead of falling
391 * back to generic knowledge compilation over correlated per-length
392 * tokens; the pre-created gate is content-addressed, so the rewriter's
393 * later @c create_gate of the same UUID is an idempotent no-op.
394 */
395Datum reachability_materialize_hops(PG_FUNCTION_ARGS)
396{
397 try {
398 if (PG_ARGISNULL(9) || PG_ARGISNULL(10) || PG_ARGISNULL(11))
400 "reachability: directed, hop_bound and hop_seed must not be NULL");
401
402 auto rows = edgesFromArgs(fcinfo, 4);
403 const bool directed = PG_GETARG_BOOL(9);
404 const int32 hop_bound = PG_GETARG_INT32(10);
405 const int32 hop_seed = PG_GETARG_INT32(11);
406 if (hop_bound < 0 ||
407 static_cast<unsigned>(hop_bound) > ReachabilityCompiler::MAX_HOP_BOUND)
408 provsql_error("reachability: hop bound %d out of [0,%u]",
410
411 const auto sources = sourcesFromArgs(fcinfo, 6);
412
414 try {
416 rows, sources, directed, static_cast<unsigned>(hop_bound));
417 } catch (TreeDecompositionException &) {
419 "reachability: data treewidth exceeds the supported limit (%d)",
421 }
422
423 std::vector<gate_t> roots;
424 roots.reserve(all.roots.size() + all.within_roots.size());
425 for (const auto &vr : all.roots)
426 roots.push_back(vr.root);
427 for (const auto &vr : all.within_roots)
428 roots.push_back(vr.root);
429 const auto uuid_of = materializeCertifiedDD(all.dd, roots);
430
431 /* Dedup pre-creation: per vertex, the multiset of its length tokens
432 * *as the work table carries them* (i.e. wrapped in the 'absorptive'
433 * assumption marker, like every materialised root), sorted as text,
434 * addressed in the dedicated "plus-canonical" recipe namespace that
435 * provenance_plus probes (and never creates under, so a hit there is
436 * always a deliberate pre-creation). The aliased child is the DP's
437 * native within-bound root, wrapped in the same marker so the
438 * aggregated token refuses non-absorptive evaluation too. */
439 {
440 std::unordered_map<unsigned long, std::vector<std::string> > by_vertex;
441 for (const auto &vr : all.roots)
442 by_vertex[vr.vertex].push_back(
443 uuid2string(wrapAssumedAbsorptive(uuid_of.at(vr.root))));
444 for (const auto &vr : all.within_roots) {
445 auto it = by_vertex.find(vr.vertex);
446 if (it == by_vertex.end() || it->second.size() < 2)
447 continue;
448 std::vector<std::string> texts = it->second;
449 std::sort(texts.begin(), texts.end());
450 std::string name = "plus-canonical{";
451 for (std::size_t i = 0; i < texts.size(); ++i) {
452 if (i)
453 name += ",";
454 name += texts[i];
455 }
456 name += "}";
457 const pg_uuid_t dedup = provsqlUuidV5(name);
458 const pg_uuid_t within =
459 wrapAssumedAbsorptive(uuid_of.at(vr.root));
460 provsql_internal_create_gate(&dedup, gate_plus, 1, &within);
461 /* Route tag in info2, like every materialised root: this alias is a
462 * user-visible root too, and must report 'reachability' rather than
463 * the generic 'independent'. */
466 }
467 }
468
469 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
470 MemoryContext per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
471 MemoryContext oldcontext = MemoryContextSwitchTo(per_query_ctx);
472
473 TupleDesc tupdesc;
474 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
475 MemoryContextSwitchTo(oldcontext);
477 "reachability_materialize_hops: function must return a row type");
478 }
479 tupdesc = BlessTupleDesc(tupdesc);
480
481 Tuplestorestate *tupstore = tuplestore_begin_heap(
482 rsinfo->allowedModes & SFRM_Materialize_Random, false, work_mem);
483 rsinfo->returnMode = SFRM_Materialize;
484 rsinfo->setResult = tupstore;
485 rsinfo->setDesc = tupdesc;
486
487 for (const auto &vr : all.roots) {
488 Datum values[3];
489 bool nulls[3] = {false, false, false};
490 values[0] = Int32GetDatum(static_cast<int32>(vr.vertex));
491 values[1] = Int32GetDatum(hop_seed + static_cast<int32>(vr.hops));
492 pg_uuid_t *u = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
493 *u = wrapAssumedAbsorptive(uuid_of.at(vr.root));
494 values[2] = UUIDPGetDatum(u);
495 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
496 }
497
498 MemoryContextSwitchTo(oldcontext);
499 return (Datum) 0;
500 } catch (const std::exception &e) {
501 provsql_error("reachability_materialize_hops: %s", e.what());
502 } catch (...) {
503 provsql_error("reachability_materialize_hops: unknown exception");
504 }
505 PG_RETURN_NULL();
506}
507
508/**
509 * @brief PostgreSQL-callable entry point: per-group "some member
510 * reachable" compilation and materialisation.
511 *
512 * Arguments 0..9 as @c reachability_materialize, then two parallel
513 * arrays flattening the groups: @c group_ids @c int[] and
514 * @c member_vertices @c int[] (dense vertex IDs). For each distinct
515 * group, compiles the certified circuit of "some member vertex is
516 * reachable from a present source" (@c compileAnyReach: the
517 * set-reachability bit folded through the decomposition DP, so the
518 * disjunction over the group's *correlated* per-vertex events is
519 * deterministic by construction) and materialises it; returns one
520 * @c (group_id, token) row per group, each token wrapped in the
521 * @c 'absorptive' assumption marker. The caller plants each token
522 * under the canonical address of the group's per-vertex reach tokens,
523 * keeping cross-vertex aggregations ("is some vertex of this region
524 * reachable") on the linear certified route.
525 */
526Datum reachability_materialize_any(PG_FUNCTION_ARGS)
527{
528 try {
529 if (PG_ARGISNULL(9))
530 provsql_error("reachability: directed must not be NULL");
531
532 auto rows = edgesFromArgs(fcinfo, 4);
533 const bool directed = PG_GETARG_BOOL(9);
534 const auto sources = sourcesFromArgs(fcinfo, 6);
535
536 ArrayType *gids = PG_ARGISNULL(10) ? NULL : PG_GETARG_ARRAYTYPE_P(10);
537 ArrayType *gverts = PG_ARGISNULL(11) ? NULL : PG_GETARG_ARRAYTYPE_P(11);
538 const int ng = checkedArrayLength(gids, "group ids");
539 if (checkedArrayLength(gverts, "group member vertices") != ng)
540 provsql_error("reachability: group arrays must have the same length");
541 if (ng == 0)
542 provsql_error("reachability: at least one group member is required");
543
544 const int32 *gid_data = (const int32 *) ARR_DATA_PTR(gids);
545 const int32 *gv_data = (const int32 *) ARR_DATA_PTR(gverts);
546 std::map<int32, std::vector<unsigned long> > groups;
547 for (int i = 0; i < ng; ++i)
548 groups[gid_data[i]].push_back(static_cast<unsigned long>(gv_data[i]));
549 std::vector<int32> group_ids;
550 std::vector<std::vector<unsigned long> > sets;
551 group_ids.reserve(groups.size());
552 sets.reserve(groups.size());
553 for (auto &[gid, members] : groups) {
554 group_ids.push_back(gid);
555 sets.push_back(std::move(members));
556 }
557
558 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
559 MemoryContext per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
560 MemoryContext oldcontext = MemoryContextSwitchTo(per_query_ctx);
561
562 TupleDesc tupdesc;
563 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
564 MemoryContextSwitchTo(oldcontext);
566 "reachability_materialize_any: function must return a row type");
567 }
568 tupdesc = BlessTupleDesc(tupdesc);
569
570 Tuplestorestate *tupstore = tuplestore_begin_heap(
571 rsinfo->allowedModes & SFRM_Materialize_Random, false, work_mem);
572 rsinfo->returnMode = SFRM_Materialize;
573 rsinfo->setResult = tupstore;
574 rsinfo->setDesc = tupdesc;
575
576 // One shared compilation for all groups (the prelude and the
577 // seed-independent parts of the circuit are computed and emitted
578 // once), then one materialisation pass over all the roots (shared
579 // gates are walked once).
581 try {
582 all = ReachabilityCompiler::compileAnyReachAll(rows, sources, sets,
583 directed);
584 } catch (TreeDecompositionException &) {
585 MemoryContextSwitchTo(oldcontext);
587 "reachability: data treewidth exceeds the supported limit (%d)",
589 }
590 const auto uuid_of = materializeCertifiedDD(all.dd, all.roots);
591
592 for (std::size_t i = 0; i < group_ids.size(); ++i) {
593 Datum values[2];
594 bool nulls[2] = {false, false};
595 values[0] = Int32GetDatum(group_ids[i]);
596 pg_uuid_t *u = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
597 *u = wrapAssumedAbsorptive(uuid_of.at(all.roots[i]));
598 values[1] = UUIDPGetDatum(u);
599 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
600 }
601
602 MemoryContextSwitchTo(oldcontext);
603 return (Datum) 0;
604 } catch (const std::exception &e) {
605 provsql_error("reachability_materialize_any: %s", e.what());
606 } catch (...) {
607 provsql_error("reachability_materialize_any: unknown exception");
608 }
609 PG_RETURN_NULL();
610}
611
612/**
613 * @brief PostgreSQL-callable entry point: "every member vertex
614 * reachable" (k-terminal / coverage) compilation and
615 * materialisation.
616 *
617 * Arguments 0..9 as @c reachability_materialize, then
618 * @c member_vertices @c int[] (dense vertex IDs). Compiles the
619 * certified circuit of "every member vertex is reachable from a
620 * present source" (@c compileCoverReach: the pending rescuer-set
621 * antichain folded through the decomposition DP, so the conjunction
622 * over the members' *correlated* per-vertex events is deterministic
623 * by construction), materialises it, and returns its token, wrapped
624 * in the @c 'absorptive' assumption marker. The caller plants the
625 * token under the times-canonical address of the members' per-vertex
626 * reach tokens, keeping reachability self-join conjunctions ("are
627 * these k vertices all reachable") on the linear certified route --
628 * with the joint-worlds semantics: under nonnegative min-plus the
629 * token evaluates to the cost of the cheapest covering subgraph
630 * (directed Steiner cost), shared edges paid once.
631 */
632Datum reachability_materialize_cover(PG_FUNCTION_ARGS)
633{
634 try {
635 if (PG_ARGISNULL(9))
636 provsql_error("reachability: directed must not be NULL");
637
638 auto rows = edgesFromArgs(fcinfo, 4);
639 const bool directed = PG_GETARG_BOOL(9);
640 const auto sources = sourcesFromArgs(fcinfo, 6);
641
642 ArrayType *mverts = PG_ARGISNULL(10) ? NULL : PG_GETARG_ARRAYTYPE_P(10);
643 const int nm = checkedArrayLength(mverts, "member vertices");
644 if (nm == 0)
645 provsql_error("reachability: at least one member vertex is required");
646 const int32 *mv_data = (const int32 *) ARR_DATA_PTR(mverts);
647 std::vector<unsigned long> set;
648 set.reserve(nm);
649 for (int i = 0; i < nm; ++i)
650 set.push_back(static_cast<unsigned long>(mv_data[i]));
651
653 try {
654 all = ReachabilityCompiler::compileCoverReachAll(rows, sources, {set},
655 directed);
656 } catch (TreeDecompositionException &) {
658 "reachability: data treewidth exceeds the supported limit (%d)",
660 }
661 const auto uuid_of = materializeCertifiedDD(all.dd, all.roots);
662
663 pg_uuid_t *u = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
664 *u = wrapAssumedAbsorptive(uuid_of.at(all.roots[0]));
665 PG_RETURN_UUID_P(u);
666 } catch (const std::exception &e) {
667 provsql_error("reachability_materialize_cover: %s", e.what());
668 } catch (...) {
669 provsql_error("reachability_materialize_cover: unknown exception");
670 }
671 PG_RETURN_NULL();
672}
constexpr unsigned DNNF_CERT_INFO
d-DNNF certificate value for the (gate-type-specific) per-gate info field.
std::unordered_map< gate_t, pg_uuid_t, hash_gate_t > materializeCertifiedDD(const dDNNF &dd, const std::vector< gate_t > &roots, provsql_route route)
Materialise (the reachable part of) a certified d-D into the mmap store.
pg_uuid_t wrapAssumedAbsorptive(const pg_uuid_t &child)
Wrap a materialised root in the 'absorptive' assumption marker and return the wrapper's UUID.
pg_uuid_t provsqlUuidV5(const std::string &name)
RFC 4122 version-5 UUID in the ProvSQL namespace.
Content-addressed materialisation of a certified d-D into the mmap provenance store.
Decomposition-aligned compilation of two-terminal reachability over bounded-treewidth data into a d-D...
Fix macro conflicts between PostgreSQL headers and the C++ STL/Boost.
static AllHopsResult compileAllHops(const std::vector< EdgeRow > &rows, unsigned long source, bool directed, unsigned hop_bound, std::size_t max_states=DEFAULT_MAX_STATES)
Bounded-hop variant of compileAll(): per-(vertex, exact walk length) circuits for every length up to ...
static Result compile(const std::vector< EdgeRow > &rows, unsigned long source, unsigned long target, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Compile s-t reachability over rows into a d-D.
static constexpr unsigned MAX_HOP_BOUND
Maximum supported hop bound for compileAllHops().
static AnyReachAllResult compileAnyReachAll(const std::vector< EdgeRow > &rows, const std::vector< SourceArc > &sources, const std::vector< std::vector< unsigned long > > &sets, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Multi-set variant of compileAnyReach(): one shared circuit, one root per target set.
static AnyReachAllResult compileCoverReachAll(const std::vector< EdgeRow > &rows, const std::vector< SourceArc > &sources, const std::vector< std::vector< unsigned long > > &sets, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Multi-set variant of compileCoverReach(): one shared (content-deduplicated) circuit,...
static AllResult compileAll(const std::vector< EdgeRow > &rows, unsigned long source, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Compile the reachability circuits of every vertex in one pass.
Exception thrown when a tree decomposition cannot be constructed.
static constexpr int MAX_TREEWIDTH
Maximum supported treewidth.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
void provsql_internal_create_gate(const pg_uuid_t *token, gate_type type, unsigned nb_children, const pg_uuid_t *children_data)
Internal entry point behind create_gate(): cache + worker IPC.
void provsql_internal_set_infos(const pg_uuid_t *token, unsigned info1, unsigned info2)
Internal entry point behind set_infos(): worker IPC only.
Background worker and IPC primitives for mmap-backed circuit storage.
Shared-memory segment and inter-process pipe management.
Core types, constants, and utilities shared across ProvSQL.
@ PROVSQL_ROUTE_REACHABILITY
Recursive-reachability compiler (src/reachability_evaluate.cpp).
string uuid2string(pg_uuid_t uuid)
Format a pg_uuid_t as a std::string.
C++ utility functions for UUID manipulation.
Datum reachability_materialize_cover(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: "every member vertex reachable" (k-terminal / coverage) compil...
Datum reachability_materialize_any(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: per-group "some member reachable" compilation and materialisat...
Datum reachability_materialize(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: all-targets compilation and materialisation.
Datum reachability_compile_stats(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: probability plus compilation statistics.
Datum reachability_materialize_hops(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: bounded-hop all-targets compilation and materialisation.
Datum reachability_evaluate(PG_FUNCTION_ARGS)
PostgreSQL-callable entry point: exact reachability probability.
A bounded-hop all-targets compilation.
std::vector< VertexHopRoot > roots
Per (vertex, exact length) roots.
std::vector< VertexRoot > within_roots
Per-vertex "within the bound" roots.
An all-targets compilation: one shared d-D, one root per reachable vertex.
std::vector< VertexRoot > roots
One entry per vertex reachable in the all-edges-present world (including the source itself,...
dDNNF dd
Shared circuit (gates are reused across vertices).
A multi-set any-reach compilation: one shared circuit, one root per target set.
dDNNF dd
Shared circuit (consed: identical subcircuits are the same gate).
std::vector< gate_t > roots
One root per input set, in input order.
One row of the edge relation.
std::string token
Provenance token (UUID) of the edge tuple.
std::string block_key
Block-independent (BID) key variable (UUID) when the tuple is a mulinput alternative (e....
unsigned long src
Source vertex ID.
unsigned long dst
Destination vertex ID.
double prob
Probability of the edge tuple.
unsigned block_index
Outcome index within the block (the mulinput gate's info).
A compiled reachability query: the d-D and its statistics.
One source of a multi-source compilation.
bool certain
Always-present source (no gating variable).
std::string token
Provenance token of the source tuple (unused when certain).
double prob
Source-tuple probability (unused when certain).
unsigned long vertex
Source vertex.
UUID structure.