ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
cmp_supersede.cpp
Go to the documentation of this file.
1/**
2 * @file cmp_supersede.cpp
3 * @brief SQL function @c provsql.cmp_surviving_factors() – the factors of a
4 * row annotation that an aggregate comparison does *not* subsume.
5 *
6 * When a comparison on an aggregate is lifted into the provenance circuit, its
7 * @c gate_cmp already entails that the compared group exists: the enumeration
8 * behind it ranges over the non-empty worlds of the very same per-row tokens.
9 * So the comparison supersedes the group's @c gate_delta rather than
10 * multiplying with it -- conjoining both would count group existence twice in
11 * a non-idempotent semiring.
12 *
13 * What it supersedes is precisely that δ, though, and not whatever else the
14 * row annotation happens to carry. A row token reaching the level that owns
15 * the comparison may be
16 * - the bare δ (the plain @c gamma then sigma shape),
17 * - a ⊗ mixing the δ with other factors (a view or CTE holding gamma joined
18 * with another relation), whose other factors must survive,
19 * - or something else entirely -- an earlier comparison's @c gate_cmp on the
20 * same group (sigma after sigma), an input -- which the new comparison
21 * does not subsume at all and which must be kept and multiplied.
22 * Dropping the whole annotation is right only in the first case; this walk
23 * distinguishes them structurally.
24 *
25 * A δ is subsumed when its ⊕ child's operands are exactly the provenance
26 * children of the compared aggregate's @c gate_semimod wires -- that is, when
27 * it collapses the multiplicity of the very group the comparison ranges over.
28 *
29 * The function is read-only: it returns the surviving factors flattened, and
30 * the caller rebuilds the product with @c provenance_times, so no gate is
31 * minted here.
32 *
33 * The walk needs the type and the children of a few gates, all of which this
34 * backend created moments earlier in the same query (the comparison, the
35 * aggregates under it, their semimod wires, the row tokens and their δ and
36 * ⊕). They are read one by one through @c provsql_fetch_gate, which answers
37 * from the per-session cache and asks the worker only on a miss; loading the
38 * subcircuit under all these roots at once, as was done before, cost a
39 * synchronous round trip and a serialisation per group.
40 */
41extern "C"
42{
43#include "postgres.h"
44#include "fmgr.h"
45#include "catalog/pg_type.h"
46#include "utils/array.h"
47#include "utils/uuid.h"
48#include "provsql_utils.h"
49#include "provsql_mmap.h"
50}
51
52#include <cstdlib>
53#include <cstring>
54#include <exception>
55#include <functional>
56#include <string>
57#include <unordered_map>
58#include <unordered_set>
59#include <vector>
60
61extern "C"
62{
63PG_FUNCTION_INFO_V1(cmp_surviving_factors);
64}
65
66namespace {
67
68struct UuidHash {
69 std::size_t operator()(const pg_uuid_t &u) const {
70 std::size_t h;
71 std::memcpy(&h, u.data, sizeof(h));
72 return h;
73 }
74};
75struct UuidEq {
76 bool operator()(const pg_uuid_t &a, const pg_uuid_t &b) const {
77 return std::memcmp(a.data, b.data, UUID_LEN) == 0;
78 }
79};
80using UuidSet = std::unordered_set<pg_uuid_t, UuidHash, UuidEq>;
81
82/** @brief A gate as the walk sees it: its type and its children, fetched
83 * once per call. */
84struct Gate {
85 gate_type type;
86 std::vector<pg_uuid_t> wires;
87};
88
89class Gates {
90public:
91 const Gate &operator[](const pg_uuid_t &u) {
92 auto it = memo.find(u);
93 if(it != memo.end())
94 return it->second;
95 Gate g;
96 unsigned n = 0;
97 pg_uuid_t *children = nullptr;
98 g.type = provsql_fetch_gate(&u, &n, &children);
99 if(children) {
100 g.wires.assign(children, children + n);
101 free(children);
102 }
103 return memo.emplace(u, std::move(g)).first->second;
104 }
105private:
106 std::unordered_map<pg_uuid_t, Gate, UuidHash, UuidEq> memo;
107};
108
109/** @brief Collect the provenance children of every @c gate_semimod under the
110 * @c gate_agg gates reachable from @p g (directly or under
111 * @c gate_arith): the group the comparison ranges over. */
112void collect_group_tokens(Gates &gates, const pg_uuid_t &g,
113 UuidSet &out, UuidSet &seen)
114{
115 if(!seen.insert(g).second)
116 return;
117
118 const Gate &gate = gates[g];
119
120 if(gate.type == gate_agg) {
121 for(const pg_uuid_t &ch : gate.wires) {
122 const Gate &sm = gates[ch];
123 if(sm.type != gate_semimod)
124 continue;
125 if(sm.wires.size() == 2)
126 out.insert(sm.wires[0]); // [k_gate, value_gate]
127 }
128 return;
129 }
130
131 /* A HAVING with Boolean connectives lifts to a product / sum / difference
132 * of comparison gates, so the groups being compared sit under that
133 * structure, not directly under a single cmp. */
134 const gate_type t = gate.type;
135 if(t == gate_arith || t == gate_cmp || t == gate_times ||
136 t == gate_plus || t == gate_monus || t == gate_delta) {
137 const std::vector<pg_uuid_t> wires = gate.wires; // the memo may grow
138 for(const pg_uuid_t &ch : wires)
139 collect_group_tokens(gates, ch, out, seen);
140 }
141}
142
143/** @brief Whether @p g is a δ collapsing exactly the group @p group. */
144bool delta_subsumed_by(Gates &gates, const pg_uuid_t &g, const UuidSet &group)
145{
146 const Gate &d = gates[g];
147 if(d.type != gate_delta || group.empty())
148 return false;
149 if(d.wires.size() != 1)
150 return false;
151
152 // The δ wraps the group's ⊕; a one-row group may carry that row's token
153 // directly, with no ⊕ to wrap.
154 const pg_uuid_t child = d.wires[0];
155 std::vector<pg_uuid_t> operands;
156 const Gate &c = gates[child];
157 if(c.type == gate_plus)
158 operands = c.wires;
159 else
160 operands.push_back(child);
161
162 if(operands.size() != group.size())
163 return false;
164 for(const pg_uuid_t &o : operands)
165 if(group.find(o) == group.end())
166 return false;
167 return true;
168}
169
170/** @brief Append the factors of @p g that survive the comparison.
171 *
172 * A ⊗ is flattened so a δ nested inside it can be dropped on its own; a
173 * subsumed δ contributes nothing; anything else stands as one factor. */
174void surviving_factors(Gates &gates, const pg_uuid_t &g, const UuidSet &group,
175 std::vector<pg_uuid_t> &out)
176{
177 if(delta_subsumed_by(gates, g, group))
178 return;
179
180 if(gates[g].type == gate_times) {
181 const std::vector<pg_uuid_t> wires = gates[g].wires;
182 for(const pg_uuid_t &ch : wires)
183 surviving_factors(gates, ch, group, out);
184 return;
185 }
186
187 out.push_back(g);
188}
189
190} // namespace
191
192/**
193 * @brief @c cmp_surviving_factors(tokens uuid[], cmp uuid) -> uuid[]
194 *
195 * Given the row-annotation factors at the level owning the comparison and
196 * the lifted comparison gate, returns the factors the comparison does not
197 * subsume, flattened; NULL on a NULL argument.
198 */
199Datum cmp_surviving_factors(PG_FUNCTION_ARGS)
200{
201 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
202 PG_RETURN_NULL();
203
204 try {
205 ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
206 const pg_uuid_t cmp = *DatumGetUUIDP(PG_GETARG_DATUM(1));
207 Datum *elems;
208 bool *nulls;
209 int nelems;
210
211 if(ARR_NDIM(arr) > 1)
212 provsql_error("cmp_surviving_factors: tokens must be a 1-D array");
213
214 deconstruct_array(arr, UUIDOID, 16, false, 'c', &elems, &nulls, &nelems);
215
216 Gates gates;
217 UuidSet group, seen;
218 collect_group_tokens(gates, cmp, group, seen);
219
220 std::vector<Datum> kept;
221 UuidSet emitted;
222
223 for(int i = 0; i < nelems; ++i) {
224 if(nulls[i])
225 continue;
226 std::vector<pg_uuid_t> factors;
227 surviving_factors(gates, *DatumGetUUIDP(elems[i]), group, factors);
228 for(const pg_uuid_t &f : factors) {
229 if(!emitted.insert(f).second)
230 continue; // one copy of a factor shared by two inputs
231 pg_uuid_t *p = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
232 *p = f;
233 kept.push_back(UUIDPGetDatum(p));
234 }
235 }
236
237 {
238 ArrayType *res = construct_array(kept.data(), (int) kept.size(),
239 UUIDOID, 16, false, 'c');
240 PG_RETURN_ARRAYTYPE_P(res);
241 }
242 } catch(const std::exception &e) {
243 provsql_error("cmp_surviving_factors: %s", e.what());
244 } catch(...) {
245 provsql_error("cmp_surviving_factors: Unknown exception");
246 }
247
248 PG_RETURN_NULL(); // unreachable: provsql_error does not return
249}
Datum cmp_surviving_factors(PG_FUNCTION_ARGS)
cmp_surviving_factors(tokens uuid[], cmp uuid) -> uuid[]
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
gate_type provsql_fetch_gate(const pg_uuid_t *token, unsigned *nb_children_out, pg_uuid_t **children_out)
PostgreSQL-callable wrapper for get_gate_type().
Background worker and IPC primitives for mmap-backed circuit storage.
Core types, constants, and utilities shared across ProvSQL.
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)