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