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
89PGDLLEXPORT void provsql_mmap_worker(Datum ignored)
90{
91 BackgroundWorkerUnblockSignals();
93 close(provsql_shared_state->pipebmw);
94 close(provsql_shared_state->pipembr);
95 provsql_log("%s initialized", MyBgworkerEntry->bgw_name);
96
98
100}
101
103{
104 BackgroundWorker worker;
105
106 snprintf(worker.bgw_name, BGW_MAXLEN, "ProvSQL MMap Worker");
107#if PG_VERSION_NUM >= 110000
108 snprintf(worker.bgw_type, BGW_MAXLEN, "ProvSQL MMap");
109#endif
110
111 worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
112 worker.bgw_start_time = BgWorkerStart_PostmasterStart;
113 worker.bgw_restart_time = 1;
114
115 snprintf(worker.bgw_library_name, BGW_MAXLEN, "provsql");
116 snprintf(worker.bgw_function_name, BGW_MAXLEN, "provsql_mmap_worker");
117#if PG_VERSION_NUM < 100000
118 worker.bgw_main = NULL;
119#endif
120
121 worker.bgw_main_arg = (Datum) 0;
122 worker.bgw_notify_pid = 0;
123
124 RegisterBackgroundWorker(&worker);
125}
126
127#endif /* PROVSQL_INPROCESS_STORE */
128
129PG_FUNCTION_INFO_V1(get_gate_type);
130/** @brief PostgreSQL-callable wrapper for get_gate_type().
131 *
132 * On cache miss this fetches BOTH the gate type and its children from
133 * the worker, in one critical section, then caches them together. If
134 * we cached only the type (with an empty children list), a subsequent
135 * get_children() call for the same token would consult the cache, find
136 * the entry, and return 0 children : never querying the worker for the
137 * real children. provsql.provenance_evaluate hits exactly that pattern
138 * (it calls get_gate_type first, then unnest(get_children(...))) and
139 * silently folds plus/times gates over an empty set.
140 */
141Datum get_gate_type(PG_FUNCTION_ARGS)
142{
143 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
144 gate_type type;
145 constants_t constants=get_constants(true);
146 unsigned nb_children = 0;
147 pg_uuid_t *children = NULL;
148
149 if(PG_ARGISNULL(0))
150 PG_RETURN_NULL();
151
152 type = circuit_cache_get_type(*token);
153 if(type!=gate_invalid)
154 PG_RETURN_INT32(constants.GATE_TYPE_TO_OID[type]); ;
155
156 /* Type fetch (message 't'). */
157 STARTWRITEM();
158 ADDWRITEM("t", char);
159 ADDWRITEM(&MyDatabaseId, Oid);
160 ADDWRITEM(token, pg_uuid_t);
161
163
164 if(!SENDWRITEM() || !READB(type, gate_type)) {
166 provsql_error("Cannot communicate on pipe (message type t)");
167 }
168
169 /* Children fetch (message 'c'), batched in the same critical
170 * section so the cache entry below is complete. Skipped when the
171 * token is unknown (worker reports gate_invalid). */
172 if(type != gate_invalid) {
173 STARTWRITEM();
174 ADDWRITEM("c", char);
175 ADDWRITEM(&MyDatabaseId, Oid);
176 ADDWRITEM(token, pg_uuid_t);
177
178 if(!SENDWRITEM() || !READB(nb_children, unsigned)) {
180 provsql_error("Cannot communicate on pipe (message type c during get_gate_type)");
181 }
182
183 if(nb_children > 0) {
184 children = calloc(nb_children, sizeof(pg_uuid_t));
185 if(!READB_BYTES(children, nb_children * sizeof(pg_uuid_t))) {
187 provsql_error("Cannot read children from pipe (during get_gate_type)");
188 }
189 }
190 }
191
193
194 /* Skip caching the gate_input lazy default: MMappedCircuit::getGateType
195 * returns gate_input both for real input gates and for tokens that are
196 * not yet in the mapping. Caching the latter would poison subsequent
197 * create_gate() calls in this session (the cache hit would short-circuit
198 * the worker IPC, dropping the gate). The cost is one extra IPC per
199 * lookup of a real input gate -- acceptable. */
200 if(!(type == gate_input && nb_children == 0))
201 circuit_cache_create_gate(*token, type, nb_children, children);
202 if(children) free(children);
203 PG_RETURN_INT32(constants.GATE_TYPE_TO_OID[type]);
204}
205
206/** @brief Internal entry point behind create_gate(): cache + worker IPC.
207 *
208 * Factored out of the SQL-callable wrapper so in-extension C/C++ code
209 * (e.g. the decomposition-aligned reachability materialiser) can create
210 * gates without Datum marshalling or gate-type-OID lookups. Same
211 * semantics: write-through to the per-session cache, then the C message
212 * to the background worker; MMappedCircuit::createGate is idempotent on
213 * already-mapped tokens. */
215 unsigned nb_children,
216 const pg_uuid_t *children_data)
217{
218 /* Populate the per-session cache, but unconditionally fall through to
219 * the worker IPC: a cache hit only proves "this token has been seen
220 * in this session before" (e.g. by get_gate_type returning the
221 * gate_input lazy default for an unknown token) -- not "the worker
222 * already has a gate for it". Skipping the IPC on a cache hit caused
223 * silently-dropped create_gate calls under concurrent backends.
224 * MMappedCircuit::createGate is idempotent on already-mapped tokens. */
225 circuit_cache_create_gate(*token, type, nb_children, children_data);
226
227 STARTWRITEM();
228 ADDWRITEM("C", char);
229 ADDWRITEM(&MyDatabaseId, Oid);
230 ADDWRITEM(token, pg_uuid_t);
231 ADDWRITEM(&type, gate_type);
232 ADDWRITEM(&nb_children, unsigned);
233
234#ifdef PROVSQL_INPROCESS_STORE
235 /* The in-memory FIFO has no PIPE_BUF atomicity limit: always send the
236 gate and all its children as a single message. */
237 if(1) {
238#else
239 if(PIPE_BUF-bufferpos>nb_children*sizeof(pg_uuid_t)) {
240#endif
241 // Enough space in the buffer for an atomic write, no need of
242 // exclusive locks
243
244 for(unsigned i=0; i<nb_children; ++i)
245 ADDWRITEM(&children_data[i], pg_uuid_t);
246
248 if(!SENDWRITEM()) {
250 provsql_error("Cannot write to pipe (message type C)");
251 }
253 }
254#ifndef PROVSQL_INPROCESS_STORE
255 else {
256 // Not enough space in buffer, pipe write won't be atomic, we need to
257 // make several writes and use locks
258 unsigned children_per_batch = PIPE_BUF/sizeof(pg_uuid_t);
259
261
262 if(!SENDWRITEM()) {
264 provsql_error("Cannot write to pipe (message type C)");
265 }
266
267 for(unsigned j=0; j<1+(nb_children-1)/children_per_batch; ++j) {
268 STARTWRITEM();
269
270 for(unsigned i=j*children_per_batch; i<(j+1)*children_per_batch && i<nb_children; ++i) {
271 ADDWRITEM(&children_data[i], pg_uuid_t);
272 }
273
274 if(!SENDWRITEM()) {
276 provsql_error("Cannot write to pipe (message type C)");
277 }
278 }
279
281 }
282#endif
283}
284
285/** @brief Internal entry point behind set_prob(): worker IPC only. Returns
286 * whether the worker accepted the probability (false on a non-input gate). */
287bool provsql_internal_set_prob(const pg_uuid_t *token, double prob)
288{
289 char result;
290
291 STARTWRITEM();
292 ADDWRITEM("P", char);
293 ADDWRITEM(&MyDatabaseId, Oid);
294 ADDWRITEM(token, pg_uuid_t);
295 ADDWRITEM(&prob, double);
296
298 if(!SENDWRITEM() || !READB(result, char)) {
300 provsql_error("Cannot write to pipe");
301 }
303
304 return result;
305}
306
307/** @brief Internal entry point behind set_infos(): worker IPC only. */
308void provsql_internal_set_infos(const pg_uuid_t *token, unsigned info1,
309 unsigned info2)
310{
311 STARTWRITEM();
312 ADDWRITEM("I", char);
313 ADDWRITEM(&MyDatabaseId, Oid);
314 ADDWRITEM(token, pg_uuid_t);
315 ADDWRITEM(&info1, unsigned);
316 ADDWRITEM(&info2, unsigned);
317
319 if(!SENDWRITEM()) {
321 provsql_error("Cannot write to pipe (message type I)");
322 }
324}
325
326PG_FUNCTION_INFO_V1(create_gate);
327/** @brief PostgreSQL-callable wrapper for create_gate(). */
328Datum create_gate(PG_FUNCTION_ARGS)
329{
330 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
331 Oid oid_type = PG_GETARG_INT32(1);
332 ArrayType *children = PG_ARGISNULL(2)?NULL:PG_GETARG_ARRAYTYPE_P(2);
333 unsigned nb_children = 0;
334 gate_type type = gate_invalid;
335 constants_t constants;
336 pg_uuid_t *children_data;
337
338 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
339 provsql_error("Invalid NULL value passed to create_gate");
340
341 if(children) {
342 if(ARR_NDIM(children) > 1)
343 provsql_error("Invalid multi-dimensional array passed to create_gate");
344 else if(ARR_NDIM(children) == 1)
345 nb_children = *ARR_DIMS(children);
346 }
347
348 constants=get_constants(true);
349
350 for(int i=0; i<nb_gate_types; ++i) {
351 if(constants.GATE_TYPE_TO_OID[i]==oid_type) {
352 type = i;
353 break;
354 }
355 }
356 if(type == gate_invalid) {
357 provsql_error("Invalid gate type");
358 }
359
360 if(nb_children>0)
361 children_data = (pg_uuid_t*) ARR_DATA_PTR(children);
362 else
363 children_data = NULL;
364
365 provsql_internal_create_gate(token, type, nb_children, children_data);
366
367 PG_RETURN_VOID();
368}
369
370PG_FUNCTION_INFO_V1(set_prob);
371/** @brief PostgreSQL-callable wrapper for set_prob(). */
372Datum set_prob(PG_FUNCTION_ARGS)
373{
374 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
375 double prob = PG_GETARG_FLOAT8(1);
376
377 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
378 provsql_error("Invalid NULL value passed to set_prob");
379
380 if(!provsql_internal_set_prob(token, prob))
381 provsql_error("set_prob called on non-input gate");
382
383 PG_RETURN_VOID();
384}
385
386PG_FUNCTION_INFO_V1(set_infos);
387/** @brief PostgreSQL-callable wrapper for set_infos(). */
388Datum set_infos(PG_FUNCTION_ARGS)
389{
390 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
391 unsigned info1 = PG_GETARG_INT32(1);
392 unsigned info2 = PG_GETARG_INT32(2);
393
394
395 if(PG_ARGISNULL(1))
396 info1=0;
397 if(PG_ARGISNULL(2))
398 info2=0;
399
400 provsql_internal_set_infos(token, info1, info2);
401
402 PG_RETURN_VOID();
403}
404
405/** @brief Internal entry point behind set_extra(): worker IPC only. */
406void provsql_internal_set_extra(const pg_uuid_t *token, const char *str)
407{
408 unsigned len=strlen(str);
409
410 STARTWRITEM();
411 ADDWRITEM("E", char);
412 ADDWRITEM(&MyDatabaseId, Oid);
413 ADDWRITEM(token, pg_uuid_t);
414 ADDWRITEM(&len, unsigned);
415
416#ifdef PROVSQL_INPROCESS_STORE
417 provsql_buffer_ensure(bufferpos+len);
418#else
419 assert(PIPE_BUF-bufferpos>len);
420#endif
421 memcpy(buffer+bufferpos, str, len), bufferpos+=len;
422
424 if(!SENDWRITEM()) {
426 provsql_error("Cannot write to pipe (message type E)");
427 }
429}
430
431PG_FUNCTION_INFO_V1(set_extra);
432/** @brief PostgreSQL-callable wrapper for set_extra(). */
433Datum set_extra(PG_FUNCTION_ARGS)
434{
435 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
436 text *data = PG_GETARG_TEXT_P(1);
437 char *str=text_to_cstring(data);
438
439 provsql_internal_set_extra(token, str);
440 pfree(str);
441
442 PG_RETURN_VOID();
443}
444
445PG_FUNCTION_INFO_V1(get_extra);
446/** @brief PostgreSQL-callable wrapper for get_extra(). */
447Datum get_extra(PG_FUNCTION_ARGS)
448{
449 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
450 text *result;
451 unsigned len;
452
453 if(PG_ARGISNULL(0))
454 PG_RETURN_NULL();
455
456 STARTWRITEM();
457 ADDWRITEM("e", char);
458 ADDWRITEM(&MyDatabaseId, Oid);
459 ADDWRITEM(token, pg_uuid_t);
460
462
463 if(!SENDWRITEM() || !READB(len, unsigned)) {
465 provsql_error("Cannot communicate with pipe (message type e)");
466 }
467
468 result = palloc(len + VARHDRSZ);
469 SET_VARSIZE(result, VARHDRSZ + len);
470
471 if(!READB_BYTES(VARDATA(result), len)) {
473 provsql_error("Cannot communicate with pipe (message type e)");
474 }
475
477
478 PG_RETURN_TEXT_P(result);
479}
480
481PG_FUNCTION_INFO_V1(get_nb_gates);
482/** @brief PostgreSQL-callable wrapper for get_nb_gates(). */
483Datum get_nb_gates(PG_FUNCTION_ARGS)
484{
485 unsigned long nb;
486
487 STARTWRITEM();
488 ADDWRITEM("n", char);
489 ADDWRITEM(&MyDatabaseId, Oid);
490
492
493 if(!SENDWRITEM() || !READB(nb, unsigned long)) {
495 provsql_error("Cannot communicate with pipe (message type n)");
496 }
497
499
500 PG_RETURN_INT64((long) nb);
501}
502
503PG_FUNCTION_INFO_V1(get_children);
504/** @brief PostgreSQL-callable wrapper for get_children(). */
505Datum get_children(PG_FUNCTION_ARGS)
506{
507 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
508 ArrayType *result = NULL;
509 unsigned nb_children;
510 pg_uuid_t *children;
511 Datum *children_ptr;
512 constants_t constants;
513
514 if(PG_ARGISNULL(0))
515 PG_RETURN_NULL();
516
517 nb_children = circuit_cache_get_children(*token, &children);
518
519 if(!children) {
520 STARTWRITEM();
521 ADDWRITEM("c", char);
522 ADDWRITEM(&MyDatabaseId, Oid);
523 ADDWRITEM(token, pg_uuid_t);
524
526
527 if(!SENDWRITEM()) {
529 provsql_error("Cannot write to pipe (message type c)");
530 }
531
532 if(!READB(nb_children, unsigned)) {
534 provsql_error("Cannot read response from pipe (message type c)");
535 }
536
537 children=calloc(nb_children, sizeof(pg_uuid_t));
538
539 if(!READB_BYTES(children, nb_children*sizeof(pg_uuid_t))) {
541 provsql_error("Cannot read from pipe (message type c)");
542 }
544
545 /* Skip caching when the worker reports zero children: we cannot
546 * distinguish a real zero-child gate (input/zero/one/...) from a
547 * token unknown to the worker, and caching the latter poisons
548 * subsequent create_gate() calls in this session. */
549 if(nb_children > 0)
550 circuit_cache_create_gate(*token, gate_invalid, nb_children, children);
551 }
552
553 children_ptr = palloc(nb_children * sizeof(Datum));
554 for(unsigned i=0; i<nb_children; ++i)
555 children_ptr[i] = UUIDPGetDatum(&children[i]);
556
557 constants=get_constants(true);
558 result = construct_array(
559 children_ptr,
560 nb_children,
561 constants.OID_TYPE_UUID,
562 16,
563 false,
564 'c');
565 pfree(children_ptr);
566 free(children);
567
568 PG_RETURN_ARRAYTYPE_P(result);
569}
570
571PG_FUNCTION_INFO_V1(get_prob);
572/** @brief PostgreSQL-callable wrapper for get_prob(). */
573Datum get_prob(PG_FUNCTION_ARGS)
574{
575 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
576 double result;
577
578 if(PG_ARGISNULL(0))
579 PG_RETURN_NULL();
580
581 STARTWRITEM();
582 ADDWRITEM("p", char);
583 ADDWRITEM(&MyDatabaseId, Oid);
584 ADDWRITEM(token, pg_uuid_t);
585
587
588 if(!SENDWRITEM() || !READB(result, double)) {
590 provsql_error("Cannot communicate with pipe (message type p)");
591 }
592
594
595 if(isnan(result))
596 PG_RETURN_NULL();
597 else
598 PG_RETURN_FLOAT8(result);
599}
600
601/** @brief Translate a SQL-side kind label into the persisted enum value. */
602static uint8_t parse_table_kind(const char *label)
603{
604 if(strcmp(label, "tid") == 0) return PROVSQL_TABLE_TID;
605 if(strcmp(label, "bid") == 0) return PROVSQL_TABLE_BID;
606 if(strcmp(label, "opaque") == 0) return PROVSQL_TABLE_OPAQUE;
607 provsql_error("set_table_info: unknown table kind '%s' (expected "
608 "'tid', 'bid', or 'opaque')", label);
609 return PROVSQL_TABLE_TID; /* unreachable */
610}
611
612/** @brief Inverse of @c parse_table_kind for use by @c get_table_info. */
613static const char *table_kind_label(uint8_t kind)
614{
615 switch(kind) {
616 case PROVSQL_TABLE_TID: return "tid";
617 case PROVSQL_TABLE_BID: return "bid";
618 case PROVSQL_TABLE_OPAQUE: return "opaque";
619 }
620 provsql_error("get_table_info: unknown table kind value %u", kind);
621 return NULL; /* unreachable */
622}
623
624PG_FUNCTION_INFO_V1(set_table_info);
625/**
626 * @brief PostgreSQL-callable wrapper for setTableInfo() over the IPC pipe.
627 *
628 * Stores per-relation provenance metadata used by the safe-query
629 * optimisation. @p relid is the @c pg_class OID of the relation;
630 * @p kind is one of the textual labels @c 'tid' / @c 'bid' /
631 * @c 'opaque' (see @c provsql_table_kind in @c MMappedTableInfo.h);
632 * @p block_key is an @c int2 array (possibly empty) listing the
633 * block-key column numbers when @p kind is @c 'bid'.
634 */
635Datum set_table_info(PG_FUNCTION_ARGS)
636{
637 Oid relid;
638 text *kind_text;
639 char *kind_str;
640 uint8_t kind;
641 ArrayType *block_key;
642 uint16 block_key_n = 0;
643 int16 *block_key_data = NULL;
644 Size payload_size;
645
646 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
647 provsql_error("Invalid NULL value passed to set_table_info");
648
649 relid = PG_GETARG_OID(0);
650 kind_text = PG_GETARG_TEXT_PP(1);
651 kind_str = text_to_cstring(kind_text);
652 kind = parse_table_kind(kind_str);
653 pfree(kind_str);
654 block_key = PG_ARGISNULL(2) ? NULL : PG_GETARG_ARRAYTYPE_P(2);
655
656 if(block_key) {
657 if(ARR_NDIM(block_key) > 1)
658 provsql_error("Invalid multi-dimensional array passed to set_table_info");
659 else if(ARR_NDIM(block_key) == 1)
660 block_key_n = *ARR_DIMS(block_key);
661 if(block_key_n > 0)
662 block_key_data = (int16 *) ARR_DATA_PTR(block_key);
663 }
664
665 if(block_key_n > PROVSQL_TABLE_INFO_MAX_BLOCK_KEY)
666 provsql_error("set_table_info: block key wider than %d columns "
667 "(%u given) is not supported",
669
670 payload_size = sizeof(char) + sizeof(Oid) + sizeof(Oid) + sizeof(uint8)
671 + sizeof(uint16) + block_key_n * sizeof(int16);
672 if(payload_size > PIPE_BUF)
673 provsql_error("set_table_info: IPC payload exceeds PIPE_BUF");
674
675 STARTWRITEM();
676 ADDWRITEM("T", char);
677 ADDWRITEM(&MyDatabaseId, Oid);
678 ADDWRITEM(&relid, Oid);
679 ADDWRITEM(&kind, uint8);
680 ADDWRITEM(&block_key_n, uint16);
681 for(uint16 i = 0; i < block_key_n; ++i)
682 ADDWRITEM(&block_key_data[i], int16);
683
685 if(!SENDWRITEM()) {
687 provsql_error("Cannot write to pipe (message type T)");
688 }
690
691 /* Broadcast a relcache invalidation so every backend re-fetches on
692 * next access. Standard DDL (the ALTER TABLE in add_provenance and
693 * repair_key) already does this, but set_table_info is also called
694 * from DML paths that do not (INSERT INTO T SELECT, UPDATE under
695 * provsql.update_provenance, ...) and the upgrade-script backfill
696 * runs outside any DDL on the target relation. Guarded by a
697 * pg_class existence check so the sql_drop event-trigger path,
698 * which calls remove_table_info on a relid that has just been
699 * deleted from pg_class, does not raise "cache lookup failed". */
700 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
701 CacheInvalidateRelcacheByRelid(relid);
702
703 PG_RETURN_VOID();
704}
705
706PG_FUNCTION_INFO_V1(remove_table_info);
707/** @brief PostgreSQL-callable wrapper for removeTableInfo() over the IPC pipe. */
708Datum remove_table_info(PG_FUNCTION_ARGS)
709{
710 Oid relid;
711
712 if(PG_ARGISNULL(0))
713 provsql_error("Invalid NULL value passed to remove_table_info");
714
715 relid = PG_GETARG_OID(0);
716
717 STARTWRITEM();
718 ADDWRITEM("D", char);
719 ADDWRITEM(&MyDatabaseId, Oid);
720 ADDWRITEM(&relid, Oid);
721
723 if(!SENDWRITEM()) {
725 provsql_error("Cannot write to pipe (message type D)");
726 }
728
729 /* Same guard as set_table_info: skip the broadcast when the relation
730 * is already gone (typical for the sql_drop event-trigger path,
731 * where pg_class no longer has a row for this OID). */
732 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
733 CacheInvalidateRelcacheByRelid(relid);
734
735 PG_RETURN_VOID();
736}
737
738/**
739 * @brief C-callable IPC fetch for per-table provenance metadata.
740 *
741 * Sends an @c 's' message to the background worker and reads back the
742 * response. No caching: every call hits the worker. Use
743 * @c provsql_lookup_table_info() for the cached, planner-hot-path
744 * variant.
745 *
746 * @param relid pg_class OID of the relation to look up.
747 * @param out On success, filled with the stored record.
748 * @return @c true if the worker returned a record, @c false otherwise.
749 */
751{
752 char found;
753
754 STARTWRITEM();
755 ADDWRITEM("s", char);
756 ADDWRITEM(&MyDatabaseId, Oid);
757 ADDWRITEM(&relid, Oid);
758
760
761 if(!SENDWRITEM() || !READB(found, char)) {
763 provsql_error("Cannot communicate with pipe (message type s)");
764 }
765 if(found) {
766 if(!READB(out->kind, uint8_t) || !READB(out->block_key_n, uint16)) {
768 provsql_error("Cannot communicate with pipe (message type s)");
769 }
772 provsql_error("provsql_fetch_table_info: server returned an unexpectedly wide block key");
773 }
774 for(uint16 i = 0; i < out->block_key_n; ++i)
775 if(!READB(out->block_key[i], AttrNumber)) {
777 provsql_error("Cannot communicate with pipe (message type s)");
778 }
779 out->relid = relid;
780 }
781
783 return found != 0;
784}
785
786PG_FUNCTION_INFO_V1(get_table_info);
787/**
788 * @brief PostgreSQL-callable wrapper around the cached table-info lookup.
789 *
790 * Returns @c NULL when no record exists for @p relid; otherwise a
791 * record @c (kind text, block_key int2[]) where @c kind is one of
792 * @c 'tid' / @c 'bid' / @c 'opaque'. Goes through
793 * @c provsql_lookup_table_info so repeated calls in the same session
794 * do not pay for IPC.
795 */
796Datum get_table_info(PG_FUNCTION_ARGS)
797{
798 Oid relid;
800 TupleDesc tupdesc;
801 Datum values[2];
802 bool nulls[2] = {false, false};
803 Datum *elems;
804 ArrayType *arr;
805
806 if(PG_ARGISNULL(0))
807 PG_RETURN_NULL();
808
809 relid = PG_GETARG_OID(0);
810
811 if(!provsql_lookup_table_info(relid, &info))
812 PG_RETURN_NULL();
813
814 if(get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
815 provsql_error("get_table_info: expected composite return type");
816 tupdesc = BlessTupleDesc(tupdesc);
817
818 values[0] = CStringGetTextDatum(table_kind_label(info.kind));
819
820 elems = palloc(info.block_key_n * sizeof(Datum));
821 for(uint16 i = 0; i < info.block_key_n; ++i)
822 elems[i] = Int16GetDatum(info.block_key[i]);
823 arr = construct_array(elems, info.block_key_n, INT2OID, 2, true, 's');
824 pfree(elems);
825 values[1] = PointerGetDatum(arr);
826
827 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
828}
829
830PG_FUNCTION_INFO_V1(set_ancestors);
831/**
832 * @brief PostgreSQL-callable wrapper for setTableAncestry() over the
833 * IPC pipe.
834 *
835 * Records the base-relation ancestor set of a tracked relation.
836 * @p relid is the @c pg_class OID of the relation; @p ancestors is
837 * an @c oid[] (possibly empty) listing the base @c add_provenance /
838 * @c repair_key relations this one's atoms ultimately come from.
839 * The worker preserves the relation's existing @c kind / @c
840 * block_key half on update.
841 *
842 * Silently no-op on the worker side when @p relid has no kind
843 * record yet -- the safe-query rewriter only consults ancestry
844 * for tracked relations, so callers should run
845 * @c add_provenance / @c repair_key / @c set_table_info first.
846 */
847Datum set_ancestors(PG_FUNCTION_ARGS)
848{
849 Oid relid;
850 ArrayType *ancestors;
851 uint16 ancestor_n = 0;
852 Oid *ancestor_data = NULL;
853 Size payload_size;
854
855 if(PG_ARGISNULL(0))
856 provsql_error("Invalid NULL value passed to set_ancestors");
857
858 relid = PG_GETARG_OID(0);
859 ancestors = PG_ARGISNULL(1) ? NULL : PG_GETARG_ARRAYTYPE_P(1);
860
861 if(ancestors) {
862 if(ARR_NDIM(ancestors) > 1)
863 provsql_error("Invalid multi-dimensional array passed to set_ancestors");
864 else if(ARR_NDIM(ancestors) == 1)
865 ancestor_n = *ARR_DIMS(ancestors);
866 if(ancestor_n > 0)
867 ancestor_data = (Oid *) ARR_DATA_PTR(ancestors);
868 }
869
870 if(ancestor_n > PROVSQL_TABLE_INFO_MAX_ANCESTORS)
871 provsql_error("set_ancestors: ancestor set wider than %d entries "
872 "(%u given) is not supported",
874
875 payload_size = sizeof(char) + sizeof(Oid) + sizeof(Oid)
876 + sizeof(uint16) + ancestor_n * sizeof(Oid);
877 if(payload_size > PIPE_BUF)
878 provsql_error("set_ancestors: IPC payload exceeds PIPE_BUF");
879
880 STARTWRITEM();
881 ADDWRITEM("A", char);
882 ADDWRITEM(&MyDatabaseId, Oid);
883 ADDWRITEM(&relid, Oid);
884 ADDWRITEM(&ancestor_n, uint16);
885 for(uint16 i = 0; i < ancestor_n; ++i)
886 ADDWRITEM(&ancestor_data[i], Oid);
887
889 if(!SENDWRITEM()) {
891 provsql_error("Cannot write to pipe (message type A)");
892 }
894
895 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
896 CacheInvalidateRelcacheByRelid(relid);
897
898 PG_RETURN_VOID();
899}
900
901PG_FUNCTION_INFO_V1(remove_ancestors);
902/**
903 * @brief PostgreSQL-callable wrapper for removeTableAncestry() over
904 * the IPC pipe.
905 *
906 * Clears just the ancestor half of a per-table metadata record,
907 * leaving @c kind / @c block_key intact. Use @c remove_table_info
908 * to delete the whole record instead.
909 */
910Datum remove_ancestors(PG_FUNCTION_ARGS)
911{
912 Oid relid;
913
914 if(PG_ARGISNULL(0))
915 provsql_error("Invalid NULL value passed to remove_ancestors");
916
917 relid = PG_GETARG_OID(0);
918
919 STARTWRITEM();
920 ADDWRITEM("R", char);
921 ADDWRITEM(&MyDatabaseId, Oid);
922 ADDWRITEM(&relid, Oid);
923
925 if(!SENDWRITEM()) {
927 provsql_error("Cannot write to pipe (message type R)");
928 }
930
931 if(SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
932 CacheInvalidateRelcacheByRelid(relid);
933
934 PG_RETURN_VOID();
935}
936
937/**
938 * @brief C-callable IPC fetch for the ancestor half of a per-table
939 * metadata record.
940 *
941 * Sends an @c 'a' message to the background worker and reads back
942 * the response. No caching: every call hits the worker. Use
943 * @c provsql_lookup_ancestry for the cached, planner-hot-path
944 * variant.
945 *
946 * @param relid pg_class OID of the relation to look up.
947 * @param ancestor_n_out On @c true return, count of valid entries.
948 * @param ancestors_out On @c true return, the ancestor OIDs
949 * (caller buffer of
950 * @c PROVSQL_TABLE_INFO_MAX_ANCESTORS).
951 * @return @c true if the worker returned a non-zero ancestor count;
952 * @c false otherwise (no record, or empty ancestor set).
953 */
954bool provsql_fetch_ancestry(Oid relid, uint16 *ancestor_n_out,
955 Oid *ancestors_out)
956{
957 char found;
958 uint16 n = 0;
959
960 STARTWRITEM();
961 ADDWRITEM("a", char);
962 ADDWRITEM(&MyDatabaseId, Oid);
963 ADDWRITEM(&relid, Oid);
964
966
967 if(!SENDWRITEM() || !READB(found, char)) {
969 provsql_error("Cannot communicate with pipe (message type a)");
970 }
971 if(found) {
972 if(!READB(n, uint16)) {
974 provsql_error("Cannot communicate with pipe (message type a)");
975 }
978 provsql_error("provsql_fetch_ancestry: server returned an "
979 "unexpectedly wide ancestor set");
980 }
981 for(uint16 i = 0; i < n; ++i)
982 if(!READB(ancestors_out[i], Oid)) {
984 provsql_error("Cannot communicate with pipe (message type a)");
985 }
986 }
987
989 *ancestor_n_out = n;
990 /* "Found but empty" collapses to the same return as "not found":
991 * both make the safe-query rewriter take the conservative path. */
992 return found != 0 && n > 0;
993}
994
995PG_FUNCTION_INFO_V1(get_ancestors);
996/**
997 * @brief PostgreSQL-callable wrapper around the cached ancestry lookup.
998 *
999 * Returns @c NULL when no ancestor record exists (or the record is
1000 * empty); otherwise an @c oid[] listing the base-relation OIDs.
1001 * Goes through @c provsql_lookup_ancestry so repeated calls in the
1002 * same session do not pay for IPC.
1003 */
1004Datum get_ancestors(PG_FUNCTION_ARGS)
1005{
1006 Oid relid;
1007 uint16 ancestor_n;
1008 Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS];
1009 Datum *elems;
1010 ArrayType *arr;
1011
1012 if(PG_ARGISNULL(0))
1013 PG_RETURN_NULL();
1014
1015 relid = PG_GETARG_OID(0);
1016
1017 if(!provsql_lookup_ancestry(relid, &ancestor_n, ancestors))
1018 PG_RETURN_NULL();
1019
1020 elems = palloc(ancestor_n * sizeof(Datum));
1021 for(uint16 i = 0; i < ancestor_n; ++i)
1022 elems[i] = ObjectIdGetDatum(ancestors[i]);
1023 arr = construct_array(elems, ancestor_n, OIDOID,
1024 sizeof(Oid), true, 'i');
1025 pfree(elems);
1026
1027 PG_RETURN_ARRAYTYPE_P(arr);
1028}
1029
1030PG_FUNCTION_INFO_V1(get_infos);
1031/** @brief PostgreSQL-callable wrapper for get_infos(). */
1032Datum get_infos(PG_FUNCTION_ARGS)
1033{
1034 pg_uuid_t *token = DatumGetUUIDP(PG_GETARG_DATUM(0));
1035 unsigned info1 =0, info2 = 0;
1036
1037 if(PG_ARGISNULL(0))
1038 PG_RETURN_NULL();
1039
1040 STARTWRITEM();
1041 ADDWRITEM("i", char);
1042 ADDWRITEM(&MyDatabaseId, Oid);
1043 ADDWRITEM(token, pg_uuid_t);
1044
1046
1047 if(!SENDWRITEM() || !READB(info1, int) || !READB(info2, int)) {
1049 provsql_error("Cannot communicate with pipe (message type i)");
1050 }
1051
1053
1054 {
1055 TupleDesc tupdesc;
1056 Datum values[2];
1057 bool nulls[2] = {false, false};
1058
1059 get_call_result_type(fcinfo,NULL,&tupdesc);
1060 tupdesc = BlessTupleDesc(tupdesc);
1061
1062 values[0] = Int32GetDatum(info1);
1063 values[1] = Int32GetDatum(info2);
1064
1065 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
1066 }
1067}
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)
PostgreSQL-callable wrapper for get_gate_type().
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)
PostgreSQL-callable wrapper for get_gate_type().
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().
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.
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.