ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
provsql_mmap.c
Go to the documentation of this file.
1/**
2 * @file provsql_mmap.c
3 * @brief Background worker registration and IPC primitives for mmap-backed storage.
4 *
5 * Implements the PostgreSQL background worker lifecycle functions declared
6 * in @c provsql_mmap.h:
7 * - @c RegisterProvSQLMMapWorker(): registers the worker with the postmaster
8 * during @c _PG_init().
9 * - @c provsql_mmap_worker(): worker entry point; sets up signal handlers
10 * and enters @c provsql_mmap_main_loop().
11 *
12 * The IPC between normal backends and the background worker is handled in
13 * @c MMappedCircuit.cpp. This file provides the PostgreSQL-specific glue
14 * (background worker API, signal handling).
15 *
16 * Also declares the shared write buffer @c buffer[] and position counter
17 * @c bufferpos used by the @c STARTWRITEM / @c ADDWRITEM / @c SENDWRITEM
18 * macros in @c provsql_mmap.h.
19 *
20 * The gate-creation SQL functions (e.g. @c create_gate()) that backends
21 * call are also implemented here; they acquire the IPC lock, write a
22 * message to the background worker, and wait for an acknowledgment.
23 */
24#include "provsql_mmap.h"
25#include "provsql_shmem.h"
26#include "provsql_utils.h"
27#include "MMappedTableInfo.h"
28
29#include <errno.h>
30#include <unistd.h>
31#include <poll.h>
32#include <math.h>
33#include <assert.h>
34
35#include "postgres.h"
36#include "postmaster/bgworker.h"
37#include "catalog/pg_type.h" /* INT2OID etc. -- the _d.h variant
38 only exists from PG 11 onwards */
39#include "fmgr.h"
40#include "funcapi.h"
41#include "utils/array.h"
42#include "access/htup_details.h"
43#include "utils/builtins.h"
44#include "utils/inval.h"
45#include "utils/syscache.h"
46
47#include "circuit_cache.h"
48
49#ifdef PROVSQL_INPROCESS_STORE
50
51char *buffer = NULL; // flawfinder: ignore
52unsigned bufferpos = 0;
53size_t buffercap = 0;
54
55void provsql_buffer_ensure(size_t need)
56{
57 if(need > buffercap) {
58 size_t newcap = buffercap ? buffercap * 2 : 4096;
59 while(newcap < need)
60 newcap *= 2;
61 buffer = realloc(buffer, newcap);
62 if(!buffer)
63 provsql_error("ProvSQL: out of memory growing the IPC buffer");
64 buffercap = newcap;
65 }
66}
67
68/* No background worker in the single-process build. */
69
70#else
71
72char buffer[PIPE_BUF]={}; // flawfinder: ignore
73unsigned bufferpos=0;
74
75bool provsql_read_all(int fd, void *dst, size_t n)
76{
77 char *p = dst;
78 size_t remaining = n;
79 while(remaining > 0) {
80 ssize_t r = read(fd, p, remaining); // flawfinder: ignore
81 if(r <= 0)
82 return false;
83 remaining -= r;
84 p += r;
85 }
86 return true;
87}
88
89#if PG_VERSION_NUM >= 190000
90/* PostgreSQL 19 changed the default background-worker SIGTERM handler
91 * from bgworker_die() (immediate FATAL from the signal handler) to the
92 * flag-based die(), which only acts at the next CHECK_FOR_INTERRUPTS().
93 * This worker blocks in read() on the IPC pipe (restarted by
94 * SA_RESTART), so it would never observe the flag and a fast shutdown
95 * would hang on it. Restore the pre-19 semantics: the worker holds no
96 * transaction state and the mmap store is crash-safe, so exiting
97 * mid-read is fine. */
98static void provsql_worker_die(SIGNAL_ARGS)
99{
100 ereport(FATAL,
101 (errcode(ERRCODE_ADMIN_SHUTDOWN),
102 errmsg("terminating background worker \"%s\" due to administrator command",
103 MyBgworkerEntry->bgw_type)));
104}
105#endif
106
107PGDLLEXPORT void provsql_mmap_worker(Datum ignored)
108{
109#if PG_VERSION_NUM >= 190000
110 pqsignal(SIGTERM, provsql_worker_die);
111#endif
112 BackgroundWorkerUnblockSignals();
114 close(provsql_shared_state->pipebmw);
115 close(provsql_shared_state->pipembr);
116 provsql_log("%s initialized", MyBgworkerEntry->bgw_name);
117
119
121}
122
124{
125 BackgroundWorker worker;
126
127 snprintf(worker.bgw_name, BGW_MAXLEN, "ProvSQL MMap Worker");
128#if PG_VERSION_NUM >= 110000
129 snprintf(worker.bgw_type, BGW_MAXLEN, "ProvSQL MMap");
130#endif
131
132 worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
133 worker.bgw_start_time = BgWorkerStart_PostmasterStart;
134 worker.bgw_restart_time = 1;
135
136 snprintf(worker.bgw_library_name, BGW_MAXLEN, "provsql");
137 snprintf(worker.bgw_function_name, BGW_MAXLEN, "provsql_mmap_worker");
138#if PG_VERSION_NUM < 100000
139 worker.bgw_main = NULL;
140#endif
141
142 worker.bgw_main_arg = (Datum) 0;
143 worker.bgw_notify_pid = 0;
144
145 RegisterBackgroundWorker(&worker);
146}
147
148#endif /* PROVSQL_INPROCESS_STORE */
149
150PG_FUNCTION_INFO_V1(get_gate_type);
151/** @brief PostgreSQL-callable wrapper for get_gate_type().
152 *
153 * On cache miss this fetches BOTH the gate type and its children from
154 * the worker, in one critical section, then caches them together. If
155 * we cached only the type (with an empty children list), a subsequent
156 * get_children() call for the same token would consult the cache, find
157 * the entry, and return 0 children : never querying the worker for the
158 * real children. provsql.provenance_evaluate hits exactly that pattern
159 * (it calls get_gate_type first, then unnest(get_children(...))) and
160 * silently folds plus/times gates over an empty set.
161 */
162/** @brief Fetch a gate's type and children, cache-first with a worker
163 * round-trip (and cache fill) on a miss. Factored out of the
164 * get_gate_type() wrapper for in-extension callers that walk the circuit
165 * from C (e.g. the annotation-transparent set_prob()). On return
166 * @p *children_out is a @c calloc'd array to be freed by the caller, or
167 * @c NULL when the gate has no children. */
169 unsigned *nb_children_out,
170 pg_uuid_t **children_out)
171{
172 gate_type type;
173 unsigned nb_children = 0;
174 pg_uuid_t *children = NULL;
175
176 type = circuit_cache_get_type(*token);
177 if(type!=gate_invalid) {
178 *nb_children_out = circuit_cache_get_children(*token, children_out);
179 return type;
180 }
181
182 /* Type fetch (message 't'). */
183 STARTWRITEM();
184 ADDWRITEM("t", char);
185 ADDWRITEM(&MyDatabaseId, Oid);
186 ADDWRITEM(token, pg_uuid_t);
187
189
190 if(!SENDWRITEM() || !READB(type, gate_type)) {
192 provsql_error("Cannot communicate on pipe (message type t)");
193 }
194
195 /* Children fetch (message 'c'), batched in the same critical
196 * section so the cache entry below is complete. Skipped when the
197 * token is unknown (worker reports gate_invalid). */
198 if(type != gate_invalid) {
199 STARTWRITEM();
200 ADDWRITEM("c", char);
201 ADDWRITEM(&MyDatabaseId, Oid);
202 ADDWRITEM(token, pg_uuid_t);
203
204 if(!SENDWRITEM() || !READB(nb_children, unsigned)) {
206 provsql_error("Cannot communicate on pipe (message type c during get_gate_type)");
207 }
208
209 if(nb_children > 0) {
210 children = calloc(nb_children, sizeof(pg_uuid_t));
211 if(!READB_BYTES(children, nb_children * sizeof(pg_uuid_t))) {
213 provsql_error("Cannot read children from pipe (during get_gate_type)");
214 }
215 }
216 }
217
219
220 /* Skip caching the gate_input lazy default: MMappedCircuit::getGateType
221 * returns gate_input both for real input gates and for tokens that are
222 * not yet in the mapping. Caching the latter would poison subsequent
223 * create_gate() calls in this session (the cache hit would short-circuit
224 * the worker IPC, dropping the gate). The cost is one extra IPC per
225 * lookup of a real input gate -- acceptable. */
226 if(!(type == gate_input && nb_children == 0))
227 circuit_cache_create_gate(*token, type, nb_children, children);
228 *nb_children_out = nb_children;
229 *children_out = children;
230 return type;
231}
232
233Datum get_gate_type(PG_FUNCTION_ARGS)
234{
235 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
236 gate_type type;
237 constants_t constants=get_constants(true);
238 unsigned nb_children = 0;
239 pg_uuid_t *children = NULL;
240
241 if(PG_ARGISNULL(0))
242 PG_RETURN_NULL();
243
244 type = provsql_fetch_gate(token, &nb_children, &children);
245 if(children) free(children);
246 PG_RETURN_INT32(constants.GATE_TYPE_TO_OID[type]);
247}
248
249/** @brief Internal entry point behind create_gate(): cache + worker IPC.
250 *
251 * Factored out of the SQL-callable wrapper so in-extension C/C++ code
252 * (e.g. the decomposition-aligned reachability materialiser) can create
253 * gates without Datum marshalling or gate-type-OID lookups. Same
254 * semantics: write-through to the per-session cache, then the C message
255 * to the background worker; MMappedCircuit::createGate is idempotent on
256 * already-mapped tokens. */
258 unsigned nb_children,
259 const pg_uuid_t *children_data)
260{
261 /* Populate the per-session cache, but unconditionally fall through to
262 * the worker IPC: a cache hit only proves "this token has been seen
263 * in this session before" (e.g. by get_gate_type returning the
264 * gate_input lazy default for an unknown token) -- not "the worker
265 * already has a gate for it". Skipping the IPC on a cache hit caused
266 * silently-dropped create_gate calls under concurrent backends.
267 * MMappedCircuit::createGate is idempotent on already-mapped tokens. */
268 circuit_cache_create_gate(*token, type, nb_children, children_data);
269
270 STARTWRITEM();
271 ADDWRITEM("C", char);
272 ADDWRITEM(&MyDatabaseId, Oid);
273 ADDWRITEM(token, pg_uuid_t);
274 ADDWRITEM(&type, gate_type);
275 ADDWRITEM(&nb_children, unsigned);
276
277#ifdef PROVSQL_INPROCESS_STORE
278 /* The in-memory FIFO has no PIPE_BUF atomicity limit: always send the
279 gate and all its children as a single message. */
280 if(1) {
281#else
282 if(PIPE_BUF-bufferpos>nb_children*sizeof(pg_uuid_t)) {
283#endif
284 // Enough space in the buffer for an atomic write, no need of
285 // exclusive locks
286
287 for(unsigned i=0; i<nb_children; ++i)
288 ADDWRITEM(&children_data[i], pg_uuid_t);
289
291 if(!SENDWRITEM()) {
293 provsql_error("Cannot write to pipe (message type C)");
294 }
296 }
297#ifndef PROVSQL_INPROCESS_STORE
298 else {
299 // Not enough space in buffer, pipe write won't be atomic, we need to
300 // make several writes and use locks
301 unsigned children_per_batch = PIPE_BUF/sizeof(pg_uuid_t);
302
304
305 if(!SENDWRITEM()) {
307 provsql_error("Cannot write to pipe (message type C)");
308 }
309
310 for(unsigned j=0; j<1+(nb_children-1)/children_per_batch; ++j) {
311 STARTWRITEM();
312
313 for(unsigned i=j*children_per_batch; i<(j+1)*children_per_batch && i<nb_children; ++i) {
314 ADDWRITEM(&children_data[i], pg_uuid_t);
315 }
316
317 if(!SENDWRITEM()) {
319 provsql_error("Cannot write to pipe (message type C)");
320 }
321 }
322
324 }
325#endif
326}
327
328/** @brief Internal entry point behind set_prob(): worker IPC only. Returns
329 * whether the worker accepted the probability (false on a non-input gate). */
330bool provsql_internal_set_prob(const pg_uuid_t *token, double prob)
331{
332 char result;
333
334 STARTWRITEM();
335 ADDWRITEM("P", char);
336 ADDWRITEM(&MyDatabaseId, Oid);
337 ADDWRITEM(token, pg_uuid_t);
338 ADDWRITEM(&prob, double);
339
341 if(!SENDWRITEM() || !READB(result, char)) {
343 provsql_error("Cannot write to pipe");
344 }
346
347 return result;
348}
349
350/** @brief Internal entry point behind set_infos(): worker IPC only. */
351void provsql_internal_set_infos(const pg_uuid_t *token, unsigned info1,
352 unsigned info2)
353{
354 STARTWRITEM();
355 ADDWRITEM("I", char);
356 ADDWRITEM(&MyDatabaseId, Oid);
357 ADDWRITEM(token, pg_uuid_t);
358 ADDWRITEM(&info1, unsigned);
359 ADDWRITEM(&info2, unsigned);
360
362 if(!SENDWRITEM()) {
364 provsql_error("Cannot write to pipe (message type I)");
365 }
367}
368
369PG_FUNCTION_INFO_V1(create_gate);
370/** @brief PostgreSQL-callable wrapper for create_gate(). */
371Datum create_gate(PG_FUNCTION_ARGS)
372{
373 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
374 Oid oid_type = PG_GETARG_INT32(1);
375 ArrayType *children = PG_ARGISNULL(2)?NULL:PG_GETARG_ARRAYTYPE_P(2);
376 unsigned nb_children = 0;
377 gate_type type = gate_invalid;
378 constants_t constants;
379 pg_uuid_t *children_data;
380
381 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
382 provsql_error("Invalid NULL value passed to create_gate");
383
384 if(children) {
385 if(ARR_NDIM(children) > 1)
386 provsql_error("Invalid multi-dimensional array passed to create_gate");
387 if(array_contains_nulls(children))
388 provsql_error("create_gate: children array must not contain NULL "
389 "elements (filter them out before calling)");
390 if(ARR_NDIM(children) == 1)
391 nb_children = *ARR_DIMS(children);
392 }
393
394 constants=get_constants(true);
395
396 for(int i=0; i<nb_gate_types; ++i) {
397 if(constants.GATE_TYPE_TO_OID[i]==oid_type) {
398 type = i;
399 break;
400 }
401 }
402 if(type == gate_invalid) {
403 provsql_error("Invalid gate type");
404 }
405
406 if(nb_children>0)
407 children_data = (pg_uuid_t*) ARR_DATA_PTR(children);
408 else
409 children_data = NULL;
410
411 provsql_internal_create_gate(token, type, nb_children, children_data);
412
413 PG_RETURN_VOID();
414}
415
416PG_FUNCTION_INFO_V1(set_prob);
417/** @brief PostgreSQL-callable wrapper for set_prob().
418 *
419 * Transparent @c gate_annotation wrappers (an inversion-free certificate /
420 * order marker attached by the planner to a certified query's row roots)
421 * are peeled first: a probability set on a wrapped token belongs to the
422 * input gate underneath, so the documented
423 * @c "set_prob(provenance(), p) FROM t" pattern keeps working when the
424 * query happens to be certified. */
425Datum set_prob(PG_FUNCTION_ARGS)
426{
427 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
428 double prob = PG_GETARG_FLOAT8(1);
429 pg_uuid_t peeled;
430
431 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
432 provsql_error("Invalid NULL value passed to set_prob");
433
434 for(;;) {
435 unsigned nb_children = 0;
436 pg_uuid_t *children = NULL;
437 gate_type type = provsql_fetch_gate(token, &nb_children, &children);
438 if(type != gate_annotation || nb_children != 1) {
439 if(children) free(children);
440 break;
441 }
442 peeled = children[0];
443 token = &peeled;
444 free(children);
445 }
446
447 if(!provsql_internal_set_prob(token, prob))
448 provsql_error("set_prob called on non-input gate");
449
450 PG_RETURN_VOID();
451}
452
453PG_FUNCTION_INFO_V1(set_infos);
454/** @brief PostgreSQL-callable wrapper for set_infos(). */
455Datum set_infos(PG_FUNCTION_ARGS)
456{
457 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
458 unsigned info1 = PG_GETARG_INT32(1);
459 unsigned info2 = PG_GETARG_INT32(2);
460
461
462 if(PG_ARGISNULL(1))
463 info1=0;
464 if(PG_ARGISNULL(2))
465 info2=0;
466
467 provsql_internal_set_infos(token, info1, info2);
468
469 PG_RETURN_VOID();
470}
471
472/** @brief Internal entry point behind set_extra(): worker IPC only. */
473void provsql_internal_set_extra(const pg_uuid_t *token, const char *str)
474{
475 unsigned len=strlen(str);
476
477 STARTWRITEM();
478 ADDWRITEM("E", char);
479 ADDWRITEM(&MyDatabaseId, Oid);
480 ADDWRITEM(token, pg_uuid_t);
481 ADDWRITEM(&len, unsigned);
482
483#ifdef PROVSQL_INPROCESS_STORE
484 provsql_buffer_ensure(bufferpos+len);
485#else
486 assert(PIPE_BUF-bufferpos>len);
487#endif
488 memcpy(buffer+bufferpos, str, len), bufferpos+=len;
489
491 if(!SENDWRITEM()) {
493 provsql_error("Cannot write to pipe (message type E)");
494 }
496}
497
498PG_FUNCTION_INFO_V1(set_extra);
499/** @brief PostgreSQL-callable wrapper for set_extra(). */
500Datum set_extra(PG_FUNCTION_ARGS)
501{
502 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
503 text *data = PG_GETARG_TEXT_P(1);
504 char *str=text_to_cstring(data);
505
506 provsql_internal_set_extra(token, str);
507 pfree(str);
508
509 PG_RETURN_VOID();
510}
511
512PG_FUNCTION_INFO_V1(get_extra);
513/** @brief PostgreSQL-callable wrapper for get_extra(). */
514Datum get_extra(PG_FUNCTION_ARGS)
515{
516 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
517 text *result;
518 unsigned len;
519
520 if(PG_ARGISNULL(0))
521 PG_RETURN_NULL();
522
523 STARTWRITEM();
524 ADDWRITEM("e", char);
525 ADDWRITEM(&MyDatabaseId, Oid);
526 ADDWRITEM(token, pg_uuid_t);
527
529
530 if(!SENDWRITEM() || !READB(len, unsigned)) {
532 provsql_error("Cannot communicate with pipe (message type e)");
533 }
534
535 result = palloc(len + VARHDRSZ);
536 SET_VARSIZE(result, VARHDRSZ + len);
537
538 if(!READB_BYTES(VARDATA(result), len)) {
540 provsql_error("Cannot communicate with pipe (message type e)");
541 }
542
544
545 PG_RETURN_TEXT_P(result);
546}
547
548PG_FUNCTION_INFO_V1(get_nb_gates);
549/** @brief PostgreSQL-callable wrapper for get_nb_gates(). */
550Datum get_nb_gates(PG_FUNCTION_ARGS)
551{
552 unsigned long nb;
553
554 STARTWRITEM();
555 ADDWRITEM("n", char);
556 ADDWRITEM(&MyDatabaseId, Oid);
557
559
560 if(!SENDWRITEM() || !READB(nb, unsigned long)) {
562 provsql_error("Cannot communicate with pipe (message type n)");
563 }
564
566
567 PG_RETURN_INT64((long) nb);
568}
569
570PG_FUNCTION_INFO_V1(get_children);
571/** @brief PostgreSQL-callable wrapper for get_children(). */
572Datum get_children(PG_FUNCTION_ARGS)
573{
574 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
575 ArrayType *result = NULL;
576 unsigned nb_children;
577 pg_uuid_t *children;
578 Datum *children_ptr;
579 constants_t constants;
580
581 if(PG_ARGISNULL(0))
582 PG_RETURN_NULL();
583
584 nb_children = circuit_cache_get_children(*token, &children);
585
586 if(!children) {
587 STARTWRITEM();
588 ADDWRITEM("c", char);
589 ADDWRITEM(&MyDatabaseId, Oid);
590 ADDWRITEM(token, pg_uuid_t);
591
593
594 if(!SENDWRITEM()) {
596 provsql_error("Cannot write to pipe (message type c)");
597 }
598
599 if(!READB(nb_children, unsigned)) {
601 provsql_error("Cannot read response from pipe (message type c)");
602 }
603
604 children=calloc(nb_children, sizeof(pg_uuid_t));
605
606 if(!READB_BYTES(children, nb_children*sizeof(pg_uuid_t))) {
608 provsql_error("Cannot read from pipe (message type c)");
609 }
611
612 /* Skip caching when the worker reports zero children: we cannot
613 * distinguish a real zero-child gate (input/zero/one/...) from a
614 * token unknown to the worker, and caching the latter poisons
615 * subsequent create_gate() calls in this session. */
616 if(nb_children > 0)
617 circuit_cache_create_gate(*token, gate_invalid, nb_children, children);
618 }
619
620 children_ptr = palloc(nb_children * sizeof(Datum));
621 for(unsigned i=0; i<nb_children; ++i)
622 children_ptr[i] = UUIDPGetDatum(&children[i]);
623
624 constants=get_constants(true);
625 result = construct_array(
626 children_ptr,
627 nb_children,
628 constants.OID_TYPE_UUID,
629 16,
630 false,
631 'c');
632 pfree(children_ptr);
633 free(children);
634
635 PG_RETURN_ARRAYTYPE_P(result);
636}
637
638PG_FUNCTION_INFO_V1(get_prob);
639/** @brief PostgreSQL-callable wrapper for get_prob(). */
640Datum get_prob(PG_FUNCTION_ARGS)
641{
642 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
643 double result;
644
645 if(PG_ARGISNULL(0))
646 PG_RETURN_NULL();
647
648 STARTWRITEM();
649 ADDWRITEM("p", char);
650 ADDWRITEM(&MyDatabaseId, Oid);
651 ADDWRITEM(token, pg_uuid_t);
652
654
655 if(!SENDWRITEM() || !READB(result, double)) {
657 provsql_error("Cannot communicate with pipe (message type p)");
658 }
659
661
662 if(isnan(result))
663 PG_RETURN_NULL();
664 else
665 PG_RETURN_FLOAT8(result);
666}
667
668/** @brief Translate a SQL-side kind label into the persisted enum value. */
669static uint8_t parse_table_kind(const char *label)
670{
671 if(strcmp(label, "tid") == 0) return PROVSQL_TABLE_TID;
672 if(strcmp(label, "bid") == 0) return PROVSQL_TABLE_BID;
673 if(strcmp(label, "opaque") == 0) return PROVSQL_TABLE_OPAQUE;
674 provsql_error("set_table_info: unknown table kind '%s' (expected "
675 "'tid', 'bid', or 'opaque')", label);
676 return PROVSQL_TABLE_TID; /* unreachable */
677}
678
679/** @brief Inverse of @c parse_table_kind for use by @c get_table_info. */
680static const char *table_kind_label(uint8_t kind)
681{
682 switch(kind) {
683 case PROVSQL_TABLE_TID: return "tid";
684 case PROVSQL_TABLE_BID: return "bid";
685 case PROVSQL_TABLE_OPAQUE: return "opaque";
686 }
687 provsql_error("get_table_info: unknown table kind value %u", kind);
688 return NULL; /* unreachable */
689}
690
691PG_FUNCTION_INFO_V1(set_table_info);
692/**
693 * @brief PostgreSQL-callable wrapper for setTableInfo() over the IPC pipe.
694 *
695 * Stores per-relation provenance metadata used by the safe-query
696 * optimisation. @p relid is the @c pg_class OID of the relation;
697 * @p kind is one of the textual labels @c 'tid' / @c 'bid' /
698 * @c 'opaque' (see @c provsql_table_kind in @c MMappedTableInfo.h);
699 * @p block_key is an @c int2 array (possibly empty) listing the
700 * block-key column numbers when @p kind is @c 'bid'.
701 */
702Datum set_table_info(PG_FUNCTION_ARGS)
703{
704 Oid relid;
705 text *kind_text;
706 char *kind_str;
707 uint8_t kind;
708 ArrayType *block_key;
709 uint16 block_key_n = 0;
710 int16 *block_key_data = NULL;
711 Size payload_size;
712
713 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
714 provsql_error("Invalid NULL value passed to set_table_info");
715
716 relid = PG_GETARG_OID(0);
717 kind_text = PG_GETARG_TEXT_PP(1);
718 kind_str = text_to_cstring(kind_text);
719 kind = parse_table_kind(kind_str);
720 pfree(kind_str);
721 block_key = PG_ARGISNULL(2) ? NULL : PG_GETARG_ARRAYTYPE_P(2);
722
723 if(block_key) {
724 if(ARR_NDIM(block_key) > 1)
725 provsql_error("Invalid multi-dimensional array passed to set_table_info");
726 else if(ARR_NDIM(block_key) == 1)
727 block_key_n = *ARR_DIMS(block_key);
728 if(block_key_n > 0)
729 block_key_data = (int16 *) ARR_DATA_PTR(block_key);
730 }
731
732 if(block_key_n > PROVSQL_TABLE_INFO_MAX_BLOCK_KEY)
733 provsql_error("set_table_info: block key wider than %d columns "
734 "(%u given) is not supported",
736
737 payload_size = sizeof(char) + sizeof(Oid) + sizeof(Oid) + sizeof(uint8)
738 + sizeof(uint16) + block_key_n * sizeof(int16);
739 if(payload_size > PIPE_BUF)
740 provsql_error("set_table_info: IPC payload exceeds PIPE_BUF");
741
742 STARTWRITEM();
743 ADDWRITEM("T", char);
744 ADDWRITEM(&MyDatabaseId, Oid);
745 ADDWRITEM(&relid, Oid);
746 ADDWRITEM(&kind, uint8);
747 ADDWRITEM(&block_key_n, uint16);
748 for(uint16 i = 0; i < block_key_n; ++i)
749 ADDWRITEM(&block_key_data[i], int16);
750
752 if(!SENDWRITEM()) {
754 provsql_error("Cannot write to pipe (message type T)");
755 }
757
758 /* Broadcast a relcache invalidation so every backend re-fetches on
759 * next access. Standard DDL (the ALTER TABLE in add_provenance and
760 * repair_key) already does this, but set_table_info is also called
761 * from DML paths that do not (INSERT INTO T SELECT, UPDATE under
762 * provsql.update_provenance, ...) and the upgrade-script backfill
763 * runs outside any DDL on the target relation. Guarded by a
764 * pg_class existence check so the sql_drop event-trigger path,
765 * which calls remove_table_info on a relid that has just been
766 * deleted from pg_class, does not raise "cache lookup failed". */
767 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
768 CacheInvalidateRelcacheByRelid(relid);
769
770 PG_RETURN_VOID();
771}
772
773PG_FUNCTION_INFO_V1(remove_table_info);
774/** @brief PostgreSQL-callable wrapper for removeTableInfo() over the IPC pipe. */
775Datum remove_table_info(PG_FUNCTION_ARGS)
776{
777 Oid relid;
778
779 if(PG_ARGISNULL(0))
780 provsql_error("Invalid NULL value passed to remove_table_info");
781
782 relid = PG_GETARG_OID(0);
783
784 STARTWRITEM();
785 ADDWRITEM("D", char);
786 ADDWRITEM(&MyDatabaseId, Oid);
787 ADDWRITEM(&relid, Oid);
788
790 if(!SENDWRITEM()) {
792 provsql_error("Cannot write to pipe (message type D)");
793 }
795
796 /* Same guard as set_table_info: skip the broadcast when the relation
797 * is already gone (typical for the sql_drop event-trigger path,
798 * where pg_class no longer has a row for this OID). */
799 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
800 CacheInvalidateRelcacheByRelid(relid);
801
802 PG_RETURN_VOID();
803}
804
805/**
806 * @brief C-callable IPC fetch for per-table provenance metadata.
807 *
808 * Sends an @c 's' message to the background worker and reads back the
809 * response. No caching: every call hits the worker. Use
810 * @c provsql_lookup_table_info() for the cached, planner-hot-path
811 * variant.
812 *
813 * @param relid pg_class OID of the relation to look up.
814 * @param out On success, filled with the stored record.
815 * @return @c true if the worker returned a record, @c false otherwise.
816 */
818{
819 char found;
820
821 STARTWRITEM();
822 ADDWRITEM("s", char);
823 ADDWRITEM(&MyDatabaseId, Oid);
824 ADDWRITEM(&relid, Oid);
825
827
828 if(!SENDWRITEM() || !READB(found, char)) {
830 provsql_error("Cannot communicate with pipe (message type s)");
831 }
832 if(found) {
833 if(!READB(out->kind, uint8_t) || !READB(out->block_key_n, uint16)) {
835 provsql_error("Cannot communicate with pipe (message type s)");
836 }
839 provsql_error("provsql_fetch_table_info: server returned an unexpectedly wide block key");
840 }
841 for(uint16 i = 0; i < out->block_key_n; ++i)
842 if(!READB(out->block_key[i], AttrNumber)) {
844 provsql_error("Cannot communicate with pipe (message type s)");
845 }
846 out->relid = relid;
847 }
848
850 return found != 0;
851}
852
853PG_FUNCTION_INFO_V1(get_table_info);
854/**
855 * @brief PostgreSQL-callable wrapper around the cached table-info lookup.
856 *
857 * Returns @c NULL when no record exists for @p relid; otherwise a
858 * record @c (kind text, block_key int2[]) where @c kind is one of
859 * @c 'tid' / @c 'bid' / @c 'opaque'. Goes through
860 * @c provsql_lookup_table_info so repeated calls in the same session
861 * do not pay for IPC.
862 */
863Datum get_table_info(PG_FUNCTION_ARGS)
864{
865 Oid relid;
867 TupleDesc tupdesc;
868 Datum values[2];
869 bool nulls[2] = {false, false};
870 Datum *elems;
871 ArrayType *arr;
872
873 if(PG_ARGISNULL(0))
874 PG_RETURN_NULL();
875
876 relid = PG_GETARG_OID(0);
877
878 if(!provsql_lookup_table_info(relid, &info))
879 PG_RETURN_NULL();
880
881 if(get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
882 provsql_error("get_table_info: expected composite return type");
883 tupdesc = BlessTupleDesc(tupdesc);
884
885 values[0] = CStringGetTextDatum(table_kind_label(info.kind));
886
887 elems = palloc(info.block_key_n * sizeof(Datum));
888 for(uint16 i = 0; i < info.block_key_n; ++i)
889 elems[i] = Int16GetDatum(info.block_key[i]);
890 arr = construct_array(elems, info.block_key_n, INT2OID, 2, true, 's');
891 pfree(elems);
892 values[1] = PointerGetDatum(arr);
893
894 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
895}
896
897PG_FUNCTION_INFO_V1(set_ancestors);
898/**
899 * @brief PostgreSQL-callable wrapper for setTableAncestry() over the
900 * IPC pipe.
901 *
902 * Records the base-relation ancestor set of a tracked relation.
903 * @p relid is the @c pg_class OID of the relation; @p ancestors is
904 * an @c oid[] (possibly empty) listing the base @c add_provenance /
905 * @c repair_key relations this one's atoms ultimately come from.
906 * The worker preserves the relation's existing @c kind / @c
907 * block_key half on update.
908 *
909 * Silently no-op on the worker side when @p relid has no kind
910 * record yet -- the safe-query rewriter only consults ancestry
911 * for tracked relations, so callers should run
912 * @c add_provenance / @c repair_key / @c set_table_info first.
913 */
914Datum set_ancestors(PG_FUNCTION_ARGS)
915{
916 Oid relid;
917 ArrayType *ancestors;
918 uint16 ancestor_n = 0;
919 Oid *ancestor_data = NULL;
920 Size payload_size;
921
922 if(PG_ARGISNULL(0))
923 provsql_error("Invalid NULL value passed to set_ancestors");
924
925 relid = PG_GETARG_OID(0);
926 ancestors = PG_ARGISNULL(1) ? NULL : PG_GETARG_ARRAYTYPE_P(1);
927
928 if(ancestors) {
929 if(ARR_NDIM(ancestors) > 1)
930 provsql_error("Invalid multi-dimensional array passed to set_ancestors");
931 else if(ARR_NDIM(ancestors) == 1)
932 ancestor_n = *ARR_DIMS(ancestors);
933 if(ancestor_n > 0)
934 ancestor_data = (Oid *) ARR_DATA_PTR(ancestors);
935 }
936
937 if(ancestor_n > PROVSQL_TABLE_INFO_MAX_ANCESTORS)
938 provsql_error("set_ancestors: ancestor set wider than %d entries "
939 "(%u given) is not supported",
941
942 payload_size = sizeof(char) + sizeof(Oid) + sizeof(Oid)
943 + sizeof(uint16) + ancestor_n * sizeof(Oid);
944 if(payload_size > PIPE_BUF)
945 provsql_error("set_ancestors: IPC payload exceeds PIPE_BUF");
946
947 STARTWRITEM();
948 ADDWRITEM("A", char);
949 ADDWRITEM(&MyDatabaseId, Oid);
950 ADDWRITEM(&relid, Oid);
951 ADDWRITEM(&ancestor_n, uint16);
952 for(uint16 i = 0; i < ancestor_n; ++i)
953 ADDWRITEM(&ancestor_data[i], Oid);
954
956 if(!SENDWRITEM()) {
958 provsql_error("Cannot write to pipe (message type A)");
959 }
961
962 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
963 CacheInvalidateRelcacheByRelid(relid);
964
965 PG_RETURN_VOID();
966}
967
968PG_FUNCTION_INFO_V1(remove_ancestors);
969/**
970 * @brief PostgreSQL-callable wrapper for removeTableAncestry() over
971 * the IPC pipe.
972 *
973 * Clears just the ancestor half of a per-table metadata record,
974 * leaving @c kind / @c block_key intact. Use @c remove_table_info
975 * to delete the whole record instead.
976 */
977Datum remove_ancestors(PG_FUNCTION_ARGS)
978{
979 Oid relid;
980
981 if(PG_ARGISNULL(0))
982 provsql_error("Invalid NULL value passed to remove_ancestors");
983
984 relid = PG_GETARG_OID(0);
985
986 STARTWRITEM();
987 ADDWRITEM("R", char);
988 ADDWRITEM(&MyDatabaseId, Oid);
989 ADDWRITEM(&relid, Oid);
990
992 if(!SENDWRITEM()) {
994 provsql_error("Cannot write to pipe (message type R)");
995 }
997
998 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
999 CacheInvalidateRelcacheByRelid(relid);
1000
1001 PG_RETURN_VOID();
1002}
1003
1004/**
1005 * @brief C-callable IPC fetch for the ancestor half of a per-table
1006 * metadata record.
1007 *
1008 * Sends an @c 'a' message to the background worker and reads back
1009 * the response. No caching: every call hits the worker. Use
1010 * @c provsql_lookup_ancestry for the cached, planner-hot-path
1011 * variant.
1012 *
1013 * @param relid pg_class OID of the relation to look up.
1014 * @param ancestor_n_out On @c true return, count of valid entries.
1015 * @param ancestors_out On @c true return, the ancestor OIDs
1016 * (caller buffer of
1017 * @c PROVSQL_TABLE_INFO_MAX_ANCESTORS).
1018 * @return @c true if the worker returned a non-zero ancestor count;
1019 * @c false otherwise (no record, or empty ancestor set).
1020 */
1021bool provsql_fetch_ancestry(Oid relid, uint16 *ancestor_n_out,
1022 Oid *ancestors_out)
1023{
1024 char found;
1025 uint16 n = 0;
1026
1027 STARTWRITEM();
1028 ADDWRITEM("a", char);
1029 ADDWRITEM(&MyDatabaseId, Oid);
1030 ADDWRITEM(&relid, Oid);
1031
1033
1034 if(!SENDWRITEM() || !READB(found, char)) {
1036 provsql_error("Cannot communicate with pipe (message type a)");
1037 }
1038 if(found) {
1039 if(!READB(n, uint16)) {
1041 provsql_error("Cannot communicate with pipe (message type a)");
1042 }
1045 provsql_error("provsql_fetch_ancestry: server returned an "
1046 "unexpectedly wide ancestor set");
1047 }
1048 for(uint16 i = 0; i < n; ++i)
1049 if(!READB(ancestors_out[i], Oid)) {
1051 provsql_error("Cannot communicate with pipe (message type a)");
1052 }
1053 }
1054
1056 *ancestor_n_out = n;
1057 /* "Found but empty" collapses to the same return as "not found":
1058 * both make the safe-query rewriter take the conservative path. */
1059 return found != 0 && n > 0;
1060}
1061
1062PG_FUNCTION_INFO_V1(get_ancestors);
1063/**
1064 * @brief PostgreSQL-callable wrapper around the cached ancestry lookup.
1065 *
1066 * Returns @c NULL when no ancestor record exists (or the record is
1067 * empty); otherwise an @c oid[] listing the base-relation OIDs.
1068 * Goes through @c provsql_lookup_ancestry so repeated calls in the
1069 * same session do not pay for IPC.
1070 */
1071Datum get_ancestors(PG_FUNCTION_ARGS)
1072{
1073 Oid relid;
1074 uint16 ancestor_n;
1075 Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS];
1076 Datum *elems;
1077 ArrayType *arr;
1078
1079 if(PG_ARGISNULL(0))
1080 PG_RETURN_NULL();
1081
1082 relid = PG_GETARG_OID(0);
1083
1084 if(!provsql_lookup_ancestry(relid, &ancestor_n, ancestors))
1085 PG_RETURN_NULL();
1086
1087 elems = palloc(ancestor_n * sizeof(Datum));
1088 for(uint16 i = 0; i < ancestor_n; ++i)
1089 elems[i] = ObjectIdGetDatum(ancestors[i]);
1090 arr = construct_array(elems, ancestor_n, OIDOID,
1091 sizeof(Oid), true, 'i');
1092 pfree(elems);
1093
1094 PG_RETURN_ARRAYTYPE_P(arr);
1095}
1096
1097PG_FUNCTION_INFO_V1(get_infos);
1098/** @brief PostgreSQL-callable wrapper for get_infos(). */
1099Datum get_infos(PG_FUNCTION_ARGS)
1100{
1101 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
1102 unsigned info1 =0, info2 = 0;
1103
1104 if(PG_ARGISNULL(0))
1105 PG_RETURN_NULL();
1106
1107 STARTWRITEM();
1108 ADDWRITEM("i", char);
1109 ADDWRITEM(&MyDatabaseId, Oid);
1110 ADDWRITEM(token, pg_uuid_t);
1111
1113
1114 if(!SENDWRITEM() || !READB(info1, int) || !READB(info2, int)) {
1116 provsql_error("Cannot communicate with pipe (message type i)");
1117 }
1118
1120
1121 {
1122 TupleDesc tupdesc;
1123 Datum values[2];
1124 bool nulls[2] = {false, false};
1125
1126 get_call_result_type(fcinfo,NULL,&tupdesc);
1127 tupdesc = BlessTupleDesc(tupdesc);
1128
1129 values[0] = Int32GetDatum(info1);
1130 values[1] = Int32GetDatum(info2);
1131
1132 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
1133 }
1134}
void destroy_provsql_mmap()
Unmap and close the mmap files.
void provsql_mmap_main_loop()
Main processing loop of the mmap background worker.
void initialize_provsql_mmap()
Open (or create) the mmap files and initialise the circuit store.
Per-table provenance metadata persisted alongside the circuit store.
#define PROVSQL_TABLE_INFO_MAX_BLOCK_KEY
Cap on the number of block-key columns recorded per relation.
@ PROVSQL_TABLE_TID
@ PROVSQL_TABLE_BID
@ PROVSQL_TABLE_OPAQUE
#define PROVSQL_TABLE_INFO_MAX_ANCESTORS
Cap on the number of base ancestors recorded per relation.
C-linkage interface to the in-process provenance circuit cache.
gate_type circuit_cache_get_type(pg_uuid_t token)
Retrieve the type of a cached gate.
unsigned circuit_cache_get_children(pg_uuid_t token, pg_uuid_t **children)
Retrieve the children of a cached gate.
bool circuit_cache_create_gate(pg_uuid_t token, gate_type type, unsigned nb_children, const pg_uuid_t *children)
Insert a new gate into the circuit cache.
Datum get_gate_type(PG_FUNCTION_ARGS)
Datum set_ancestors(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for setTableAncestry() over the IPC pipe.
Datum set_table_info(PG_FUNCTION_ARGS)
Forward declaration of the C SQL entry points.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
#define provsql_log(fmt,...)
Write a ProvSQL message to the server log only.
void provsql_internal_set_extra(const pg_uuid_t *token, const char *str)
Internal entry point behind set_extra(): worker IPC only.
Datum get_infos(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for get_infos().
void provsql_mmap_worker(Datum ignored)
Entry point for the ProvSQL mmap background worker.
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.
Datum set_ancestors(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for setTableAncestry() over the IPC pipe.
bool provsql_read_all(int fd, void *dst, size_t n)
Read exactly n bytes from fd into dst; false on EOF/error.
static const char * table_kind_label(uint8_t kind)
Inverse of parse_table_kind for use by get_table_info.
Datum set_table_info(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for setTableInfo() over the IPC pipe.
Datum get_table_info(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper around the cached table-info lookup.
Datum remove_ancestors(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for removeTableAncestry() over the IPC pipe.
Datum set_extra(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for set_extra().
Datum get_nb_gates(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for get_nb_gates().
Datum create_gate(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for create_gate().
Datum get_ancestors(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper around the cached ancestry lookup.
static uint8_t parse_table_kind(const char *label)
Translate a SQL-side kind label into the persisted enum value.
Datum remove_table_info(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for removeTableInfo() over the IPC pipe.
Datum set_infos(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for set_infos().
bool provsql_fetch_table_info(Oid relid, ProvenanceTableInfo *out)
C-callable IPC fetch for per-table provenance metadata.
bool provsql_internal_set_prob(const pg_uuid_t *token, double prob)
Internal entry point behind set_prob(): worker IPC only.
Datum get_gate_type(PG_FUNCTION_ARGS)
bool provsql_fetch_ancestry(Oid relid, uint16 *ancestor_n_out, Oid *ancestors_out)
C-callable IPC fetch for the ancestor half of a per-table metadata record.
char buffer[PIPE_BUF]
Shared write buffer used with STARTWRITEM / ADDWRITEM / SENDWRITEM.
void provsql_internal_set_infos(const pg_uuid_t *token, unsigned info1, unsigned info2)
Internal entry point behind set_infos(): worker IPC only.
Datum get_children(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for get_children().
Datum get_extra(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for get_extra().
static gate_type provsql_fetch_gate(const pg_uuid_t *token, unsigned *nb_children_out, pg_uuid_t **children_out)
PostgreSQL-callable wrapper for get_gate_type().
Datum get_prob(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for get_prob().
unsigned bufferpos
Current write position within buffer.
void RegisterProvSQLMMapWorker(void)
Register the ProvSQL mmap background worker with PostgreSQL.
Datum set_prob(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for set_prob().
Background worker and IPC primitives for mmap-backed circuit storage.
#define READB_BYTES(ptr, n)
Read exactly n bytes of a reply from the main-to-background pipe.
#define READB(var, type)
Read one value of type from the main-to-background pipe.
#define STARTWRITEM()
Reset the shared write buffer for a new batched write.
#define ADDWRITEM(pvar, type)
Append one value of type to the shared write buffer.
#define SENDWRITEM()
Flush the shared write buffer to the background-to-main pipe atomically.
void provsql_shmem_unlock(void)
Release the ProvSQL LWLock.
void provsql_shmem_lock_exclusive(void)
Acquire the ProvSQL LWLock in exclusive mode.
provsqlSharedState * provsql_shared_state
Pointer to the ProvSQL shared-memory segment (set in provsql_shmem_startup).
void provsql_shmem_lock_shared(void)
Acquire the ProvSQL LWLock in shared mode.
Shared-memory segment and inter-process pipe management.
bool provsql_lookup_ancestry(Oid relid, uint16 *ancestor_n_out, Oid *ancestors_out)
Look up the base-ancestor set of a tracked relation.
bool provsql_lookup_table_info(Oid relid, ProvenanceTableInfo *out)
Look up per-table provenance metadata with a backend-local cache.
constants_t get_constants(bool failure_if_not_possible)
Retrieve the cached OID constants for the current database.
Core types, constants, and utilities shared across ProvSQL.
@ gate_annotation
Transparent single-child wrapper carrying a query-level annotation in extra (inversion-free certifica...
Per-relation metadata for the safe-query optimisation.
Oid relid
pg_class OID of the relation (primary key)
AttrNumber block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]
Block-key column numbers.
uint16_t block_key_n
Number of valid entries in block_key.
uint8_t kind
One of provsql_table_kind.
Structure to store the value of various constants.
Oid GATE_TYPE_TO_OID[nb_gate_types]
Array of the OID of each provenance_gate ENUM value.
Oid OID_TYPE_UUID
OID of the uuid TYPE.
UUID structure.