Skip to content

Commit 774f9a4

Browse files
committed
Add snapshot-aware catalog lookup SQL functions
Introduce four _timescaledb_functions.* lookups that resolve TimescaleDB catalog entries against the active snapshot: chunk_id_by_name(schema name, table name) -> int4 compressed_chunk_parent_id(int4) -> int4 chunk_hypertable_id(int4) -> int4 hypertable_id_to_relid(int4) -> regclass Each function reads GetActiveSnapshot() and passes it explicitly into the catalog scan rather than relying on the scanner's default of SnapshotSelf. Callers that need a non-default snapshot -- e.g. a custom logical decoding plugin running against a HistoricSnapshot -- push it before invoking: PushActiveSnapshot(snapshot); /* DirectFunctionCall ... */ PopActiveSnapshot(); From an ordinary SQL session the executor's transaction snapshot is in effect, so the functions also work for ad-hoc use. All four return NULL on miss rather than erroring, so callers can skip rows that don't (yet) resolve.
1 parent 0bbecd2 commit 774f9a4

12 files changed

Lines changed: 415 additions & 53 deletions

File tree

.unreleased/pr_9693

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #9693 Add snapshot-aware catalog lookup SQL functions

sql/chunk.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ AS $$ SELECT _timescaledb_functions.chunk_status_text(_timescaledb_functions.chu
3636
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_id_from_relid(relid OID) RETURNS INTEGER
3737
AS '@MODULE_PATHNAME@', 'ts_chunk_id_from_relid' LANGUAGE C STABLE STRICT PARALLEL SAFE;
3838

39+
-- Look up a chunk id by (schema_name, table_name) using the active snapshot.
40+
-- Intended for logical decoding plugins, which install a HistoricSnapshot via
41+
-- PushActiveSnapshot() before invoking. Returns NULL when no chunk matches.
42+
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_id_by_name(schema_name NAME, table_name NAME) RETURNS INTEGER
43+
AS '@MODULE_PATHNAME@', 'ts_chunk_id_by_name' LANGUAGE C STABLE STRICT PARALLEL SAFE;
44+
45+
-- Look up the parent (uncompressed) chunk id from a compressed chunk id, using
46+
-- the active snapshot. See chunk_id_by_name for snapshot semantics.
47+
CREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_chunk_parent_id(compressed_chunk_id INTEGER) RETURNS INTEGER
48+
AS '@MODULE_PATHNAME@', 'ts_compressed_chunk_parent_id' LANGUAGE C STABLE STRICT PARALLEL SAFE;
49+
50+
-- Look up the owning hypertable id of a chunk by chunk id, using the active
51+
-- snapshot. See chunk_id_by_name for snapshot semantics.
52+
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_hypertable_id(chunk_id INTEGER) RETURNS INTEGER
53+
AS '@MODULE_PATHNAME@', 'ts_chunk_hypertable_id' LANGUAGE C STABLE STRICT PARALLEL SAFE;
54+
3955
-- Show the definition of a chunk.
4056
CREATE OR REPLACE FUNCTION _timescaledb_functions.show_chunk(chunk REGCLASS)
4157
RETURNS TABLE(chunk_id INTEGER, hypertable_id INTEGER, schema_name NAME, table_name NAME, relkind "char", slices JSONB)

sql/hypertable.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ CREATE OR REPLACE FUNCTION @extschema@.set_integer_now_func(hypertable REGCLASS,
66
AS '@MODULE_PATHNAME@', 'ts_hypertable_set_integer_now_func'
77
LANGUAGE C VOLATILE STRICT;
88

9+
-- Look up a hypertable's relation Oid by its internal id, using the active
10+
-- snapshot. Intended for logical decoding plugins, which install a
11+
-- HistoricSnapshot via PushActiveSnapshot() before invoking. Returns NULL when
12+
-- no hypertable matches or its underlying relation cannot be resolved.
13+
CREATE OR REPLACE FUNCTION _timescaledb_functions.hypertable_relid_by_id(hypertable_id INTEGER) RETURNS REGCLASS
14+
AS '@MODULE_PATHNAME@', 'ts_hypertable_relid_by_id' LANGUAGE C STABLE STRICT PARALLEL SAFE;
15+

sql/updates/latest-dev.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,18 @@ SET index = COALESCE(index, '[]'::jsonb) ||
108108
)
109109
WHERE cs.orderby IS NOT NULL;
110110

111+
-- Snapshot-aware catalog lookup functions. Read GetActiveSnapshot() so callers
112+
-- (e.g. logical decoding plugins) that install a non-default snapshot via
113+
-- PushActiveSnapshot() can use them.
114+
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_id_by_name(schema_name NAME, table_name NAME) RETURNS INTEGER
115+
AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C STABLE STRICT PARALLEL SAFE;
116+
117+
CREATE OR REPLACE FUNCTION _timescaledb_functions.compressed_chunk_parent_id(compressed_chunk_id INTEGER) RETURNS INTEGER
118+
AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C STABLE STRICT PARALLEL SAFE;
119+
120+
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_hypertable_id(chunk_id INTEGER) RETURNS INTEGER
121+
AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C STABLE STRICT PARALLEL SAFE;
122+
123+
CREATE OR REPLACE FUNCTION _timescaledb_functions.hypertable_relid_by_id(hypertable_id INTEGER) RETURNS REGCLASS
124+
AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C STABLE STRICT PARALLEL SAFE;
125+

sql/updates/reverse-dev.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ ALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_compressed_chunk_id_
55

66
DROP FUNCTION IF EXISTS _timescaledb_functions.bloom1_contains_any_hashes(_timescaledb_internal.bloom1, bigint[]);
77
DROP FUNCTION IF EXISTS _timescaledb_functions.bloom1_hash(anyelement);
8+
9+
DROP FUNCTION IF EXISTS _timescaledb_functions.chunk_id_by_name(NAME, NAME);
10+
DROP FUNCTION IF EXISTS _timescaledb_functions.compressed_chunk_parent_id(INTEGER);
11+
DROP FUNCTION IF EXISTS _timescaledb_functions.chunk_hypertable_id(INTEGER);
12+
DROP FUNCTION IF EXISTS _timescaledb_functions.hypertable_relid_by_id(INTEGER);

src/chunk.c

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include <utils/inval.h>
5050
#include <utils/lsyscache.h>
5151
#include <utils/palloc.h>
52+
#include <utils/snapmgr.h>
5253
#include <utils/syscache.h>
5354
#include <utils/timestamp.h>
5455
#include <utils/typcache.h>
@@ -94,6 +95,9 @@ TS_FUNCTION_INFO_V1(ts_chunk_drop_single_chunk);
9495
TS_FUNCTION_INFO_V1(ts_chunk_attach_osm_table_chunk);
9596
TS_FUNCTION_INFO_V1(ts_chunk_drop_osm_chunk);
9697
TS_FUNCTION_INFO_V1(ts_chunk_id_from_relid);
98+
TS_FUNCTION_INFO_V1(ts_chunk_id_by_name);
99+
TS_FUNCTION_INFO_V1(ts_compressed_chunk_parent_id);
100+
TS_FUNCTION_INFO_V1(ts_chunk_hypertable_id);
97101
TS_FUNCTION_INFO_V1(ts_chunk_show);
98102
TS_FUNCTION_INFO_V1(ts_chunk_create);
99103
TS_FUNCTION_INFO_V1(ts_chunk_status);
@@ -2896,6 +2900,139 @@ ts_chunk_id_from_relid(PG_FUNCTION_ARGS)
28962900
PG_RETURN_INT32(last_id);
28972901
}
28982902

