36#include "access/htup_details.h"
37#if PG_VERSION_NUM >= 120000
38#include "access/table.h"
40#include "access/heapam.h"
41#define table_open(r, l) heap_open((r), (l))
42#define table_close(r, l) heap_close((r), (l))
44#include "access/genam.h"
45#include "access/skey.h"
46#include "catalog/namespace.h"
47#include "catalog/pg_type.h"
48#include "commands/trigger.h"
49#include "executor/spi.h"
52#include "utils/array.h"
53#include "utils/builtins.h"
54#include "utils/fmgroids.h"
55#include "utils/inval.h"
56#include "utils/lsyscache.h"
58#include "utils/syscache.h"
64#define PROVSQL_TABLE_INFO_ATT_RELID 1
65#define PROVSQL_TABLE_INFO_ATT_KIND 2
66#define PROVSQL_TABLE_INFO_ATT_BLOCK_KEY 3
67#define PROVSQL_TABLE_INFO_ATT_ANCESTORS 4
80 Oid nsp = get_namespace_oid(
"provsql",
true);
85 rel = get_relname_relid(
"table_info", nsp);
89 *index_oid = get_relname_relid(
"table_info_pkey", nsp);
101 Oid rel_oid, index_oid = InvalidOid;
108 if(relid == InvalidOid)
112 if(!OidIsValid(rel_oid))
115 memset(out, 0,
sizeof(*out));
121 BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
122 scan = systable_beginscan(rel, index_oid, OidIsValid(index_oid),
125 if(HeapTupleIsValid(htup = systable_getnext(scan))) {
126 TupleDesc tupdesc = RelationGetDescr(rel);
134 char *kind = text_to_cstring(DatumGetTextPP(d));
135 if(strcmp(kind,
"tid") == 0)
137 else if(strcmp(kind,
"bid") == 0)
146 ArrayType *arr = DatumGetArrayTypeP(d);
147 if(ARR_NDIM(arr) == 1 && !array_contains_nulls(arr)) {
148 int n = ARR_DIMS(arr)[0];
152 memcpy(out->
block_key, ARR_DATA_PTR(arr), n *
sizeof(AttrNumber));
158 ArrayType *arr = DatumGetArrayTypeP(d);
159 if(ARR_NDIM(arr) == 1 && !array_contains_nulls(arr)) {
160 int n = ARR_DIMS(arr)[0];
164 memcpy(out->
ancestors, ARR_DATA_PTR(arr), n *
sizeof(Oid));
169 systable_endscan(scan);
198 Oid *argtypes, Datum *values)
202 if(SPI_connect() != SPI_OK_CONNECT)
203 provsql_error(
"Cannot connect to SPI while updating provsql.table_info");
204 rc = SPI_execute_with_args(sql, nargs, argtypes, values, NULL,
false, 0);
205 if(rc != SPI_OK_INSERT && rc != SPI_OK_UPDATE && rc != SPI_OK_DELETE)
206 provsql_error(
"Cannot update provsql.table_info (SPI code %d)", rc);
216 provsql_error(
"set_table_info: unknown table kind '%s' (expected "
217 "'tid', 'bid', or 'opaque')", label);
229 provsql_error(
"get_table_info: unknown table kind value %u", kind);
248 ArrayType *block_key;
249 uint16 block_key_n = 0;
250 Oid argtypes[3] = { OIDOID, TEXTOID, INT2ARRAYOID };
253 if(PG_ARGISNULL(0) || PG_ARGISNULL(1))
254 provsql_error(
"Invalid NULL value passed to set_table_info");
256 relid = PG_GETARG_OID(0);
257 kind_str = text_to_cstring(PG_GETARG_TEXT_PP(1));
260 block_key = PG_ARGISNULL(2) ? NULL : PG_GETARG_ARRAYTYPE_P(2);
262 if(ARR_NDIM(block_key) > 1)
263 provsql_error(
"Invalid multi-dimensional array passed to set_table_info");
264 else if(ARR_NDIM(block_key) == 1)
265 block_key_n = *ARR_DIMS(block_key);
268 provsql_error(
"set_table_info: block key wider than %d columns "
269 "(%u given) is not supported",
272 values[0] = ObjectIdGetDatum(relid);
273 values[1] = CStringGetTextDatum(kind_str);
274 values[2] = block_key ? PointerGetDatum(block_key)
275 : PointerGetDatum(construct_empty_array(INT2OID));
278 "INSERT INTO provsql.table_info(relid, kind, block_key) "
279 "VALUES ($1::regclass, $2, $3) "
280 "ON CONFLICT (relid) DO UPDATE "
281 "SET kind = EXCLUDED.kind, block_key = EXCLUDED.block_key",
282 3, argtypes, values);
292 Oid argtypes[1] = { OIDOID };
296 provsql_error(
"Invalid NULL value passed to remove_table_info");
298 values[0] = ObjectIdGetDatum(PG_GETARG_OID(0));
300 "DELETE FROM provsql.table_info WHERE relid = $1::regclass",
301 1, argtypes, values);
318 ArrayType *ancestors;
319 uint16 ancestor_n = 0;
320 Oid argtypes[2] = { OIDOID, OIDARRAYOID };
326 relid = PG_GETARG_OID(0);
327 ancestors = PG_ARGISNULL(1) ? NULL : PG_GETARG_ARRAYTYPE_P(1);
330 if(ARR_NDIM(ancestors) > 1)
331 provsql_error(
"Invalid multi-dimensional array passed to set_ancestors");
332 else if(ARR_NDIM(ancestors) == 1)
333 ancestor_n = *ARR_DIMS(ancestors);
336 provsql_error(
"set_ancestors: ancestor set wider than %d entries "
337 "(%u given) is not supported",
340 values[0] = ObjectIdGetDatum(relid);
341 values[1] = ancestors ? PointerGetDatum(ancestors)
342 : PointerGetDatum(construct_empty_array(OIDOID));
345 "UPDATE provsql.table_info SET ancestors = $2 WHERE relid = $1::regclass",
346 2, argtypes, values);
355 Oid argtypes[1] = { OIDOID };
359 provsql_error(
"Invalid NULL value passed to remove_ancestors");
361 values[0] = ObjectIdGetDatum(PG_GETARG_OID(0));
363 "UPDATE provsql.table_info SET ancestors = ARRAY[]::oid[] "
364 "WHERE relid = $1::regclass",
365 1, argtypes, values);
385 bool nulls[2] = {
false,
false};
392 relid = PG_GETARG_OID(0);
397 if(get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
398 provsql_error(
"get_table_info: expected composite return type");
399 tupdesc = BlessTupleDesc(tupdesc);
405 elems[i] = Int16GetDatum(info.
block_key[i]);
406 arr = construct_array(elems, info.
block_key_n, INT2OID, 2,
true,
's');
408 values[1] = PointerGetDatum(arr);
410 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
431 relid = PG_GETARG_OID(0);
436 elems = palloc(ancestor_n *
sizeof(Datum));
437 for(uint16 i = 0; i < ancestor_n; ++i)
438 elems[i] = ObjectIdGetDatum(ancestors[i]);
439 arr = construct_array(elems, ancestor_n, OIDOID,
440 sizeof(Oid),
true,
'i');
443 PG_RETURN_ARRAYTYPE_P(arr);
464 TriggerData *trigdata = (TriggerData *) fcinfo->context;
469 if(!CALLED_AS_TRIGGER(fcinfo))
470 provsql_error(
"provsql_table_info_invalidate: not called as a trigger");
472 tupdesc = trigdata->tg_relation->rd_att;
474 if(TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
475 tuples[n++] = trigdata->tg_trigtuple;
476 else if(TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
477 tuples[n++] = trigdata->tg_trigtuple;
480 tuples[n++] = trigdata->tg_trigtuple;
481 tuples[n++] = trigdata->tg_newtuple;
484 for(
int i = 0; i < n; ++i) {
489 if(!HeapTupleIsValid(tuples[i]))
494 relid = DatumGetObjectId(d);
495 if(OidIsValid(relid) && SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
496 CacheInvalidateRelcacheByRelid(relid);
499 return PointerGetDatum(NULL);
Per-relation provenance metadata: the in-memory record.
#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.
Datum set_ancestors(PG_FUNCTION_ARGS)
Replace the ancestor half of a relation's row, keeping its kind / block_key.
Datum set_table_info(PG_FUNCTION_ARGS)
Forward declaration of the C SQL entry points.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
bool provsql_lookup_ancestry(Oid relid, uint16 *ancestor_n_out, Oid *ancestors_out)
Look up the base-ancestor set of a tracked relation.
bool provsql_lookup_table_info(Oid relid, ProvenanceTableInfo *out)
Look up per-table provenance metadata with a backend-local cache.
Core types, constants, and utilities shared across ProvSQL.
Per-relation metadata for the safe-query optimisation.
Oid relid
pg_class OID of the relation (primary key)
AttrNumber block_key[PROVSQL_TABLE_INFO_MAX_BLOCK_KEY]
Block-key column numbers.
uint16_t block_key_n
Number of valid entries in block_key.
Oid ancestors[PROVSQL_TABLE_INFO_MAX_ANCESTORS]
Sorted, deduplicated base-relation OIDs.
uint8_t kind
One of provsql_table_kind.
uint16_t ancestor_n
Number of valid entries in ancestors (0 = no registry info).
Datum set_ancestors(PG_FUNCTION_ARGS)
Replace the ancestor half of a relation's row, keeping its kind / block_key.
#define PROVSQL_TABLE_INFO_ATT_KIND
static Oid provsql_table_info_relation(Oid *index_oid)
Resolve provsql.table_info and its primary-key index.
#define PROVSQL_TABLE_INFO_ATT_RELID
Column numbers of provsql.table_info.
static const char * table_kind_label(uint8_t kind)
Inverse of parse_table_kind for use by get_table_info.
Datum set_table_info(PG_FUNCTION_ARGS)
Upsert the kind half of a relation's provsql.table_info row.
#define table_close(r, l)
Datum get_table_info(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper around the cached kind lookup.
Datum remove_ancestors(PG_FUNCTION_ARGS)
Clear a relation's ancestor set, keeping kind / block_key.
Datum provsql_table_info_invalidate(PG_FUNCTION_ARGS)
Row trigger on provsql.table_info: broadcast a relcache invalidation for the relation whose metadata ...
Datum get_ancestors(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper around the cached ancestry lookup.
static bool provsql_read_table_info(Oid relid, ProvenanceTableInfo *out)
Read the row of relid into out.
static uint8_t parse_table_kind(const char *label)
Translate a SQL-side kind label into the persisted enum value.
Datum remove_table_info(PG_FUNCTION_ARGS)
Delete a relation's provsql.table_info row.
bool provsql_fetch_table_info(Oid relid, ProvenanceTableInfo *out)
Raw IPC fetch (no cache).
bool provsql_fetch_ancestry(Oid relid, uint16 *ancestor_n_out, Oid *ancestors_out)
Raw IPC fetch for the ancestry half (no cache).
static void provsql_table_info_exec(const char *sql, int nargs, Oid *argtypes, Datum *values)
Run sql with nargs bound parameters through SPI.
#define PROVSQL_TABLE_INFO_ATT_ANCESTORS
#define PROVSQL_TABLE_INFO_ATT_BLOCK_KEY