ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
MMappedCircuit.cpp
Go to the documentation of this file.
1/**
2 * @file MMappedCircuit.cpp
3 * @brief Persistent mmap-backed circuit: implementation and background-worker entry points.
4 *
5 * Implements the @c MMappedCircuit methods declared in @c MMappedCircuit.h,
6 * the @c createGenericCircuit() free function, and the background-worker
7 * entry points declared in @c provsql_mmap.h:
8 *
9 * - @c initialize_provsql_mmap(): called by the background worker at
10 * startup; the per-database @c MMappedCircuit instances themselves are
11 * opened lazily, on the first message for their database.
12 * - @c destroy_provsql_mmap(): called on shutdown; syncs and deletes the
13 * singleton.
14 * - @c provsql_mmap_main_loop(): the worker's main loop; receives gate-
15 * creation messages from backends over the IPC pipe and writes them
16 * to the mmap store.
17 *
18 * The @c createGenericCircuit() function performs a BFS from a root UUID,
19 * reading gates from the mmap store and building an in-memory @c GenericCircuit.
20 */
21#include <cerrno>
22#include <cmath>
23#include <map>
24#include <sstream>
25#include <string>
26
27#include "MMappedCircuit.h"
28#include "GenericCircuit.h"
29#include "Circuit.hpp"
30#include "provsql_utils_cpp.h"
31
32#include <poll.h>
33#include <fcntl.h>
34#include <unistd.h>
35#include <cstring>
36
37extern "C" {
38#include "miscadmin.h"
39#include "common/relpath.h"
40#include "utils/palloc.h"
41#include "provsql_mmap.h"
42#include "provsql_shmem.h"
43}
44
45/** @brief Per-database mmap-backed provenance circuits, keyed by database OID. */
46static std::map<Oid, MMappedCircuit*> circuits;
47
48/** @brief Whether any circuit has been written to since the last flush.
49 *
50 * The store's bytes reach the kernel as soon as a message is applied, but
51 * they reach the disk only when someone forces them; a machine that loses
52 * power in between loses whatever the kernel had not written back. The
53 * worker forces them a short while after the last write (see the main
54 * loop below), which bounds that loss the way
55 * @c synchronous_commit @c = @c off bounds the heap's. */
56static bool store_dirty = false;
57
58std::string MMappedCircuit::storePath(Oid db_oid, Oid db_tablespace,
59 const char *filename)
60{
61 /* GetDatabasePath yields the directory relative to the data directory:
62 "base/<oid>" for the default tablespace, and
63 "pg_tblspc/<ts>/PG_<ver>_<cat>/<oid>" for a database created in (or
64 moved to) another one. Resolving it here, rather than hardcoding
65 base/, is what makes the store follow CREATE DATABASE ... TABLESPACE
66 and ALTER DATABASE ... SET TABLESPACE. */
67 char *rel = GetDatabasePath(db_oid, db_tablespace);
68 std::string path = std::string(DataDir) + "/" + rel + "/" + filename;
69 pfree(rel);
70 return path;
71}
72
73MMappedCircuit::MMappedCircuit(Oid db_oid, Oid db_tablespace, bool read_only) :
75 storePath(db_oid, db_tablespace, MAPPING_FILENAME),
76 storePath(db_oid, db_tablespace, GATES_FILENAME),
77 storePath(db_oid, db_tablespace, WIRES_FILENAME),
78 storePath(db_oid, db_tablespace, EXTRA_FILENAME),
79 read_only) {}
80
82{
83 /* circuits are opened lazily on first IPC message */
84}
85
87{
88 for(auto &kv: circuits)
89 delete kv.second;
90 circuits.clear();
91}
92
93/** @brief Add a gate record and only then make it reachable under @p token.
94 *
95 * The record has to be complete before the mapping entry that points at
96 * it exists: killed between the two, the store keeps an unreferenced
97 * record, which costs nothing, whereas the other order leaves a mapping
98 * entry indexing past the end of the record vector and every gate
99 * created afterwards resolves one slot off, for the rest of the store's
100 * life. */
102 const std::vector<pg_uuid_t> &children)
103{
104 const unsigned long idx = gates.nbElements();
105 const unsigned long wires_idx = wires.nbElements();
106 for(const auto &c: children)
107 wires.add(c);
108 gates.add({type, static_cast<unsigned>(children.size()), wires_idx});
109 mapping.publish(token, idx);
110}
111
113 pg_uuid_t token, gate_type type, const std::vector<pg_uuid_t> &children)
114{
115 auto idx = mapping[token];
117 // The gate may have been lazy-added as a default gate_input below
118 // (when an earlier-arriving parent createGate referenced it as a
119 // child whose own createGate had not yet been received). Under
120 // concurrent backends, parent/child IPCs from different sessions
121 // can be interleaved such that the parent's lazy-add wins and the
122 // real create for the child is then silently dropped. Detect that
123 // case and upgrade the placeholder in place; otherwise leave the
124 // existing gate alone (real duplicate creation, idempotent).
125 bool placeholder = gates[idx].type == gate_input
126 && gates[idx].nb_children == 0;
127 bool real_create = type != gate_input || !children.empty();
128 if(placeholder && real_create) {
129 const unsigned long wires_idx = wires.nbElements();
130 for(const auto &c: children)
131 wires.add(c);
132 // Wires first, then the record's own fields: an upgrade seen
133 // half-done would otherwise claim children that are not there yet.
134 gates[idx].children_idx = wires_idx;
135 gates[idx].nb_children = static_cast<unsigned>(children.size());
136 gates[idx].type = type;
137 for(const auto &c: children)
139 appendGate(c, gate_input, {});
140 }
141 return;
142 }
143
144 appendGate(token, type, children);
145
146 for(const auto &c: children)
148 appendGate(c, gate_input, {});
149}
150
152{
153 auto idx = mapping[token];
155 return gate_input;
156 else
157 return gates[idx].type;
158}
159
160std::vector<pg_uuid_t> MMappedCircuit::getChildren(pg_uuid_t token) const
161{
162 std::vector<pg_uuid_t> result;
163 auto idx = mapping[token];
165 const GateInformation &gi = gates[idx];
166 for(unsigned long k=gi.children_idx; k<gi.children_idx+gi.nb_children; ++k)
167 result.push_back(wires[k]);
168 }
169 return result;
170}
171
172/** @brief Whether @p type is one of the gate kinds that carry a probability. */
173static bool carriesProb(gate_type type)
174{
175 return type == gate_input || type == gate_update || type == gate_mulinput;
176}
177
179 pg_uuid_t token, double prob, double *existing)
180{
181 auto idx = mapping[token];
183 idx = gates.nbElements();
184 appendGate(token, gate_input, {});
185 }
186
187 if(!carriesProb(gates[idx].type))
189
190 /* Clearing (the rollback path) always succeeds: it restores the gate
191 to the state it was in before the aborted transaction wrote to it. */
192 if(std::isnan(prob)) {
193 gates[idx].prob = NAN;
195 }
196
197 if(!hasProbAt(idx)) {
198 gates[idx].prob = prob;
200 }
201 if(gates[idx].prob == prob)
203 if(existing)
204 *existing = gates[idx].prob;
206}
207
208/** @brief Whether the record at @p idx holds a probability someone wrote.
209 *
210 * A version-1 @c gates file cannot distinguish "written as 1.0" from
211 * "never written" -- both are stored as @c 1.0 -- so on such a file a
212 * @c 1.0 counts as unset and stays writable. See @c GATES_VERSION. */
213bool MMappedCircuit::hasProbAt(unsigned long idx) const
214{
215 double p = gates[idx].prob;
216 if(std::isnan(p))
217 return false;
218 if(gates.version() < 2 && p == 1.)
219 return false;
220 return true;
221}
222
224{
225 auto idx = mapping[token];
226 if(idx == MMappedUUIDHashTable::NOTHING || !carriesProb(gates[idx].type))
227 return NAN;
228 if(!std::isnan(gates[idx].prob))
229 return gates[idx].prob;
230 /* A repaired row nobody gave a probability evaluates at the uniform
231 weight of its block, whose size repair_key recorded in info2. */
232 if(gates[idx].type == gate_mulinput && gates[idx].info2 > 0)
233 return 1. / gates[idx].info2;
234 /* Otherwise an unwritten probability evaluates as 1: a tuple nobody
235 gave a probability is certainly there. */
236 return 1.;
237}
238
239bool MMappedCircuit::hasProb(pg_uuid_t token, double *prob) const
240{
241 auto idx = mapping[token];
242 if(idx == MMappedUUIDHashTable::NOTHING || !carriesProb(gates[idx].type))
243 return false;
244 if(!hasProbAt(idx))
245 return false;
246 if(prob)
247 *prob = gates[idx].prob;
248 return true;
249}
250
251/* The two info fields are written once each, not once as a pair,
252 because they are written by different parties at different moments:
253 CertifiedDDMaterialize marks a gate certified (info1) as it builds it,
254 and tags it afterwards with the route that made it a query's root
255 (info2), which it only knows once the whole d-D is built. Zero is
256 "nothing recorded" for each field, so a field can go 0 -> v once and
257 a write of 0 over a v records nothing rather than clearing it. */
259 pg_uuid_t token, unsigned info1, unsigned info2,
260 std::pair<unsigned, unsigned> *existing)
261{
262 auto idx = mapping[token];
265
266 GateInformation &gi = gates[idx];
267
268 if((info1 != 0 && gi.info1 != 0 && gi.info1 != info1)
269 || (info2 != 0 && gi.info2 != 0 && gi.info2 != info2)) {
270 if(existing)
271 *existing = std::make_pair(gi.info1, gi.info2);
273 }
274
275 const unsigned w1 = info1 != 0 ? info1 : gi.info1;
276 const unsigned w2 = info2 != 0 ? info2 : gi.info2;
277 if(w1 == gi.info1 && w2 == gi.info2)
279
280 gi.info1 = w1;
281 gi.info2 = w2;
283}
284
286 pg_uuid_t token, const std::string &s, std::string *existing)
287{
288 auto idx = mapping[token];
291
292 /* Already carries exactly these bytes: nothing to do. Every caller
293 writes the extra string right after creating a content-addressed
294 gate, so recomputing the same expression offers the same bytes back;
295 appending them a second time would abandon the first copy in the
296 extra file for good, which is the store's main source of dead
297 space. */
298 if(gates[idx].extra_len == s.size()) {
299 bool same = true;
300 for(unsigned long k=0; k<s.size(); ++k)
301 if(extra[gates[idx].extra_idx + k] != s[k]) {
302 same = false;
303 break;
304 }
305 if(same)
307 }
308
309 if(gates[idx].extra_len > 0) {
310 if(existing)
311 *existing = getExtra(token);
313 }
314
315 gates[idx].extra_idx=extra.nbElements();
316 for(auto c: s)
317 extra.add(c);
318 gates[idx].extra_len=s.size();
320}
321
322std::pair<unsigned, unsigned> MMappedCircuit::getInfos(pg_uuid_t token) const
323{
324 auto idx = mapping[token];
326 return std::make_pair(0, 0);
327 } else {
328 const GateInformation &gi = gates[idx];
329 return std::make_pair(gi.info1, gi.info2);
330 }
331}
332
333std::string MMappedCircuit::getExtra(pg_uuid_t token) const
334{
335 std::string result;
336
337 auto idx = mapping[token];
339 for(unsigned long start=gates[idx].extra_idx, k=start, end=start+gates[idx].extra_len; k<end; ++k)
340 result+=extra[k];
341 }
342
343 return result;
344}
345
346/** @brief Suffix of the files a clean-up builds beside the live ones. */
347static constexpr const char *CLEANUP_SUFFIX = ".new";
348/** @brief Marker file present only while a clean-up's rename sequence is
349 * in flight; see @c finishInterruptedCleanup. */
350static constexpr const char *CLEANUP_MARKER = "provsql_cleanup.commit";
351
352/**
353 * @brief Bring a database's store to a definite state before opening it.
354 *
355 * A clean-up writes four new files, then renames them over the live ones
356 * one at a time. A crash inside that sequence would leave a mixture, so
357 * the sequence is bracketed by a marker file: present, it says the new
358 * files are complete and the renames must be finished; absent, it says
359 * any @c ".new" files are the debris of a clean-up that never got that
360 * far and are to be discarded.
361 */
362static void finishInterruptedCleanup(Oid db_oid, Oid db_tablespace)
363{
364 const char *names[4] = { "provsql_mapping.mmap", "provsql_gates.mmap",
365 "provsql_wires.mmap", "provsql_extra.mmap" };
366 std::string marker = MMappedCircuit::storePath(db_oid, db_tablespace,
368 const bool committed = (access(marker.c_str(), F_OK) == 0);
369
370 for(const char *name: names) {
371 std::string target = MMappedCircuit::storePath(db_oid, db_tablespace, name);
372 std::string fresh = target + CLEANUP_SUFFIX;
373 if(access(fresh.c_str(), F_OK) != 0)
374 continue;
375 if(committed)
376 rename(fresh.c_str(), target.c_str());
377 else
378 unlink(fresh.c_str());
379 }
380
381 if(committed)
382 unlink(marker.c_str());
383}
384
385/** @brief Return (creating lazily if needed) the circuit for @p db_oid. */
386static MMappedCircuit *getCircuit(Oid db_oid, Oid db_tablespace)
387{
388 auto it = circuits.find(db_oid);
389 if(it == circuits.end()) {
390 finishInterruptedCleanup(db_oid, db_tablespace);
391 circuits[db_oid] = new MMappedCircuit(db_oid, db_tablespace);
392 return circuits[db_oid];
393 }
394 return it->second;
395}
396
397/**
398 * @brief Rebuild a database's store, keeping only what @p roots reach.
399 *
400 * The store only ever grows: a gate is never removed, because removing
401 * one is unsafe while anything might still reference it, and because a
402 * content-addressed gate can be re-adopted by a query at any moment.
403 * This is the one operation allowed to remove gates, and it is safe only
404 * because the caller holds the database exclusively -- no other session
405 * is connected, so nothing can adopt an orphan while the sweep runs.
406 *
407 * On @p dry_run the mark phase runs and the counts are reported, but
408 * nothing is written.
409 */
410static void cleanupStore(Oid db_oid, Oid db_tablespace,
411 const std::vector<pg_uuid_t> &roots, bool dry_run,
413{
414 MMappedCircuit *circuit = getCircuit(db_oid, db_tablespace);
415
416 auto before = circuit->counts();
417 out->gates_before = before.gates;
418 out->wires_before = before.wires;
419 out->extra_before = before.extra_bytes;
420
421 std::vector<bool> live;
422 unsigned long nb_live = circuit->mark(roots, live);
423
424 if(dry_run) {
425 /* Report what a real run would keep, without the wire and extra
426 counts of the rewrite -- those need the sweep to be exact. The
427 live gate count is what the operator is deciding on. */
428 out->gates_after = nb_live;
429 out->wires_after = 0;
430 out->extra_after = 0;
431 return;
432 }
433
434 const char *names[4] = { "provsql_mapping.mmap", "provsql_gates.mmap",
435 "provsql_wires.mmap", "provsql_extra.mmap" };
436 std::string target[4], fresh[4];
437 for(int i=0; i<4; ++i) {
438 target[i] = MMappedCircuit::storePath(db_oid, db_tablespace, names[i]);
439 fresh[i] = target[i] + CLEANUP_SUFFIX;
440 unlink(fresh[i].c_str());
441 }
442
443 auto after = circuit->sweepInto(live, fresh[0], fresh[1], fresh[2], fresh[3]);
444 out->gates_after = after.gates;
445 out->wires_after = after.wires;
446 out->extra_after = after.extra_bytes;
447
448 /* Close the live store before the swap, so its dirty bits are cleared
449 and the next message reopens the rebuilt files. */
450 delete circuit;
451 circuits.erase(db_oid);
452
453 std::string marker = MMappedCircuit::storePath(db_oid, db_tablespace,
455 int mfd = open(marker.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0600); // flawfinder: ignore
456 if(mfd == -1) {
457 const char *reason = strerror(errno);
458 provsql_error("circuit_cleanup: cannot create %s: %s",
459 marker.c_str(), reason);
460 }
461 fsync(mfd);
462 close(mfd);
463
464 for(int i=0; i<4; ++i)
465 if(rename(fresh[i].c_str(), target[i].c_str())) {
466 const char *reason = strerror(errno);
467 provsql_error("circuit_cleanup: cannot replace %s: %s",
468 target[i].c_str(), reason);
469 }
470
471 unlink(marker.c_str());
472}
473
474#ifdef PROVSQL_INPROCESS_STORE
475/* Single-process build: the backend builds the in-memory circuit directly
476 from this process's store, instead of round-tripping a Boost-serialised
477 copy through the FIFO (which only existed to cross the worker/backend
478 process boundary). This is also what lets the WASM build avoid the
479 compiled libboost_serialization dependency. */
480GenericCircuit provsql_inproc_generic_circuit(pg_uuid_t token)
481{
482 return getCircuit(MyDatabaseId, MyDatabaseTableSpace)->createGenericCircuit(token);
483}
484
485GenericCircuit provsql_inproc_joint_circuit(
486 const std::vector<pg_uuid_t> &roots)
487{
488 return getCircuit(MyDatabaseId, MyDatabaseTableSpace)->createGenericCircuit(roots);
489}
490#endif
491
492extern "C" void provsql_store_flush(void)
493{
494 for(auto &kv: circuits)
495 kv.second->flush();
496 store_dirty = false;
497}
498
499extern "C" void provsql_mmap_dispatch(char c, Oid db_oid, Oid db_tablespace)
500{
501 MMappedCircuit *circuit = getCircuit(db_oid, db_tablespace);
502
503 if(c=='C' || c=='G' || c=='P' || c=='I' || c=='E')
504 store_dirty = true;
505
506 switch(c) {
507 case 'C':
508 {
509 pg_uuid_t token;
510 gate_type type;
511 unsigned nb_children;
512
513 if(!READM(token, pg_uuid_t) || !READM(type, gate_type) || !READM(nb_children, unsigned))
514 provsql_error("Cannot read from pipe (message type C)"); ;
515
516 std::vector<pg_uuid_t> children(nb_children);
517 for(unsigned i=0; i<nb_children; ++i)
518 if(!READM(children[i], pg_uuid_t))
519 provsql_error("Cannot read from pipe (message type C)");
520
521 circuit->createGate(token, type, children);
522 break;
523 }
524
525 case 'G':
526 {
527 /* A gate with what it records, in one message that is not answered:
528 the sender's address determines the infos and the text, so the
529 write-once rule cannot refuse them. If it does, someone wrote
530 something else at that address by hand (or hashes collided): the
531 first value stays, and the worker's log says so. */
532 pg_uuid_t token;
533 gate_type type;
534 unsigned nb_children, info1, info2, len;
535 char has_infos;
536
537 if(!READM(token, pg_uuid_t) || !READM(type, gate_type) || !READM(nb_children, unsigned))
538 provsql_error("Cannot read from pipe (message type G)");
539
540 std::vector<pg_uuid_t> children(nb_children);
541 for(unsigned i=0; i<nb_children; ++i)
542 if(!READM(children[i], pg_uuid_t))
543 provsql_error("Cannot read from pipe (message type G)");
544 if(!READM(has_infos, char) || !READM(info1, unsigned) || !READM(info2, unsigned)
545 || !READM(len, unsigned))
546 provsql_error("Cannot read from pipe (message type G)");
547 std::vector<char> data(len);
548 if(len > 0 && !READM_BYTES(data.data(), len))
549 provsql_error("Cannot read from pipe (message type G)");
550
551 circuit->createGate(token, type, children);
552 if(has_infos) {
553 std::pair<unsigned, unsigned> existing{0, 0};
554 if(circuit->setInfos(token, info1, info2, &existing)
556 provsql_warning("gate %s already records the annotation (%u, %u), not (%u, %u)",
557 uuid2string(token).c_str(), existing.first, existing.second,
558 info1, info2);
559 }
560 if(len > 0) {
561 std::string existing;
562 if(circuit->setExtra(token, std::string(data.data(), len), &existing)
564 provsql_warning("gate %s already records the annotation \"%s\", not \"%s\"",
565 uuid2string(token).c_str(), existing.c_str(),
566 std::string(data.data(), len).c_str());
567 }
568 break;
569 }
570
571 case 'P':
572 {
573 pg_uuid_t token;
574 double prob, existing = 0.;
575
576 if(!READM(token, pg_uuid_t) || !READM(prob, double))
577 provsql_error("Cannot read from pipe (message type P)");
578
579 auto result = circuit->setProb(token, prob, &existing);
580 char return_value = static_cast<char>(result);
581
582 if(!WRITEB(&return_value, char) || !WRITEB(&existing, double))
583 provsql_error("Cannot write response to pipe (message type P)");
584 break;
585 }
586
587 case 'q':
588 {
589 /* Is a probability written on this gate, and which one? Distinct
590 from 'p', which reports the value an evaluation would use and so
591 cannot tell an unwritten probability from one written as 1. */
592 pg_uuid_t token;
593 double prob = 0.;
594
595 if(!READM(token, pg_uuid_t))
596 provsql_error("Cannot read from pipe (message type q)");
597
598 char has = circuit->hasProb(token, &prob) ? 1 : 0;
599
600 if(!WRITEB(&has, char) || !WRITEB(&prob, double))
601 provsql_error("Cannot write response to pipe (message type q)");
602 break;
603 }
604
605 case 'I':
606 case 'E':
607 {
608 /* Infos or text written after the gate. Nothing in this version
609 sends them (a gate is created with what it records, message G);
610 the install scripts of earlier versions, run by an upgrade, and
611 WAL records of earlier versions still do. Applied write-once
612 like G, and not answered. */
613 pg_uuid_t token;
614
615 if(!READM(token, pg_uuid_t))
616 provsql_error("Cannot read from pipe (message type %c)", c);
617 if(c == 'I') {
618 unsigned info1, info2;
619 std::pair<unsigned, unsigned> existing{0, 0};
620 if(!READM(info1, unsigned) || !READM(info2, unsigned))
621 provsql_error("Cannot read from pipe (message type I)");
622 if(circuit->setInfos(token, info1, info2, &existing)
624 provsql_warning("gate %s already records the annotation (%u, %u), not (%u, %u)",
625 uuid2string(token).c_str(), existing.first, existing.second,
626 info1, info2);
627 } else {
628 unsigned len;
629 if(!READM(len, unsigned))
630 provsql_error("Cannot read from pipe (message type E)");
631 if(len > 0) {
632 std::vector<char> data(len);
633 std::string existing;
634 if(!READM_BYTES(data.data(), len))
635 provsql_error("Cannot read from pipe (message type E)");
636 if(circuit->setExtra(token, std::string(data.data(), len), &existing)
638 provsql_warning("gate %s already records the annotation \"%s\", not \"%s\"",
639 uuid2string(token).c_str(), existing.c_str(),
640 std::string(data.data(), len).c_str());
641 }
642 }
643 break;
644 }
645
646 case 't':
647 {
648 pg_uuid_t token;
649
650 if(!READM(token, pg_uuid_t))
651 provsql_error("Cannot read from pipe (message type t)");
652
653 gate_type type = circuit->getGateType(token);
654
655 if(!WRITEB(&type, gate_type))
656 provsql_error("Cannot write response to pipe (message type t)");
657 break;
658 }
659
660 case 'n':
661 {
662 unsigned long nb = circuit->getNbGates();
663
664 if(!WRITEB(&nb, unsigned long))
665 provsql_error("Cannot write response to pipe (message type n)");
666 break;
667 }
668
669 case 'c':
670 {
671 pg_uuid_t token;
672
673 if(!READM(token, pg_uuid_t))
674 provsql_error("Cannot read from pipe (message type c)");
675
676 auto children = circuit->getChildren(token);
677 unsigned nb_children = children.size();
678 if(!WRITEB(&nb_children, unsigned))
679 provsql_error("Cannot write response to pipe (message type c)");
680
681 if(!WRITEB_BYTES(children.data(), nb_children*sizeof(pg_uuid_t)))
682 provsql_error("Cannot write response to pipe (message type c)");
683 break;
684 }
685
686 case 'p':
687 {
688 pg_uuid_t token;
689
690 if(!READM(token, pg_uuid_t))
691 provsql_error("Cannot read from pipe (message type p)");
692
693 double prob = circuit->getProb(token);
694
695 if(!WRITEB(&prob, double))
696 provsql_error("Cannot write response to pipe (message type p)");
697 break;
698 }
699
700 case 'i':
701 {
702 pg_uuid_t token;
703
704 if(!READM(token, pg_uuid_t))
705 provsql_error("Cannot read from pipe (message type i)");
706
707 auto infos = circuit->getInfos(token);
708
709 if(!WRITEB(&infos.first, unsigned) || !WRITEB(&infos.second, unsigned))
710 provsql_error("Cannot write response to pipe (message type i)");
711 break;
712 }
713
714 case 'e':
715 {
716 pg_uuid_t token;
717
718 if(!READM(token, pg_uuid_t))
719 provsql_error("Cannot read from pipe (message type e)");
720
721 auto str = circuit->getExtra(token);
722 unsigned len = str.size();
723
724 if(!WRITEB(&len, unsigned) || !WRITEB_BYTES(str.data(), len))
725 provsql_error("Cannot write response to pipe (message type e)");
726 break;
727 }
728
729 case 'g':
730 {
731 pg_uuid_t token;
732
733 if(!READM(token, pg_uuid_t))
734 provsql_error("Cannot read from pipe (message type g)");
735
736#ifdef PROVSQL_INPROCESS_STORE
737 /* Unreachable: the backend calls provsql_inproc_generic_circuit
738 directly instead of issuing the 'g' message. */
739 provsql_error("message type g is not used by the in-process store");
740#else
741 std::stringstream ss;
742 boost::archive::binary_oarchive oa(ss);
743 oa << circuit->createGenericCircuit(token);
744
745 ss.seekg(0, std::ios::end);
746 unsigned long size = ss.tellg();
747 ss.seekg(0, std::ios::beg);
748
749 if(!WRITEB(&size, unsigned long) || !WRITEB_BYTES(ss.str().data(), size))
750 provsql_error("Cannot write to pipe (message type g)");
751#endif
752 break;
753 }
754
755
756
757
758
759
760
761 case 'X':
762 {
763 /* Clean-up: rebuild the store keeping only what the roots reach.
764 The caller holds the database exclusively (see
765 provsql.circuit_cleanup), so nothing can adopt an orphan while
766 this runs. */
767 char dry_run;
768 unsigned long nb_roots;
769
770 if(!READM(dry_run, char) || !READM(nb_roots, unsigned long))
771 provsql_error("Cannot read from pipe (message type X)");
772
773 std::vector<pg_uuid_t> roots(nb_roots);
774 for(unsigned long i=0; i<nb_roots; ++i)
775 if(!READM(roots[i], pg_uuid_t))
776 provsql_error("Cannot read from pipe (message type X)");
777
779 cleanupStore(db_oid, db_tablespace, roots, dry_run != 0, &res);
780
781 if(!WRITEB(&res.gates_before, uint64) || !WRITEB(&res.gates_after, uint64)
782 || !WRITEB(&res.wires_before, uint64) || !WRITEB(&res.wires_after, uint64)
783 || !WRITEB(&res.extra_before, uint64) || !WRITEB(&res.extra_after, uint64))
784 provsql_error("Cannot write response to pipe (message type X)");
785 break;
786 }
787
788 case 'S':
789 {
790 /* Sync barrier: force everything written so far to stable storage
791 and say so. Because the pipe is FIFO and the worker single
792 threaded, the reply also proves every earlier message from this
793 backend has been *applied*, not merely queued. */
795
796 char ack = 1;
797 if(!WRITEB(&ack, char))
798 provsql_error("Cannot write response to pipe (message type S)");
799 break;
800 }
801
802 case 'k':
803 {
804 /* Consistency report; see MMappedCircuit::Check. */
805 MMappedCircuit::Check chk = circuit->check();
806 char unclean = chk.unclean ? 1 : 0;
807 if(!WRITEB(&unclean, char)
808 || !WRITEB(&chk.nb_gates, unsigned long)
809 || !WRITEB(&chk.nb_mapping, unsigned long)
810 || !WRITEB(&chk.next_value, unsigned long)
811 || !WRITEB(&chk.dangling_indices, unsigned long)
812 || !WRITEB(&chk.unreferenced, unsigned long)
813 || !WRITEB(&chk.bad_wires, unsigned long)
814 || !WRITEB(&chk.bad_extra, unsigned long))
815 provsql_error("Cannot write response to pipe (message type k)");
816 break;
817 }
818
819 case 'j':
820 {
821 /* Joint-circuit load: BFS from a vector of roots so a shared
822 * subgraph reachable from multiple roots collapses to a single
823 * gate_t. Used by getJointCircuit() to load an RV's sub-DAG
824 * together with a conditioning gate that sits above it in the
825 * persisted DAG. */
826 unsigned nb_roots;
827 if(!READM(nb_roots, unsigned))
828 provsql_error("Cannot read from pipe (message type j)");
829
830 std::vector<pg_uuid_t> roots(nb_roots);
831 for(unsigned i=0; i<nb_roots; ++i)
832 if(!READM(roots[i], pg_uuid_t))
833 provsql_error("Cannot read from pipe (message type j)");
834
835#ifdef PROVSQL_INPROCESS_STORE
836 /* Unreachable: the backend calls provsql_inproc_joint_circuit
837 directly instead of issuing the 'j' message. */
838 provsql_error("message type j is not used by the in-process store");
839#else
840 std::stringstream ss;
841 boost::archive::binary_oarchive oa(ss);
842 oa << circuit->createGenericCircuit(roots);
843
844 ss.seekg(0, std::ios::end);
845 unsigned long size = ss.tellg();
846 ss.seekg(0, std::ios::beg);
847
848 if(!WRITEB(&size, unsigned long) || !WRITEB_BYTES(ss.str().data(), size))
849 provsql_error("Cannot write to pipe (message type j)");
850#endif
851 break;
852 }
853
854 default:
855 provsql_error("Wrong message type: %c", c);
856 }
857}
858
859#ifndef PROVSQL_INPROCESS_STORE
861{
862 char c;
863
864 for(;;) {
865 struct pollfd pfd;
866 pfd.fd = provsql_shared_state->pipebmr;
867 pfd.events = POLLIN;
868 pfd.revents = 0;
869
870 /* Wait indefinitely while there is nothing to force out; once a write
871 has landed, wake up after the flush interval and force it, so an
872 idle store is never more than that far behind the disk. Bytes
873 already read into the buffer are served first: poll() does not see
874 them. */
876 int r = poll(&pfd, 1,
878 if(r < 0) {
879 if(errno == EINTR)
880 continue;
881 break;
882 }
883 if(r == 0) {
885 continue;
886 }
887 }
888
889 if(!READM(c, char))
890 break;
891 Oid db_oid, db_tablespace;
892 if(!READM(db_oid, Oid) || !READM(db_tablespace, Oid))
893 provsql_error("Cannot read message header from pipe");
894 provsql_mmap_dispatch(c, db_oid, db_tablespace);
895 }
896
897 int e = errno;
898 provsql_error("Reading from pipe: %s", strerror(e));
899}
900#endif
901
903{
904 gates.sync();
905 wires.sync();
906 mapping.sync();
907 extra.sync();
908}
909
911{
912 /* Order matters as much here as it does on the write path: the records
913 a mapping entry points at must be on disk before the entry is, so
914 that a machine crash between the two flushes leaves an unreferenced
915 record rather than a dangling index. */
916 wires.flush();
917 extra.flush();
918 gates.flush();
919 mapping.flush();
920}
921
923{
924 return mapping.uncleanShutdown() || gates.uncleanShutdown()
925 || wires.uncleanShutdown() || extra.uncleanShutdown();
926}
927
929{
930 Check c{};
932 c.nb_gates = gates.nbElements();
933 c.nb_mapping = mapping.nbElements();
934 c.next_value = mapping.nextValue();
935
936 std::vector<bool> referenced(c.nb_gates, false);
937 for(unsigned long k=0; k<mapping.capacity(); ++k) {
938 unsigned long v = mapping.slotValue(k);
940 continue;
941 if(v >= c.nb_gates)
943 else
944 referenced[v] = true;
945 }
946 for(unsigned long i=0; i<c.nb_gates; ++i) {
947 if(!referenced[i])
948 ++c.unreferenced;
949 const GateInformation &gi = gates[i];
950 if(gi.children_idx + gi.nb_children > wires.nbElements())
951 ++c.bad_wires;
952 if(gi.extra_idx + gi.extra_len > extra.nbElements())
953 ++c.bad_extra;
954 }
955 return c;
956}
957
958
959unsigned long MMappedCircuit::mark(const std::vector<pg_uuid_t> &roots,
960 std::vector<bool> &live) const
961{
962 const unsigned long n = gates.nbElements();
963 live.assign(n, false);
964 unsigned long nb_live = 0;
965
966 std::vector<unsigned long> stack;
967 for(const auto &r: roots) {
968 auto idx = mapping[r];
969 if(idx != MMappedUUIDHashTable::NOTHING && idx < n && !live[idx]) {
970 live[idx] = true;
971 ++nb_live;
972 stack.push_back(idx);
973 }
974 }
975
976 while(!stack.empty()) {
977 unsigned long idx = stack.back();
978 stack.pop_back();
979 const GateInformation &gi = gates[idx];
980 if(gi.children_idx + gi.nb_children > wires.nbElements())
981 continue; /* a torn record; check() reports it, do not follow it */
982 for(unsigned long k=gi.children_idx; k<gi.children_idx+gi.nb_children; ++k) {
983 auto child = mapping[wires[k]];
984 if(child != MMappedUUIDHashTable::NOTHING && child < n && !live[child]) {
985 live[child] = true;
986 ++nb_live;
987 stack.push_back(child);
988 }
989 }
990 }
991
992 return nb_live;
993}
994
996 const std::vector<bool> &live,
997 const std::string &mp, const std::string &gp,
998 const std::string &wp, const std::string &ep) const
999{
1000 Counts out{};
1001 const unsigned long n = gates.nbElements();
1002
1003 /* Old index -> new index, assigned in old order so the rewritten store
1004 keeps the creation order of what it keeps. */
1005 std::vector<unsigned long> newidx(n, MMappedUUIDHashTable::NOTHING);
1006 {
1007 unsigned long next = 0;
1008 for(unsigned long i=0; i<n; ++i)
1009 if(live[i])
1010 newidx[i] = next++;
1011 }
1012
1013 const bool legacy = legacyProbabilities();
1014
1015 {
1016 MMappedUUIDHashTable nmapping(mp.c_str(), false, MAGIC_MAPPING);
1017 MMappedVector<GateInformation> ngates(gp.c_str(), false, MAGIC_GATES,
1019 MMappedVector<pg_uuid_t> nwires(wp.c_str(), false, MAGIC_WIRES);
1020 MMappedVector<char> nextra(ep.c_str(), false, MAGIC_EXTRA);
1021
1022 for(unsigned long i=0; i<n; ++i) {
1023 if(!live[i])
1024 continue;
1025 GateInformation gi = gates[i];
1026
1027 /* A record whose ranges run past the vectors they index is torn --
1028 check() counts them -- so copying by those ranges would read off
1029 the end. Keep the gate, drop what cannot be read: the rebuilt
1030 store is then at least internally consistent, which is the point
1031 of running the clean-up on a damaged store. */
1032 const unsigned long old_children = gi.children_idx;
1033 gi.children_idx = nwires.nbElements();
1034 if(old_children + gi.nb_children > wires.nbElements())
1035 gi.nb_children = 0;
1036 for(unsigned c=0; c<gi.nb_children; ++c)
1037 nwires.add(wires[old_children + c]);
1038
1039 const unsigned long old_extra = gi.extra_idx;
1040 if(old_extra + gi.extra_len > extra.nbElements())
1041 gi.extra_len = 0;
1042 if(gi.extra_len > 0) {
1043 gi.extra_idx = nextra.nbElements();
1044 for(unsigned k=0; k<gi.extra_len; ++k)
1045 nextra.add(extra[old_extra + k]);
1046 } else {
1047 gi.extra_idx = 0;
1048 }
1049
1050 /* A version-1 file stored 1.0 both for "written as certain" and for
1051 "never written"; the rewrite is where that ambiguity is settled,
1052 conservatively, in favour of unwritten -- the reading the store
1053 has been giving those gates all along. */
1054 if(legacy && gi.prob == 1.
1055 && (gi.type == gate_input || gi.type == gate_update
1056 || gi.type == gate_mulinput))
1057 gi.prob = NAN;
1058
1059 ngates.add(gi);
1060 }
1061
1062 for(unsigned long k=0; k<mapping.capacity(); ++k) {
1063 unsigned long v = mapping.slotValue(k);
1064 if(v == MMappedUUIDHashTable::NOTHING || v >= n || !live[v])
1065 continue;
1066 nmapping.publish(mapping.slotKey(k), newidx[v]);
1067 }
1068
1069 out.gates = ngates.nbElements();
1070 out.wires = nwires.nbElements();
1071 out.extra_bytes = nextra.nbElements();
1072
1073 ngates.flush();
1074 nwires.flush();
1075 nextra.flush();
1076 nmapping.flush();
1077 }
1078
1079 return out;
1080}
1081
1082/**
1083 * @brief Lexicographic less-than comparison for @c pg_uuid_t.
1084 * @param a Left UUID.
1085 * @param b Right UUID.
1086 * @return @c true if @p a is lexicographically less than @p b.
1087 */
1088bool operator<(const pg_uuid_t a, const pg_uuid_t b)
1089{
1090 return memcmp(&a, &b, sizeof(pg_uuid_t))<0;
1091}
1092
1094{
1095 return createGenericCircuit(std::vector<pg_uuid_t>{token});
1096}
1097
1099 const std::vector<pg_uuid_t> &roots) const
1100{
1101 /* Seed the work list with every root. std::set deduplicates so a
1102 * UUID listed twice (or reached as a child of one root and the
1103 * other's root itself) is processed only once. Shared subgraphs
1104 * therefore land on a single gate_t in `result` -- the property
1105 * that lets the conditional MC sampler couple the indicator and
1106 * value paths through @c Sampler::scalar_cache_ / @c bool_cache_. */
1107 std::set<pg_uuid_t> to_process, processed;
1108 for(const auto &r : roots)
1109 to_process.insert(r);
1110
1111 GenericCircuit result;
1112
1113 while(!to_process.empty()) {
1114 pg_uuid_t uuid = *to_process.begin();
1115 to_process.erase(to_process.begin());
1116 processed.insert(uuid);
1117 std::string f{uuid2string(uuid)};
1118
1119 gate_type type = getGateType(uuid);
1120 gate_t id = result.setGate(f, type);
1121 double prob = getProb(uuid);
1122 if(!std::isnan(prob))
1123 result.setProb(id, prob);
1124
1125 std::vector<pg_uuid_t> children = getChildren(uuid);
1126 for(unsigned i=0; i<children.size(); ++i) {
1127 result.addWire(
1128 id,
1129 result.getGate(uuid2string(children[i])));
1130 if(processed.find(children[i])==processed.end())
1131 to_process.insert(children[i]);
1132 }
1133
1134 if(type==gate_mulinput || type==gate_eq || type==gate_agg
1135 || type==gate_cmp || type==gate_arith) {
1136 auto [info1, info2] = getInfos(uuid);
1137 result.setInfos(id, info1, info2);
1138 } else if(type==gate_plus || type==gate_times || type==gate_assumed) {
1139 /* The d-DNNF certificate (DNNF_CERT_INFO in info1: deterministic
1140 * plus / decomposable times) and, alongside it in info2, the tag of
1141 * the planner-time route that produced the root (@c provsql_route);
1142 * on a gate_assumed the route tag is in info1. Copied only when
1143 * set, so unmarked gates do not bloat the in-memory infos map with
1144 * zeros. */
1145 auto [info1, info2] = getInfos(uuid);
1146 if(info1 != 0 || info2 != 0)
1147 result.setInfos(id, info1, info2);
1148 }
1149
1150 if(type==gate_project || type==gate_value || type==gate_agg
1151 || type==gate_rv || type==gate_mulinput || type==gate_annotation
1152 || type==gate_assumed || type==gate_mobius || type==gate_arith
1153 || type==gate_observe) {
1154 /* gate_assumed carries its assumption kind ('boolean' /
1155 * 'absorptive') in extra; gate_mobius carries its per-child integer
1156 * coefficients ("uuid:coeff" tokens); a gate_arith PERCENTILE
1157 * carries its fraction (agg-carrier arith gates carry a display
1158 * value there, inert for evaluation); gates stored without the
1159 * label have none and default to 'boolean' at
1160 * evaluation. */
1161 auto extra = getExtra(uuid);
1162 result.setExtra(id, extra);
1163 }
1164 }
1165
1166 return result;
1167}
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Out-of-line template method implementations for Circuit<gateType>.
Semiring-agnostic in-memory provenance circuit.
static constexpr const char * CLEANUP_SUFFIX
Suffix of the files a clean-up builds beside the live ones.
static void finishInterruptedCleanup(Oid db_oid, Oid db_tablespace)
Bring a database's store to a definite state before opening it.
static void cleanupStore(Oid db_oid, Oid db_tablespace, const std::vector< pg_uuid_t > &roots, bool dry_run, provsql_cleanup_result *out)
Rebuild a database's store, keeping only what roots reach.
static bool carriesProb(gate_type type)
Whether type is one of the gate kinds that carry a probability.
static std::map< Oid, MMappedCircuit * > circuits
Per-database mmap-backed provenance circuits, keyed by database OID.
void destroy_provsql_mmap()
Unmap and close the mmap files.
void provsql_store_flush(void)
Force every open circuit to stable storage.
void provsql_mmap_dispatch(char c, Oid db_oid, Oid db_tablespace)
Handle a single IPC message: read its payload and write its reply.
bool operator<(const pg_uuid_t a, const pg_uuid_t b)
Lexicographic less-than comparison for pg_uuid_t.
void provsql_mmap_main_loop()
Main processing loop of the mmap background worker.
static MMappedCircuit * getCircuit(Oid db_oid, Oid db_tablespace)
Return (creating lazily if needed) the circuit for db_oid.
void initialize_provsql_mmap()
Initialise the circuit store.
static bool store_dirty
Whether any circuit has been written to since the last flush.
static constexpr const char * CLEANUP_MARKER
Marker file present only while a clean-up's rename sequence is in flight; see finishInterruptedCleanu...
Persistent, mmap-backed storage for the full provenance circuit.
void addWire(gate_t f, gate_t t)
Add a directed wire from gate f (parent) to gate t (child).
Definition Circuit.hpp:81
gate_t getGate(const uuid &u)
Return (or create) the gate associated with UUID u.
Definition Circuit.hpp:33
In-memory provenance circuit with semiring-generic evaluation.
void setInfos(gate_t g, unsigned info1, unsigned info2)
Set the integer annotation pair for gate g.
gate_t setGate(gate_type type) override
Allocate a new gate with type type and no UUID.
void setExtra(gate_t g, const std::string &ex)
Attach a string extra to gate g.
void setProb(gate_t g, double p)
Set the probability for gate g.
Persistent mmap-backed representation of the provenance circuit.
static constexpr uint16_t GATES_VERSION
Format version of the gates file this build writes.
unsigned long mark(const std::vector< pg_uuid_t > &roots, std::vector< bool > &live) const
Mark every gate reachable from roots.
SetAnnotationResult setInfos(pg_uuid_t token, unsigned info1, unsigned info2, std::pair< unsigned, unsigned > *existing=nullptr)
Write the info1 / info2 annotations of a gate, once.
MMappedUUIDHashTable mapping
UUID → gate-index hash table.
void createGate(pg_uuid_t token, gate_type type, const std::vector< pg_uuid_t > &children)
Persist a new gate to the mmap store.
Counts counts() const
This store's current size.
std::string getExtra(pg_uuid_t token) const
Return the variable-length string annotation for gate token.
bool legacyProbabilities() const
Whether this store's gates file predates the NaN unset-probability convention (see GATES_VERSION).
SetProbResult setProb(pg_uuid_t token, double prob, double *existing=nullptr)
Write a gate's probability, once.
void appendGate(pg_uuid_t token, gate_type type, const std::vector< pg_uuid_t > &children)
Append a complete gate record, then publish token for it.
unsigned long getNbGates() const
Return the total number of gates stored in the circuit.
SetProbResult
Outcome of setProb.
@ NotProbGate
The token names a gate that carries no probability.
@ Unchanged
The gate already held exactly this probability.
@ Written
The gate had no probability and now holds the given one.
@ AlreadySet
The gate holds a different probability; refused.
static constexpr const char * GATES_FILENAME
Backing file for gates.
Counts sweepInto(const std::vector< bool > &live, const std::string &mp, const std::string &gp, const std::string &wp, const std::string &ep) const
Copy the gates flagged in live into a fresh set of files beside the current ones (suffix "....
gate_type getGateType(pg_uuid_t token) const
Return the type of the gate identified by token.
Check check() const
Walk the store and report what does not add up.
void sync()
Flush all backing files to disk with msync().
static std::string storePath(Oid db_oid, Oid db_tablespace, const char *filename)
Build the full path of a file in a database's store directory, the one GetDatabasePath resolves for d...
static constexpr const char * WIRES_FILENAME
Backing file for wires.
MMappedCircuit(const std::string &mp, const std::string &gp, const std::string &wp, const std::string &ep, bool read_only)
Delegating constructor that accepts pre-built paths.
GenericCircuit createGenericCircuit(pg_uuid_t token) const
Build an in-memory GenericCircuit rooted at token.
bool uncleanShutdown() const
Whether any backing file was found still marked open-for-writing when it was opened – the previous wr...
static constexpr const char * EXTRA_FILENAME
Backing file for extra.
static constexpr uint64_t MAGIC_WIRES
static constexpr uint64_t MAGIC_GATES
8-byte magic constants identifying each mmap file type.
static constexpr const char * MAPPING_FILENAME
Backing file for mapping.
bool hasProb(pg_uuid_t token, double *prob) const
Report whether token names a gate that holds a probability.
MMappedVector< char > extra
Variable-length string data.
static constexpr uint64_t MAGIC_EXTRA
double getProb(pg_uuid_t token) const
Return the probability an evaluation would use for token.
std::vector< pg_uuid_t > getChildren(pg_uuid_t token) const
Return the child UUIDs of the gate identified by token.
MMappedVector< GateInformation > gates
Gate metadata array.
SetAnnotationResult setExtra(pg_uuid_t token, const std::string &s, std::string *existing=nullptr)
Attach a variable-length string annotation to a gate, once.
SetAnnotationResult
Outcome of setInfos or setExtra.
@ Unchanged
The gate already held exactly this annotation.
@ Written
The gate had none and now holds the given annotation.
@ NoSuchGate
The token names no gate.
@ AlreadySet
The gate holds a different annotation; refused.
bool hasProbAt(unsigned long idx) const
Whether the gate record at index idx holds a written probability (see GATES_VERSION for the version-1...
MMappedVector< pg_uuid_t > wires
Flattened child UUID array.
static constexpr uint64_t MAGIC_MAPPING
std::pair< unsigned, unsigned > getInfos(pg_uuid_t token) const
Return the info1 / info2 pair for the gate token.
void flush()
Force every backing file to stable storage.
Persistent open-addressing hash table mapping UUIDs to integers.
std::pair< unsigned long, bool > publish(pg_uuid_t u, unsigned long value)
Insert UUID u with a caller-chosen value.
static constexpr unsigned long NOTHING
Sentinel returned by operator[]() when the UUID is not present.
void flush()
Force the backing file to stable storage (MappedRegion::flush()).
Append-only, mmap-backed vector of elements of type T.
void flush()
Force the backing file to stable storage (MappedRegion::flush()).
unsigned long nbElements() const
Return the number of elements currently stored.
void add(const T &value)
Append an element to the end of the vector.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
#define provsql_warning(fmt,...)
Emit a ProvSQL warning message (execution continues).
bool provsql_worker_buffered(void)
Whether the worker's read buffer holds unread bytes, which poll() cannot see.
Background worker and IPC primitives for mmap-backed circuit storage.
#define WRITEB_BYTES(ptr, n)
Write n reply bytes to the main-to-background pipe.
#define READM(var, type)
Read one value of type from the background-to-main pipe.
#define WRITEB(pvar, type)
Write one value of type to the main-to-background pipe.
#define PROVSQL_STORE_FLUSH_INTERVAL_MS
How long the worker waits after a write before forcing the store to stable storage.
#define READM_BYTES(ptr, n)
Read exactly n bytes of a request from the background-to-main pipe.
provsqlSharedState * provsql_shared_state
Pointer to the ProvSQL shared-memory segment (set in provsql_shmem_startup).
Shared-memory segment and inter-process pipe management.
@ gate_observe
Latent-variable observation (likelihood-weighting evidence): one wire → an observed bare gate_rv leaf...
@ gate_rv
Continuous random-variable leaf (extra encodes distribution).
@ gate_annotation
Transparent single-child wrapper carrying a query-level annotation in extra (inversion-free certifica...
@ gate_mobius
Signed Möbius combination: a MEASURE-only gate carrying one integer coefficient per child (in extra,...
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)
@ gate_assumed
Structural marker over a single child whose sub-circuit was computed under a Boolean-provenance assum...
string uuid2string(pg_uuid_t uuid)
Format a pg_uuid_t as a std::string.
C++ utility functions for UUID manipulation.
Per-gate metadata stored in the gates MMappedVector.
double prob
Associated probability, NaN when unset.
unsigned info2
General-purpose integer annotation 2.
unsigned long children_idx
Start index of this gate's children in wires.
unsigned info1
General-purpose integer annotation 1.
unsigned long extra_idx
Start index in extra for string data.
unsigned extra_len
Byte length of the string data in extra.
unsigned nb_children
Number of children.
gate_type type
Kind of gate (input, plus, times…).
What provsql.check_store() reports about a store.
unsigned long dangling_indices
Mapping entries indexing past the records.
unsigned long nb_gates
Gate records.
unsigned long bad_extra
Records whose extra runs past the extra file.
bool unclean
A file was left marked open-for-writing.
unsigned long nb_mapping
Mapping entries.
unsigned long unreferenced
Records no mapping entry points at.
unsigned long bad_wires
Records whose children run past the wires.
unsigned long next_value
Next index the mapping would assign.
The size of a store, in the three units that matter.
unsigned long extra_bytes
Bytes of variable-length annotation.
unsigned long gates
Gate records.
unsigned long wires
Child wires.
What provsql.circuit_cleanup() reports.
uint64 wires_before
Child wires before.
uint64 wires_after
Child wires kept.
uint64 extra_before
Annotation bytes before.
uint64 gates_after
Gate records kept.
uint64 extra_after
Annotation bytes kept.
uint64 gates_before
Gate records before.