2903+
/*
2904+
* Look up a chunk id by (schema, table) using the active snapshot.
2905+
*
2906+
* Reads the snapshot via GetActiveSnapshot() so a logical decoding plugin can
2907+
* install a HistoricSnapshot via PushActiveSnapshot() before invoking. Returns
2908+
* NULL when no matching chunk exists.
2909+
*/
2910+
Datum
2911+
ts_chunk_id_by_name(PG_FUNCTION_ARGS)
2912+
{
2913+
Name schema = PG_GETARG_NAME(0);
2914+
Name table = PG_GETARG_NAME(1);
2915+
ScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);
2916+
int32 chunk_id = INVALID_CHUNK_ID;
2917+
bool found = false;
2918+
2919+
iterator.ctx.snapshot = GetActiveSnapshot();
2920+
iterator.ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_SCHEMA_NAME_INDEX);
2921+
2922+
ts_scan_iterator_scan_key_init(&iterator,
2923+
Anum_chunk_schema_name_idx_schema_name,
2924+
BTEqualStrategyNumber,
2925+
F_NAMEEQ,
2926+
NameGetDatum(schema));
2927+
ts_scan_iterator_scan_key_init(&iterator,
2928+
Anum_chunk_schema_name_idx_table_name,
2929+
BTEqualStrategyNumber,
2930+
F_NAMEEQ,
2931+
NameGetDatum(table));
2932+
2933+
ts_scanner_foreach(&iterator)
2934+
{
2935+
bool isnull;
2936+
Datum value = slot_getattr(iterator.tinfo->slot, Anum_chunk_id, &isnull);
2937+
2938+
if (!isnull)
2939+
{
2940+
chunk_id = DatumGetInt32(value);
2941+
found = true;
2942+
}
2943+
break;
2944+
}
2945+
ts_scan_iterator_close(&iterator);
2946+
2947+
if (!found)
2948+
PG_RETURN_NULL();
2949+
2950+
PG_RETURN_INT32(chunk_id);
2951+
}
2952+
2953+
/*
2954+
* Look up the parent (uncompressed) chunk id from a compressed chunk id, using
2955+
* the active snapshot. See ts_chunk_id_by_name for snapshot semantics.
2956+
*/
2957+
Datum
2958+
ts_compressed_chunk_parent_id(PG_FUNCTION_ARGS)
2959+
{
2960+
int32 compressed_chunk_id = PG_GETARG_INT32(0);
2961+
ScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);
2962+
int32 chunk_id = INVALID_CHUNK_ID;
2963+
bool found = false;
2964+
2965+
iterator.ctx.snapshot = GetActiveSnapshot();
2966+
iterator.ctx.index =
2967+
catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_COMPRESSED_CHUNK_ID_INDEX);
2968+
2969+
ts_scan_iterator_scan_key_init(&iterator,
2970+
Anum_chunk_compressed_chunk_id_idx_compressed_chunk_id,
2971+
BTEqualStrategyNumber,
2972+
F_INT4EQ,
2973+
Int32GetDatum(compressed_chunk_id));
2974+
2975+
ts_scanner_foreach(&iterator)
2976+
{
2977+
bool isnull;
2978+
Datum value = slot_getattr(iterator.tinfo->slot, Anum_chunk_id, &isnull);
2979+
2980+
if (!isnull)
2981+
{
2982+
chunk_id = DatumGetInt32(value);
2983+
found = true;
2984+
}
2985+
break;
2986+
}
2987+
ts_scan_iterator_close(&iterator);
2988+
2989+
if (!found)
2990+
PG_RETURN_NULL();
2991+
2992+
PG_RETURN_INT32(chunk_id);
2993+
}
2994+
2995+
/*
2996+
* Look up the owning hypertable id of a chunk by chunk id, using the active
2997+
* snapshot. See ts_chunk_id_by_name for snapshot semantics.
2998+
*/
2999+
Datum
3000+
ts_chunk_hypertable_id(PG_FUNCTION_ARGS)
3001+
{
3002+
int32 chunk_id = PG_GETARG_INT32(0);
3003+
ScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);
3004+
int32 hypertable_id = 0;
3005+
bool found = false;
3006+
3007+
iterator.ctx.snapshot = GetActiveSnapshot();
3008+
iterator.ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_ID_INDEX);
3009+
3010+
ts_scan_iterator_scan_key_init(&iterator,
3011+
Anum_chunk_idx_id,
3012+
BTEqualStrategyNumber,
3013+
F_INT4EQ,
3014+
Int32GetDatum(chunk_id));
3015+
3016+
ts_scanner_foreach(&iterator)
3017+
{
3018+
bool isnull;
3019+
Datum value = slot_getattr(iterator.tinfo->slot, Anum_chunk_hypertable_id, &isnull);
3020+
3021+
if (!isnull)
3022+
{
3023+
hypertable_id = DatumGetInt32(value);
3024+
found = true;
3025+
}
3026+
break;
3027+
}
3028+
ts_scan_iterator_close(&iterator);
3029+
3030+
if (!found)
3031+
PG_RETURN_NULL();
3032+
3033+
PG_RETURN_INT32(hypertable_id);
3034+
}
3035+
28993036
bool
29003037
ts_chunk_exists_relid(Oid relid)
29013038
{

src/hypertable.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,59 @@ ts_hypertable_id_to_relid(int32 hypertable_id, bool return_invalid)
382382
return relid;
383383
}
384384

