ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
provsql_migrate_mmap.cpp
Go to the documentation of this file.
1/**
2 * @file provsql_migrate_mmap.cpp
3 * @brief Migrate old flat provsql mmap files to the per-database layout.
4 *
5 * Old format: $PGDATA/provsql_*.mmap (no 16-byte header)
6 * New format: $PGDATA/base/<db_oid>/provsql_*.mmap (16-byte header)
7 *
8 * For each database that has provsql installed, the tool:
9 * 1. Enumerates provenance-tracked tables via libpq.
10 * 2. Collects root UUIDs from those tables.
11 * 3. BFS-traverses the old flat circuit from those roots.
12 * 4. Writes per-database new-format files.
13 *
14 * Usage: provsql_migrate_mmap -D <pgdata> -c <connstr>
15 *
16 * connstr should point to the postgres / template1 database so the tool
17 * can enumerate all databases. The tool then re-connects per database.
18 * If connstr already contains "dbname=", append " dbname=<target>" to
19 * override it (libpq uses the last occurrence).
20 *
21 * Gates belonging to tables in more than one database are written to
22 * every relevant database. UUID v4 collisions across databases are
23 * negligible.
24 *
25 * The tool skips any database whose $PGDATA/base/<oid>/provsql_gates.mmap
26 * already exists.
27 */
28
29/* Include libpq first — it provides Oid (unsigned int) via postgres_ext.h
30 * before any PostgreSQL server headers are pulled in. */
31#include <libpq-fe.h>
32
33#include <cassert>
34#include <cerrno>
35#include <cstdint>
36#include <cstdio>
37#include <cstdlib>
38#include <cstring>
39#include <functional>
40#include <iostream>
41#include <queue>
42#include <stdexcept>
43#include <string>
44#include <unordered_map>
45#include <unordered_set>
46#include <utility>
47#include <vector>
48
49#include <fcntl.h>
50#include <sys/mman.h>
51#include <sys/stat.h>
52#include <unistd.h>
53
54// ── Portable types ────────────────────────────────────────────────────────────
55
56struct pg_uuid_t { uint8_t data[16]; };
57
58struct UUIDHash {
59 size_t operator()(pg_uuid_t u) const noexcept {
60 uint64_t h; memcpy(&h, u.data, 8); return static_cast<size_t>(h);
61 }
62};
63struct UUIDEq {
64 bool operator()(pg_uuid_t a, pg_uuid_t b) const noexcept {
65 return memcmp(a.data, b.data, 16) == 0;
66 }
67};
68using UUIDSet = std::unordered_set<pg_uuid_t, UUIDHash, UUIDEq>;
69
70// gate_type must match provsql_utils.h exactly (enum values are on-disk ABI).
77
78// GateInformation must match MMappedCircuit.h exactly (on-disk ABI).
79struct GateInformation {
81 unsigned nb_children;
82 unsigned long children_idx;
83 double prob;
84 unsigned info1;
85 unsigned info2;
86 unsigned long extra_idx;
87 unsigned extra_len;
88
92 GateInformation(gate_type t, unsigned n, unsigned long idx)
93 : type(t), nb_children(n), children_idx(idx),
94 prob(1.0), info1(0), info2(0), extra_idx(0), extra_len(0) {}
95};
96
97// ── Old-format readers ────────────────────────────────────────────────────────
98//
99// Old MMappedVector<T> on-disk:
100// unsigned long nb_elements;
101// unsigned long capacity;
102// T d[];
103//
104// Old MMappedUUIDHashTable on-disk:
105// unsigned log_size;
106// (4-byte implicit padding)
107// unsigned long nb_elements;
108// unsigned long next_value;
109// value_t t[]; where value_t = { pg_uuid_t uuid; unsigned long value; }
110
111static constexpr unsigned long NOTHING = static_cast<unsigned long>(-1);
112
113struct OldVecHdr {
114 unsigned long nb_elements;
115 unsigned long capacity;
116};
117
120 unsigned long value;
121};
122
124 unsigned log_size;
125 /* 4-byte implicit padding */
126 unsigned long nb_elements;
127 unsigned long next_value;
128 /* OldHashSlot t[] follows */
129};
130
132 void *base_ = nullptr;
133 size_t sz_ = 0;
134 const OldVecHdr *hdr_ = nullptr;
135 const uint8_t *elems_ = nullptr;
136 size_t esz_ = 0;
137public:
138 void open(const std::string &path, size_t elem_size) {
139 esz_ = elem_size;
140 int fd = ::open(path.c_str(), O_RDONLY); // flawfinder: ignore
141 if (fd < 0)
142 throw std::runtime_error("Cannot open " + path + ": " + strerror(errno));
143 sz_ = static_cast<size_t>(lseek(fd, 0, SEEK_END));
144 base_ = ::mmap(nullptr, sz_, PROT_READ, MAP_SHARED, fd, 0);
145 ::close(fd);
146 if (base_ == MAP_FAILED)
147 throw std::runtime_error("mmap(" + path + "): " + strerror(errno));
148 hdr_ = reinterpret_cast<const OldVecHdr *>(base_);
149 elems_ = reinterpret_cast<const uint8_t *>(hdr_ + 1);
150 }
151 ~OldMMapVec() { if (base_ && base_ != MAP_FAILED) munmap(base_, sz_); }
152 unsigned long size() const { return hdr_ ? hdr_->nb_elements : 0; }
153 const void *at(unsigned long k) const { return elems_ + k * esz_; }
154};
155
157 void *base_ = nullptr;
158 size_t sz_ = 0;
159 const OldTableHdr *hdr_ = nullptr;
160 const OldHashSlot *slots_ = nullptr;
161 unsigned long cap_ = 0;
162public:
163 void open(const std::string &path) {
164 int fd = ::open(path.c_str(), O_RDONLY); // flawfinder: ignore
165 if (fd < 0)
166 throw std::runtime_error("Cannot open " + path + ": " + strerror(errno));
167 sz_ = static_cast<size_t>(lseek(fd, 0, SEEK_END));
168 base_ = ::mmap(nullptr, sz_, PROT_READ, MAP_SHARED, fd, 0);
169 ::close(fd);
170 if (base_ == MAP_FAILED)
171 throw std::runtime_error("mmap(" + path + "): " + strerror(errno));
172 hdr_ = reinterpret_cast<const OldTableHdr *>(base_);
173 slots_ = reinterpret_cast<const OldHashSlot *>(hdr_ + 1);
174 cap_ = 1UL << hdr_->log_size;
175 }
176 ~OldMMapHash() { if (base_ && base_ != MAP_FAILED) munmap(base_, sz_); }
177
178 unsigned long lookup(pg_uuid_t u) const {
179 if (!hdr_) return NOTHING;
180 uint64_t h; memcpy(&h, u.data, 8);
181 unsigned long k = h % cap_;
182 while (slots_[k].value != NOTHING && memcmp(slots_[k].uuid.data, u.data, 16))
183 k = (k + 1) % cap_;
184 return slots_[k].value;
185 }
186
187 /* Return all stored (uuid, index) pairs. */
188 std::vector<std::pair<pg_uuid_t, unsigned long>> allEntries() const {
189 std::vector<std::pair<pg_uuid_t, unsigned long>> out;
190 if (!hdr_) return out;
191 out.reserve(hdr_->nb_elements);
192 for (unsigned long i = 0; i < cap_; ++i)
193 if (slots_[i].value != NOTHING)
194 out.emplace_back(slots_[i].uuid, slots_[i].value);
195 return out;
196 }
197};
198
204
205 void open(const std::string &pgdata) {
206 mapping.open(pgdata + "/provsql_mapping.mmap");
207 gates.open (pgdata + "/provsql_gates.mmap", sizeof(GateInformation));
208 wires.open (pgdata + "/provsql_wires.mmap", sizeof(pg_uuid_t));
209 extra.open (pgdata + "/provsql_extra.mmap", sizeof(char));
210 }
211
213 unsigned long idx = mapping.lookup(u);
214 if (idx == NOTHING) return nullptr;
215 return reinterpret_cast<const GateInformation *>(gates.at(idx));
216 }
217
218 std::vector<pg_uuid_t> getChildren(const GateInformation *gi) const {
219 std::vector<pg_uuid_t> result;
220 for (unsigned long k = gi->children_idx; k < gi->children_idx + gi->nb_children; ++k)
221 result.push_back(*reinterpret_cast<const pg_uuid_t *>(wires.at(k)));
222 return result;
223 }
224
225 std::string getExtra(const GateInformation *gi) const {
226 std::string s;
227 s.reserve(gi->extra_len);
228 for (unsigned long k = gi->extra_idx; k < gi->extra_idx + gi->extra_len; ++k)
229 s += *reinterpret_cast<const char *>(extra.at(k));
230 return s;
231 }
232};
233
234// ── New-format writers ────────────────────────────────────────────────────────
235//
236// Replicates the on-disk layout of MMappedVector<T> and MMappedUUIDHashTable
237// without depending on PostgreSQL server headers.
238//
239// New MMappedVector<T> on-disk:
240// uint64_t magic; uint16_t version; uint16_t elem_size; uint32_t _reserved;
241// unsigned long nb_elements; unsigned long capacity;
242// T d[];
243//
244// New MMappedUUIDHashTable on-disk:
245// uint64_t magic; uint16_t version; uint16_t elem_size; uint32_t _reserved;
246// unsigned log_size; (4-byte implicit padding)
247// unsigned long nb_elements; unsigned long next_value;
248// value_t t[];
249
250static constexpr uint64_t MAGIC_GATES =
251 uint64_t('P') | uint64_t('v') << 8 | uint64_t('S') << 16 | uint64_t('G') << 24 |
252 uint64_t('a') << 32 | uint64_t('t') << 40 | uint64_t('e') << 48 | uint64_t('s') << 56;
253static constexpr uint64_t MAGIC_WIRES =
254 uint64_t('P') | uint64_t('v') << 8 | uint64_t('S') << 16 | uint64_t('W') << 24 |
255 uint64_t('i') << 32 | uint64_t('r') << 40 | uint64_t('e') << 48 | uint64_t('s') << 56;
256static constexpr uint64_t MAGIC_MAPPING =
257 uint64_t('P') | uint64_t('v') << 8 | uint64_t('S') << 16 | uint64_t('M') << 24 |
258 uint64_t('a') << 32 | uint64_t('p') << 40 | uint64_t('n') << 48 | uint64_t('g') << 56;
259static constexpr uint64_t MAGIC_EXTRA =
260 uint64_t('P') | uint64_t('v') << 8 | uint64_t('S') << 16 | uint64_t('E') << 24 |
261 uint64_t('x') << 32 | uint64_t('t') << 40 | uint64_t('r') << 48 | uint64_t('a') << 56;
262
263struct NewVecHdr {
264 uint64_t magic;
265 uint16_t version;
266 uint16_t elem_size;
267 uint32_t _reserved;
268 unsigned long nb_elements;
269 unsigned long capacity;
270};
271
274 unsigned long value;
275};
276
278 uint64_t magic;
279 uint16_t version;
280 uint16_t elem_size;
281 uint32_t _reserved;
282 unsigned log_size;
283 /* 4-byte implicit padding */
284 unsigned long nb_elements;
285 unsigned long next_value;
286};
287
288static void writeNewVector(const std::string &path, uint64_t magic,
289 size_t elem_size, const uint8_t *raw, unsigned long nb)
290{
291 unsigned long cap = 1UL << 16; /* STARTING_CAPACITY */
292 while (cap < nb) cap *= 2;
293
294 size_t fsz = sizeof(NewVecHdr) + elem_size * cap;
295 int fd = ::open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600); // flawfinder: ignore
296 if (fd < 0) throw std::runtime_error("Cannot create " + path + ": " + strerror(errno));
297 if (ftruncate(fd, static_cast<off_t>(fsz))) {
298 ::close(fd);
299 throw std::runtime_error("ftruncate(" + path + "): " + strerror(errno));
300 }
301 void *base = ::mmap(nullptr, fsz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
302 ::close(fd);
303 if (base == MAP_FAILED) throw std::runtime_error("mmap(" + path + "): " + strerror(errno));
304
305 auto *hdr = reinterpret_cast<NewVecHdr *>(base);
306 hdr->magic = magic;
307 hdr->version = 1;
308 hdr->elem_size = static_cast<uint16_t>(elem_size);
309 hdr->_reserved = 0;
310 hdr->nb_elements = nb;
311 hdr->capacity = cap;
312 if (nb > 0) memcpy(hdr + 1, raw, nb * elem_size);
313
314 msync(base, fsz, MS_SYNC);
315 munmap(base, fsz);
316}
317
318static void writeNewHashTable(const std::string &path,
319 const std::vector<std::pair<pg_uuid_t, unsigned long>> &entries)
320{
321 unsigned log_size = 16;
322 while ((1UL << log_size) < entries.size() * 2)
323 ++log_size;
324 unsigned long cap = 1UL << log_size;
325
326 size_t fsz = sizeof(NewTableHdr) + cap * sizeof(NewHashSlot);
327 int fd = ::open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600); // flawfinder: ignore
328 if (fd < 0) throw std::runtime_error("Cannot create " + path + ": " + strerror(errno));
329 if (ftruncate(fd, static_cast<off_t>(fsz))) {
330 ::close(fd);
331 throw std::runtime_error("ftruncate(" + path + "): " + strerror(errno));
332 }
333 void *base = ::mmap(nullptr, fsz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
334 ::close(fd);
335 if (base == MAP_FAILED) throw std::runtime_error("mmap(" + path + "): " + strerror(errno));
336
337 auto *hdr = reinterpret_cast<NewTableHdr *>(base);
338 hdr->magic = MAGIC_MAPPING;
339 hdr->version = 1;
340 hdr->elem_size = static_cast<uint16_t>(sizeof(NewHashSlot));
341 hdr->_reserved = 0;
342 hdr->log_size = log_size;
343 hdr->nb_elements = entries.size();
344 hdr->next_value = entries.size();
345
346 auto *slots = reinterpret_cast<NewHashSlot *>(hdr + 1);
347 for (unsigned long i = 0; i < cap; ++i)
348 slots[i].value = NOTHING;
349
350 for (auto &[u, v] : entries) {
351 uint64_t h; memcpy(&h, u.data, 8);
352 unsigned long k = h % cap;
353 while (slots[k].value != NOTHING)
354 k = (k + 1) % cap;
355 slots[k].uuid = u;
356 slots[k].value = v;
357 }
358
359 msync(base, fsz, MS_SYNC);
360 munmap(base, fsz);
361}
362
363// ── libpq RAII helpers ────────────────────────────────────────────────────────
364
365struct Conn {
366 PGconn *c;
367 explicit Conn(const std::string &cs) : c(PQconnectdb(cs.c_str())) {
368 if (PQstatus(c) != CONNECTION_OK)
369 throw std::runtime_error(std::string("libpq connect: ") + PQerrorMessage(c));
370 }
371 ~Conn() { PQfinish(c); }
372 PGconn *get() const { return c; }
373};
374
375struct Res {
376 PGresult *r;
377 explicit Res(PGresult *r) : r(r) {}
378 ~Res() { PQclear(r); }
379 bool ok() const { return PQresultStatus(r) == PGRES_TUPLES_OK; }
380 int ntuples() const { return PQntuples(r); }
381 const char *get(int row, int col) const { return PQgetvalue(r, row, col); }
382};
383
384// ── UUID parsing ──────────────────────────────────────────────────────────────
385
386static pg_uuid_t parseUUID(const char *s)
387{
388 auto hex = [](char c) -> uint8_t {
389 if (c >= '0' && c <= '9') return static_cast<uint8_t>(c - '0');
390 if (c >= 'a' && c <= 'f') return static_cast<uint8_t>(c - 'a' + 10);
391 return static_cast<uint8_t>(c - 'A' + 10);
392 };
393 pg_uuid_t u{};
394 int b = 0;
395 while (*s && b < 16) {
396 if (*s == '-') { ++s; continue; }
397 u.data[b++] = static_cast<uint8_t>((hex(s[0]) << 4) | hex(s[1]));
398 s += 2;
399 }
400 return u;
401}
402
403// ── BFS to collect reachable gates ───────────────────────────────────────────
404
405struct GateData {
407 std::vector<pg_uuid_t> children;
408 std::string extra_str;
409};
410
411/* Returns map: old_gate_index -> GateData for all reachable gates. */
412static std::unordered_map<unsigned long, GateData>
413collectReachable(const OldCircuit &old, const UUIDSet &roots)
414{
415 std::unordered_map<unsigned long, GateData> result;
416 std::queue<pg_uuid_t> todo;
417 UUIDSet visited;
418
419 for (auto &u : roots)
420 if (!visited.count(u)) { visited.insert(u); todo.push(u); }
421
422 while (!todo.empty()) {
423 pg_uuid_t u = todo.front(); todo.pop();
424
425 unsigned long idx = old.mapping.lookup(u);
426 if (idx == NOTHING) continue; /* implicit input gate, no children */
427 if (result.count(idx)) continue;
428
429 const GateInformation *gi = reinterpret_cast<const GateInformation *>(old.gates.at(idx));
430 GateData gd;
431 gd.gi = *gi;
432 gd.children = old.getChildren(gi);
433 gd.extra_str = old.getExtra(gi);
434
435 auto children_copy = gd.children; /* save before move */
436 result[idx] = std::move(gd);
437
438 for (auto &child : children_copy)
439 if (!visited.count(child)) { visited.insert(child); todo.push(child); }
440 }
441 return result;
442}
443
444// ── Per-database migration ────────────────────────────────────────────────────
445
446static void migrateDatabase(const OldCircuit &old, const std::string &pgdata,
447 Oid db_oid, const UUIDSet &roots)
448{
449 std::string dir = pgdata + "/base/" + std::to_string(db_oid);
450 std::string gates_path = dir + "/provsql_gates.mmap";
451
452 struct stat st;
453 if (stat(gates_path.c_str(), &st) == 0) {
454 std::cerr << " Skipping db " << db_oid << " (provsql_gates.mmap already exists)\n";
455 return;
456 }
457
458 auto reachable = collectReachable(old, roots);
459 std::cerr << " db " << db_oid << ": " << reachable.size() << " reachable gates\n";
460
461 /* Build reverse map: old_index -> UUID, from the full old hash table. */
462 std::unordered_map<unsigned long, pg_uuid_t> old_idx_to_uuid;
463 old_idx_to_uuid.reserve(old.mapping.allEntries().size());
464 for (auto &[u, v] : old.mapping.allEntries())
465 old_idx_to_uuid[v] = u;
466
467 /* Assign compact new indices.
468 * Pass 1: explicitly stored gates in reachable.
469 * Pass 2: implicit input children (referenced but absent from mapping). */
470 std::unordered_map<pg_uuid_t, unsigned long, UUIDHash, UUIDEq> uuid_to_new;
471 unsigned long next_new = 0;
472
473 for (auto &[old_idx, gd] : reachable) {
474 auto it = old_idx_to_uuid.find(old_idx);
475 if (it != old_idx_to_uuid.end() && !uuid_to_new.count(it->second))
476 uuid_to_new[it->second] = next_new++;
477 }
478 for (auto &[old_idx, gd] : reachable) {
479 for (auto &child : gd.children)
480 if (!uuid_to_new.count(child))
481 uuid_to_new[child] = next_new++;
482 }
483 /* Also include roots that have no explicit gate record (rare but safe). */
484 for (auto &u : roots)
485 if (!uuid_to_new.count(u))
486 uuid_to_new[u] = next_new++;
487
488 unsigned long N = next_new;
489 if (N == 0) { std::cerr << " No gates to write, skipping.\n"; return; }
490
491 /* Build UUID-indexed view: new_idx -> uuid. */
492 std::vector<pg_uuid_t> new_idx_to_uuid(N);
493 for (auto &[u, ni] : uuid_to_new)
494 new_idx_to_uuid[ni] = u;
495
496 /* Build new gates, wires, extra arrays. */
497 std::vector<GateInformation> new_gates(N);
498 std::vector<pg_uuid_t> new_wires;
499 std::vector<uint8_t> new_extra;
500
501 for (unsigned long ni = 0; ni < N; ++ni) {
502 pg_uuid_t u = new_idx_to_uuid[ni];
503 unsigned long old_idx = old.mapping.lookup(u);
504
505 if (old_idx == NOTHING) {
506 new_gates[ni] = GateInformation(gate_input, 0, new_wires.size());
507 continue;
508 }
509
510 auto rit = reachable.find(old_idx);
511 if (rit == reachable.end()) {
512 new_gates[ni] = GateInformation(gate_input, 0, new_wires.size());
513 continue;
514 }
515
516 const GateData &gd = rit->second;
517 unsigned long wires_start = new_wires.size();
518
519 for (auto &child : gd.children) {
520 /* Remap child UUID to its new index, stored as the UUID itself —
521 * wires still store UUIDs in both old and new format. */
522 new_wires.push_back(child);
523 }
524
525 new_gates[ni] = gd.gi;
526 new_gates[ni].children_idx = wires_start;
527 new_gates[ni].extra_idx = 0;
528 new_gates[ni].extra_len = 0;
529
530 if (!gd.extra_str.empty()) {
531 new_gates[ni].extra_idx = new_extra.size();
532 new_gates[ni].extra_len = static_cast<unsigned>(gd.extra_str.size());
533 for (char c : gd.extra_str)
534 new_extra.push_back(static_cast<uint8_t>(c));
535 }
536 }
537
538 /* Build mapping entries for the new hash table. */
539 std::vector<std::pair<pg_uuid_t, unsigned long>> mapping_entries(
540 uuid_to_new.begin(), uuid_to_new.end());
541
542 /* Write the four files. */
543 writeNewVector(gates_path, MAGIC_GATES, sizeof(GateInformation),
544 reinterpret_cast<const uint8_t *>(new_gates.data()), N);
545
546 writeNewVector(dir + "/provsql_wires.mmap", MAGIC_WIRES, sizeof(pg_uuid_t),
547 reinterpret_cast<const uint8_t *>(new_wires.data()), new_wires.size());
548
549 writeNewVector(dir + "/provsql_extra.mmap", MAGIC_EXTRA, sizeof(char),
550 new_extra.data(), new_extra.size());
551
552 writeNewHashTable(dir + "/provsql_mapping.mmap", mapping_entries);
553
554 std::cerr << " Wrote " << N << " gates to " << dir << "\n";
555}
556
557// ── main ──────────────────────────────────────────────────────────────────────
558
559int main(int argc, char **argv)
560{
561 std::string pgdata, connstr;
562
563 for (int i = 1; i < argc; ++i) {
564 if (strcmp(argv[i], "-D") == 0 && i + 1 < argc) pgdata = argv[++i];
565 else if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) connstr = argv[++i];
566 }
567
568 if (pgdata.empty() || connstr.empty()) {
569 std::cerr << "Usage: provsql_migrate_mmap -D <pgdata> -c <connstr>\n";
570 return 1;
571 }
572
573 struct stat st;
574 if (stat((pgdata + "/provsql_gates.mmap").c_str(), &st) != 0) {
575 std::cerr << "No provsql_gates.mmap found in " << pgdata << " — nothing to migrate.\n";
576 return 0;
577 }
578
579 std::cerr << "Opening old circuit files in " << pgdata << "...\n";
580 OldCircuit old;
581 try {
582 old.open(pgdata);
583 } catch (const std::exception &e) {
584 std::cerr << "Error: " << e.what() << "\n";
585 return 1;
586 }
587
588 std::cerr << "Connecting to cluster...\n";
589 try {
590 Conn conn(connstr);
591
592 Res dbs(PQexec(conn.get(),
593 "SELECT oid, datname FROM pg_database "
594 "WHERE datallowconn AND NOT datistemplate "
595 "ORDER BY oid"));
596 if (!dbs.ok()) {
597 std::cerr << "pg_database query failed: " << PQerrorMessage(conn.get()) << "\n";
598 return 1;
599 }
600
601 for (int i = 0; i < dbs.ntuples(); ++i) {
602 Oid db_oid = static_cast<Oid>(atol(dbs.get(i, 0)));
603 std::string dbname = dbs.get(i, 1);
604 std::cerr << "Database " << dbname << " (oid=" << db_oid << ")...\n";
605
606 /* Connect to this specific database (libpq uses last dbname= value). */
607 PGconn *dbc = PQconnectdb((connstr + " dbname=" + dbname).c_str());
608 if (PQstatus(dbc) != CONNECTION_OK) {
609 std::cerr << " Cannot connect: " << PQerrorMessage(dbc) << "\n";
610 PQfinish(dbc);
611 continue;
612 }
613
614 /* Check provsql is installed. */
615 {
616 PGresult *r = PQexec(dbc, "SELECT 1 FROM pg_extension WHERE extname='provsql'");
617 bool has_provsql = PQresultStatus(r) == PGRES_TUPLES_OK && PQntuples(r) > 0;
618 PQclear(r);
619 if (!has_provsql) { PQfinish(dbc); continue; }
620 }
621 std::cerr << " provsql is installed\n";
622
623 /* Find provenance-tracked tables (those with a 'provsql' column
624 * outside the provsql schema itself). */
625 PGresult *r = PQexec(dbc,
626 "SELECT attrelid::regclass::text "
627 "FROM pg_attribute "
628 "JOIN pg_class ON attrelid = pg_class.oid "
629 "JOIN pg_namespace ON relnamespace = pg_namespace.oid "
630 "WHERE attname = 'provsql' AND attisdropped = false "
631 " AND relkind = 'r' AND nspname <> 'provsql'");
632
633 if (PQresultStatus(r) != PGRES_TUPLES_OK) {
634 std::cerr << " Attribute query failed: " << PQerrorMessage(dbc) << "\n";
635 PQclear(r); PQfinish(dbc); continue;
636 }
637
638 std::vector<std::string> tables;
639 for (int j = 0; j < PQntuples(r); ++j)
640 tables.emplace_back(PQgetvalue(r, j, 0));
641 PQclear(r);
642
643 UUIDSet roots;
644 for (const auto &tname : tables) {
645 /* Safe: tname comes from pg_class via regclass cast, not user input. */
646 std::string q = "SELECT provsql FROM " + tname;
647 PGresult *tr = PQexec(dbc, q.c_str());
648 if (PQresultStatus(tr) != PGRES_TUPLES_OK) {
649 std::cerr << " Query on " << tname << " failed, skipping\n";
650 PQclear(tr); continue;
651 }
652 for (int k = 0; k < PQntuples(tr); ++k) {
653 const char *s = PQgetvalue(tr, k, 0);
654 if (s && s[0]) roots.insert(parseUUID(s));
655 }
656 PQclear(tr);
657 }
658 PQfinish(dbc);
659
660 std::cerr << " " << roots.size() << " root UUIDs across "
661 << tables.size() << " table(s)\n";
662
663 if (!roots.empty())
664 migrateDatabase(old, pgdata, db_oid, roots);
665 }
666 } catch (const std::exception &e) {
667 std::cerr << "Error: " << e.what() << "\n";
668 return 1;
669 }
670
671 /* All databases processed successfully: remove the old flat files so
672 * the new background worker does not find them and so that the upgrade
673 * script warning is not triggered again. */
674 static const char *old_files[] = {
675 "provsql_gates.mmap",
676 "provsql_wires.mmap",
677 "provsql_mapping.mmap",
678 "provsql_extra.mmap",
679 nullptr
680 };
681 std::cerr << "Removing old flat circuit files from " << pgdata << ":\n";
682 for (int i = 0; old_files[i]; ++i) {
683 std::string path = pgdata + "/" + old_files[i];
684 if (unlink(path.c_str()) == 0)
685 std::cerr << " Deleted " << path << "\n";
686 else if (errno != ENOENT)
687 std::cerr << " Warning: could not delete " << path << ": " << strerror(errno) << "\n";
688 }
689
690 std::cerr << "Migration complete.\n"
691 "Please restart the PostgreSQL server.\n";
692 return 0;
693}
void open(const std::string &path)
unsigned long lookup(pg_uuid_t u) const
std::vector< std::pair< pg_uuid_t, unsigned long > > allEntries() const
const OldHashSlot * slots_
const OldTableHdr * hdr_
void open(const std::string &path, size_t elem_size)
const void * at(unsigned long k) const
const uint8_t * elems_
unsigned long size() const
const OldVecHdr * hdr_
static pg_uuid_t parseUUID(const char *s)
static constexpr uint64_t MAGIC_GATES
std::unordered_set< pg_uuid_t, UUIDHash, UUIDEq > UUIDSet
static constexpr uint64_t MAGIC_WIRES
int main(int argc, char **argv)
static void migrateDatabase(const OldCircuit &old, const std::string &pgdata, Oid db_oid, const UUIDSet &roots)
static constexpr unsigned long NOTHING
static void writeNewVector(const std::string &path, uint64_t magic, size_t elem_size, const uint8_t *raw, unsigned long nb)
static void writeNewHashTable(const std::string &path, const std::vector< std::pair< pg_uuid_t, unsigned long > > &entries)
static std::unordered_map< unsigned long, GateData > collectReachable(const OldCircuit &old, const UUIDSet &roots)
static constexpr uint64_t MAGIC_EXTRA
static constexpr uint64_t MAGIC_MAPPING
PGconn * get() const
Conn(const std::string &cs)
std::string extra_str
GateInformation gi
std::vector< pg_uuid_t > children
Per-gate metadata stored in the gates MMappedVector.
double prob
Associated probability (default 1.0).
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.
GateInformation(gate_type t, unsigned n, unsigned long idx)
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, …).
unsigned long nb_elements
unsigned long next_value
unsigned long nb_elements
unsigned long capacity
void open(const std::string &pgdata)
std::vector< pg_uuid_t > getChildren(const GateInformation *gi) const
std::string getExtra(const GateInformation *gi) const
const GateInformation * getGate(pg_uuid_t u) const
unsigned long nb_elements
unsigned long next_value
unsigned long capacity
unsigned long nb_elements
PGresult * r
Res(PGresult *r)
int ntuples() const
bool ok() const
const char * get(int row, int col) const
bool operator()(pg_uuid_t a, pg_uuid_t b) const noexcept
size_t operator()(pg_uuid_t u) const noexcept
UUID structure.