ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
provsql_utils.c
Go to the documentation of this file.
1/**
2 * @file provsql_utils.c
3 * @brief OID lookup, constants cache, and utility functions for ProvSQL.
4 *
5 * Implements the functions declared in @c provsql_utils.h:
6 * - @c get_constants(): retrieves and caches per-database OIDs for all
7 * ProvSQL types, functions, and operators.
8 * - @c find_equality_operator(): looks up the @c = operator OID for a
9 * given pair of types.
10 *
11 * The constants cache is a sorted, dynamically-grown array of
12 * @c database_constants_t records (one per PostgreSQL database OID)
13 * stored in process-local memory and searched with binary search.
14 * The @c reset_constants_cache() SQL function forces a cache invalidation
15 * for the current database, which is needed after @c ALTER EXTENSION.
16 *
17 * Several helper functions (@c get_func_oid, @c get_provsql_func_oid,
18 * @c OperatorGet, @c get_enum_oid, @c binary_oper_exact) are adapted
19 * from PostgreSQL source code that is not exported as a public API.
20 */
21#include "postgres.h"
22#include "access/htup_details.h"
23#if PG_VERSION_NUM >= 120000
24#include "access/table.h" /* table_open / table_close */
25#else
26#include "access/heapam.h" /* heap_open / heap_close (PG <12) */
27#define table_open(r, l) heap_open((r), (l))
28#define table_close(r, l) heap_close((r), (l))
29#endif
30#include "access/genam.h"
31#include "miscadmin.h"
32#include "catalog/indexing.h" /* ConstraintRelidTypidNameIndexId */
33#include "catalog/namespace.h"
34#include "catalog/pg_attribute.h"
35#include "catalog/pg_constraint.h"
36#include "catalog/pg_index.h"
37#include "catalog/pg_type.h"
38#include "catalog/pg_enum.h"
39#include "catalog/pg_namespace.h"
40#include "catalog/pg_operator.h"
41#include "catalog/pg_type.h"
42#include "fmgr.h"
43#include "nodes/value.h"
44#include "parser/parse_func.h"
45#include "utils/fmgroids.h"
46#include "utils/syscache.h"
47#include "utils/lsyscache.h"
48#include "utils/inval.h"
49
50#include <string.h>
51
52#include "provsql_utils.h"
53
54const char *gate_type_name[] = {
55 "input",
56 "plus",
57 "times",
58 "monus",
59 "project",
60 "zero",
61 "one",
62 "eq",
63 "agg",
64 "semimod",
65 "cmp",
66 "delta",
67 "value",
68 "mulinput",
69 "update",
70 "rv",
71 "arith",
72 "mixture",
73 "assumed_boolean",
74 "invalid"
75};
76
77/**
78 * @brief Look up an exactly matching binary operator OID.
79 *
80 * Copied and adapted from @c parse_oper.c (PostgreSQL internals, not
81 * exported). Returns @c InvalidOid if no exact match exists.
82 *
83 * @param opname Qualified operator name (a @c List of @c String nodes).
84 * @param arg1 OID of the left operand type.
85 * @param arg2 OID of the right operand type.
86 * @return OID of the matching operator, or @c InvalidOid.
87 */
88static Oid
89binary_oper_exact(List *opname, Oid arg1, Oid arg2)
90{
91 Oid result;
92 bool was_unknown = false;
93
94 /* Unspecified type for one of the arguments? then use the other */
95 if ((arg1 == UNKNOWNOID) && (arg2 != InvalidOid))
96 {
97 arg1 = arg2;
98 was_unknown = true;
99 }
100 else if ((arg2 == UNKNOWNOID) && (arg1 != InvalidOid))
101 {
102 arg2 = arg1;
103 was_unknown = true;
104 }
105
106 result = OpernameGetOprid(opname, arg1, arg2);
107 if (OidIsValid(result))
108 return result;
109
110 if (was_unknown)
111 {
112 /* arg1 and arg2 are the same here, need only look at arg1 */
113 Oid basetype = getBaseType(arg1);
114
115 if (basetype != arg1)
116 {
117 result = OpernameGetOprid(opname, basetype, basetype);
118 if (OidIsValid(result))
119 return result;
120 }
121 }
122
123 return InvalidOid;
124}
125
126/* Adapted from PostgreSQL code that is not exported (see parse_oper.c
127 * and the static function oper_select_candidate therein).
128 */
129Oid find_equality_operator(Oid ltypeId, Oid rtypeId)
130{
131 List * const equals=list_make1(makeString("="));
132
133 FuncCandidateList clist;
134 Oid inputOids[2] = {ltypeId,rtypeId};
135 int ncandidates;
136
137 Oid result = binary_oper_exact(equals, ltypeId, rtypeId);
138
139 if(result!=InvalidOid)
140 return result;
141
142 clist = OpernameGetCandidates(equals, 'b', false);
143
144 ncandidates = func_match_argtypes(2, inputOids,
145 clist, &clist);
146
147 if (ncandidates == 0)
148 return InvalidOid;
149 else if (ncandidates == 1)
150 return clist->oid;
151
152 clist = func_select_candidate(2, inputOids, clist);
153
154 if(clist)
155 return clist->oid;
156 else
157 return InvalidOid;
158}
159
160/**
161 * @brief Return the OID of a globally qualified function named @p s.
162 *
163 * Looks up the function in the default search path. Returns 0 if no
164 * matching function is found.
165 *
166 * @param s Function name (unqualified).
167 * @return OID of the function, or 0 if not found.
168 */
169static Oid get_func_oid(char *s)
170{
171 FuncCandidateList fcl=FuncnameGetCandidates(
172 list_make1(makeString(s)),
173 -1,
174 NIL,
175 false,
176 false,
177#if PG_VERSION_NUM >= 140000
178 false,
179#endif
180 false);
181 if(fcl)
182 return fcl->oid;
183 else
184 return 0;
185}
186
187/**
188 * @brief Return the OID of a @c provsql-schema function named @p s.
189 *
190 * Looks up the function in the @c provsql schema. Returns 0 if not found.
191 *
192 * @param s Function name (without schema prefix).
193 * @return OID of the function, or 0 if not found.
194 */
195static Oid get_provsql_func_oid(char *s)
196{
197 FuncCandidateList fcl=FuncnameGetCandidates(
198 list_make2(makeString("provsql"),makeString(s)),
199 -1,
200 NIL,
201 false,
202 false,
203#if PG_VERSION_NUM >= 140000
204 false,
205#endif
206 false);
207 if(fcl)
208 return fcl->oid;
209 else
210 return 0;
211}
212
213/**
214 * @brief Retrieve operator and function OIDs for a named operator.
215 *
216 * Copied and adapted from @c pg_operator.c (PostgreSQL internals, not
217 * exported). Looks up the operator by name, namespace, and operand types
218 * in the system cache.
219 *
220 * @param operatorName Operator symbol string (e.g. @c "<>").
221 * @param operatorNamespace OID of the schema containing the operator.
222 * @param leftObjectId OID of the left operand type.
223 * @param rightObjectId OID of the right operand type.
224 * @param operatorObjectId Output: OID of the operator, or 0 if not found.
225 * @param functionObjectId Output: OID of the underlying function, or 0.
226 */
227static void OperatorGet(
228 const char *operatorName,
229 Oid operatorNamespace,
230 Oid leftObjectId,
231 Oid rightObjectId,
232 Oid *operatorObjectId,
233 Oid *functionObjectId)
234{
235 HeapTuple tup;
236 bool defined;
237
238 tup = SearchSysCache4(OPERNAMENSP,
239 PointerGetDatum(operatorName),
240 ObjectIdGetDatum(leftObjectId),
241 ObjectIdGetDatum(rightObjectId),
242 ObjectIdGetDatum(operatorNamespace));
243 if (HeapTupleIsValid(tup))
244 {
245 Form_pg_operator oprform = (Form_pg_operator) GETSTRUCT(tup);
246#if PG_VERSION_NUM >= 120000
247 *operatorObjectId = oprform->oid;
248#else
249 *operatorObjectId = HeapTupleGetOid(tup);
250#endif
251 *functionObjectId = oprform->oprcode;
252 defined = RegProcedureIsValid(oprform->oprcode);
253 ReleaseSysCache(tup);
254 }
255 else
256 {
257 defined = false;
258 }
259
260 if(!defined) {
261 *operatorObjectId = 0;
262 *functionObjectId = 0;
263 }
264}
265
266/**
267 * @brief Return the OID of a specific enum label within an enum type.
268 *
269 * @param enumtypoid OID of the enum type (e.g. @c provenance_gate).
270 * @param label C-string label of the enum value to look up.
271 * @return OID of the enum label's @c pg_enum row, or
272 * @c InvalidOid if the label is not present.
273 */
274static Oid get_enum_oid(Oid enumtypoid, const char *label)
275{
276 HeapTuple tup;
277 Oid ret;
278
279 tup = SearchSysCache2(ENUMTYPOIDNAME,
280 ObjectIdGetDatum(enumtypoid),
281 CStringGetDatum(label));
282 if (!HeapTupleIsValid(tup))
283 return InvalidOid;
284
285#if PG_VERSION_NUM >= 120000
286 ret = ((Form_pg_enum) GETSTRUCT(tup))->oid;
287#else
288 ret = HeapTupleGetOid(tup);
289#endif
290
291 ReleaseSysCache(tup);
292
293 return ret;
294}
295
296/**
297 * @brief Query the system catalogs to populate a fresh @c constants_t.
298 *
299 * Performs all OID lookups required by ProvSQL in a single pass through
300 * the system caches. The @c CheckOid() macro aborts (or returns early,
301 * depending on @p failure_if_not_possible) if any OID resolves to
302 * @c InvalidOid.
303 *
304 * @param failure_if_not_possible If @c true, raise a @c provsql_error when
305 * any OID cannot be resolved. If @c false, return a @c constants_t
306 * with @c ok==false instead.
307 * @return Fully populated @c constants_t on success, or @c ok==false on
308 * failure when @p failure_if_not_possible is @c false.
309 */
310static constants_t initialize_constants(bool failure_if_not_possible)
311{
312 constants_t constants;
313 constants.ok = false;
314
315 /** @brief Abort or return early if OID field @p o of @p constants is invalid. */
316 #define CheckOid(o) if(constants.o==InvalidOid) { \
317 if(failure_if_not_possible) \
318 provsql_error("Could not initialize provsql constants"); \
319 else \
320 return constants; }
321
322 constants.OID_SCHEMA_PROVSQL = get_namespace_oid("provsql", true);
323 CheckOid(OID_SCHEMA_PROVSQL);
324
325 constants.OID_TYPE_UUID = TypenameGetTypid("uuid");
326 CheckOid(OID_TYPE_UUID);
327
328 constants.OID_TYPE_GATE_TYPE = GetSysCacheOid2(
329 TYPENAMENSP,
330#if PG_VERSION_NUM >= 120000
331 Anum_pg_type_oid,
332#endif
333 CStringGetDatum("provenance_gate"),
334 ObjectIdGetDatum(constants.OID_SCHEMA_PROVSQL)
335 );
336 CheckOid(OID_TYPE_GATE_TYPE);
337
338 constants.OID_TYPE_AGG_TOKEN = GetSysCacheOid2(
339 TYPENAMENSP,
340#if PG_VERSION_NUM >= 120000
341 Anum_pg_type_oid,
342#endif
343 CStringGetDatum("agg_token"),
344 ObjectIdGetDatum(constants.OID_SCHEMA_PROVSQL)
345 );
346 CheckOid(OID_TYPE_AGG_TOKEN);
347
348 constants.OID_TYPE_UUID = TypenameGetTypid("uuid");
349 CheckOid(OID_TYPE_UUID);
350
351 constants.OID_TYPE_UUID_ARRAY = TypenameGetTypid("_uuid");
352 CheckOid(OID_TYPE_UUID_ARRAY);
353
354 constants.OID_TYPE_INT = TypenameGetTypid("int4");
355 CheckOid(OID_TYPE_INT);
356
357 constants.OID_TYPE_BOOL = TypenameGetTypid("bool");
358 CheckOid(OID_TYPE_BOOL);
359
360 constants.OID_TYPE_FLOAT = TypenameGetTypid("float8");
361 CheckOid(OID_TYPE_FLOAT);
362
363 constants.OID_TYPE_INT_ARRAY = TypenameGetTypid("_int4");
364 CheckOid(OID_TYPE_INT_ARRAY);
365
366 constants.OID_TYPE_VARCHAR = TypenameGetTypid("varchar");
367 CheckOid(OID_TYPE_VARCHAR);
368
369#if PG_VERSION_NUM >= 140000
370 constants.OID_TYPE_TSTZMULTIRANGE = TypenameGetTypid("tstzmultirange");
371 CheckOid(OID_TYPE_TSTZMULTIRANGE);
372 constants.OID_TYPE_NUMMULTIRANGE = TypenameGetTypid("nummultirange");
373 CheckOid(OID_TYPE_NUMMULTIRANGE);
374 constants.OID_TYPE_INT4MULTIRANGE = TypenameGetTypid("int4multirange");
375 CheckOid(OID_TYPE_INT4MULTIRANGE);
376#else
377 constants.OID_TYPE_TSTZMULTIRANGE = InvalidOid;
378 constants.OID_TYPE_NUMMULTIRANGE = InvalidOid;
379 constants.OID_TYPE_INT4MULTIRANGE = InvalidOid;
380#endif
381
382 constants.OID_FUNCTION_ARRAY_AGG = get_func_oid("array_agg");
383 CheckOid(OID_FUNCTION_ARRAY_AGG);
384
385 constants.OID_FUNCTION_PROVENANCE_PLUS = get_provsql_func_oid("provenance_plus");
386 CheckOid(OID_FUNCTION_PROVENANCE_PLUS);
387
388 constants.OID_FUNCTION_PROVENANCE_TIMES = get_provsql_func_oid("provenance_times");
389 CheckOid(OID_FUNCTION_PROVENANCE_TIMES);
390
391 constants.OID_FUNCTION_PROVENANCE_MONUS = get_provsql_func_oid("provenance_monus");
392 CheckOid(OID_FUNCTION_PROVENANCE_MONUS);
393
394 constants.OID_FUNCTION_PROVENANCE_PROJECT = get_provsql_func_oid("provenance_project");
395 CheckOid(OID_FUNCTION_PROVENANCE_PROJECT);
396
397 constants.OID_FUNCTION_PROVENANCE_EQ = get_provsql_func_oid("provenance_eq");
398 CheckOid(OID_FUNCTION_PROVENANCE_EQ);
399
400 constants.OID_FUNCTION_PROVENANCE = get_provsql_func_oid("provenance");
401 CheckOid(OID_FUNCTION_PROVENANCE);
402
403 constants.OID_FUNCTION_PROVENANCE_DELTA = get_provsql_func_oid("provenance_delta");
404 CheckOid(OID_FUNCTION_PROVENANCE_DELTA);
405
406 constants.OID_FUNCTION_PROVENANCE_AGGREGATE = get_provsql_func_oid("provenance_aggregate");
407 CheckOid(OID_FUNCTION_PROVENANCE_AGGREGATE);
408
409 constants.OID_FUNCTION_PROVENANCE_SEMIMOD = get_provsql_func_oid("provenance_semimod");
410 CheckOid(OID_FUNCTION_PROVENANCE_SEMIMOD);
411
412 constants.OID_FUNCTION_GATE_ZERO = get_provsql_func_oid("gate_zero");
413 CheckOid(OID_FUNCTION_GATE_ZERO);
414
415 constants.OID_FUNCTION_GATE_ONE = get_provsql_func_oid("gate_one");
416 CheckOid(OID_FUNCTION_GATE_ONE);
417
418 constants.OID_FUNCTION_PROVENANCE_CMP = get_provsql_func_oid("provenance_cmp");
419 CheckOid(OID_FUNCTION_PROVENANCE_CMP);
420
421 constants.OID_FUNCTION_AGG_TOKEN_UUID = get_provsql_func_oid("agg_token_uuid");
422 CheckOid(OID_FUNCTION_AGG_TOKEN_UUID);
423
424 /* random_variable type and its operator procedures will ship in
425 * 1.5.0. Older schemas (notably the 1.0.0 baseline used by
426 * extension_upgrade) do not have them. Treat each lookup as
427 * optional -- if the catalog lacks the symbol, the OID stays
428 * InvalidOid and downstream code (rv_cmp_index, the planner-hook
429 * walker) silently no-ops on such schemas because real OpExpr
430 * funcoids never equal InvalidOid. Mirrors the
431 * GET_GATE_TYPE_OID_OPTIONAL pattern above. */
432 constants.OID_TYPE_RANDOM_VARIABLE = GetSysCacheOid2(
433 TYPENAMENSP,
434#if PG_VERSION_NUM >= 120000
435 Anum_pg_type_oid,
436#endif
437 CStringGetDatum("random_variable"),
438 ObjectIdGetDatum(constants.OID_SCHEMA_PROVSQL)
439 );
440
441 /* rv_aggregate_semimod helper used by the RV-returning aggregate
442 * rewrite (sum, avg, and any future aggregate whose result type is
443 * random_variable). The planner-hook routes on aggtype instead of a
444 * per-aggregate OID, so no individual aggregate OID needs to be
445 * cached here; an InvalidOid for this helper just leaves the rewrite
446 * disabled on older schemas that lack the continuous-distribution
447 * surface (1.0.0 baseline used by extension_upgrade). */
449 get_provsql_func_oid("rv_aggregate_semimod");
450
451 /* assume_boolean is installed by the 1.6.0 upgrade script. Treat
452 * its absence as a soft signal: on older schemas the safe-query
453 * rewriter (gated behind provsql.boolean_provenance) refuses to
454 * fire because it cannot mark the per-row root with a
455 * gate_assumed_boolean for downstream semiring-compatibility
456 * enforcement. Optional lookup matches the pattern used above for
457 * rv_aggregate_semimod. */
459 get_provsql_func_oid("assume_boolean");
460
461 /* random_variable_{eq,ne,le,lt,ge,gt} -- order matches the
462 * ComparisonOperator enum in src/Aggregation.h (EQ=0, NE=1, LE=2,
463 * LT=3, GE=4, GT=5). */
464 constants.OID_FUNCTION_RV_CMP[0] = get_provsql_func_oid("random_variable_eq");
465 constants.OID_FUNCTION_RV_CMP[1] = get_provsql_func_oid("random_variable_ne");
466 constants.OID_FUNCTION_RV_CMP[2] = get_provsql_func_oid("random_variable_le");
467 constants.OID_FUNCTION_RV_CMP[3] = get_provsql_func_oid("random_variable_lt");
468 constants.OID_FUNCTION_RV_CMP[4] = get_provsql_func_oid("random_variable_ge");
469 constants.OID_FUNCTION_RV_CMP[5] = get_provsql_func_oid("random_variable_gt");
470
471 OperatorGet("<>", PG_CATALOG_NAMESPACE, constants.OID_TYPE_UUID, constants.OID_TYPE_UUID, &constants.OID_OPERATOR_NOT_EQUAL_UUID, &constants.OID_FUNCTION_NOT_EQUAL_UUID);
472 CheckOid(OID_OPERATOR_NOT_EQUAL_UUID);
473 CheckOid(OID_FUNCTION_NOT_EQUAL_UUID);
474
475 /** @brief Look up the OID of provenance_gate enum value @p x and store it in constants. */
476 #define GET_GATE_TYPE_OID(x) { \
477 constants.GATE_TYPE_TO_OID[gate_ ## x] = get_enum_oid( \
478 constants.OID_TYPE_GATE_TYPE, \
479 #x); \
480 if(constants.GATE_TYPE_TO_OID[gate_ ## x]==InvalidOid) \
481 provsql_error("Could not initialize provsql gate type " #x); }
482
483 /** @brief Like @c GET_GATE_TYPE_OID but tolerates a missing enum value.
484 *
485 * Used for gate types added in releases newer than the oldest schema
486 * the @c extension_upgrade test exercises (currently 1.0.0). An
487 * intermediate state where a 1.0.0 database has been bound to a newer
488 * shared library is possible (e.g. between @c CREATE @c EXTENSION
489 * @c VERSION and @c ALTER @c EXTENSION @c UPDATE) and must not abort
490 * @c get_constants -- the missing OID stays @c InvalidOid and any
491 * attempt to actually create such a gate fails later in
492 * @c create_gate's "Invalid gate type" branch. When the upgrade
493 * scripts catch up, the lookup succeeds and the gate becomes usable
494 * normally. No state for @c InvalidOid is stored, so the field
495 * keeps its zero-init value (which is @c InvalidOid).
496 */
497 #define GET_GATE_TYPE_OID_OPTIONAL(x) { \
498 constants.GATE_TYPE_TO_OID[gate_ ## x] = get_enum_oid( \
499 constants.OID_TYPE_GATE_TYPE, \
500 #x); }
501
502 GET_GATE_TYPE_OID(input);
503 GET_GATE_TYPE_OID(plus);
504 GET_GATE_TYPE_OID(times);
505 GET_GATE_TYPE_OID(monus);
506 GET_GATE_TYPE_OID(project);
507 GET_GATE_TYPE_OID(zero);
511 GET_GATE_TYPE_OID(semimod);
513 GET_GATE_TYPE_OID(delta);
514 GET_GATE_TYPE_OID(value);
515 GET_GATE_TYPE_OID(mulinput);
516 GET_GATE_TYPE_OID(update);
520 GET_GATE_TYPE_OID_OPTIONAL(assumed_boolean);
521
522 constants.ok=true;
523
524 return constants;
525}
526
527static database_constants_t *constants_cache; ///< Per-database OID constants cache (sorted by database OID)
528static unsigned constants_cache_len=0; ///< Number of valid entries in @c constants_cache
529
530constants_t get_constants(bool failure_if_not_possible)
531{
532 int start=0, end=constants_cache_len-1;
533 database_constants_t *constants_cache2;
534
535
536 while(end>=start) {
537 unsigned mid=(start+end)/2;
538 if(constants_cache[mid].database<MyDatabaseId)
539 start=mid+1;
540 else if(constants_cache[mid].database>MyDatabaseId)
541 end=mid-1;
542 else
543 return constants_cache[mid].constants;
544 }
545
546 constants_cache2=calloc(constants_cache_len+1, sizeof(database_constants_t));
547 for(unsigned i=0; i<start; ++i)
548 constants_cache2[i]=constants_cache[i];
549
550 constants_cache2[start].database=MyDatabaseId;
551 constants_cache2[start].constants=initialize_constants(failure_if_not_possible);
552
553 for(unsigned i=start; i<constants_cache_len; ++i)
554 constants_cache2[i+1]=constants_cache[i];
555 free(constants_cache);
556 constants_cache=constants_cache2;
558
559 return constants_cache[start].constants;
560}
561
562/* -------------------------------------------------------------------------
563 * Per-backend table-info cache
564 *
565 * Sorted array of @c table_info_cache_entry, binary-searched on @c relid.
566 * Used by @c provsql_lookup_table_info to amortise IPC across repeated
567 * lookups during query planning. Entries are invalidated by
568 * @c invalidate_table_info_cache_callback, which is hooked into
569 * PostgreSQL's relcache invalidation channel and so reacts to local
570 * DDL, cross-backend invalidations broadcast via
571 * @c CacheInvalidateRelcacheByRelid, and explicit "invalidate all"
572 * (relid == InvalidOid) events.
573 * ------------------------------------------------------------------------- */
574
576 Oid relid; ///< pg_class OID (sort key)
577 bool valid; ///< false => refresh on next access
578 bool present; ///< when valid: was a record found at the worker?
579 uint8 kind; ///< when present: provsql_table_kind value
580 uint16 block_key_n; ///< when present: number of block-key columns
581 AttrNumber block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]; ///< when present: block-key column numbers
583
584static table_info_cache_entry *table_info_cache = NULL; ///< Sorted by @c relid
585static unsigned table_info_cache_len = 0;
587
588/** Find @p relid in the cache. Returns the index on hit; otherwise
589 * @c -1 and writes the insertion point to @p *insert_at. */
590static int table_info_cache_find(Oid relid, int *insert_at)
591{
592 int start = 0, end = (int)table_info_cache_len - 1;
593 while(end >= start) {
594 int mid = (start + end) / 2;
595 if(table_info_cache[mid].relid < relid)
596 start = mid + 1;
597 else if(table_info_cache[mid].relid > relid)
598 end = mid - 1;
599 else
600 return mid;
601 }
602 if(insert_at) *insert_at = start;
603 return -1;
604}
605
606/** Insert a fresh entry at @p pos (which must be the value returned by
607 * the most recent @c table_info_cache_find that reported a miss). */
608static void table_info_cache_insert(int pos, Oid relid, bool present,
609 const ProvenanceTableInfo *info)
610{
611 table_info_cache_entry *new_buf = calloc(table_info_cache_len + 1,
612 sizeof(table_info_cache_entry));
613 for(int i = 0; i < pos; ++i)
614 new_buf[i] = table_info_cache[i];
615 new_buf[pos].relid = relid;
616 new_buf[pos].valid = true;
617 new_buf[pos].present = present;
618 if(present) {
619 new_buf[pos].kind = info->kind;
620 new_buf[pos].block_key_n = info->block_key_n;
621 memcpy(new_buf[pos].block_key, info->block_key,
622 info->block_key_n * sizeof(AttrNumber));
623 }
624 for(unsigned i = (unsigned)pos; i < table_info_cache_len; ++i)
625 new_buf[i + 1] = table_info_cache[i];
626 free(table_info_cache);
627 table_info_cache = new_buf;
629}
630
631/** Relcache callback: PostgreSQL fires this whenever a relation's
632 * relcache entry is invalidated (locally or via shared invalidation
633 * from another backend). @p relid == @c InvalidOid means
634 * "invalidate everything." */
635static void invalidate_table_info_cache_callback(Datum arg, Oid relid)
636{
637 int pos;
638 (void) arg;
639 if(relid == InvalidOid) {
640 for(unsigned i = 0; i < table_info_cache_len; ++i)
641 table_info_cache[i].valid = false;
642 return;
643 }
644 pos = table_info_cache_find(relid, NULL);
645 if(pos >= 0)
646 table_info_cache[pos].valid = false;
647}
648
650{
651 int insert_at = 0;
652 int pos;
654 bool present;
655
657 CacheRegisterRelcacheCallback(invalidate_table_info_cache_callback,
658 (Datum) 0);
660 }
661
662 pos = table_info_cache_find(relid, &insert_at);
663
664 if(pos >= 0 && table_info_cache[pos].valid) {
666 if(!e->present)
667 return false;
668 out->relid = relid;
669 out->kind = e->kind;
670 out->block_key_n = e->block_key_n;
671 memcpy(out->block_key, e->block_key,
672 e->block_key_n * sizeof(AttrNumber));
673 return true;
674 }
675
676 present = provsql_fetch_table_info(relid, &info);
677
678 if(pos >= 0) {
679 /* Refresh an existing, now-stale slot in place. */
681 e->valid = true;
682 e->present = present;
683 if(present) {
684 e->kind = info.kind;
685 e->block_key_n = info.block_key_n;
686 memcpy(e->block_key, info.block_key,
687 info.block_key_n * sizeof(AttrNumber));
688 }
689 } else {
690 table_info_cache_insert(insert_at, relid, present, &info);
691 }
692
693 if(present) {
694 *out = info;
695 return true;
696 }
697 return false;
698}
699
700/* -------------------------------------------------------------------------
701 * Per-backend ancestry cache
702 *
703 * Structurally identical to the table-info cache above but keyed on
704 * just the ancestor half of the on-disk record. The two halves share
705 * the storage (one @c ProvenanceTableInfo per relation) but are
706 * cached separately so a hot-path safe-query lookup that only needs
707 * the kind doesn't pull the ancestor array, and vice versa. The
708 * relcache callback below reuses the channel of the kind cache so
709 * any @c set_ancestors / @c add_provenance / @c repair_key DDL
710 * broadcast invalidates both caches in lock-step.
711 * ------------------------------------------------------------------------- */
712
713typedef struct ancestry_cache_entry {
714 Oid relid; ///< pg_class OID (sort key)
715 bool valid; ///< false => refresh on next access
716 bool present; ///< when valid: did the worker return any ancestors?
717 uint16 ancestor_n; ///< when present: count
718 Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS]; ///< when present: ancestor OIDs
720
722static unsigned ancestry_cache_len = 0;
724
725static int ancestry_cache_find(Oid relid, int *insert_at)
726{
727 int start = 0, end = (int)ancestry_cache_len - 1;
728 while(end >= start) {
729 int mid = (start + end) / 2;
730 if(ancestry_cache[mid].relid < relid)
731 start = mid + 1;
732 else if(ancestry_cache[mid].relid > relid)
733 end = mid - 1;
734 else
735 return mid;
736 }
737 if(insert_at) *insert_at = start;
738 return -1;
739}
740
741static void ancestry_cache_insert(int pos, Oid relid, bool present,
742 uint16 ancestor_n, const Oid *ancestors)
743{
744 ancestry_cache_entry *new_buf = calloc(ancestry_cache_len + 1,
745 sizeof(ancestry_cache_entry));
746 for(int i = 0; i < pos; ++i)
747 new_buf[i] = ancestry_cache[i];
748 new_buf[pos].relid = relid;
749 new_buf[pos].valid = true;
750 new_buf[pos].present = present;
751 if(present) {
752 new_buf[pos].ancestor_n = ancestor_n;
753 memcpy(new_buf[pos].ancestors, ancestors,
754 ancestor_n * sizeof(Oid));
755 }
756 for(unsigned i = (unsigned)pos; i < ancestry_cache_len; ++i)
757 new_buf[i + 1] = ancestry_cache[i];
758 free(ancestry_cache);
759 ancestry_cache = new_buf;
761}
762
763static void invalidate_ancestry_cache_callback(Datum arg, Oid relid)
764{
765 int pos;
766 (void) arg;
767 if(relid == InvalidOid) {
768 for(unsigned i = 0; i < ancestry_cache_len; ++i)
769 ancestry_cache[i].valid = false;
770 return;
771 }
772 pos = ancestry_cache_find(relid, NULL);
773 if(pos >= 0)
774 ancestry_cache[pos].valid = false;
775}
776
777bool provsql_lookup_ancestry(Oid relid, uint16 *ancestor_n_out,
778 Oid *ancestors_out)
779{
780 int insert_at = 0;
781 int pos;
782 uint16 n = 0;
784 bool present;
785
787 CacheRegisterRelcacheCallback(invalidate_ancestry_cache_callback,
788 (Datum) 0);
790 }
791
792 pos = ancestry_cache_find(relid, &insert_at);
793
794 if(pos >= 0 && ancestry_cache[pos].valid) {
796 if(!e->present)
797 return false;
798 *ancestor_n_out = e->ancestor_n;
799 memcpy(ancestors_out, e->ancestors,
800 e->ancestor_n * sizeof(Oid));
801 return true;
802 }
803
804 present = provsql_fetch_ancestry(relid, &n, ancestors);
805
806 if(pos >= 0) {
808 e->valid = true;
809 e->present = present;
810 if(present) {
811 e->ancestor_n = n;
812 memcpy(e->ancestors, ancestors, n * sizeof(Oid));
813 }
814 } else {
815 ancestry_cache_insert(insert_at, relid, present, n, ancestors);
816 }
817
818 if(present) {
819 *ancestor_n_out = n;
820 memcpy(ancestors_out, ancestors, n * sizeof(Oid));
821 return true;
822 }
823 return false;
824}
825
826/* -------------------------------------------------------------------------
827 * Per-backend relation-keys cache (§2 PK-FD support)
828 *
829 * Sibling of the @c table_info_cache above; structurally identical
830 * (sorted-by-relid array, binary search, relcache-invalidated) but
831 * keyed on @c pg_class OIDs of @em arbitrary relations -- not just
832 * provenance-tracked ones -- because the §2 / §3 / §5 detector
833 * passes also need to reason about PRIMARY KEY constraints on
834 * deterministic dimension tables and self-joined relations.
835 *
836 * Each cached entry holds up to @c PROVSQL_KEY_CACHE_MAX_KEYS keys
837 * (PRIMARY KEY plus NOT-NULL UNIQUE constraints from
838 * @c pg_constraint, filtered by @c contype @c IN @c ('p','u')).
839 * The relcache callback uses a separate static flag from the
840 * table-info cache's, so a DROP CONSTRAINT / DROP TABLE invalidates
841 * both caches independently.
842 * ------------------------------------------------------------------------- */
843
851
853static unsigned key_cache_len = 0;
855
856static int key_cache_find(Oid relid, int *insert_at)
857{
858 int start = 0, end = (int) key_cache_len - 1;
859 while(end >= start) {
860 int mid = (start + end) / 2;
861 if(key_cache[mid].relid < relid)
862 start = mid + 1;
863 else if(key_cache[mid].relid > relid)
864 end = mid - 1;
865 else
866 return mid;
867 }
868 if(insert_at) *insert_at = start;
869 return -1;
870}
871
872static void key_cache_insert(int pos, Oid relid,
873 const ProvenanceRelationKeys *keys)
874{
875 key_cache_entry *new_buf = calloc(key_cache_len + 1,
876 sizeof(key_cache_entry));
877 for(int i = 0; i < pos; ++i)
878 new_buf[i] = key_cache[i];
879 new_buf[pos].relid = relid;
880 new_buf[pos].valid = true;
881 new_buf[pos].has_keys = keys->key_n > 0;
882 new_buf[pos].key_n = keys->key_n;
883 if(keys->key_n > 0)
884 memcpy(new_buf[pos].keys, keys->keys,
885 keys->key_n * sizeof(ProvenanceRelationKey));
886 for(unsigned i = (unsigned)pos; i < key_cache_len; ++i)
887 new_buf[i + 1] = key_cache[i];
888 free(key_cache);
889 key_cache = new_buf;
891}
892
893static void invalidate_key_cache_callback(Datum arg, Oid relid)
894{
895 int pos;
896 (void) arg;
897 if(relid == InvalidOid) {
898 for(unsigned i = 0; i < key_cache_len; ++i)
899 key_cache[i].valid = false;
900 return;
901 }
902 pos = key_cache_find(relid, NULL);
903 if(pos >= 0)
904 key_cache[pos].valid = false;
905}
906
907/**
908 * @brief Read the PRIMARY-KEY and NOT-NULL-UNIQUE keys of @p relid
909 * from the system catalogs.
910 *
911 * Scans @c pg_constraint for entries with @c conrelid @c = @p relid
912 * and @c contype @c IN @c ('p','u'), then resolves each constraint's
913 * column list via @c pg_index.indkey (the constraint's index is
914 * recorded in @c pg_constraint.conindid). For UNIQUE constraints,
915 * verifies every constituent column has @c pg_attribute.attnotnull
916 * @c = @c true; UNIQUE-with-NULLABLE constraints are rejected
917 * (UNIQUE allows multiple rows with NULL in PostgreSQL, so the
918 * @c ∅ @c → @c attr FD does not hold without NOT NULL).
919 *
920 * Stores up to @c PROVSQL_KEY_CACHE_MAX_KEYS keys; subsequent keys
921 * are silently dropped. Skips constraints whose column count
922 * exceeds @c PROVSQL_KEY_CACHE_MAX_KEY_COLS. Both elisions are
923 * conservatively safe (the §2 detector simply does not see the
924 * dropped FDs).
925 */
926static bool fetch_relation_keys(Oid relid, ProvenanceRelationKeys *out)
927{
928 Relation conrel;
929 SysScanDesc scan;
930 ScanKeyData skey;
931 HeapTuple htup;
932
933 out->relid = relid;
934 out->key_n = 0;
935
936 conrel = table_open(ConstraintRelationId, AccessShareLock);
937 ScanKeyInit(&skey,
938 Anum_pg_constraint_conrelid,
939 BTEqualStrategyNumber, F_OIDEQ,
940 ObjectIdGetDatum(relid));
941 scan = systable_beginscan(conrel,
942#if PG_VERSION_NUM >= 110000
943 ConstraintRelidTypidNameIndexId,
944#else
945 ConstraintRelidIndexId, /* PG 10 name */
946#endif
947 true, NULL, 1, &skey);
948
949 while(HeapTupleIsValid(htup = systable_getnext(scan))) {
950 Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(htup);
951 HeapTuple idxtup;
952 Form_pg_index idx;
953 Oid indexrelid;
954 int k;
956 bool ok_not_null = true;
957
958 if(con->contype != CONSTRAINT_PRIMARY && con->contype != CONSTRAINT_UNIQUE)
959 continue;
961 break;
962
963 indexrelid = con->conindid;
964 if(!OidIsValid(indexrelid))
965 continue;
966 idxtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
967 if(!HeapTupleIsValid(idxtup))
968 continue;
969 idx = (Form_pg_index) GETSTRUCT(idxtup);
970
971 if(idx->indnatts <= 0 || idx->indnatts > PROVSQL_KEY_CACHE_MAX_KEY_COLS) {
972 ReleaseSysCache(idxtup);
973 continue;
974 }
975
976 key = &out->keys[out->key_n];
977 key->col_n = (uint16) idx->indnatts;
978 for(k = 0; k < idx->indnatts; ++k) {
979 AttrNumber attno = idx->indkey.values[k];
980 key->cols[k] = attno;
981
982 if(con->contype == CONSTRAINT_UNIQUE) {
983 HeapTuple atttup =
984 SearchSysCache2(ATTNUM,
985 ObjectIdGetDatum(relid),
986 Int16GetDatum(attno));
987 if(!HeapTupleIsValid(atttup)) {
988 ok_not_null = false;
989 break;
990 } else {
991 Form_pg_attribute attform = (Form_pg_attribute) GETSTRUCT(atttup);
992 if(!attform->attnotnull)
993 ok_not_null = false;
994 ReleaseSysCache(atttup);
995 if(!ok_not_null)
996 break;
997 }
998 }
999 }
1000 ReleaseSysCache(idxtup);
1001
1002 if(!ok_not_null)
1003 continue; /* nullable UNIQUE -- skip */
1004
1005 ++out->key_n;
1006 }
1007
1008 systable_endscan(scan);
1009 table_close(conrel, AccessShareLock);
1010
1011 return out->key_n > 0;
1012}
1013
1015{
1016 int insert_at = 0;
1017 int pos;
1019 bool has_keys;
1020
1022 CacheRegisterRelcacheCallback(invalidate_key_cache_callback, (Datum) 0);
1024 }
1025
1026 pos = key_cache_find(relid, &insert_at);
1027 if(pos >= 0 && key_cache[pos].valid) {
1028 key_cache_entry *e = &key_cache[pos];
1029 out->relid = relid;
1030 out->key_n = e->key_n;
1031 if(e->key_n > 0)
1032 memcpy(out->keys, e->keys, e->key_n * sizeof(ProvenanceRelationKey));
1033 return e->has_keys;
1034 }
1035
1036 has_keys = fetch_relation_keys(relid, &fresh);
1037
1038 if(pos >= 0) {
1039 key_cache_entry *e = &key_cache[pos];
1040 e->valid = true;
1041 e->has_keys = has_keys;
1042 e->key_n = fresh.key_n;
1043 if(fresh.key_n > 0)
1044 memcpy(e->keys, fresh.keys, fresh.key_n * sizeof(ProvenanceRelationKey));
1045 } else {
1046 key_cache_insert(insert_at, relid, &fresh);
1047 }
1048
1049 *out = fresh;
1050 return has_keys;
1051}
1052
1053PG_FUNCTION_INFO_V1(reset_constants_cache);
1054/**
1055 * @brief SQL function to invalidate the OID constants cache.
1056 *
1057 * Forces a fresh OID lookup for the current database on the next call to
1058 * @c get_constants(). Must be called after @c ALTER EXTENSION provsql
1059 * UPDATE to ensure cached OIDs are refreshed.
1060 * @return Void datum.
1061 */
1062Datum reset_constants_cache(PG_FUNCTION_ARGS)
1063{
1064 int start=0, end=constants_cache_len-1;
1065
1066 while(end>=start) {
1067 unsigned mid=(start+end)/2;
1068 if(constants_cache[mid].database<MyDatabaseId)
1069 start=mid+1;
1070 else if(constants_cache[mid].database>MyDatabaseId)
1071 end=mid-1;
1072 else {
1073 constants_cache[mid].constants = initialize_constants(true);
1074 break;
1075 }
1076 }
1077
1078 PG_RETURN_VOID();
1079}
#define PROVSQL_TABLE_INFO_MAX_BLOCK_KEY
Cap on the number of block-key columns recorded per relation.
#define PROVSQL_TABLE_INFO_MAX_ANCESTORS
Cap on the number of base ancestors recorded per relation.
bool provsql_fetch_table_info(Oid relid, ProvenanceTableInfo *out)
C-callable IPC fetch for per-table provenance metadata.
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.
static bool key_cache_callback_registered
static unsigned table_info_cache_len
static bool table_info_callback_registered
const char * gate_type_name[]
Names of gate types.
static Oid get_enum_oid(Oid enumtypoid, const char *label)
Return the OID of a specific enum label within an enum type.
static int key_cache_find(Oid relid, int *insert_at)
static void invalidate_ancestry_cache_callback(Datum arg, Oid relid)
static void OperatorGet(const char *operatorName, Oid operatorNamespace, Oid leftObjectId, Oid rightObjectId, Oid *operatorObjectId, Oid *functionObjectId)
Retrieve operator and function OIDs for a named operator.
Datum reset_constants_cache(PG_FUNCTION_ARGS)
SQL function to invalidate the OID constants cache.
Oid find_equality_operator(Oid ltypeId, Oid rtypeId)
Find the equality operator OID for two given types.
static void ancestry_cache_insert(int pos, Oid relid, bool present, uint16 ancestor_n, const Oid *ancestors)
#define GET_GATE_TYPE_OID_OPTIONAL(x)
bool provsql_lookup_ancestry(Oid relid, uint16 *ancestor_n_out, Oid *ancestors_out)
Look up the base-ancestor set of a tracked relation.
static Oid get_provsql_func_oid(char *s)
Return the OID of a provsql-schema function named s.
static database_constants_t * constants_cache
Per-database OID constants cache (sorted by database OID).
static void invalidate_table_info_cache_callback(Datum arg, Oid relid)
Relcache callback: PostgreSQL fires this whenever a relation's relcache entry is invalidated (locally...
bool provsql_lookup_table_info(Oid relid, ProvenanceTableInfo *out)
Look up per-table provenance metadata with a backend-local cache.
#define table_close(r, l)
static bool ancestry_callback_registered
static void table_info_cache_insert(int pos, Oid relid, bool present, const ProvenanceTableInfo *info)
Insert a fresh entry at pos (which must be the value returned by the most recent table_info_cache_fin...
static table_info_cache_entry * table_info_cache
Sorted by relid.
constants_t get_constants(bool failure_if_not_possible)
Retrieve the cached OID constants for the current database.
static unsigned constants_cache_len
Number of valid entries in constants_cache.
static int table_info_cache_find(Oid relid, int *insert_at)
Find relid in the cache.
static int ancestry_cache_find(Oid relid, int *insert_at)
static void invalidate_key_cache_callback(Datum arg, Oid relid)
bool provsql_lookup_relation_keys(Oid relid, ProvenanceRelationKeys *out)
Look up the PRIMARY-KEY and NOT-NULL-UNIQUE keys of a relation with a backend-local cache.
static unsigned ancestry_cache_len
static bool fetch_relation_keys(Oid relid, ProvenanceRelationKeys *out)
Read the PRIMARY-KEY and NOT-NULL-UNIQUE keys of relid from the system catalogs.
static Oid get_func_oid(char *s)
Return the OID of a globally qualified function named s.
static unsigned key_cache_len
static Oid binary_oper_exact(List *opname, Oid arg1, Oid arg2)
Look up an exactly matching binary operator OID.
#define GET_GATE_TYPE_OID(x)
#define CheckOid(o)
static void key_cache_insert(int pos, Oid relid, const ProvenanceRelationKeys *keys)
static key_cache_entry * key_cache
#define table_open(r, l)
static constants_t initialize_constants(bool failure_if_not_possible)
Query the system catalogs to populate a fresh constants_t.
static ancestry_cache_entry * ancestry_cache
Core types, constants, and utilities shared across ProvSQL.
#define PROVSQL_KEY_CACHE_MAX_KEY_COLS
#define PROVSQL_KEY_CACHE_MAX_KEYS
Upper bounds for the relation-key cache.
One PRIMARY-KEY or NOT-NULL-UNIQUE key on a relation.
AttrNumber cols[PROVSQL_KEY_CACHE_MAX_KEY_COLS]
Per-relation set of PRIMARY-KEY and NOT-NULL-UNIQUE keys.
ProvenanceRelationKey keys[PROVSQL_KEY_CACHE_MAX_KEYS]
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.
uint16 ancestor_n
when present: count
Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS]
when present: ancestor OIDs
Oid relid
pg_class OID (sort key)
bool present
when valid: did the worker return any ancestors?
bool valid
false => refresh on next access
Structure to store the value of various constants.
Oid OID_FUNCTION_PROVENANCE_EQ
OID of the provenance_eq FUNCTION.
Oid OID_FUNCTION_PROVENANCE_AGGREGATE
OID of the provenance_aggregate FUNCTION.
Oid OID_FUNCTION_PROVENANCE_SEMIMOD
OID of the provenance_semimod FUNCTION.
Oid OID_FUNCTION_PROVENANCE
OID of the provenance FUNCTION.
Oid OID_FUNCTION_AGG_TOKEN_UUID
OID of the agg_token_uuid FUNCTION.
Oid OID_FUNCTION_RV_AGGREGATE_SEMIMOD
OID of rv_aggregate_semimod helper (uuid, rv -> rv) used to wrap each per-row argument of an RV-retur...
Oid OID_TYPE_VARCHAR
OID of the VARCHAR TYPE.
Oid OID_FUNCTION_GATE_ZERO
OID of the provenance_zero FUNCTION.
Oid OID_SCHEMA_PROVSQL
OID of the provsql SCHEMA.
Oid OID_TYPE_GATE_TYPE
OID of the provenance_gate TYPE.
Oid OID_FUNCTION_PROVENANCE_PROJECT
OID of the provenance_project FUNCTION.
Oid OID_TYPE_FLOAT
OID of the FLOAT TYPE.
Oid OID_TYPE_AGG_TOKEN
OID of the agg_token TYPE.
Oid OID_FUNCTION_ARRAY_AGG
OID of the array_agg FUNCTION.
Oid OID_TYPE_INT
OID of the INT TYPE.
Oid OID_FUNCTION_PROVENANCE_PLUS
OID of the provenance_plus FUNCTION.
Oid OID_OPERATOR_NOT_EQUAL_UUID
OID of the <> operator on UUIDs FUNCTION.
Oid OID_TYPE_UUID
OID of the uuid TYPE.
Oid OID_TYPE_TSTZMULTIRANGE
OID of the tstzmultirange TYPE (PG14+, InvalidOid otherwise).
bool ok
true if constants were loaded
Oid OID_TYPE_INT_ARRAY
OID of the INT[] TYPE.
Oid OID_FUNCTION_PROVENANCE_DELTA
OID of the provenance_delta FUNCTION.
Oid OID_FUNCTION_ASSUME_BOOLEAN
OID of provsql.assume_boolean(uuid)->uuid.
Oid OID_FUNCTION_PROVENANCE_TIMES
OID of the provenance_times FUNCTION.
Oid OID_FUNCTION_PROVENANCE_MONUS
OID of the provenance_monus FUNCTION.
Oid OID_TYPE_BOOL
OID of the BOOL TYPE.
Oid OID_FUNCTION_NOT_EQUAL_UUID
OID of the = operator on UUIDs FUNCTION.
Oid OID_FUNCTION_GATE_ONE
OID of the provenance_one FUNCTION.
Oid OID_TYPE_NUMMULTIRANGE
OID of the nummultirange TYPE (PG14+, InvalidOid otherwise).
Oid OID_TYPE_UUID_ARRAY
OID of the uuid[] TYPE.
Oid OID_TYPE_RANDOM_VARIABLE
OID of the random_variable TYPE.
Oid OID_FUNCTION_PROVENANCE_CMP
OID of the provenance_cmp FUNCTION.
Oid OID_TYPE_INT4MULTIRANGE
OID of the int4multirange TYPE (PG14+, InvalidOid otherwise).
Oid OID_FUNCTION_RV_CMP[6]
OIDs of the random_variable_{eq,ne,le,lt,ge,gt} comparison procedure functions, indexed by the Compar...
Structure to store the value of various constants for a specific database.
Oid database
OID of the database these constants belong to.
constants_t constants
Cached OID constants for this database.
uint16 key_n
bool valid
Oid relid
bool has_keys
ProvenanceRelationKey keys[PROVSQL_KEY_CACHE_MAX_KEYS]
uint8 kind
when present: provsql_table_kind value
Oid relid
pg_class OID (sort key)
uint16 block_key_n
when present: number of block-key columns
AttrNumber block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]
when present: block-key column numbers
bool present
when valid: was a record found at the worker?
bool valid
false => refresh on next access