385+
/*
386+
* Look up a hypertable's relation Oid by its internal id, using the active
387+
* snapshot.
388+
*
389+
* Reads the snapshot via GetActiveSnapshot() so a logical decoding plugin can
390+
* install a HistoricSnapshot via PushActiveSnapshot() before invoking. Returns
391+
* NULL when the hypertable does not exist or its underlying schema/relation
392+
* cannot be resolved against the same snapshot.
393+
*/
394+
TS_FUNCTION_INFO_V1(ts_hypertable_relid_by_id);
395+
Datum
396+
ts_hypertable_relid_by_id(PG_FUNCTION_ARGS)
397+
{
398+
int32 hypertable_id = PG_GETARG_INT32(0);
399+
ScanIterator iterator =
400+
ts_scan_iterator_create(HYPERTABLE, AccessShareLock, CurrentMemoryContext);
401+
Oid relid = InvalidOid;
402+
403+
iterator.ctx.snapshot = GetActiveSnapshot();
404+
iterator.ctx.index = catalog_get_index(ts_catalog_get(), HYPERTABLE, HYPERTABLE_ID_INDEX);
405+
406+
ts_scan_iterator_scan_key_init(&iterator,
407+
Anum_hypertable_pkey_idx_id,
408+
BTEqualStrategyNumber,
409+
F_INT4EQ,
410+
Int32GetDatum(hypertable_id));
411+
412+
ts_scanner_foreach(&iterator)
413+
{
414+
bool schema_isnull;
415+
bool table_isnull;
416+
Datum schema_datum =
417+
slot_getattr(iterator.tinfo->slot, Anum_hypertable_schema_name, &schema_isnull);
418+
Datum table_datum =
419+
slot_getattr(iterator.tinfo->slot, Anum_hypertable_table_name, &table_isnull);
420+
421+
if (!schema_isnull && !table_isnull)
422+
{
423+
Oid schema_oid = get_namespace_oid(NameStr(*DatumGetName(schema_datum)), true);
424+
425+
if (OidIsValid(schema_oid))
426+
relid = get_relname_relid(NameStr(*DatumGetName(table_datum)), schema_oid);
427+
}
428+
break;
429+
}
430+
ts_scan_iterator_close(&iterator);
431+
432+
if (!OidIsValid(relid))
433+
PG_RETURN_NULL();
434+
435+
PG_RETURN_OID(relid);
436+
}
437+
385438
int32
386439
ts_hypertable_relid_to_id(Oid relid)
387440
{

0 commit comments

Comments
 (0)