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 "compatibility.h"
43#include "fmgr.h"
44#include "nodes/value.h"
45#include "parser/parse_func.h"
46#include "utils/fmgroids.h"
47#include "utils/syscache.h"
48#include "utils/lsyscache.h"
49#include "utils/inval.h"
50
51#include <string.h>
52
53#include "provsql_utils.h"
54
55const char *gate_type_name[] = {
56 "input",
57 "plus",
58 "times",
59 "monus",
60 "project",
61 "zero",
62 "one",
63 "eq",
64 "agg",
65 "semimod",
66 "cmp",
67 "delta",
68 "value",
69 "mulinput",
70 "update",
71 "rv",
72 "arith",
73 "mixture",
74 "assumed",
75 "annotation",
76 "conditioned",
77 "mobius",
78 "case",
79 "observe",
80 "invalid"
81};
82
83/**
84 * @brief Look up an exactly matching binary operator OID.
85 *
86 * Copied and adapted from @c parse_oper.c (PostgreSQL internals, not
87 * exported). Returns @c InvalidOid if no exact match exists.
88 *
89 * @param opname Qualified operator name (a @c List of @c String nodes).
90 * @param arg1 OID of the left operand type.
91 * @param arg2 OID of the right operand type.
92 * @return OID of the matching operator, or @c InvalidOid.
93 */
94static Oid
95binary_oper_exact(List *opname, Oid arg1, Oid arg2)
96{
97 Oid result;
98 bool was_unknown = false;
99
100 /* Unspecified type for one of the arguments? then use the other */
101 if ((arg1 == UNKNOWNOID) && (arg2 != InvalidOid))
102 {
103 arg1 = arg2;
104 was_unknown = true;
105 }
106 else if ((arg2 == UNKNOWNOID) && (arg1 != InvalidOid))
107 {
108 arg2 = arg1;
109 was_unknown = true;
110 }
111
112 result = OpernameGetOprid(opname, arg1, arg2);
113 if (OidIsValid(result))
114 return result;
115
116 if (was_unknown)
117 {
118 /* arg1 and arg2 are the same here, need only look at arg1 */
119 Oid basetype = getBaseType(arg1);
120
121 if (basetype != arg1)
122 {
123 result = OpernameGetOprid(opname, basetype, basetype);
124 if (OidIsValid(result))
125 return result;
126 }
127 }
128
129 return InvalidOid;
130}
131
132/* Adapted from PostgreSQL code that is not exported (see parse_oper.c
133 * and the static function oper_select_candidate therein).
134 */
135Oid find_equality_operator(Oid ltypeId, Oid rtypeId)
136{
137 List * const equals=list_make1(makeString("="));
138
139 FuncCandidateList clist;
140 Oid inputOids[2] = {ltypeId,rtypeId};
141 int ncandidates;
142
143 Oid result = binary_oper_exact(equals, ltypeId, rtypeId);
144
145 if(result!=InvalidOid)
146 return result;
147
148 clist = OpernameGetCandidatesCompat(equals, 'b', false);
149
150 ncandidates = func_match_argtypes(2, inputOids,
151 clist, &clist);
152
153 if (ncandidates == 0)
154 return InvalidOid;
155 else if (ncandidates == 1)
156 return clist->oid;
157
158 clist = func_select_candidate(2, inputOids, clist);
159
160 if(clist)
161 return clist->oid;
162 else
163 return InvalidOid;
164}
165
166/**
167 * @brief Return the OID of a globally qualified function named @p s.
168 *
169 * Looks up the function in the default search path. Returns 0 if no
170 * matching function is found.
171 *
172 * @param s Function name (unqualified).
173 * @return OID of the function, or 0 if not found.
174 */
175static Oid get_func_oid(char *s)
176{
177 FuncCandidateList fcl=FuncnameGetCandidatesCompat(
178 list_make1(makeString(s)),
179 -1,
180 NIL,
181 false,
182 false,
183 false,
184 false);
185 if(fcl)
186 return fcl->oid;
187 else
188 return 0;
189}
190
191/**
192 * @brief Return the OID of a @c provsql-schema function named @p s.
193 *
194 * Looks up the function in the @c provsql schema. Returns 0 if not found.
195 *
196 * @param s Function name (without schema prefix).
197 * @return OID of the function, or 0 if not found.
198 */
199static Oid get_provsql_func_oid(char *s)
200{
201 FuncCandidateList fcl=FuncnameGetCandidatesCompat(
202 list_make2(makeString("provsql"),makeString(s)),
203 -1,
204 NIL,
205 false,
206 false,
207 false,
208 false);
209 if(fcl)
210 return fcl->oid;
211 else
212 return 0;
213}
214
215/**
216 * @brief Return the OID of a @c provsql-schema function named @p s with a
217 * specific argument-type signature.
218 *
219 * Unlike @c get_provsql_func_oid (which returns an arbitrary overload), this
220 * disambiguates overloaded functions by exact argument types. Needed for the
221 * @c probability / @c probability_evaluate families, which now carry both a
222 * @c uuid and a @c boolean first-argument overload. Returns 0 if not found.
223 */
224static Oid get_provsql_func_oid_args(char *s, int nargs, Oid *argtypes)
225{
226 return LookupFuncName(list_make2(makeString("provsql"), makeString(s)),
227 nargs, argtypes, true /* missing_ok */);
228}
229
230/**
231 * @brief Retrieve operator and function OIDs for a named operator.
232 *
233 * Copied and adapted from @c pg_operator.c (PostgreSQL internals, not
234 * exported). Looks up the operator by name, namespace, and operand types
235 * in the system cache.
236 *
237 * @param operatorName Operator symbol string (e.g. @c "<>").
238 * @param operatorNamespace OID of the schema containing the operator.
239 * @param leftObjectId OID of the left operand type.
240 * @param rightObjectId OID of the right operand type.
241 * @param operatorObjectId Output: OID of the operator, or 0 if not found.
242 * @param functionObjectId Output: OID of the underlying function, or 0.
243 */
244static void OperatorGet(
245 const char *operatorName,
246 Oid operatorNamespace,
247 Oid leftObjectId,
248 Oid rightObjectId,
249 Oid *operatorObjectId,
250 Oid *functionObjectId)
251{
252 HeapTuple tup;
253 bool defined;
254
255 tup = SearchSysCache4(OPERNAMENSP,
256 PointerGetDatum(operatorName),
257 ObjectIdGetDatum(leftObjectId),
258 ObjectIdGetDatum(rightObjectId),
259 ObjectIdGetDatum(operatorNamespace));
260 if (HeapTupleIsValid(tup))
261 {
262 Form_pg_operator oprform = (Form_pg_operator) GETSTRUCT(tup);
263#if PG_VERSION_NUM >= 120000
264 *operatorObjectId = oprform->oid;
265#else
266 *operatorObjectId = HeapTupleGetOid(tup);
267#endif
268 *functionObjectId = oprform->oprcode;
269 defined = RegProcedureIsValid(oprform->oprcode);
270 ReleaseSysCache(tup);
271 }
272 else
273 {
274 defined = false;
275 }
276
277 if(!defined) {
278 *operatorObjectId = 0;
279 *functionObjectId = 0;
280 }
281}
282
283/**
284 * @brief Return the OID of a specific enum label within an enum type.
285 *
286 * @param enumtypoid OID of the enum type (e.g. @c provenance_gate).
287 * @param label C-string label of the enum value to look up.
288 * @return OID of the enum label's @c pg_enum row, or
289 * @c InvalidOid if the label is not present.
290 */
291static Oid get_enum_oid(Oid enumtypoid, const char *label)
292{
293 HeapTuple tup;
294 Oid ret;
295
296 tup = SearchSysCache2(ENUMTYPOIDNAME,
297 ObjectIdGetDatum(enumtypoid),
298 CStringGetDatum(label));
299 if (!HeapTupleIsValid(tup))
300 return InvalidOid;
301
302#if PG_VERSION_NUM >= 120000
303 ret = ((Form_pg_enum) GETSTRUCT(tup))->oid;
304#else
305 ret = HeapTupleGetOid(tup);
306#endif
307
308 ReleaseSysCache(tup);
309
310 return ret;
311}
312
313/**
314 * @brief Query the system catalogs to populate a fresh @c constants_t.
315 *
316 * Performs all OID lookups required by ProvSQL in a single pass through
317 * the system caches. The @c CheckOid() macro aborts (or returns early,
318 * depending on @p failure_if_not_possible) if any OID resolves to
319 * @c InvalidOid.
320 *
321 * @param failure_if_not_possible If @c true, raise a @c provsql_error when
322 * any OID cannot be resolved. If @c false, return a @c constants_t
323 * with @c ok==false instead.
324 * @return Fully populated @c constants_t on success, or @c ok==false on
325 * failure when @p failure_if_not_possible is @c false.
326 */
327static constants_t initialize_constants(bool failure_if_not_possible)
328{
329 constants_t constants;
330 constants.ok = false;
331
332 /** @brief Abort or return early if OID field @p o of @p constants is invalid. */
333 #define CheckOid(o) if(constants.o==InvalidOid) { \
334 if(failure_if_not_possible) \
335 provsql_error("Could not initialize provsql constants"); \
336 else \
337 return constants; }
338
339 constants.OID_SCHEMA_PROVSQL = get_namespace_oid("provsql", true);
340 CheckOid(OID_SCHEMA_PROVSQL);
341
342 constants.OID_TYPE_UUID = TypenameGetTypid("uuid");
343 CheckOid(OID_TYPE_UUID);
344
345 constants.OID_TYPE_GATE_TYPE = GetSysCacheOid2(
346 TYPENAMENSP,
347#if PG_VERSION_NUM >= 120000
348 Anum_pg_type_oid,
349#endif
350 CStringGetDatum("provenance_gate"),
351 ObjectIdGetDatum(constants.OID_SCHEMA_PROVSQL)
352 );
353 CheckOid(OID_TYPE_GATE_TYPE);
354
355 constants.OID_TYPE_AGG_TOKEN = GetSysCacheOid2(
356 TYPENAMENSP,
357#if PG_VERSION_NUM >= 120000
358 Anum_pg_type_oid,
359#endif
360 CStringGetDatum("agg_token"),
361 ObjectIdGetDatum(constants.OID_SCHEMA_PROVSQL)
362 );
363 CheckOid(OID_TYPE_AGG_TOKEN);
364
365 constants.OID_TYPE_UUID = TypenameGetTypid("uuid");
366 CheckOid(OID_TYPE_UUID);
367
368 constants.OID_TYPE_UUID_ARRAY = TypenameGetTypid("_uuid");
369 CheckOid(OID_TYPE_UUID_ARRAY);
370
371 constants.OID_TYPE_INT = TypenameGetTypid("int4");
372 CheckOid(OID_TYPE_INT);
373
374 constants.OID_TYPE_BOOL = TypenameGetTypid("bool");
375 CheckOid(OID_TYPE_BOOL);
376
377 constants.OID_TYPE_FLOAT = TypenameGetTypid("float8");
378 CheckOid(OID_TYPE_FLOAT);
379
380 constants.OID_TYPE_INT_ARRAY = TypenameGetTypid("_int4");
381 CheckOid(OID_TYPE_INT_ARRAY);
382
383 constants.OID_TYPE_VARCHAR = TypenameGetTypid("varchar");
384 CheckOid(OID_TYPE_VARCHAR);
385
386#if PG_VERSION_NUM >= 140000
387 constants.OID_TYPE_TSTZMULTIRANGE = TypenameGetTypid("tstzmultirange");
388 CheckOid(OID_TYPE_TSTZMULTIRANGE);
389 constants.OID_TYPE_NUMMULTIRANGE = TypenameGetTypid("nummultirange");
390 CheckOid(OID_TYPE_NUMMULTIRANGE);
391 constants.OID_TYPE_INT4MULTIRANGE = TypenameGetTypid("int4multirange");
392 CheckOid(OID_TYPE_INT4MULTIRANGE);
393#else
394 constants.OID_TYPE_TSTZMULTIRANGE = InvalidOid;
395 constants.OID_TYPE_NUMMULTIRANGE = InvalidOid;
396 constants.OID_TYPE_INT4MULTIRANGE = InvalidOid;
397#endif
398
399 constants.OID_FUNCTION_ARRAY_AGG = get_func_oid("array_agg");
400 CheckOid(OID_FUNCTION_ARRAY_AGG);
401
402 constants.OID_FUNCTION_PROVENANCE_PLUS = get_provsql_func_oid("provenance_plus");
403 CheckOid(OID_FUNCTION_PROVENANCE_PLUS);
404
405 constants.OID_FUNCTION_PROVENANCE_TIMES = get_provsql_func_oid("provenance_times");
406 CheckOid(OID_FUNCTION_PROVENANCE_TIMES);
407
408 constants.OID_FUNCTION_PROVENANCE_MONUS = get_provsql_func_oid("provenance_monus");
409 CheckOid(OID_FUNCTION_PROVENANCE_MONUS);
410
411 constants.OID_FUNCTION_PROVENANCE_PROJECT = get_provsql_func_oid("provenance_project");
412 CheckOid(OID_FUNCTION_PROVENANCE_PROJECT);
413
414 constants.OID_FUNCTION_PROVENANCE_EQ = get_provsql_func_oid("provenance_eq");
415 CheckOid(OID_FUNCTION_PROVENANCE_EQ);
416
417 constants.OID_FUNCTION_PROVENANCE = get_provsql_func_oid("provenance");
418 CheckOid(OID_FUNCTION_PROVENANCE);
419
420 constants.OID_FUNCTION_PROVENANCE_DELTA = get_provsql_func_oid("provenance_delta");
421 CheckOid(OID_FUNCTION_PROVENANCE_DELTA);
422
423 constants.OID_FUNCTION_PROVENANCE_AGGREGATE = get_provsql_func_oid("provenance_aggregate");
424 CheckOid(OID_FUNCTION_PROVENANCE_AGGREGATE);
425
426 constants.OID_FUNCTION_PROVENANCE_SEMIMOD = get_provsql_func_oid("provenance_semimod");
427 CheckOid(OID_FUNCTION_PROVENANCE_SEMIMOD);
428 /* Optional: absent from schemas older than 1.13.0. */
430 get_provsql_func_oid("provenance_semimod_nullable");
431 /* Optional: absent from schemas older than 1.13.0. */
433 get_provsql_func_oid("row_number_as_rank");
435 get_provsql_func_oid("window_distinct_tokens");
436 constants.OID_FUNCTION_PLAIN = get_provsql_func_oid("plain");
437 constants.OID_FUNCTION_PLAIN_TRUTH = get_provsql_func_oid("plain_truth");
439 get_provsql_func_oid("agg_token_plain_text");
441 get_provsql_func_oid("agg_token_frozen_value");
443 get_provsql_func_oid("agg_token_explode_children");
444 /* Optional: absent from schemas older than 1.13.0. */
446 get_provsql_func_oid("agg_possible_values");
448 get_provsql_func_oid("agg_guard_holds");
450 get_provsql_func_oid("provenance_semimod_flat");
452 get_provsql_func_oid("provenance_semimod_nested");
454 get_provsql_func_oid("provenance_contributions_cat");
455 constants.OID_FUNCTION_GATE_NULL = get_provsql_func_oid("gate_null");
456
457 constants.OID_FUNCTION_GATE_ZERO = get_provsql_func_oid("gate_zero");
458 CheckOid(OID_FUNCTION_GATE_ZERO);
459
460 constants.OID_FUNCTION_GATE_ONE = get_provsql_func_oid("gate_one");
461 CheckOid(OID_FUNCTION_GATE_ONE);
462
463 constants.OID_FUNCTION_PROVENANCE_CMP = get_provsql_func_oid("provenance_cmp");
464 CheckOid(OID_FUNCTION_PROVENANCE_CMP);
465
466 constants.OID_FUNCTION_AGG_TOKEN_UUID = get_provsql_func_oid("agg_token_uuid");
467 CheckOid(OID_FUNCTION_AGG_TOKEN_UUID);
468
469 /* Used by the aggregate-carrier CASE lowering (build_agg_case) to lift a
470 * numeric constant branch (e.g. `ELSE 0`) into a value gate. */
471 constants.OID_FUNCTION_AGG_VALUE_GATE = get_provsql_func_oid("agg_value_gate");
472
473 /* The next two are used by the agg_token JOIN query rewriting. */
474 constants.OID_FUNCTION_GET_CHILDREN = get_provsql_func_oid("get_children");
475 CheckOid(OID_FUNCTION_GET_CHILDREN);
476
477 constants.OID_FUNCTION_GET_EXTRA = get_provsql_func_oid("get_extra");
478 CheckOid(OID_FUNCTION_GET_EXTRA);
479
480 /* Pick the unnest(anyarray) overload explicitly; PG 14+ also has
481 * unnest(anymultirange), which get_func_oid would pick up first on
482 * some versions. */
483 {
484 Oid argtypes[1] = { ANYARRAYOID };
485 constants.OID_UNNEST = LookupFuncName(list_make1(makeString("unnest")),
486 1, argtypes, true);
487 }
488 CheckOid(OID_UNNEST);
489
490 /* random_variable type and its operator procedures will ship in
491 * 1.5.0. Older schemas (notably the 1.0.0 baseline used by
492 * extension_upgrade) do not have them. Treat each lookup as
493 * optional -- if the catalog lacks the symbol, the OID stays
494 * InvalidOid and downstream code (rv_cmp_index, the planner-hook
495 * walker) silently no-ops on such schemas because real OpExpr
496 * funcoids never equal InvalidOid. Mirrors the
497 * GET_GATE_TYPE_OID_OPTIONAL pattern above. */
498 constants.OID_TYPE_RANDOM_VARIABLE = GetSysCacheOid2(
499 TYPENAMENSP,
500#if PG_VERSION_NUM >= 120000
501 Anum_pg_type_oid,
502#endif
503 CStringGetDatum("random_variable"),
504 ObjectIdGetDatum(constants.OID_SCHEMA_PROVSQL)
505 );
507 OidIsValid(constants.OID_TYPE_RANDOM_VARIABLE)
508 ? get_array_type(constants.OID_TYPE_RANDOM_VARIABLE)
509 : InvalidOid;
510
511 /* provsql.greatest / provsql.least (VARIADIC random_variable[]): the
512 * order-statistic constructors the planner lifts a builtin GREATEST / LEAST
513 * over random_variable arguments into. Optional (InvalidOid on older
514 * schemas disables the lift; the qualified provsql.greatest(...) still
515 * works). */
516 constants.OID_FUNCTION_RV_GREATEST = get_provsql_func_oid("greatest");
517 constants.OID_FUNCTION_RV_LEAST = get_provsql_func_oid("least");
518
519 /* rv_aggregate_semimod helper used by the RV-returning aggregate
520 * rewrite (sum, avg, and any future aggregate whose result type is
521 * random_variable). The planner-hook routes on aggtype instead of a
522 * per-aggregate OID, so no individual aggregate OID needs to be
523 * cached here; an InvalidOid for this helper just leaves the rewrite
524 * disabled on older schemas that lack the continuous-distribution
525 * surface (1.0.0 baseline used by extension_upgrade). */
526 /* Disambiguate by signature: rv_aggregate_semimod is now overloaded
527 * (2-arg identity-0 wrap and 3-arg identity-parameterised wrap), so a
528 * bare-name lookup could return either. Pin the 2-arg overload used for
529 * sum and the avg numerator. */
530 {
531 Oid semimod2[2] = { constants.OID_TYPE_UUID,
532 constants.OID_TYPE_RANDOM_VARIABLE };
534 get_provsql_func_oid_args("rv_aggregate_semimod", 2, semimod2);
535 }
536
537 /* Per-aggregate identity dispatch for the RV-aggregate rewrite: the
538 * 3-arg identity-parameterised wrap (product / max / min bake their
539 * identity element into the mixture), the avg denominator indicator, the
540 * RV division for avg's sum/sum rewrite, and the five RV-returning
541 * aggregate OIDs the rewrite keys on. The `constants' struct is not
542 * zero-initialised, so every field is set unconditionally; the arg-typed
543 * lookups reference the random_variable type and are skipped (left
544 * InvalidOid) on a schema predating the continuous-distribution surface,
545 * which disables only the identity/avg refinements. */
546 constants.OID_FUNCTION_RV_AGGREGATE_SEMIMOD_ID = InvalidOid;
547 constants.OID_FUNCTION_RV_AGGREGATE_INDICATOR = InvalidOid;
548 constants.OID_FUNCTION_RV_AGGREGATE_INDICATOR_VALUED = InvalidOid;
549 constants.OID_FUNCTION_RV_DIV = InvalidOid;
550 constants.OID_AGG_SUM_RV = InvalidOid;
551 constants.OID_AGG_PRODUCT_RV = InvalidOid;
552 constants.OID_AGG_AVG_RV = InvalidOid;
553 constants.OID_AGG_MAX_RV = InvalidOid;
554 constants.OID_AGG_MIN_RV = InvalidOid;
555 constants.OID_AGG_RV_SUM_OR_NULL = InvalidOid;
556 constants.OID_AGG_COVAR_POP_RV = InvalidOid;
557 constants.OID_AGG_COVAR_SAMP_RV = InvalidOid;
558 constants.OID_AGG_CORR_RV = InvalidOid;
559 constants.OID_AGG_STDDEV_POP_RV = InvalidOid;
560 constants.OID_AGG_STDDEV_SAMP_RV = InvalidOid;
561 constants.OID_AGG_PERCENTILE_CONT_RV = InvalidOid;
562 constants.OID_AGG_RV_COVAR_POP_IMPL = InvalidOid;
563 constants.OID_AGG_RV_COVAR_SAMP_IMPL = InvalidOid;
564 constants.OID_AGG_RV_CORR_IMPL = InvalidOid;
565 constants.OID_AGG_RV_STDDEV_POP_IMPL = InvalidOid;
566 constants.OID_AGG_RV_STDDEV_SAMP_IMPL = InvalidOid;
567 constants.OID_AGG_RV_PERCENTILE_IMPL = InvalidOid;
568 if (OidIsValid(constants.OID_TYPE_RANDOM_VARIABLE)) {
569 Oid rvarg[1] = { constants.OID_TYPE_RANDOM_VARIABLE };
570 Oid rvarg2[2] = { constants.OID_TYPE_RANDOM_VARIABLE,
571 constants.OID_TYPE_RANDOM_VARIABLE };
572 Oid rvarg3[3] = { constants.OID_TYPE_RANDOM_VARIABLE,
573 constants.OID_TYPE_RANDOM_VARIABLE,
574 constants.OID_TYPE_RANDOM_VARIABLE };
575 Oid pctarg[2] = { FLOAT8OID, constants.OID_TYPE_RANDOM_VARIABLE };
576 Oid pctimplarg[3] = { FLOAT8OID, constants.OID_TYPE_RANDOM_VARIABLE,
577 constants.OID_TYPE_RANDOM_VARIABLE };
578 Oid semimod3[3] = { constants.OID_TYPE_UUID,
579 constants.OID_TYPE_RANDOM_VARIABLE, FLOAT8OID };
581 get_provsql_func_oid_args("rv_aggregate_semimod", 3, semimod3);
582 {
583 /* rv_aggregate_indicator is overloaded: resolve each signature
584 * explicitly (the by-name lookup returns an arbitrary overload). */
585 Oid indarg1[1] = { constants.OID_TYPE_UUID };
586 Oid indarg2[2] = { constants.OID_TYPE_UUID,
587 constants.OID_TYPE_RANDOM_VARIABLE };
589 get_provsql_func_oid_args("rv_aggregate_indicator", 1, indarg1);
591 get_provsql_func_oid_args("rv_aggregate_indicator", 2, indarg2);
592 }
593 constants.OID_FUNCTION_RV_DIV =
594 get_provsql_func_oid("random_variable_div");
595 constants.OID_AGG_SUM_RV = get_provsql_func_oid_args("sum", 1, rvarg);
596 constants.OID_AGG_PRODUCT_RV = get_provsql_func_oid_args("product", 1, rvarg);
597 constants.OID_AGG_AVG_RV = get_provsql_func_oid_args("avg", 1, rvarg);
598 constants.OID_AGG_MAX_RV = get_provsql_func_oid_args("max", 1, rvarg);
599 constants.OID_AGG_MIN_RV = get_provsql_func_oid_args("min", 1, rvarg);
600 constants.OID_AGG_RV_SUM_OR_NULL =
601 get_provsql_func_oid_args("rv_sum_or_null", 1, rvarg);
602 /* SQL-standard statistic aggregates over RV rows (public forms) and
603 * the indicator-carrying _impl rewrite targets. Optional lookups:
604 * absent on schemas predating them, which leaves the public forms
605 * unrecognised so they run their own certain-row fold. */
606 constants.OID_AGG_COVAR_POP_RV =
607 get_provsql_func_oid_args("covar_pop", 2, rvarg2);
608 constants.OID_AGG_COVAR_SAMP_RV =
609 get_provsql_func_oid_args("covar_samp", 2, rvarg2);
610 constants.OID_AGG_CORR_RV =
611 get_provsql_func_oid_args("corr", 2, rvarg2);
612 constants.OID_AGG_STDDEV_POP_RV =
613 get_provsql_func_oid_args("stddev_pop", 1, rvarg);
614 constants.OID_AGG_STDDEV_SAMP_RV =
615 get_provsql_func_oid_args("stddev_samp", 1, rvarg);
617 get_provsql_func_oid_args("percentile_cont", 2, pctarg);
618 constants.OID_AGG_RV_COVAR_POP_IMPL =
619 get_provsql_func_oid_args("rv_covar_pop_impl", 3, rvarg3);
621 get_provsql_func_oid_args("rv_covar_samp_impl", 3, rvarg3);
622 constants.OID_AGG_RV_CORR_IMPL =
623 get_provsql_func_oid_args("rv_corr_impl", 3, rvarg3);
625 get_provsql_func_oid_args("rv_stddev_pop_impl", 2, rvarg2);
627 get_provsql_func_oid_args("rv_stddev_samp_impl", 2, rvarg2);
629 get_provsql_func_oid_args("rv_percentile_impl", 3, pctimplarg);
630 }
631
632 /* choose(anyelement): keeps the first non-NULL value of a group. Used by
633 * the scalar-subquery decorrelation to pick the single matched value.
634 * Optional lookup (0 on schemas predating it). */
635 constants.OID_FUNCTION_CHOOSE = get_provsql_func_oid("choose");
636
637 /* array_collect(anynonarray): array_agg, but {} over no rows. Optional
638 * lookup (0 on schemas predating it, where array_agg is used instead). */
639 constants.OID_FUNCTION_ARRAY_COLLECT = get_provsql_func_oid("array_collect");
640
641 /* assume_boolean is installed by the 1.6.0 upgrade script. Treat
642 * its absence as a soft signal: on older schemas the safe-query
643 * rewriter (gated behind the 'boolean' provenance class) refuses to
644 * fire because it cannot mark the per-row root with a
645 * gate_assumed for downstream semiring-compatibility
646 * enforcement. Optional lookup matches the pattern used above for
647 * rv_aggregate_semimod. */
649 get_provsql_func_oid("assume_boolean");
650
651 /* annotate(uuid,text) -- transparent annotation wrapper carrying the
652 * inversion-free certificate / order keys. Optional like the helpers
653 * above: InvalidOid on an older schema simply disables the carrier. */
654 constants.OID_FUNCTION_ANNOTATE =
655 get_provsql_func_oid("annotate");
656
657 /* inversion_free_key(text,text,int) -- builds the per-input order-key string
658 * for the inversion-free path. Optional: InvalidOid on an older schema
659 * disables per-input markers (the path then declines and falls back). */
661 get_provsql_func_oid("inversion_free_key");
662
663 /* cond(uuid,uuid) / given(uuid) -- the conditioning operator and its
664 * whole-tuple output marker. Optional: InvalidOid on a schema predating
665 * the conditioning feature disables the given() rewrite. */
666 /* Optional: absent from extension versions predating the structural
667 * supersede, whose constants must still initialise. */
669 get_provsql_func_oid("provenance_cmp_times");
670 constants.OID_FUNCTION_COND = get_provsql_func_oid("cond");
671 /* given is overloaded: given(uuid) is the evidence carrier / whole-tuple
672 * output marker; given(boolean) is the predicate placeholder the planner
673 * rewrites (both the function form and the prefix "| (predicate)"). */
674 {
675 Oid uuid_arg[1] = { constants.OID_TYPE_UUID };
676 constants.OID_FUNCTION_GIVEN = get_provsql_func_oid_args("given", 1, uuid_arg);
677 }
678
679 /* random_variable_cond(random_variable,uuid) and its Boolean-predicate
680 * placeholder random_variable_cond_predicate(random_variable,boolean): the
681 * planner rewrites "X | (predicate)" into the former over the gate built
682 * from the latter's Boolean operand. Optional (older schemas). */
683 constants.OID_FUNCTION_RV_COND =
684 get_provsql_func_oid("random_variable_cond");
685 constants.OID_FUNCTION_AGG_COND =
686 get_provsql_func_oid("agg_token_cond");
687 /* The "X | (predicate)" placeholders, one per carrier (and the prefix
688 * whole-tuple form). The planner rewrites each into the matching
689 * conditioning constructor over the gate built from the Boolean operand. */
691 get_provsql_func_oid("cond_predicate");
693 get_provsql_func_oid("random_variable_cond_predicate");
695 get_provsql_func_oid("agg_token_cond_predicate");
697 get_provsql_func_oid("predicate_cond_predicate");
698 {
699 Oid bool_arg[1] = { constants.OID_TYPE_BOOL };
701 get_provsql_func_oid_args("given", 1, bool_arg);
702 }
704 get_provsql_func_oid("regular_indicator");
705
706 /* probability(<predicate>) surface: the real probability_evaluate(uuid,
707 * text, text) is the rewrite target; probability(boolean, text, text) is
708 * the placeholder the planner lifts. Disambiguated by argument type since
709 * each name is overloaded. Optional (InvalidOid on schemas predating the
710 * boolean overload). */
711 {
712 Oid uuid_sig[3] = { constants.OID_TYPE_UUID, TEXTOID, TEXTOID };
713 Oid bool_sig[3] = { BOOLOID, TEXTOID, TEXTOID };
715 get_provsql_func_oid_args("probability_evaluate", 3, uuid_sig);
717 get_provsql_func_oid_args("probability", 3, bool_sig);
718 }
719
720 /* rv_case(uuid[]) -> random_variable: builds a gate_case from the planner's
721 * flattened guard/value wire list. Optional (InvalidOid disables the
722 * CASE-over-RV rewrite on schemas predating gate_case). */
723 constants.OID_FUNCTION_RV_CASE = get_provsql_func_oid("rv_case");
724
725 /* agg_case(uuid[]) -> agg_token: the aggregate-carrier analogue of rv_case.
726 * Optional (InvalidOid disables the CASE-over-aggregate rewrite). */
727 constants.OID_FUNCTION_AGG_CASE = get_provsql_func_oid("agg_case");
728
729 /* random_variable_{eq,ne,le,lt,ge,gt} -- order matches the
730 * ComparisonOperator enum in src/Aggregation.h (EQ=0, NE=1, LE=2,
731 * LT=3, GE=4, GT=5). */
732 constants.OID_FUNCTION_RV_CMP[0] = get_provsql_func_oid("random_variable_eq");
733 constants.OID_FUNCTION_RV_CMP[1] = get_provsql_func_oid("random_variable_ne");
734 constants.OID_FUNCTION_RV_CMP[2] = get_provsql_func_oid("random_variable_le");
735 constants.OID_FUNCTION_RV_CMP[3] = get_provsql_func_oid("random_variable_lt");
736 constants.OID_FUNCTION_RV_CMP[4] = get_provsql_func_oid("random_variable_ge");
737 constants.OID_FUNCTION_RV_CMP[5] = get_provsql_func_oid("random_variable_gt");
738
739 OperatorGet("<>", PG_CATALOG_NAMESPACE, constants.OID_TYPE_UUID, constants.OID_TYPE_UUID, &constants.OID_OPERATOR_NOT_EQUAL_UUID, &constants.OID_FUNCTION_NOT_EQUAL_UUID);
740 CheckOid(OID_OPERATOR_NOT_EQUAL_UUID);
741 CheckOid(OID_FUNCTION_NOT_EQUAL_UUID);
742
743 /** @brief Look up the OID of provenance_gate enum value @p x and store it in constants. */
744 #define GET_GATE_TYPE_OID(x) { \
745 constants.GATE_TYPE_TO_OID[gate_ ## x] = get_enum_oid( \
746 constants.OID_TYPE_GATE_TYPE, \
747 #x); \
748 if(constants.GATE_TYPE_TO_OID[gate_ ## x]==InvalidOid) \
749 provsql_error("Could not initialize provsql gate type " #x); }
750
751 /** @brief Like @c GET_GATE_TYPE_OID but tolerates a missing enum value.
752 *
753 * Used for gate types added in releases newer than the oldest schema
754 * the @c extension_upgrade test exercises (currently 1.0.0). An
755 * intermediate state where a 1.0.0 database has been bound to a newer
756 * shared library is possible (e.g. between @c CREATE @c EXTENSION
757 * @c VERSION and @c ALTER @c EXTENSION @c UPDATE) and must not abort
758 * @c get_constants -- the missing OID stays @c InvalidOid and any
759 * attempt to actually create such a gate fails later in
760 * @c create_gate's "Invalid gate type" branch. When the upgrade
761 * scripts catch up, the lookup succeeds and the gate becomes usable
762 * normally. No state for @c InvalidOid is stored, so the field
763 * keeps its zero-init value (which is @c InvalidOid).
764 */
765 #define GET_GATE_TYPE_OID_OPTIONAL(x) { \
766 constants.GATE_TYPE_TO_OID[gate_ ## x] = get_enum_oid( \
767 constants.OID_TYPE_GATE_TYPE, \
768 #x); }
769
770 GET_GATE_TYPE_OID(input);
771 GET_GATE_TYPE_OID(plus);
772 GET_GATE_TYPE_OID(times);
773 GET_GATE_TYPE_OID(monus);
774 GET_GATE_TYPE_OID(project);
775 GET_GATE_TYPE_OID(zero);
779 GET_GATE_TYPE_OID(semimod);
781 GET_GATE_TYPE_OID(delta);
782 GET_GATE_TYPE_OID(value);
783 GET_GATE_TYPE_OID(mulinput);
784 GET_GATE_TYPE_OID(update);
789 /* The 'assumed' label was 'assumed_boolean' before 1.10.0; ALTER TYPE
790 * ... RENAME VALUE keeps the enum value's OID, so accepting the
791 * former label is equivalent -- and necessary when this cache is
792 * built in a session that later runs the upgrade chain across the
793 * rename (the extension-upgrade test does exactly that). */
794 if(constants.GATE_TYPE_TO_OID[gate_assumed] == InvalidOid)
795 constants.GATE_TYPE_TO_OID[gate_assumed] =
796 get_enum_oid(constants.OID_TYPE_GATE_TYPE, "assumed_boolean");
797 GET_GATE_TYPE_OID_OPTIONAL(annotation);
798 GET_GATE_TYPE_OID_OPTIONAL(conditioned);
802
803 constants.ok=true;
804
805 return constants;
806}
807
808static database_constants_t *constants_cache; ///< Per-database OID constants cache (sorted by database OID)
809static unsigned constants_cache_len=0; ///< Number of valid entries in @c constants_cache
810
811constants_t get_constants(bool failure_if_not_possible)
812{
813 int start=0, end=constants_cache_len-1;
814 database_constants_t *constants_cache2;
815 constants_t constants;
816
817 while(end>=start) {
818 unsigned mid=(start+end)/2;
819 if(constants_cache[mid].database<MyDatabaseId)
820 start=mid+1;
821 else if(constants_cache[mid].database>MyDatabaseId)
822 end=mid-1;
823 else
824 return constants_cache[mid].constants;
825 }
826
827 constants=initialize_constants(failure_if_not_possible);
828
829 /* Only memoize successful lookups. A failed lookup (ok==false) means
830 * the provsql extension was not fully visible to this backend at this
831 * point -- e.g. the connection's first planned query ran before
832 * CREATE EXTENSION, or during a DROP/CREATE/ALTER EXTENSION ... UPDATE
833 * window. Caching that failure would permanently disable provenance
834 * tracking on the backend: a long-lived pooled connection would never
835 * recover, silently dropping the provsql column on every query until
836 * reset_constants_cache() is called by hand. Returning without
837 * caching lets the next call retry until the extension is available. */
838 if(!constants.ok)
839 return constants;
840
841 constants_cache2=calloc(constants_cache_len+1, sizeof(database_constants_t));
842 for(unsigned i=0; i<start; ++i)
843 constants_cache2[i]=constants_cache[i];
844
845 constants_cache2[start].database=MyDatabaseId;
846 constants_cache2[start].constants=constants;
847
848 for(unsigned i=start; i<constants_cache_len; ++i)
849 constants_cache2[i+1]=constants_cache[i];
850 free(constants_cache);
851 constants_cache=constants_cache2;
853
854 return constants_cache[start].constants;
855}
856
857/* -------------------------------------------------------------------------
858 * Per-backend table-info cache
859 *
860 * Sorted array of @c table_info_cache_entry, binary-searched on @c relid.
861 * Used by @c provsql_lookup_table_info to amortise IPC across repeated
862 * lookups during query planning. Entries are invalidated by
863 * @c invalidate_table_info_cache_callback, which is hooked into
864 * PostgreSQL's relcache invalidation channel and so reacts to local
865 * DDL, cross-backend invalidations broadcast via
866 * @c CacheInvalidateRelcacheByRelid, and explicit "invalidate all"
867 * (relid == InvalidOid) events.
868 * ------------------------------------------------------------------------- */
869
871 Oid relid; ///< pg_class OID (sort key)
872 bool valid; ///< false => refresh on next access
873 bool present; ///< when valid: was a record found at the worker?
874 uint8 kind; ///< when present: provsql_table_kind value
875 uint16 block_key_n; ///< when present: number of block-key columns
876 AttrNumber block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]; ///< when present: block-key column numbers
878
879static table_info_cache_entry *table_info_cache = NULL; ///< Sorted by @c relid
880static unsigned table_info_cache_len = 0;
882
883/** Find @p relid in the cache. Returns the index on hit; otherwise
884 * @c -1 and writes the insertion point to @p *insert_at. */
885static int table_info_cache_find(Oid relid, int *insert_at)
886{
887 int start = 0, end = (int)table_info_cache_len - 1;
888 while(end >= start) {
889 int mid = (start + end) / 2;
890 if(table_info_cache[mid].relid < relid)
891 start = mid + 1;
892 else if(table_info_cache[mid].relid > relid)
893 end = mid - 1;
894 else
895 return mid;
896 }
897 if(insert_at) *insert_at = start;
898 return -1;
899}
900
901/** Insert a fresh entry at @p pos (which must be the value returned by
902 * the most recent @c table_info_cache_find that reported a miss). */
903static void table_info_cache_insert(int pos, Oid relid, bool present,
904 const ProvenanceTableInfo *info)
905{
906 table_info_cache_entry *new_buf = calloc(table_info_cache_len + 1,
907 sizeof(table_info_cache_entry));
908 for(int i = 0; i < pos; ++i)
909 new_buf[i] = table_info_cache[i];
910 new_buf[pos].relid = relid;
911 new_buf[pos].valid = true;
912 new_buf[pos].present = present;
913 if(present) {
914 new_buf[pos].kind = info->kind;
915 new_buf[pos].block_key_n = info->block_key_n;
916 memcpy(new_buf[pos].block_key, info->block_key,
917 info->block_key_n * sizeof(AttrNumber));
918 }
919 for(unsigned i = (unsigned)pos; i < table_info_cache_len; ++i)
920 new_buf[i + 1] = table_info_cache[i];
921 free(table_info_cache);
922 table_info_cache = new_buf;
924}
925
926/** Relcache callback: PostgreSQL fires this whenever a relation's
927 * relcache entry is invalidated (locally or via shared invalidation
928 * from another backend). @p relid == @c InvalidOid means
929 * "invalidate everything." */
930static void invalidate_table_info_cache_callback(Datum arg, Oid relid)
931{
932 int pos;
933 (void) arg;
934 if(relid == InvalidOid) {
935 for(unsigned i = 0; i < table_info_cache_len; ++i)
936 table_info_cache[i].valid = false;
937 return;
938 }
939 pos = table_info_cache_find(relid, NULL);
940 if(pos >= 0)
941 table_info_cache[pos].valid = false;
942}
943
945{
946 int insert_at = 0;
947 int pos;
949 bool present;
950
952 CacheRegisterRelcacheCallback(invalidate_table_info_cache_callback,
953 (Datum) 0);
955 }
956
957 pos = table_info_cache_find(relid, &insert_at);
958
959 if(pos >= 0 && table_info_cache[pos].valid) {
961 if(!e->present)
962 return false;
963 out->relid = relid;
964 out->kind = e->kind;
965 out->block_key_n = e->block_key_n;
966 memcpy(out->block_key, e->block_key,
967 e->block_key_n * sizeof(AttrNumber));
968 return true;
969 }
970
971 present = provsql_fetch_table_info(relid, &info);
972
973 if(pos >= 0) {
974 /* Refresh an existing, now-stale slot in place. */
976 e->valid = true;
977 e->present = present;
978 if(present) {
979 e->kind = info.kind;
980 e->block_key_n = info.block_key_n;
981 memcpy(e->block_key, info.block_key,
982 info.block_key_n * sizeof(AttrNumber));
983 }
984 } else {
985 table_info_cache_insert(insert_at, relid, present, &info);
986 }
987
988 if(present) {
989 *out = info;
990 return true;
991 }
992 return false;
993}
994
995/* -------------------------------------------------------------------------
996 * Per-backend ancestry cache
997 *
998 * Structurally identical to the table-info cache above but keyed on
999 * just the ancestor half of the on-disk record. The two halves share
1000 * the storage (one @c ProvenanceTableInfo per relation) but are
1001 * cached separately so a hot-path safe-query lookup that only needs
1002 * the kind doesn't pull the ancestor array, and vice versa. The
1003 * relcache callback below reuses the channel of the kind cache so
1004 * any @c set_ancestors / @c add_provenance / @c repair_key DDL
1005 * broadcast invalidates both caches in lock-step.
1006 * ------------------------------------------------------------------------- */
1007
1008typedef struct ancestry_cache_entry {
1009 Oid relid; ///< pg_class OID (sort key)
1010 bool valid; ///< false => refresh on next access
1011 bool present; ///< when valid: did the worker return any ancestors?
1012 uint16 ancestor_n; ///< when present: count
1013 Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS]; ///< when present: ancestor OIDs
1015
1017static unsigned ancestry_cache_len = 0;
1019
1020static int ancestry_cache_find(Oid relid, int *insert_at)
1021{
1022 int start = 0, end = (int)ancestry_cache_len - 1;
1023 while(end >= start) {
1024 int mid = (start + end) / 2;
1025 if(ancestry_cache[mid].relid < relid)
1026 start = mid + 1;
1027 else if(ancestry_cache[mid].relid > relid)
1028 end = mid - 1;
1029 else
1030 return mid;
1031 }
1032 if(insert_at) *insert_at = start;
1033 return -1;
1034}
1035
1036static void ancestry_cache_insert(int pos, Oid relid, bool present,
1037 uint16 ancestor_n, const Oid *ancestors)
1038{
1039 ancestry_cache_entry *new_buf = calloc(ancestry_cache_len + 1,
1040 sizeof(ancestry_cache_entry));
1041 for(int i = 0; i < pos; ++i)
1042 new_buf[i] = ancestry_cache[i];
1043 new_buf[pos].relid = relid;
1044 new_buf[pos].valid = true;
1045 new_buf[pos].present = present;
1046 if(present) {
1047 new_buf[pos].ancestor_n = ancestor_n;
1048 memcpy(new_buf[pos].ancestors, ancestors,
1049 ancestor_n * sizeof(Oid));
1050 }
1051 for(unsigned i = (unsigned)pos; i < ancestry_cache_len; ++i)
1052 new_buf[i + 1] = ancestry_cache[i];
1053 free(ancestry_cache);
1054 ancestry_cache = new_buf;
1056}
1057
1058static void invalidate_ancestry_cache_callback(Datum arg, Oid relid)
1059{
1060 int pos;
1061 (void) arg;
1062 if(relid == InvalidOid) {
1063 for(unsigned i = 0; i < ancestry_cache_len; ++i)
1064 ancestry_cache[i].valid = false;
1065 return;
1066 }
1067 pos = ancestry_cache_find(relid, NULL);
1068 if(pos >= 0)
1069 ancestry_cache[pos].valid = false;
1070}
1071
1072bool provsql_lookup_ancestry(Oid relid, uint16 *ancestor_n_out,
1073 Oid *ancestors_out)
1074{
1075 int insert_at = 0;
1076 int pos;
1077 uint16 n = 0;
1078 Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS];
1079 bool present;
1080
1082 CacheRegisterRelcacheCallback(invalidate_ancestry_cache_callback,
1083 (Datum) 0);
1085 }
1086
1087 pos = ancestry_cache_find(relid, &insert_at);
1088
1089 if(pos >= 0 && ancestry_cache[pos].valid) {
1091 if(!e->present)
1092 return false;
1093 *ancestor_n_out = e->ancestor_n;
1094 memcpy(ancestors_out, e->ancestors,
1095 e->ancestor_n * sizeof(Oid));
1096 return true;
1097 }
1098
1099 present = provsql_fetch_ancestry(relid, &n, ancestors);
1100
1101 if(pos >= 0) {
1103 e->valid = true;
1104 e->present = present;
1105 if(present) {
1106 e->ancestor_n = n;
1107 memcpy(e->ancestors, ancestors, n * sizeof(Oid));
1108 }
1109 } else {
1110 ancestry_cache_insert(insert_at, relid, present, n, ancestors);
1111 }
1112
1113 if(present) {
1114 *ancestor_n_out = n;
1115 memcpy(ancestors_out, ancestors, n * sizeof(Oid));
1116 return true;
1117 }
1118 return false;
1119}
1120
1121/* -------------------------------------------------------------------------
1122 * Per-backend relation-keys cache (§2 PK-FD support)
1123 *
1124 * Sibling of the @c table_info_cache above; structurally identical
1125 * (sorted-by-relid array, binary search, relcache-invalidated) but
1126 * keyed on @c pg_class OIDs of @em arbitrary relations -- not just
1127 * provenance-tracked ones -- because the §2 / §3 / §5 detector
1128 * passes also need to reason about PRIMARY KEY constraints on
1129 * deterministic dimension tables and self-joined relations.
1130 *
1131 * Each cached entry holds up to @c PROVSQL_KEY_CACHE_MAX_KEYS keys
1132 * (PRIMARY KEY plus NOT-NULL UNIQUE constraints from
1133 * @c pg_constraint, filtered by @c contype @c IN @c ('p','u')).
1134 * The relcache callback uses a separate static flag from the
1135 * table-info cache's, so a DROP CONSTRAINT / DROP TABLE invalidates
1136 * both caches independently.
1137 * ------------------------------------------------------------------------- */
1138
1146
1148static unsigned key_cache_len = 0;
1150
1151static int key_cache_find(Oid relid, int *insert_at)
1152{
1153 int start = 0, end = (int) key_cache_len - 1;
1154 while(end >= start) {
1155 int mid = (start + end) / 2;
1156 if(key_cache[mid].relid < relid)
1157 start = mid + 1;
1158 else if(key_cache[mid].relid > relid)
1159 end = mid - 1;
1160 else
1161 return mid;
1162 }
1163 if(insert_at) *insert_at = start;
1164 return -1;
1165}
1166
1167static void key_cache_insert(int pos, Oid relid,
1168 const ProvenanceRelationKeys *keys)
1169{
1170 key_cache_entry *new_buf = calloc(key_cache_len + 1,
1171 sizeof(key_cache_entry));
1172 for(int i = 0; i < pos; ++i)
1173 new_buf[i] = key_cache[i];
1174 new_buf[pos].relid = relid;
1175 new_buf[pos].valid = true;
1176 new_buf[pos].has_keys = keys->key_n > 0;
1177 new_buf[pos].key_n = keys->key_n;
1178 if(keys->key_n > 0)
1179 memcpy(new_buf[pos].keys, keys->keys,
1180 keys->key_n * sizeof(ProvenanceRelationKey));
1181 for(unsigned i = (unsigned)pos; i < key_cache_len; ++i)
1182 new_buf[i + 1] = key_cache[i];
1183 free(key_cache);
1184 key_cache = new_buf;
1185 ++key_cache_len;
1186}
1187
1188static void invalidate_key_cache_callback(Datum arg, Oid relid)
1189{
1190 int pos;
1191 (void) arg;
1192 if(relid == InvalidOid) {
1193 for(unsigned i = 0; i < key_cache_len; ++i)
1194 key_cache[i].valid = false;
1195 return;
1196 }
1197 pos = key_cache_find(relid, NULL);
1198 if(pos >= 0)
1199 key_cache[pos].valid = false;
1200}
1201
1202/**
1203 * @brief Read the PRIMARY-KEY and NOT-NULL-UNIQUE keys of @p relid
1204 * from the system catalogs.
1205 *
1206 * Scans @c pg_constraint for entries with @c conrelid @c = @p relid
1207 * and @c contype @c IN @c ('p','u'), then resolves each constraint's
1208 * column list via @c pg_index.indkey (the constraint's index is
1209 * recorded in @c pg_constraint.conindid). For UNIQUE constraints,
1210 * verifies every constituent column has @c pg_attribute.attnotnull
1211 * @c = @c true; UNIQUE-with-NULLABLE constraints are rejected
1212 * (UNIQUE allows multiple rows with NULL in PostgreSQL, so the
1213 * @c ∅ @c → @c attr FD does not hold without NOT NULL).
1214 *
1215 * Stores up to @c PROVSQL_KEY_CACHE_MAX_KEYS keys; subsequent keys
1216 * are silently dropped. Skips constraints whose column count
1217 * exceeds @c PROVSQL_KEY_CACHE_MAX_KEY_COLS. Both elisions are
1218 * conservatively safe (the §2 detector simply does not see the
1219 * dropped FDs).
1220 */
1222{
1223 Relation conrel;
1224 SysScanDesc scan;
1225 ScanKeyData skey;
1226 HeapTuple htup;
1227
1228 out->relid = relid;
1229 out->key_n = 0;
1230
1231 conrel = table_open(ConstraintRelationId, AccessShareLock);
1232 ScanKeyInit(&skey,
1233 Anum_pg_constraint_conrelid,
1234 BTEqualStrategyNumber, F_OIDEQ,
1235 ObjectIdGetDatum(relid));
1236 scan = systable_beginscan(conrel,
1237 ConstraintRelidTypidNameIndexId,
1238 true, NULL, 1, &skey);
1239
1240 while(HeapTupleIsValid(htup = systable_getnext(scan))) {
1241 Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(htup);
1242 HeapTuple idxtup;
1243 Form_pg_index idx;
1244 Oid indexrelid;
1245 int k;
1247 bool ok_not_null = true;
1248
1249 if(con->contype != CONSTRAINT_PRIMARY && con->contype != CONSTRAINT_UNIQUE)
1250 continue;
1252 break;
1253
1254 indexrelid = con->conindid;
1255 if(!OidIsValid(indexrelid))
1256 continue;
1257 idxtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
1258 if(!HeapTupleIsValid(idxtup))
1259 continue;
1260 idx = (Form_pg_index) GETSTRUCT(idxtup);
1261
1262 if(idx->indnatts <= 0 || idx->indnatts > PROVSQL_KEY_CACHE_MAX_KEY_COLS) {
1263 ReleaseSysCache(idxtup);
1264 continue;
1265 }
1266
1267 key = &out->keys[out->key_n];
1268 key->col_n = (uint16) idx->indnatts;
1269 for(k = 0; k < idx->indnatts; ++k) {
1270 AttrNumber attno = idx->indkey.values[k];
1271 key->cols[k] = attno;
1272
1273 if(con->contype == CONSTRAINT_UNIQUE) {
1274 HeapTuple atttup =
1275 SearchSysCache2(ATTNUM,
1276 ObjectIdGetDatum(relid),
1277 Int16GetDatum(attno));
1278 if(!HeapTupleIsValid(atttup)) {
1279 ok_not_null = false;
1280 break;
1281 } else {
1282 Form_pg_attribute attform = (Form_pg_attribute) GETSTRUCT(atttup);
1283 if(!attform->attnotnull)
1284 ok_not_null = false;
1285 ReleaseSysCache(atttup);
1286 if(!ok_not_null)
1287 break;
1288 }
1289 }
1290 }
1291 ReleaseSysCache(idxtup);
1292
1293 if(!ok_not_null)
1294 continue; /* nullable UNIQUE -- skip */
1295
1296 ++out->key_n;
1297 }
1298
1299 systable_endscan(scan);
1300 table_close(conrel, AccessShareLock);
1301
1302 return out->key_n > 0;
1303}
1304
1306{
1307 int insert_at = 0;
1308 int pos;
1310 bool has_keys;
1311
1313 CacheRegisterRelcacheCallback(invalidate_key_cache_callback, (Datum) 0);
1315 }
1316
1317 pos = key_cache_find(relid, &insert_at);
1318 if(pos >= 0 && key_cache[pos].valid) {
1319 key_cache_entry *e = &key_cache[pos];
1320 out->relid = relid;
1321 out->key_n = e->key_n;
1322 if(e->key_n > 0)
1323 memcpy(out->keys, e->keys, e->key_n * sizeof(ProvenanceRelationKey));
1324 return e->has_keys;
1325 }
1326
1327 has_keys = fetch_relation_keys(relid, &fresh);
1328
1329 if(pos >= 0) {
1330 key_cache_entry *e = &key_cache[pos];
1331 e->valid = true;
1332 e->has_keys = has_keys;
1333 e->key_n = fresh.key_n;
1334 if(fresh.key_n > 0)
1335 memcpy(e->keys, fresh.keys, fresh.key_n * sizeof(ProvenanceRelationKey));
1336 } else {
1337 key_cache_insert(insert_at, relid, &fresh);
1338 }
1339
1340 *out = fresh;
1341 return has_keys;
1342}
1343
1344PG_FUNCTION_INFO_V1(reset_constants_cache);
1345/**
1346 * @brief SQL function to invalidate the OID constants cache.
1347 *
1348 * Forces a fresh OID lookup for the current database on the next call to
1349 * @c get_constants(). Must be called after @c ALTER EXTENSION provsql
1350 * UPDATE to ensure cached OIDs are refreshed.
1351 * @return Void datum.
1352 */
1353Datum reset_constants_cache(PG_FUNCTION_ARGS)
1354{
1355 int start=0, end=constants_cache_len-1;
1356
1357 while(end>=start) {
1358 unsigned mid=(start+end)/2;
1359 if(constants_cache[mid].database<MyDatabaseId)
1360 start=mid+1;
1361 else if(constants_cache[mid].database>MyDatabaseId)
1362 end=mid-1;
1363 else {
1364 constants_cache[mid].constants = initialize_constants(true);
1365 break;
1366 }
1367 }
1368
1369 PG_RETURN_VOID();
1370}
#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.
PostgreSQL cross-version compatibility shims for ProvSQL.
static FuncCandidateList OpernameGetCandidatesCompat(List *names, char oprkind, bool missing_schema_ok)
Version-agnostic wrapper around OpernameGetCandidates().
static FuncCandidateList FuncnameGetCandidatesCompat(List *names, int nargs, List *argnames, bool expand_variadic, bool expand_defaults, bool include_out_arguments, bool missing_ok)
Version-agnostic wrapper around FuncnameGetCandidates().
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.
static Oid get_provsql_func_oid_args(char *s, int nargs, Oid *argtypes)
Return the OID of a provsql-schema function named s with a specific argument-type signature.
#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
@ gate_assumed
Structural marker over a single child whose sub-circuit was computed under a Boolean-provenance assum...
bool provsql_fetch_table_info(Oid relid, ProvenanceTableInfo *out)
Raw IPC fetch (no cache).
Definition table_info.c:175
bool provsql_fetch_ancestry(Oid relid, uint16 *ancestor_n_out, Oid *ancestors_out)
Raw IPC fetch for the ancestry half (no cache).
Definition table_info.c:180
#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_REGULAR_INDICATOR
OID of provsql.regular_indicator(boolean): the deterministic gate_one/gate_zero indicator the planner...
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_WINDOW_DISTINCT_TOKENS
OID of window_distinct_tokens, used for dense_rank(); InvalidOid on a schema that predates it.
Oid OID_FUNCTION_PROVENANCE_SEMIMOD
OID of the provenance_semimod FUNCTION.
Oid OID_FUNCTION_RV_DIV
OID of random_variable_div(rv, rv) -> rv: builds the avg num/denom division gate.
Oid OID_AGG_AVG_RV
provsql.avg(random_variable)
Oid OID_AGG_RV_CORR_IMPL
provsql.rv_corr_impl(ind rv, x rv, y rv)
Oid OID_AGG_RV_SUM_OR_NULL
provsql.rv_sum_or_null(random_variable): the avg-numerator sum, NULL on an empty group (so avg is NUL...
Oid OID_AGG_SUM_RV
OIDs of the RV-returning aggregates, keyed for the per-aggregate identity dispatch in make_rv_aggrega...
Oid OID_FUNCTION_CHOOSE
OID of the choose(anyelement) aggregate (keeps the first non-NULL value); used to decorrelate scalar ...
Oid OID_AGG_RV_STDDEV_SAMP_IMPL
provsql.rv_stddev_samp_impl(ind rv, x rv)
Oid OID_AGG_STDDEV_SAMP_RV
provsql.stddev_samp(rv)
Oid OID_FUNCTION_ANNOTATE
OID of provsql.annotate(uuid,text)->uuid.
Oid OID_FUNCTION_PROVENANCE
OID of the provenance FUNCTION.
Oid OID_FUNCTION_AGG_TOKEN_PLAIN_TEXT
OID of agg_token_plain_text(), the sort key of an ORDER BY on an aggregate result; InvalidOid on a sc...
Oid OID_FUNCTION_RV_LEAST
provsql.least(VARIADIC random_variable[])
Oid OID_FUNCTION_RV_CASE
OID of provsql.rv_case(uuid[])->random_variable.
Oid OID_FUNCTION_INVERSION_FREE_KEY
OID of provsql.inversion_free_key(text,text,int)->text.
Oid OID_FUNCTION_AGG_VALUE_GATE
agg_value_gate(numeric) -> uuid
Oid OID_FUNCTION_AGG_TOKEN_UUID
OID of the agg_token_uuid FUNCTION.
Oid OID_AGG_STDDEV_POP_RV
provsql.stddev_pop(rv)
Oid OID_FUNCTION_RV_AGGREGATE_SEMIMOD
OID of rv_aggregate_semimod(uuid, rv) -> rv: wraps a per-row argument as mixture(prov,...
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_FUNCTION_AGG_TOKEN_FROZEN_VALUE
OID of agg_token_frozen_value(), the value of an aggregate result that ProvSQL casts to a plain value...
Oid OID_TYPE_RANDOM_VARIABLE_ARRAY
OID of the random_variable[] TYPE.
Oid OID_AGG_PRODUCT_RV
provsql.product(random_variable)
Oid OID_FUNCTION_AGG_GUARD_HOLDS
OID of agg_guard_holds(), the truth of a comparison of aggregate results in the database as it is,...
Oid OID_FUNCTION_AGG_TOKEN_EXPLODE_CHILDREN
OID of agg_token_explode_children(), the children of the gate of a choose() result exploded into rows...
Oid OID_FUNCTION_COND
OID of provsql.cond(uuid,uuid)->uuid.
Oid OID_TYPE_GATE_TYPE
OID of the provenance_gate TYPE.
Oid OID_FUNCTION_PROVENANCE_PROJECT
OID of the provenance_project FUNCTION.
Oid OID_FUNCTION_PROVENANCE_CONTRIBUTIONS_CAT
OID of the provenance_contributions_cat aggregate, which collects them; InvalidOid on a schema that p...
Oid OID_FUNCTION_RV_AGGREGATE_INDICATOR
OID of rv_aggregate_indicator(uuid) -> rv: the avg denominator wrap mixture(prov, 1,...
Oid OID_FUNCTION_GET_CHILDREN
OID of the get_children FUNCTION.
Oid OID_FUNCTION_ROW_NUMBER_AS_RANK
OID of row_number_as_rank, which row_number() over tracked relations is rewritten into; InvalidOid on...
Oid OID_FUNCTION_RV_AGGREGATE_SEMIMOD_ID
OID of the 3-arg rv_aggregate_semimod(uuid, rv, float8): identity-parameterised wrap mixture(prov,...
Oid OID_UNNEST
OID of the unnest(anyarray) FUNCTION.
Oid OID_FUNCTION_COND_PREDICATE
cond_predicate(uuid,boolean)
Oid OID_AGG_MIN_RV
provsql.min(random_variable)
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 GATE_TYPE_TO_OID[nb_gate_types]
Array of the OID of each provenance_gate ENUM value.
Oid OID_FUNCTION_PLAIN
OID of the marker plain(), which keeps a LIMIT a truncation of the actual result; InvalidOid on a sch...
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_AGG_RV_PERCENTILE_IMPL
provsql.rv_percentile_impl(fraction float8, ind rv, x rv)
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_AGG_POSSIBLE_VALUES
OID of agg_possible_values(), the values an aggregate result takes over the possible worlds,...
Oid OID_FUNCTION_PROVENANCE_TIMES
OID of the provenance_times FUNCTION.
Oid OID_FUNCTION_PROVENANCE_MONUS
OID of the provenance_monus FUNCTION.
Oid OID_FUNCTION_AGG_COND_PREDICATE
agg_token_cond_predicate(agg_token,boolean)
Oid OID_FUNCTION_GIVEN_PREDICATE
given_predicate(boolean) – prefix whole-tuple
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_ARRAY_COLLECT
OID of the array_collect(anynonarray) aggregate (array_agg with {} over no rows); the value of a deco...
Oid OID_FUNCTION_AGG_CASE
OID of agg_case(uuid[]), the agg_token constructor the planner hook lowers an aggregate-carrier CASE ...
Oid OID_AGG_CORR_RV
provsql.corr(rv, rv)
Oid OID_FUNCTION_GIVEN
OID of provsql.given(uuid)->uuid.
Oid OID_FUNCTION_PLAIN_TRUTH
OID of plain_truth(), whether a token holds in the database as it is; InvalidOid on a schema that pre...
Oid OID_FUNCTION_GATE_ONE
OID of the provenance_one FUNCTION.
Oid OID_FUNCTION_PROVENANCE_SEMIMOD_NESTED
OID of provenance_semimod_nested(), the contribution of an aggregate result aggregated by an aggregat...
Oid OID_AGG_COVAR_SAMP_RV
provsql.covar_samp(rv, rv)
Oid OID_TYPE_NUMMULTIRANGE
OID of the nummultirange TYPE (PG14+, InvalidOid otherwise).
Oid OID_FUNCTION_RV_AGGREGATE_INDICATOR_VALUED
OID of rv_aggregate_indicator(uuid, rv) -> rv: NULL when the row's value is NULL (SQL NULL-skip for a...
Oid OID_FUNCTION_PROVENANCE_SEMIMOD_FLAT
OID of provenance_semimod_flat(), the contributions of an aggregate result aggregated again; InvalidO...
Oid OID_FUNCTION_PROBABILITY_EVALUATE
OID of the real provsql.probability_evaluate(uuid,text,text).
Oid OID_FUNCTION_RV_COND
OID of provsql.random_variable_cond(random_variable,uuid).
Oid OID_FUNCTION_RV_COND_PREDICATE
random_variable_cond_predicate(random_variable,boolean)
Oid OID_TYPE_UUID_ARRAY
OID of the uuid[] TYPE.
Oid OID_FUNCTION_PREDICATE_COND_PREDICATE
predicate_cond_predicate(boolean,boolean) – (A)|(B), both events
Oid OID_AGG_MAX_RV
provsql.max(random_variable)
Oid OID_FUNCTION_AGG_COND
OID of provsql.agg_token_cond(agg_token,uuid): the conditioning constructor for the agg_token carrier...
Oid OID_FUNCTION_PROBABILITY_PREDICATE
OID of the probability(boolean,text,text) placeholder.
Oid OID_FUNCTION_GATE_NULL
OID of gate_null(), the value gate of the NULL value (a NULL branch of a CASE over aggregates).
Oid OID_TYPE_RANDOM_VARIABLE
OID of the random_variable TYPE.
Oid OID_AGG_COVAR_POP_RV
SQL-standard statistic aggregates over random_variable rows and their internal indicator-carrying rew...
Oid OID_FUNCTION_PROVENANCE_CMP
OID of the provenance_cmp FUNCTION.
Oid OID_AGG_PERCENTILE_CONT_RV
provsql.percentile_cont(float8) WITHIN GROUP (ORDER BY rv)
Oid OID_FUNCTION_RV_GREATEST
provsql.greatest(VARIADIC random_variable[])
Oid OID_AGG_RV_COVAR_POP_IMPL
provsql.rv_covar_pop_impl(ind rv, x rv, y rv)
Oid OID_AGG_RV_STDDEV_POP_IMPL
provsql.rv_stddev_pop_impl(ind rv, x rv)
Oid OID_TYPE_INT4MULTIRANGE
OID of the int4multirange TYPE (PG14+, InvalidOid otherwise).
Oid OID_AGG_RV_COVAR_SAMP_IMPL
provsql.rv_covar_samp_impl(ind rv, x rv, y rv)
Oid OID_FUNCTION_PROVENANCE_SEMIMOD_NULLABLE
OID of provenance_semimod_nullable, the variant used for aggregates that see their NULL inputs (array...
Oid OID_FUNCTION_GET_EXTRA
OID of the get_extra FUNCTION.
Oid OID_FUNCTION_RV_CMP[6]
OIDs of the random_variable_{eq,ne,le,lt,ge,gt} comparison procedure functions, indexed by the Compar...
Oid OID_FUNCTION_PROVENANCE_CMP_TIMES
OID of the provenance_cmp_times FUNCTION.
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