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 */
33extern "C"
34{
35#include "postgres.h"
36#include "fmgr.h"
37#include "catalog/pg_type.h"
38#include "utils/array.h"
39#include "utils/uuid.h"
40#include "provsql_utils.h"
41}
42
43#include <exception>
44#include <string>
45#include <unordered_set>
46#include <vector>
47
48#include "CircuitFromMMap.h"
49#include "GenericCircuit.h"
50#include "provsql_utils_cpp.h"
51
52extern "C"
53{
54PG_FUNCTION_INFO_V1(cmp_surviving_factors);
55}
56
57namespace {
58
59/** @brief Collect the provenance children of every @c gate_semimod under the
60 * @c gate_agg gates reachable from @p g (directly or under
61 * @c gate_arith): the group the comparison ranges over. */
62void collect_group_tokens(const GenericCircuit &gc, gate_t g,
63 std::unordered_set<gate_t> &out,
64 std::unordered_set<gate_t> &seen)
65{
66 if(!seen.insert(g).second)
67 return;
68
69 const gate_type t = gc.getGateType(g);
70
71 if(t == gate_agg) {
72 for(gate_t ch : gc.getWires(g)) {
73 if(gc.getGateType(ch) != gate_semimod)
74 continue;
75 const auto &sm = gc.getWires(ch);
76 if(sm.size() == 2)
77 out.insert(sm[0]); // [k_gate, value_gate]
78 }
79 return;
80 }
81
82 /* A HAVING with Boolean connectives lifts to a product / sum / difference
83 * of comparison gates, so the groups being compared sit under that
84 * structure, not directly under a single cmp. */
85 if(t == gate_arith || t == gate_cmp || t == gate_times ||
86 t == gate_plus || t == gate_monus || t == gate_delta)
87 for(gate_t ch : gc.getWires(g))
88 collect_group_tokens(gc, ch, out, seen);
89}
90
91/** @brief Whether @p g is a δ collapsing exactly the group @p group. */
92bool delta_subsumed_by(const GenericCircuit &gc, gate_t g,
93 const std::unordered_set<gate_t> &group)
94{
95 if(gc.getGateType(g) != gate_delta || group.empty())
96 return false;
97
98 const auto &dw = gc.getWires(g);
99 if(dw.size() != 1)
100 return false;
101
102 // The δ wraps the group's ⊕; a one-row group may carry that row's token
103 // directly, with no ⊕ to wrap.
104 std::vector<gate_t> operands;
105 if(gc.getGateType(dw[0]) == gate_plus) {
106 const auto &pw = gc.getWires(dw[0]);
107 operands.assign(pw.begin(), pw.end());
108 } else {
109 operands.push_back(dw[0]);
110 }
111
112 if(operands.size() != group.size())
113 return false;
114 for(gate_t o : operands)
115 if(group.find(o) == group.end())
116 return false;
117 return true;
118}
119
120/** @brief Append the factors of @p g that survive the comparison.
121 *
122 * A ⊗ is flattened so a δ nested inside it can be dropped on its own; a
123 * subsumed δ contributes nothing; anything else stands as one factor. */
124void surviving_factors(const GenericCircuit &gc, gate_t g,
125 const std::unordered_set<gate_t> &group,
126 std::vector<gate_t> &out)
127{
128 if(delta_subsumed_by(gc, g, group))
129 return;
130
131 if(gc.getGateType(g) == gate_times) {
132 for(gate_t ch : gc.getWires(g))
133 surviving_factors(gc, ch, group, out);
134 return;
135 }
136
137 out.push_back(g);
138}
139
140} // namespace
141
142/**
143 * @brief @c cmp_surviving_factors(tokens uuid[], cmp uuid) -> uuid[]
144 *
145 * Given the row-annotation factors at the level owning a lifted comparison and
146 * that comparison's gate, returns the factors the comparison does not subsume,
147 * for the caller to multiply with it. NULL entries are dropped.
148 */
149Datum cmp_surviving_factors(PG_FUNCTION_ARGS)
150{
151 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
152 PG_RETURN_NULL();
153
154 try {
155 ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
156 pg_uuid_t cmp = *DatumGetUUIDP(PG_GETARG_DATUM(1));
157 Datum *elems;
158 bool *nulls;
159 int nelems;
160
161 if(ARR_NDIM(arr) > 1)
162 provsql_error("cmp_surviving_factors: tokens must be a 1-D array");
163
164 deconstruct_array(arr, UUIDOID, 16, false, 'c', &elems, &nulls, &nelems);
165
166 /* The row tokens are siblings of the comparison, not its descendants, so
167 * they must be loaded into one circuit with it: only then do a delta and
168 * the aggregate's semimod wires resolve to the same gate_t and become
169 * comparable. */
170 std::vector<pg_uuid_t> roots;
171 roots.push_back(cmp);
172 for(int i = 0; i < nelems; ++i) {
173 if(nulls[i])
174 continue;
175 roots.push_back(*DatumGetUUIDP(elems[i]));
176 }
177
178 std::vector<gate_t> gates;
179 GenericCircuit gc = getJointCircuit(roots, gates);
180
181 std::unordered_set<gate_t> group, seen;
182 collect_group_tokens(gc, gates[0], group, seen);
183
184 std::vector<Datum> kept;
185 std::unordered_set<gate_t> emitted;
186
187 for(std::size_t r = 1; r < gates.size(); ++r) {
188 std::vector<gate_t> factors;
189 surviving_factors(gc, gates[r], group, factors);
190 for(gate_t f : factors) {
191 if(!emitted.insert(f).second)
192 continue; // one copy of a factor shared by two inputs
193 pg_uuid_t out = string2uuid(gc.getUUID(f));
194 pg_uuid_t *p = (pg_uuid_t *) palloc(sizeof(pg_uuid_t));
195 *p = out;
196 kept.push_back(UUIDPGetDatum(p));
197 }
198 }
199
200 {
201 ArrayType *res = construct_array(kept.data(), (int) kept.size(),
202 UUIDOID, 16, false, 'c');
203 PG_RETURN_ARRAYTYPE_P(res);
204 }
205 } catch(const std::exception &e) {
206 provsql_error("cmp_surviving_factors: %s", e.what());
207 } catch(...) {
208 provsql_error("cmp_surviving_factors: Unknown exception");
209 }
210
211 PG_RETURN_NULL(); // unreachable: provsql_error does not return
212}
GenericCircuit getJointCircuit(const std::vector< pg_uuid_t > &tokens, std::vector< gate_t > &gates)
Multi-root variant of getJointCircuit.
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.
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
In-memory provenance circuit with semiring-generic evaluation.
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.
Core types, constants, and utilities shared across ProvSQL.
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)
pg_uuid_t string2uuid(const string &source)
Parse a UUID string into a pg_uuid_t.
C++ utility functions for UUID manipulation.
UUID structure.