ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
CertifiedDDMaterialize.cpp
Go to the documentation of this file.
1/**
2 * @file CertifiedDDMaterialize.cpp
3 * @brief Implementation of certified-d-D materialisation into the store.
4 *
5 * See @c CertifiedDDMaterialize.h. Extracted from the reachability
6 * compiler's SQL glue so the joint-width UCQ compiler shares the same
7 * content-addressed materialisation (and so probability / Shapley /
8 * expectation all go through the standard @c probability_evaluate path on
9 * the materialised token).
10 */
11extern "C" {
12#include "postgres.h"
13#include "miscadmin.h"
14#include "utils/uuid.h"
15
16#include "provsql_utils.h"
17#include "provsql_mmap.h"
18}
19
21#include "BooleanCircuit.h"
22#include "provsql_utils_cpp.h"
23
24#include <algorithm>
25#include <cstdint>
26#include <string>
27#include <unordered_set>
28#include <vector>
29
30namespace {
31
32/**
33 * @brief Minimal SHA-1 (RFC 3174), used for RFC 4122 version-5 UUIDs.
34 *
35 * Self-contained so the content addressing builds on every supported
36 * PostgreSQL version.
37 */
38void sha1(const unsigned char *data, std::size_t len, unsigned char out[20])
39{
40 uint32_t h[5] = {0x67452301u, 0xEFCDAB89u, 0x98BADCFEu, 0x10325476u,
41 0xC3D2E1F0u};
42 const std::size_t total = ((len + 8) / 64 + 1) * 64;
43 std::vector<unsigned char> buf(total, 0);
44 std::copy(data, data + len, buf.begin());
45 buf[len] = 0x80;
46 const uint64_t bits = static_cast<uint64_t>(len) * 8;
47 for (int i = 0; i < 8; ++i)
48 buf[total - 1 - i] = static_cast<unsigned char>(bits >> (8 * i));
49
50 for (std::size_t chunk = 0; chunk < total; chunk += 64) {
51 uint32_t w[80];
52 for (int i = 0; i < 16; ++i)
53 w[i] = (static_cast<uint32_t>(buf[chunk + 4*i]) << 24) |
54 (static_cast<uint32_t>(buf[chunk + 4*i + 1]) << 16) |
55 (static_cast<uint32_t>(buf[chunk + 4*i + 2]) << 8) |
56 static_cast<uint32_t>(buf[chunk + 4*i + 3]);
57 for (int i = 16; i < 80; ++i) {
58 const uint32_t v = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
59 w[i] = (v << 1) | (v >> 31);
60 }
61 uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
62 for (int i = 0; i < 80; ++i) {
63 uint32_t f, k;
64 if (i < 20) {
65 f = (b & c) | (~b & d);
66 k = 0x5A827999u;
67 } else if (i < 40) {
68 f = b ^ c ^ d;
69 k = 0x6ED9EBA1u;
70 } else if (i < 60) {
71 f = (b & c) | (b & d) | (c & d);
72 k = 0x8F1BBCDCu;
73 } else {
74 f = b ^ c ^ d;
75 k = 0xCA62C1D6u;
76 }
77 const uint32_t tmp = ((a << 5) | (a >> 27)) + f + e + k + w[i];
78 e = d;
79 d = c;
80 c = (b << 30) | (b >> 2);
81 b = a;
82 a = tmp;
83 }
84 h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e;
85 }
86 for (int i = 0; i < 5; ++i) {
87 out[4*i] = static_cast<unsigned char>(h[i] >> 24);
88 out[4*i + 1] = static_cast<unsigned char>(h[i] >> 16);
89 out[4*i + 2] = static_cast<unsigned char>(h[i] >> 8);
90 out[4*i + 3] = static_cast<unsigned char>(h[i]);
91 }
92}
93
94} // namespace
95
96extern "C" void provsql_sha1(const unsigned char *data, size_t len,
97 unsigned char out[20])
98{
99 sha1(data, len, out);
100}
101
102pg_uuid_t provsqlUuidV5(const std::string &name)
103{
104 // uuid_ns_provsql() = 920d4f02-8718-5319-9532-d4ab83a64489
105 static const unsigned char ns[16] = {
106 0x92, 0x0d, 0x4f, 0x02, 0x87, 0x18, 0x53, 0x19,
107 0x95, 0x32, 0xd4, 0xab, 0x83, 0xa6, 0x44, 0x89
108 };
109 std::vector<unsigned char> data(16 + name.size());
110 std::copy(ns, ns + 16, data.begin());
111 std::copy(name.begin(), name.end(), data.begin() + 16);
112 unsigned char digest[20];
113 sha1(data.data(), data.size(), digest);
114 pg_uuid_t u;
115 std::copy(digest, digest + 16, u.data);
116 u.data[6] = static_cast<unsigned char>((u.data[6] & 0x0F) | 0x50);
117 u.data[8] = static_cast<unsigned char>((u.data[8] & 0x3F) | 0x80);
118 return u;
119}
120
121std::unordered_map<gate_t, pg_uuid_t, hash_gate_t> materializeCertifiedDD(
122 const dDNNF &dd, const std::vector<gate_t> &roots, provsql_route route)
123{
124 std::unordered_map<gate_t, pg_uuid_t, hash_gate_t> uuid_of;
125 /* Tokens this backend has already materialised, across calls: the
126 * store is append-only, so a create this backend has sent once never
127 * needs re-sending. Per-backend, bounded. */
128 static std::unordered_set<std::string> created;
129 constexpr std::size_t kCreatedCap = 4u << 20;
130 if (created.size() > kCreatedCap)
131 created.clear();
132 pg_uuid_t one_uuid;
133 bool have_one = false;
134
135 const auto ensureOne = [&]() {
136 if (!have_one) {
139 0, NULL);
140 have_one = true;
141 }
142 return one_uuid;
143 };
144 const auto createOnce = [&](const pg_uuid_t &token, gate_type type,
145 const std::vector<pg_uuid_t> &children,
146 bool certified) {
147 const std::string key = uuid2string(token);
148 if (!created.insert(key).second)
149 return;
150 /* The certificate goes with the gate, in one
151 * unanswered message: any route certifying
152 * this shape records the same thing. */
154 &token, type,
155 static_cast<unsigned>(children.size()),
156 children.empty() ? NULL : children.data(),
157 certified, DNNF_CERT_INFO, 0, NULL);
158 };
159
160 std::vector<gate_t> stack(roots);
161 while (!stack.empty()) {
162 CHECK_FOR_INTERRUPTS();
163 const gate_t g = stack.back();
164 if (uuid_of.find(g) != uuid_of.end()) {
165 stack.pop_back();
166 continue;
167 }
168
169 const auto t = dd.getGateType(g);
170
171 if (t == BooleanGate::IN || t == BooleanGate::MULIN) {
172 const std::string tok = dd.getUUID(g);
173 if (!tok.empty() && tok[0] == '\x01') {
174 // A synthetic stick-breaking coin: the joint-width compiler
175 // introduces these when it expands a repair_key / BID exclusion
176 // block into shared independent events; they carry a probability
177 // but no store UUID. Materialise each as an independent
178 // gate_input with that probability. The UUID is content-addressed
179 // on the synthetic token, which names the block and the split
180 // inside it (see UCQJointCompiler::emitInput), so the coins shared
181 // by a block's values collapse to one store gate -- that sharing
182 // is the mutual exclusion -- while coins of different blocks, or
183 // of the same block with different masses, stay apart.
184 const pg_uuid_t token = provsqlUuidV5(tok);
185 if (created.insert(uuid2string(token)).second) {
187 double existing = 0.;
188 if (provsql_internal_set_prob(&token, dd.getProb(g), &existing)
190 provsql_error("stick-breaking coin %s already carries "
191 "probability %g, not the %g this compilation "
192 "needs: two distinct coins hashed to one gate",
193 uuid2string(token).c_str(), existing,
194 dd.getProb(g));
195 }
196 uuid_of[g] = token;
197 } else {
198 // Existing tokens (the leaf provenance): never re-created.
199 uuid_of[g] = string2uuid(tok);
200 }
201 stack.pop_back();
202 continue;
203 }
204
205 bool ready = true;
206 for (const auto &c : dd.getWires(g))
207 if (uuid_of.find(c) == uuid_of.end()) {
208 stack.push_back(c);
209 ready = false;
210 }
211 if (!ready)
212 continue;
213
214 const bool certified = dd.isDNNFCertified(g);
215 pg_uuid_t token;
216
217 switch (t) {
218 case BooleanGate::NOT:
219 {
220 const pg_uuid_t child = uuid_of[dd.getWires(g)[0]];
221 const pg_uuid_t one = ensureOne();
222 token = provsqlUuidV5("monus" + uuid2string(one) + uuid2string(child));
223 createOnce(token, gate_monus, {one, child}, false);
224 }
225 break;
226
227 case BooleanGate::AND:
228 case BooleanGate::OR:
229 {
230 const auto &wires = dd.getWires(g);
231 if (wires.empty()) {
232 if (t == BooleanGate::AND)
233 token = ensureOne();
234 else {
236 createOnce(token, gate_zero, {}, false);
237 }
238 } else if (wires.size() == 1) {
239 token = uuid_of[wires[0]];
240 } else {
241 std::vector<std::string> texts;
242 texts.reserve(wires.size());
243 for (const auto &c : wires)
244 texts.push_back(uuid2string(uuid_of[c]));
245 std::sort(texts.begin(), texts.end());
246 std::string name = (t == BooleanGate::AND ? "times{" : "plus{");
247 for (std::size_t i = 0; i < texts.size(); ++i) {
248 if (i)
249 name += ",";
250 name += texts[i];
251 }
252 name += "}";
253 token = provsqlUuidV5(name);
254 std::vector<pg_uuid_t> children;
255 children.reserve(wires.size());
256 for (const auto &c : wires)
257 children.push_back(uuid_of[c]);
258 createOnce(token, t == BooleanGate::AND ? gate_times : gate_plus,
259 children, certified);
260 }
261 }
262 break;
263
264 default:
265 provsql_error("materializeCertifiedDD: unsupported gate type");
266 throw std::runtime_error("unreachable");
267 }
268
269 uuid_of[g] = token;
270 stack.pop_back();
271 }
272
273 /* Name the producing route on each materialised root, so the probability
274 * dispatcher can report the route as its own evaluation method instead of
275 * the generic 'independent'. The name goes on a transparent annotation
276 * wrapper of the root, "route:<name>", not on the root itself: a root is
277 * addressed by its shape, and another route, or the ordinary rewriting,
278 * may build the same gate; the wrapper's address hashes the name, so it
279 * records nothing that does not follow from its address. Only a certified
280 * multi-wire AND/OR root is wrapped: a root that collapsed to a bare leaf
281 * token, to a shared constant or to a single child is not a gate this
282 * materialisation owns. The map then points the root at its wrapper. */
283 if (route != PROVSQL_ROUTE_NONE)
284 for (const auto &r : roots) {
285 const auto t = dd.getGateType(r);
286 if ((t != BooleanGate::AND && t != BooleanGate::OR)
287 || dd.getWires(r).size() < 2 || !dd.isDNNFCertified(r))
288 continue;
289 const auto it = uuid_of.find(r);
290 if (it != uuid_of.end())
291 it->second = wrapRoute(it->second, route);
292 }
293
294 return uuid_of;
295}
296
298{
299 static std::unordered_set<std::string> created;
300 constexpr std::size_t kCreatedCap = 1u << 20;
301 if (created.size() > kCreatedCap)
302 created.clear();
303
304 const std::string text = std::string("route:") + provsql_route_name(route);
305 const pg_uuid_t token =
306 provsqlUuidV5("annotation" + uuid2string(child) + text);
307 if (created.insert(uuid2string(token)).second)
309 false, 0, 0, text.c_str());
310 return token;
311}
312
314{
315 static std::unordered_set<std::string> created;
316 constexpr std::size_t kCreatedCap = 1u << 20;
317 if (created.size() > kCreatedCap)
318 created.clear();
319
320 const pg_uuid_t token =
321 provsqlUuidV5("assumedabsorptive" + uuid2string(child));
322 if (created.insert(uuid2string(token)).second) {
323 /* The route tag, not the assumption kind, is what identifies this as the
324 * reachability compiler's output (see provsql_route). */
327 "absorptive");
328 }
329 return token;
330}
Boolean provenance circuit with support for knowledge compilation.
constexpr unsigned DNNF_CERT_INFO
d-DNNF certificate value for the (gate-type-specific) per-gate info field.
@ NOT
Logical negation of a single child gate.
@ OR
Logical disjunction of child gates.
@ AND
Logical conjunction of child gates.
@ IN
Input (variable) gate representing a base tuple.
@ MULIN
Multivalued-input gate (one of several options).
pg_uuid_t wrapRoute(const pg_uuid_t &child, provsql_route route)
Wrap a materialised root in a transparent annotation naming the route that produced it,...
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.
void provsql_sha1(const unsigned char *data, size_t len, unsigned char out[20])
SHA-1 of len bytes of data (RFC 3174).
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.
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
bool isDNNFCertified(gate_t g) const
Is gate g certified by the d-DNNF per-gate marking?
double getProb(gate_t g) const
Return the probability stored for gate g.
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
A d-DNNF circuit supporting exact probabilistic and game-theoretic evaluation.
Definition dDNNF.h:71
#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_create_gate_with(const pg_uuid_t *token, gate_type type, unsigned nb_children, const pg_uuid_t *children, bool has_infos, unsigned info1, unsigned info2, const char *extra)
Create a gate together with its infos and its text, in one message that is not answered.
provsql_set_prob_result provsql_internal_set_prob(const pg_uuid_t *token, double prob, double *existing)
Write a gate's probability from in-extension C/C++ code.
Background worker and IPC primitives for mmap-backed circuit storage.
@ PROVSQL_SET_PROB_ALREADY_SET
Holds a different value; refused.
Core types, constants, and utilities shared across ProvSQL.
#define PROVSQL_GATE_ZERO_UUID
UUID of the semiring zero gate: the result of the SQL function gate_zero(), created with the extensio...
#define PROVSQL_GATE_ONE_UUID
UUID of the semiring one gate, the result of gate_one().
@ gate_annotation
Transparent single-child wrapper carrying a query-level annotation in extra (inversion-free certifica...
@ gate_assumed
Structural marker over a single child whose sub-circuit was computed under a Boolean-provenance assum...
provsql_route
Tags identifying the planner-time route that produced a circuit.
@ PROVSQL_ROUTE_NONE
No route rewrite: ordinary lineage.
@ PROVSQL_ROUTE_REACHABILITY
Recursive-reachability compiler (src/reachability_evaluate.cpp).
static const char * provsql_route_name(provsql_route route)
The name of a route, as the probability dispatcher reports it and as the annotation wrapper of a mate...
pg_uuid_t string2uuid(const string &source)
Parse a UUID string into a pg_uuid_t.
string uuid2string(pg_uuid_t uuid)
Format a pg_uuid_t as a std::string.
C++ utility functions for UUID manipulation.