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
429 constants.OID_FUNCTION_GATE_ZERO = get_provsql_func_oid("gate_zero");
430 CheckOid(OID_FUNCTION_GATE_ZERO);
431
432 constants.OID_FUNCTION_GATE_ONE = get_provsql_func_oid("gate_one");
433 CheckOid(OID_FUNCTION_GATE_ONE);
434
435 constants.OID_FUNCTION_PROVENANCE_CMP = get_provsql_func_oid("provenance_cmp");
436 CheckOid(OID_FUNCTION_PROVENANCE_CMP);
437
438 constants.OID_FUNCTION_AGG_TOKEN_UUID = get_provsql_func_oid("agg_token_uuid");
439 CheckOid(OID_FUNCTION_AGG_TOKEN_UUID);
440
441 /* Used by the aggregate-carrier CASE lowering (build_agg_case) to lift a
442 * numeric constant branch (e.g. `ELSE 0`) into a value gate. */
443 constants.OID_FUNCTION_AGG_VALUE_GATE = get_provsql_func_oid("agg_value_gate");
444
445 /* The next two are used by the agg_token JOIN query rewriting. */
446 constants.OID_FUNCTION_GET_CHILDREN = get_provsql_func_oid("get_children");
447 CheckOid(OID_FUNCTION_GET_CHILDREN);
448
449 constants.OID_FUNCTION_GET_EXTRA = get_provsql_func_oid("get_extra");
450 CheckOid(OID_FUNCTION_GET_EXTRA);
451
452 /* Pick the unnest(anyarray) overload explicitly; PG 14+ also has
453 * unnest(anymultirange), which get_func_oid would pick up first on
454 * some versions. */
455 {
456 Oid argtypes[1] = { ANYARRAYOID };
457 constants.OID_UNNEST = LookupFuncName(list_make1(makeString("unnest")),
458 1, argtypes, true);
459 }
460 CheckOid(OID_UNNEST);
461
462 /* random_variable type and its operator procedures will ship in
463 * 1.5.0. Older schemas (notably the 1.0.0 baseline used by
464 * extension_upgrade) do not have them. Treat each lookup as
465 * optional -- if the catalog lacks the symbol, the OID stays
466 * InvalidOid and downstream code (rv_cmp_index, the planner-hook
467 * walker) silently no-ops on such schemas because real OpExpr
468 * funcoids never equal InvalidOid. Mirrors the
469 * GET_GATE_TYPE_OID_OPTIONAL pattern above. */
470 constants.OID_TYPE_RANDOM_VARIABLE = GetSysCacheOid2(
471 TYPENAMENSP,
472#if PG_VERSION_NUM >= 120000
473 Anum_pg_type_oid,
474#endif
475 CStringGetDatum("random_variable"),
476 ObjectIdGetDatum(constants.OID_SCHEMA_PROVSQL)
477 );
479 OidIsValid(constants.OID_TYPE_RANDOM_VARIABLE)
480 ? get_array_type(constants.OID_TYPE_RANDOM_VARIABLE)
481 : InvalidOid;
482
483 /* provsql.greatest / provsql.least (VARIADIC random_variable[]): the
484 * order-statistic constructors the planner lifts a builtin GREATEST / LEAST
485 * over random_variable arguments into. Optional (InvalidOid on older
486 * schemas disables the lift; the qualified provsql.greatest(...) still
487 * works). */
488 constants.OID_FUNCTION_RV_GREATEST = get_provsql_func_oid("greatest");
489 constants.OID_FUNCTION_RV_LEAST = get_provsql_func_oid("least");
490
491 /* rv_aggregate_semimod helper used by the RV-returning aggregate
492 * rewrite (sum, avg, and any future aggregate whose result type is
493 * random_variable). The planner-hook routes on aggtype instead of a
494 * per-aggregate OID, so no individual aggregate OID needs to be
495 * cached here; an InvalidOid for this helper just leaves the rewrite
496 * disabled on older schemas that lack the continuous-distribution
497 * surface (1.0.0 baseline used by extension_upgrade). */
498 /* Disambiguate by signature: rv_aggregate_semimod is now overloaded
499 * (2-arg identity-0 wrap and 3-arg identity-parameterised wrap), so a
500 * bare-name lookup could return either. Pin the 2-arg overload used for
501 * sum and the avg numerator. */
502 {
503 Oid semimod2[2] = { constants.OID_TYPE_UUID,
504 constants.OID_TYPE_RANDOM_VARIABLE };
506 get_provsql_func_oid_args("rv_aggregate_semimod", 2, semimod2);
507 }
508
509 /* Per-aggregate identity dispatch for the RV-aggregate rewrite: the
510 * 3-arg identity-parameterised wrap (product / max / min bake their
511 * identity element into the mixture), the avg denominator indicator, the
512 * RV division for avg's sum/sum rewrite, and the five RV-returning
513 * aggregate OIDs the rewrite keys on. The `constants' struct is not
514 * zero-initialised, so every field is set unconditionally; the arg-typed
515 * lookups reference the random_variable type and are skipped (left
516 * InvalidOid) on a schema predating the continuous-distribution surface,
517 * which disables only the identity/avg refinements. */
518 constants.OID_FUNCTION_RV_AGGREGATE_SEMIMOD_ID = InvalidOid;
519 constants.OID_FUNCTION_RV_AGGREGATE_INDICATOR = InvalidOid;
520 constants.OID_FUNCTION_RV_AGGREGATE_INDICATOR_VALUED = InvalidOid;
521 constants.OID_FUNCTION_RV_DIV = InvalidOid;
522 constants.OID_AGG_SUM_RV = InvalidOid;
523 constants.OID_AGG_PRODUCT_RV = InvalidOid;
524 constants.OID_AGG_AVG_RV = InvalidOid;
525 constants.OID_AGG_MAX_RV = InvalidOid;
526 constants.OID_AGG_MIN_RV = InvalidOid;
527 constants.OID_AGG_RV_SUM_OR_NULL = InvalidOid;
528 constants.OID_AGG_COVAR_POP_RV = InvalidOid;
529 constants.OID_AGG_COVAR_SAMP_RV = InvalidOid;
530 constants.OID_AGG_CORR_RV = InvalidOid;
531 constants.OID_AGG_STDDEV_POP_RV = InvalidOid;
532 constants.OID_AGG_STDDEV_SAMP_RV = InvalidOid;
533 constants.OID_AGG_PERCENTILE_CONT_RV = InvalidOid;
534 constants.OID_AGG_RV_COVAR_POP_IMPL = InvalidOid;
535 constants.OID_AGG_RV_COVAR_SAMP_IMPL = InvalidOid;
536 constants.OID_AGG_RV_CORR_IMPL = InvalidOid;
537 constants.OID_AGG_RV_STDDEV_POP_IMPL = InvalidOid;
538 constants.OID_AGG_RV_STDDEV_SAMP_IMPL = InvalidOid;
539 constants.OID_AGG_RV_PERCENTILE_IMPL = InvalidOid;
540 if (OidIsValid(constants.OID_TYPE_RANDOM_VARIABLE)) {
541 Oid rvarg[1] = { constants.OID_TYPE_RANDOM_VARIABLE };
542 Oid rvarg2[2] = { constants.OID_TYPE_RANDOM_VARIABLE,
543 constants.OID_TYPE_RANDOM_VARIABLE };
544 Oid rvarg3[3] = { constants.OID_TYPE_RANDOM_VARIABLE,
545 constants.OID_TYPE_RANDOM_VARIABLE,
546 constants.OID_TYPE_RANDOM_VARIABLE };
547 Oid pctarg[2] = { FLOAT8OID, constants.OID_TYPE_RANDOM_VARIABLE };
548 Oid pctimplarg[3] = { FLOAT8OID, constants.OID_TYPE_RANDOM_VARIABLE,
549 constants.OID_TYPE_RANDOM_VARIABLE };
550 Oid semimod3[3] = { constants.OID_TYPE_UUID,
551 constants.OID_TYPE_RANDOM_VARIABLE, FLOAT8OID };
553 get_provsql_func_oid_args("rv_aggregate_semimod", 3, semimod3);
554 {
555 /* rv_aggregate_indicator is overloaded: resolve each signature
556 * explicitly (the by-name lookup returns an arbitrary overload). */
557 Oid indarg1[1] = { constants.OID_TYPE_UUID };
558 Oid indarg2[2] = { constants.OID_TYPE_UUID,
559 constants.OID_TYPE_RANDOM_VARIABLE };
561 get_provsql_func_oid_args("rv_aggregate_indicator", 1, indarg1);
563 get_provsql_func_oid_args("rv_aggregate_indicator", 2, indarg2);
564 }
565 constants.OID_FUNCTION_RV_DIV =
566 get_provsql_func_oid("random_variable_div");
567 constants.OID_AGG_SUM_RV = get_provsql_func_oid_args("sum", 1, rvarg);
568 constants.OID_AGG_PRODUCT_RV = get_provsql_func_oid_args("product", 1, rvarg);
569 constants.OID_AGG_AVG_RV = get_provsql_func_oid_args("avg", 1, rvarg);
570 constants.OID_AGG_MAX_RV = get_provsql_func_oid_args("max", 1, rvarg);
571 constants.OID_AGG_MIN_RV = get_provsql_func_oid_args("min", 1, rvarg);
572 constants.OID_AGG_RV_SUM_OR_NULL =
573 get_provsql_func_oid_args("rv_sum_or_null", 1, rvarg);
574 /* SQL-standard statistic aggregates over RV rows (public forms) and
575 * the indicator-carrying _impl rewrite targets. Optional lookups:
576 * absent on schemas predating them, which leaves the public forms
577 * unrecognised so they run their own certain-row fold. */
578 constants.OID_AGG_COVAR_POP_RV =
579 get_provsql_func_oid_args("covar_pop", 2, rvarg2);
580 constants.OID_AGG_COVAR_SAMP_RV =
581 get_provsql_func_oid_args("covar_samp", 2, rvarg2);
582 constants.OID_AGG_CORR_RV =
583 get_provsql_func_oid_args("corr", 2, rvarg2);
584 constants.OID_AGG_STDDEV_POP_RV =
585 get_provsql_func_oid_args("stddev_pop", 1, rvarg);
586 constants.OID_AGG_STDDEV_SAMP_RV =
587 get_provsql_func_oid_args("stddev_samp", 1, rvarg);
589 get_provsql_func_oid_args("percentile_cont", 2, pctarg);
590 constants.OID_AGG_RV_COVAR_POP_IMPL =
591 get_provsql_func_oid_args("rv_covar_pop_impl", 3, rvarg3);
593 get_provsql_func_oid_args("rv_covar_samp_impl", 3, rvarg3);
594 constants.OID_AGG_RV_CORR_IMPL =
595 get_provsql_func_oid_args("rv_corr_impl", 3, rvarg3);
597 get_provsql_func_oid_args("rv_stddev_pop_impl", 2, rvarg2);
599 get_provsql_func_oid_args("rv_stddev_samp_impl", 2, rvarg2);
601 get_provsql_func_oid_args("rv_percentile_impl", 3, pctimplarg);
602 }
603
604 /* choose(anyelement): keeps the first non-NULL value of a group. Used by
605 * the scalar-subquery decorrelation to pick the single matched value.
606 * Optional lookup (0 on schemas predating it). */
607 constants.OID_FUNCTION_CHOOSE = get_provsql_func_oid("choose");
608
609 /* assume_boolean is installed by the 1.6.0 upgrade script. Treat
610 * its absence as a soft signal: on older schemas the safe-query
611 * rewriter (gated behind the 'boolean' provenance class) refuses to
612 * fire because it cannot mark the per-row root with a
613 * gate_assumed for downstream semiring-compatibility
614 * enforcement. Optional lookup matches the pattern used above for
615 * rv_aggregate_semimod. */
617 get_provsql_func_oid("assume_boolean");
618
619 /* annotate(uuid,text) -- transparent annotation wrapper carrying the
620 * inversion-free certificate / order keys. Optional like the helpers
621 * above: InvalidOid on an older schema simply disables the carrier. */
622 constants.OID_FUNCTION_ANNOTATE =
623 get_provsql_func_oid("annotate");
624
625 /* inversion_free_key(text,text,int) -- builds the per-input order-key string
626 * for the inversion-free path. Optional: InvalidOid on an older schema
627 * disables per-input markers (the path then declines and falls back). */
629 get_provsql_func_oid("inversion_free_key");
630
631 /* cond(uuid,uuid) / given(uuid) -- the conditioning operator and its
632 * whole-tuple output marker. Optional: InvalidOid on a schema predating
633 * the conditioning feature disables the given() rewrite. */
634 /* Optional: absent from extension versions predating the structural
635 * supersede, whose constants must still initialise. */
637 get_provsql_func_oid("provenance_cmp_times");
638 constants.OID_FUNCTION_COND = get_provsql_func_oid("cond");
639 /* given is overloaded: given(uuid) is the evidence carrier / whole-tuple
640 * output marker; given(boolean) is the predicate placeholder the planner
641 * rewrites (both the function form and the prefix "| (predicate)"). */
642 {
643 Oid uuid_arg[1] = { constants.OID_TYPE_UUID };
644 constants.OID_FUNCTION_GIVEN = get_provsql_func_oid_args("given", 1, uuid_arg);
645 }
646
647 /* random_variable_cond(random_variable,uuid) and its Boolean-predicate
648 * placeholder random_variable_cond_predicate(random_variable,boolean): the
649 * planner rewrites "X | (predicate)" into the former over the gate built
650 * from the latter's Boolean operand. Optional (older schemas). */
651 constants.OID_FUNCTION_RV_COND =
652 get_provsql_func_oid("random_variable_cond");
653 constants.OID_FUNCTION_AGG_COND =
654 get_provsql_func_oid("agg_token_cond");
655 /* The "X | (predicate)" placeholders, one per carrier (and the prefix
656 * whole-tuple form). The planner rewrites each into the matching
657 * conditioning constructor over the gate built from the Boolean operand. */
659 get_provsql_func_oid("cond_predicate");
661 get_provsql_func_oid("random_variable_cond_predicate");
663 get_provsql_func_oid("agg_token_cond_predicate");
665 get_provsql_func_oid("predicate_cond_predicate");
666 {
667 Oid bool_arg[1] = { constants.OID_TYPE_BOOL };
669 get_provsql_func_oid_args("given", 1, bool_arg);
670 }
672 get_provsql_func_oid("regular_indicator");
673
674 /* probability(<predicate>) surface: the real probability_evaluate(uuid,
675 * text, text) is the rewrite target; probability(boolean, text, text) is
676 * the placeholder the planner lifts. Disambiguated by argument type since
677 * each name is overloaded. Optional (InvalidOid on schemas predating the
678 * boolean overload). */
679 {
680 Oid uuid_sig[3] = { constants.OID_TYPE_UUID, TEXTOID, TEXTOID };
681 Oid bool_sig[3] = { BOOLOID, TEXTOID, TEXTOID };
683 get_provsql_func_oid_args("probability_evaluate", 3, uuid_sig);
685 get_provsql_func_oid_args("probability", 3, bool_sig);
686 }
687
688 /* rv_case(uuid[]) -> random_variable: builds a gate_case from the planner's
689 * flattened guard/value wire list. Optional (InvalidOid disables the
690 * CASE-over-RV rewrite on schemas predating gate_case). */
691 constants.OID_FUNCTION_RV_CASE = get_provsql_func_oid("rv_case");
692
693 /* agg_case(uuid[]) -> agg_token: the aggregate-carrier analogue of rv_case.
694 * Optional (InvalidOid disables the CASE-over-aggregate rewrite). */
695 constants.OID_FUNCTION_AGG_CASE = get_provsql_func_oid("agg_case");
696
697 /* random_variable_{eq,ne,le,lt,ge,gt} -- order matches the
698 * ComparisonOperator enum in src/Aggregation.h (EQ=0, NE=1, LE=2,
699 * LT=3, GE=4, GT=5). */
700 constants.OID_FUNCTION_RV_CMP[0] = get_provsql_func_oid("random_variable_eq");
701 constants.OID_FUNCTION_RV_CMP[1] = get_provsql_func_oid("random_variable_ne");
702 constants.OID_FUNCTION_RV_CMP[2] = get_provsql_func_oid("random_variable_le");
703 constants.OID_FUNCTION_RV_CMP[3] = get_provsql_func_oid("random_variable_lt");
704 constants.OID_FUNCTION_RV_CMP[4] = get_provsql_func_oid("random_variable_ge");
705 constants.OID_FUNCTION_RV_CMP[5] = get_provsql_func_oid("random_variable_gt");
706
707 OperatorGet("<>", PG_CATALOG_NAMESPACE, constants.OID_TYPE_UUID, constants.OID_TYPE_UUID, &constants.OID_OPERATOR_NOT_EQUAL_UUID, &constants.OID_FUNCTION_NOT_EQUAL_UUID);
708 CheckOid(OID_OPERATOR_NOT_EQUAL_UUID);
709 CheckOid(OID_FUNCTION_NOT_EQUAL_UUID);
710
711 /** @brief Look up the OID of provenance_gate enum value @p x and store it in constants. */
712 #define GET_GATE_TYPE_OID(x) { \
713 constants.GATE_TYPE_TO_OID[gate_ ## x] = get_enum_oid( \
714 constants.OID_TYPE_GATE_TYPE, \
715 #x); \
716 if(constants.GATE_TYPE_TO_OID[gate_ ## x]==InvalidOid) \
717 provsql_error("Could not initialize provsql gate type " #x); }
718
719 /** @brief Like @c GET_GATE_TYPE_OID but tolerates a missing enum value.
720 *
721 * Used for gate types added in releases newer than the oldest schema
722 * the @c extension_upgrade test exercises (currently 1.0.0). An
723 * intermediate state where a 1.0.0 database has been bound to a newer
724 * shared library is possible (e.g. between @c CREATE @c EXTENSION
725 * @c VERSION and @c ALTER @c EXTENSION @c UPDATE) and must not abort
726 * @c get_constants -- the missing OID stays @c InvalidOid and any
727 * attempt to actually create such a gate fails later in
728 * @c create_gate's "Invalid gate type" branch. When the upgrade
729 * scripts catch up, the lookup succeeds and the gate becomes usable
730 * normally. No state for @c InvalidOid is stored, so the field
731 * keeps its zero-init value (which is @c InvalidOid).
732 */
733 #define GET_GATE_TYPE_OID_OPTIONAL(x) { \
734 constants.GATE_TYPE_TO_OID[gate_ ## x] = get_enum_oid( \
735 constants.OID_TYPE_GATE_TYPE, \
736 #x); }
737
738 GET_GATE_TYPE_OID(input);
739 GET_GATE_TYPE_OID(plus);
740 GET_GATE_TYPE_OID(times);
741 GET_GATE_TYPE_OID(monus);
742 GET_GATE_TYPE_OID(project);
743 GET_GATE_TYPE_OID(zero);
747 GET_GATE_TYPE_OID(semimod);
749 GET_GATE_TYPE_OID(delta);
750 GET_GATE_TYPE_OID(value);
751 GET_GATE_TYPE_OID(mulinput);
752 GET_GATE_TYPE_OID(update);
757 /* The 'assumed' label was 'assumed_boolean' before 1.10.0; ALTER TYPE
758 * ... RENAME VALUE keeps the enum value's OID, so accepting the
759 * former label is equivalent -- and necessary when this cache is
760 * built in a session that later runs the upgrade chain across the
761 * rename (the extension-upgrade test does exactly that). */
762 if(constants.GATE_TYPE_TO_OID[gate_assumed] == InvalidOid)
763 constants.GATE_TYPE_TO_OID[gate_assumed] =
764 get_enum_oid(constants.OID_TYPE_GATE_TYPE, "assumed_boolean");
765 GET_GATE_TYPE_OID_OPTIONAL(annotation);
766 GET_GATE_TYPE_OID_OPTIONAL(conditioned);
770
771 constants.ok=true;
772
773 return constants;
774}
775
776static database_constants_t *constants_cache; ///< Per-database OID constants cache (sorted by database OID)
777static unsigned constants_cache_len=0; ///< Number of valid entries in @c constants_cache
778
779constants_t get_constants(bool failure_if_not_possible)
780{
781 int start=0, end=constants_cache_len-1;
782 database_constants_t *constants_cache2;
783 constants_t constants;
784
785 while(end>=start) {
786 unsigned mid=(start+end)/2;
787 if(constants_cache[mid].database<MyDatabaseId)
788 start=mid+1;
789 else if(constants_cache[mid].database>MyDatabaseId)
790 end=mid-1;
791 else
792 return constants_cache[mid].constants;
793 }
794
795 constants=initialize_constants(failure_if_not_possible);
796
797 /* Only memoize successful lookups. A failed lookup (ok==false) means
798 * the provsql extension was not fully visible to this backend at this
799 * point -- e.g. the connection's first planned query ran before
800 * CREATE EXTENSION, or during a DROP/CREATE/ALTER EXTENSION ... UPDATE
801 * window. Caching that failure would permanently disable provenance
802 * tracking on the backend: a long-lived pooled connection would never
803 * recover, silently dropping the provsql column on every query until
804 * reset_constants_cache() is called by hand. Returning without
805 * caching lets the next call retry until the extension is available. */
806 if(!constants.ok)
807 return constants;
808
809 constants_cache2=calloc(constants_cache_len+1, sizeof(database_constants_t));
810 for(unsigned i=0; i<start; ++i)
811 constants_cache2[i]=constants_cache[i];
812
813 constants_cache2[start].database=MyDatabaseId;
814 constants_cache2[start].constants=constants;
815
816 for(unsigned i=start; i<constants_cache_len; ++i)
817 constants_cache2[i+1]=constants_cache[i];
818 free(constants_cache);
819 constants_cache=constants_cache2;
821
822 return constants_cache[start].constants;
823}
824
825/* -------------------------------------------------------------------------
826 * Per-backend table-info cache
827 *
828 * Sorted array of @c table_info_cache_entry, binary-searched on @c relid.
829 * Used by @c provsql_lookup_table_info to amortise IPC across repeated
830 * lookups during query planning. Entries are invalidated by
831 * @c invalidate_table_info_cache_callback, which is hooked into
832 * PostgreSQL's relcache invalidation channel and so reacts to local
833 * DDL, cross-backend invalidations broadcast via
834 * @c CacheInvalidateRelcacheByRelid, and explicit "invalidate all"
835 * (relid == InvalidOid) events.
836 * ------------------------------------------------------------------------- */
837
839 Oid relid; ///< pg_class OID (sort key)
840 bool valid; ///< false => refresh on next access
841 bool present; ///< when valid: was a record found at the worker?
842 uint8 kind; ///< when present: provsql_table_kind value
843 uint16 block_key_n; ///< when present: number of block-key columns
844 AttrNumber block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]; ///< when present: block-key column numbers
846
847static table_info_cache_entry *table_info_cache = NULL; ///< Sorted by @c relid
848static unsigned table_info_cache_len = 0;
850
851/** Find @p relid in the cache. Returns the index on hit; otherwise
852 * @c -1 and writes the insertion point to @p *insert_at. */
853static int table_info_cache_find(Oid relid, int *insert_at)
854{
855 int start = 0, end = (int)table_info_cache_len - 1;
856 while(end >= start) {
857 int mid = (start + end) / 2;
858 if(table_info_cache[mid].relid < relid)
859 start = mid + 1;
860 else if(table_info_cache[mid].relid > relid)
861 end = mid - 1;
862 else
863 return mid;
864 }
865 if(insert_at) *insert_at = start;
866 return -1;
867}
868
869/** Insert a fresh entry at @p pos (which must be the value returned by
870 * the most recent @c table_info_cache_find that reported a miss). */
871static void table_info_cache_insert(int pos, Oid relid, bool present,
872 const ProvenanceTableInfo *info)
873{
874 table_info_cache_entry *new_buf = calloc(table_info_cache_len + 1,
875 sizeof(table_info_cache_entry));
876 for(int i = 0; i < pos; ++i)
877 new_buf[i] = table_info_cache[i];
878 new_buf[pos].relid = relid;
879 new_buf[pos].valid = true;
880 new_buf[pos].present = present;
881 if(present) {
882 new_buf[pos].kind = info->kind;
883 new_buf[pos].block_key_n = info->block_key_n;
884 memcpy(new_buf[pos].block_key, info->block_key,
885 info->block_key_n * sizeof(AttrNumber));
886 }
887 for(unsigned i = (unsigned)pos; i < table_info_cache_len; ++i)
888 new_buf[i + 1] = table_info_cache[i];
889 free(table_info_cache);
890 table_info_cache = new_buf;
892}
893
894/** Relcache callback: PostgreSQL fires this whenever a relation's
895 * relcache entry is invalidated (locally or via shared invalidation
896 * from another backend). @p relid == @c InvalidOid means
897 * "invalidate everything." */
898static void invalidate_table_info_cache_callback(Datum arg, Oid relid)
899{
900 int pos;
901 (void) arg;
902 if(relid == InvalidOid) {
903 for(unsigned i = 0; i < table_info_cache_len; ++i)
904 table_info_cache[i].valid = false;
905 return;
906 }
907 pos = table_info_cache_find(relid, NULL);
908 if(pos >= 0)
909 table_info_cache[pos].valid = false;
910}
911
913{
914 int insert_at = 0;
915 int pos;
917 bool present;
918
920 CacheRegisterRelcacheCallback(invalidate_table_info_cache_callback,
921 (Datum) 0);
923 }
924
925 pos = table_info_cache_find(relid, &insert_at);
926
927 if(pos >= 0 && table_info_cache[pos].valid) {
929 if(!e->present)
930 return false;
931 out->relid = relid;
932 out->kind = e->kind;
933 out->block_key_n = e->block_key_n;
934 memcpy(out->block_key, e->block_key,
935 e->block_key_n * sizeof(AttrNumber));
936 return true;
937 }
938
939 present = provsql_fetch_table_info(relid, &info);
940
941 if(pos >= 0) {
942 /* Refresh an existing, now-stale slot in place. */
944 e->valid = true;
945 e->present = present;
946 if(present) {
947 e->kind = info.kind;
948 e->block_key_n = info.block_key_n;
949 memcpy(e->block_key, info.block_key,
950 info.block_key_n * sizeof(AttrNumber));
951 }
952 } else {
953 table_info_cache_insert(insert_at, relid, present, &info);
954 }
955
956 if(present) {
957 *out = info;
958 return true;
959 }
960 return false;
961}
962
963/* -------------------------------------------------------------------------
964 * Per-backend ancestry cache
965 *
966 * Structurally identical to the table-info cache above but keyed on
967 * just the ancestor half of the on-disk record. The two halves share
968 * the storage (one @c ProvenanceTableInfo per relation) but are
969 * cached separately so a hot-path safe-query lookup that only needs
970 * the kind doesn't pull the ancestor array, and vice versa. The
971 * relcache callback below reuses the channel of the kind cache so
972 * any @c set_ancestors / @c add_provenance / @c repair_key DDL
973 * broadcast invalidates both caches in lock-step.
974 * ------------------------------------------------------------------------- */
975
976typedef struct ancestry_cache_entry {
977 Oid relid; ///< pg_class OID (sort key)
978 bool valid; ///< false => refresh on next access
979 bool present; ///< when valid: did the worker return any ancestors?
980 uint16 ancestor_n; ///< when present: count
981 Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS]; ///< when present: ancestor OIDs
983
985static unsigned ancestry_cache_len = 0;
987
988static int ancestry_cache_find(Oid relid, int *insert_at)
989{
990 int start = 0, end = (int)ancestry_cache_len - 1;
991 while(end >= start) {
992 int mid = (start + end) / 2;
993 if(ancestry_cache[mid].relid < relid)
994 start = mid + 1;
995 else if(ancestry_cache[mid].relid > relid)
996 end = mid - 1;
997 else
998 return mid;
999 }
1000 if(insert_at) *insert_at = start;
1001 return -1;
1002}
1003
1004static void ancestry_cache_insert(int pos, Oid relid, bool present,
1005 uint16 ancestor_n, const Oid *ancestors)
1006{
1007 ancestry_cache_entry *new_buf = calloc(ancestry_cache_len + 1,
1008 sizeof(ancestry_cache_entry));
1009 for(int i = 0; i < pos; ++i)
1010 new_buf[i] = ancestry_cache[i];
1011 new_buf[pos].relid = relid;
1012 new_buf[pos].valid = true;
1013 new_buf[pos].present = present;
1014 if(present) {
1015 new_buf[pos].ancestor_n = ancestor_n;
1016 memcpy(new_buf[pos].ancestors, ancestors,
1017 ancestor_n * sizeof(Oid));
1018 }
1019 for(unsigned i = (unsigned)pos; i < ancestry_cache_len; ++i)
1020 new_buf[i + 1] = ancestry_cache[i];
1021 free(ancestry_cache);
1022 ancestry_cache = new_buf;
1024}
1025
1026static void invalidate_ancestry_cache_callback(Datum arg, Oid relid)
1027{
1028 int pos;
1029 (void) arg;
1030 if(relid == InvalidOid) {
1031 for(unsigned i = 0; i < ancestry_cache_len; ++i)
1032 ancestry_cache[i].valid = false;
1033 return;
1034 }
1035 pos = ancestry_cache_find(relid, NULL);
1036 if(pos >= 0)
1037 ancestry_cache[pos].valid = false;
1038}
1039
1040bool provsql_lookup_ancestry(Oid relid, uint16 *ancestor_n_out,
1041 Oid *ancestors_out)
1042{
1043 int insert_at = 0;
1044 int pos;
1045 uint16 n = 0;
1046 Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS];
1047 bool present;
1048
1050 CacheRegisterRelcacheCallback(invalidate_ancestry_cache_callback,
1051 (Datum) 0);
1053 }
1054
1055 pos = ancestry_cache_find(relid, &insert_at);
1056
1057 if(pos >= 0 && ancestry_cache[pos].valid) {
1059 if(!e->present)
1060 return false;
1061 *ancestor_n_out = e->ancestor_n;
1062 memcpy(ancestors_out, e->ancestors,
1063 e->ancestor_n * sizeof(Oid));
1064 return true;
1065 }
1066
1067 present = provsql_fetch_ancestry(relid, &n, ancestors);
1068
1069 if(pos >= 0) {
1071 e->valid = true;
1072 e->present = present;
1073 if(present) {
1074 e->ancestor_n = n;
1075 memcpy(e->ancestors, ancestors, n * sizeof(Oid));
1076 }
1077 } else {
1078 ancestry_cache_insert(insert_at, relid, present, n, ancestors);
1079 }
1080
1081 if(present) {
1082 *ancestor_n_out = n;
1083 memcpy(ancestors_out, ancestors, n * sizeof(Oid));
1084 return true;
1085 }
1086 return false;
1087}
1088
1089/* -------------------------------------------------------------------------
1090 * Per-backend relation-keys cache (§2 PK-FD support)
1091 *
1092 * Sibling of the @c table_info_cache above; structurally identical
1093 * (sorted-by-relid array, binary search, relcache-invalidated) but
1094 * keyed on @c pg_class OIDs of @em arbitrary relations -- not just
1095 * provenance-tracked ones -- because the §2 / §3 / §5 detector
1096 * passes also need to reason about PRIMARY KEY constraints on
1097 * deterministic dimension tables and self-joined relations.
1098 *
1099 * Each cached entry holds up to @c PROVSQL_KEY_CACHE_MAX_KEYS keys
1100 * (PRIMARY KEY plus NOT-NULL UNIQUE constraints from
1101 * @c pg_constraint, filtered by @c contype @c IN @c ('p','u')).
1102 * The relcache callback uses a separate static flag from the
1103 * table-info cache's, so a DROP CONSTRAINT / DROP TABLE invalidates
1104 * both caches independently.
1105 * ------------------------------------------------------------------------- */
1106
1114
1116static unsigned key_cache_len = 0;
1118
1119static int key_cache_find(Oid relid, int *insert_at)
1120{
1121 int start = 0, end = (int) key_cache_len - 1;
1122 while(end >= start) {
1123 int mid = (start + end) / 2;
1124 if(key_cache[mid].relid < relid)
1125 start = mid + 1;
1126 else if(key_cache[mid].relid > relid)
1127 end = mid - 1;
1128 else
1129 return mid;
1130 }
1131 if(insert_at) *insert_at = start;
1132 return -1;
1133}
1134
1135static void key_cache_insert(int pos, Oid relid,
1136 const ProvenanceRelationKeys *keys)
1137{
1138 key_cache_entry *new_buf = calloc(key_cache_len + 1,
1139 sizeof(key_cache_entry));
1140 for(int i = 0; i < pos; ++i)
1141 new_buf[i] = key_cache[i];
1142 new_buf[pos].relid = relid;
1143 new_buf[pos].valid = true;
1144 new_buf[pos].has_keys = keys->key_n > 0;
1145 new_buf[pos].key_n = keys->key_n;
1146 if(keys->key_n > 0)
1147 memcpy(new_buf[pos].keys, keys->keys,
1148 keys->key_n * sizeof(ProvenanceRelationKey));
1149 for(unsigned i = (unsigned)pos; i < key_cache_len; ++i)
1150 new_buf[i + 1] = key_cache[i];
1151 free(key_cache);
1152 key_cache = new_buf;
1153 ++key_cache_len;
1154}
1155
1156static void invalidate_key_cache_callback(Datum arg, Oid relid)
1157{
1158 int pos;
1159 (void) arg;
1160 if(relid == InvalidOid) {
1161 for(unsigned i = 0; i < key_cache_len; ++i)
1162 key_cache[i].valid = false;
1163 return;
1164 }
1165 pos = key_cache_find(relid, NULL);
1166 if(pos >= 0)
1167 key_cache[pos].valid = false;
1168}
1169
1170/**
1171 * @brief Read the PRIMARY-KEY and NOT-NULL-UNIQUE keys of @p relid
1172 * from the system catalogs.
1173 *
1174 * Scans @c pg_constraint for entries with @c conrelid @c = @p relid
1175 * and @c contype @c IN @c ('p','u'), then resolves each constraint's
1176 * column list via @c pg_index.indkey (the constraint's index is
1177 * recorded in @c pg_constraint.conindid). For UNIQUE constraints,
1178 * verifies every constituent column has @c pg_attribute.attnotnull
1179 * @c = @c true; UNIQUE-with-NULLABLE constraints are rejected
1180 * (UNIQUE allows multiple rows with NULL in PostgreSQL, so the
1181 * @c ∅ @c → @c attr FD does not hold without NOT NULL).
1182 *
1183 * Stores up to @c PROVSQL_KEY_CACHE_MAX_KEYS keys; subsequent keys
1184 * are silently dropped. Skips constraints whose column count
1185 * exceeds @c PROVSQL_KEY_CACHE_MAX_KEY_COLS. Both elisions are
1186 * conservatively safe (the §2 detector simply does not see the
1187 * dropped FDs).
1188 */
1190{
1191 Relation conrel;
1192 SysScanDesc scan;
1193 ScanKeyData skey;
1194 HeapTuple htup;
1195
1196 out->relid = relid;
1197 out->key_n = 0;
1198
1199 conrel = table_open(ConstraintRelationId, AccessShareLock);
1200 ScanKeyInit(&skey,
1201 Anum_pg_constraint_conrelid,
1202 BTEqualStrategyNumber, F_OIDEQ,
1203 ObjectIdGetDatum(relid));
1204 scan = systable_beginscan(conrel,
1205#if PG_VERSION_NUM >= 110000
1206 ConstraintRelidTypidNameIndexId,
1207#else
1208 ConstraintRelidIndexId, /* PG 10 name */
1209#endif
1210 true, NULL, 1, &skey);
1211
1212 while(HeapTupleIsValid(htup = systable_getnext(scan))) {
1213 Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(htup);
1214 HeapTuple idxtup;
1215 Form_pg_index idx;
1216 Oid indexrelid;
1217 int k;
1219 bool ok_not_null = true;
1220
1221 if(con->contype != CONSTRAINT_PRIMARY && con->contype != CONSTRAINT_UNIQUE)
1222 continue;
1224 break;
1225
1226 indexrelid = con->conindid;
1227 if(!OidIsValid(indexrelid))
1228 continue;
1229 idxtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
1230 if(!HeapTupleIsValid(idxtup))
1231 continue;
1232 idx = (Form_pg_index) GETSTRUCT(idxtup);
1233
1234 if(idx->indnatts <= 0 || idx->indnatts > PROVSQL_KEY_CACHE_MAX_KEY_COLS) {
1235 ReleaseSysCache(idxtup);
1236 continue;
1237 }
1238
1239 key = &out->keys[out->key_n];
1240 key->col_n = (uint16) idx->indnatts;
1241 for(k = 0; k < idx->indnatts; ++k) {
1242 AttrNumber attno = idx->indkey.values[k];
1243 key->cols[k] = attno;
1244
1245 if(con->contype == CONSTRAINT_UNIQUE) {
1246 HeapTuple atttup =
1247 SearchSysCache2(ATTNUM,
1248 ObjectIdGetDatum(relid),
1249 Int16GetDatum(attno));
1250 if(!HeapTupleIsValid(atttup)) {
1251 ok_not_null = false;
1252 break;
1253 } else {
1254 Form_pg_attribute attform = (Form_pg_attribute) GETSTRUCT(atttup);
1255 if(!attform->attnotnull)
1256 ok_not_null = false;
1257 ReleaseSysCache(atttup);
1258 if(!ok_not_null)
1259 break;
1260 }
1261 }
1262 }
1263 ReleaseSysCache(idxtup);
1264
1265 if(!ok_not_null)
1266 continue; /* nullable UNIQUE -- skip */
1267
1268 ++out->key_n;
1269 }
1270
1271 systable_endscan(scan);
1272 table_close(conrel, AccessShareLock);
1273
1274 return out->key_n > 0;
1275}
1276
1278{
1279 int insert_at = 0;
1280 int pos;
1282 bool has_keys;
1283
1285 CacheRegisterRelcacheCallback(invalidate_key_cache_callback, (Datum) 0);
1287 }
1288
1289 pos = key_cache_find(relid, &insert_at);
1290 if(pos >= 0 && key_cache[pos].valid) {
1291 key_cache_entry *e = &key_cache[pos];
1292 out->relid = relid;
1293 out->key_n = e->key_n;
1294 if(e->key_n > 0)
1295 memcpy(out->keys, e->keys, e->key_n * sizeof(ProvenanceRelationKey));
1296 return e->has_keys;
1297 }
1298
1299 has_keys = fetch_relation_keys(relid, &fresh);
1300
1301 if(pos >= 0) {
1302 key_cache_entry *e = &key_cache[pos];
1303 e->valid = true;
1304 e->has_keys = has_keys;
1305 e->key_n = fresh.key_n;
1306 if(fresh.key_n > 0)
1307 memcpy(e->keys, fresh.keys, fresh.key_n * sizeof(ProvenanceRelationKey));
1308 } else {
1309 key_cache_insert(insert_at, relid, &fresh);
1310 }
1311
1312 *out = fresh;
1313 return has_keys;
1314}
1315
1316PG_FUNCTION_INFO_V1(reset_constants_cache);
1317/**
1318 * @brief SQL function to invalidate the OID constants cache.
1319 *
1320 * Forces a fresh OID lookup for the current database on the next call to
1321 * @c get_constants(). Must be called after @c ALTER EXTENSION provsql
1322 * UPDATE to ensure cached OIDs are refreshed.
1323 * @return Void datum.
1324 */
1325Datum reset_constants_cache(PG_FUNCTION_ARGS)
1326{
1327 int start=0, end=constants_cache_len-1;
1328
1329 while(end>=start) {
1330 unsigned mid=(start+end)/2;
1331 if(constants_cache[mid].database<MyDatabaseId)
1332 start=mid+1;
1333 else if(constants_cache[mid].database>MyDatabaseId)
1334 end=mid-1;
1335 else {
1336 constants_cache[mid].constants = initialize_constants(true);
1337 break;
1338 }
1339 }
1340
1341 PG_RETURN_VOID();
1342}
#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().
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.
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...
#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_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_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_TYPE_RANDOM_VARIABLE_ARRAY
OID of the random_variable[] TYPE.
Oid OID_AGG_PRODUCT_RV
provsql.product(random_variable)
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_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_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_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_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_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_GATE_ONE
OID of the provenance_one FUNCTION.
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_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_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_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