Skip to content

Commit 0eaddbd

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 0eaddbd

12 files changed

Lines changed: 425 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: 143 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,145 @@ 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+
{
2949+
PG_RETURN_NULL();
2950+
}
2951+
2952+
PG_RETURN_INT32(chunk_id);
2953+
}
2954+
2955+
/*
2956+
* Look up the parent (uncompressed) chunk id from a compressed chunk id, using
2957+
* the active snapshot. See ts_chunk_id_by_name for snapshot semantics.
2958+
*/
2959+
Datum
2960+
ts_compressed_chunk_parent_id(PG_FUNCTION_ARGS)
2961+
{
2962+
int32 compressed_chunk_id = PG_GETARG_INT32(0);
2963+
ScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);
2964+
int32 chunk_id = INVALID_CHUNK_ID;
2965+
bool found = false;
2966+
2967+
iterator.ctx.snapshot = GetActiveSnapshot();
2968+
iterator.ctx.index =
2969+
catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_COMPRESSED_CHUNK_ID_INDEX);
2970+
2971+
ts_scan_iterator_scan_key_init(&iterator,
2972+
Anum_chunk_compressed_chunk_id_idx_compressed_chunk_id,
2973+
BTEqualStrategyNumber,
2974+
F_INT4EQ,
2975+
Int32GetDatum(compressed_chunk_id));
2976+
2977+
ts_scanner_foreach(&iterator)
2978+
{
2979+
bool isnull;
2980+
Datum value = slot_getattr(iterator.tinfo->slot, Anum_chunk_id, &isnull);
2981+
2982+
if (!isnull)
2983+
{
2984+
chunk_id = DatumGetInt32(value);
2985+
found = true;
2986+
}
2987+
break;
2988+
}
2989+
ts_scan_iterator_close(&iterator);
2990+
2991+
if (!found)
2992+
{
2993+
PG_RETURN_NULL();
2994+
}
2995+
2996+
PG_RETURN_INT32(chunk_id);
2997+
}
2998+
2999+
/*
3000+
* Look up the owning hypertable id of a chunk by chunk id, using the active
3001+
* snapshot. See ts_chunk_id_by_name for snapshot semantics.
3002+
*/
3003+
Datum
3004+
ts_chunk_hypertable_id(PG_FUNCTION_ARGS)
3005+
{
3006+
int32 chunk_id = PG_GETARG_INT32(0);
3007+
ScanIterator iterator = ts_scan_iterator_create(CHUNK, AccessShareLock, CurrentMemoryContext);
3008+
int32 hypertable_id = 0;
3009+
bool found = false;
3010+
3011+
iterator.ctx.snapshot = GetActiveSnapshot();
3012+
iterator.ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_ID_INDEX);
3013+
3014+
ts_scan_iterator_scan_key_init(&iterator,
3015+
Anum_chunk_idx_id,
3016+
BTEqualStrategyNumber,
3017+
F_INT4EQ,
3018+
Int32GetDatum(chunk_id));
3019+
3020+
ts_scanner_foreach(&iterator)
3021+
{
3022+
bool isnull;
3023+
Datum value = slot_getattr(iterator.tinfo->slot, Anum_chunk_hypertable_id, &isnull);
3024+
3025+
if (!isnull)
3026+
{
3027+
hypertable_id = DatumGetInt32(value);
3028+
found = true;
3029+
}
3030+
break;
3031+
}
3032+
ts_scan_iterator_close(&iterator);
3033+
3034+
if (!found)
3035+
{
3036+
PG_RETURN_NULL();
3037+
}
3038+
3039+
PG_RETURN_INT32(hypertable_id);
3040+
}
3041+
28993042
bool
29003043
ts_chunk_exists_relid(Oid relid)
29013044
{

src/hypertable.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,63 @@ 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+
{
427+
relid = get_relname_relid(NameStr(*DatumGetName(table_datum)), schema_oid);
428+
}
429+
}
430+
break;
431+
}
432+
ts_scan_iterator_close(&iterator);
433+
434+
if (!OidIsValid(relid))
435+
{
436+
PG_RETURN_NULL();
437+
}
438+
439+
PG_RETURN_OID(relid);
440+
}
441+
385442
int32
386443
ts_hypertable_relid_to_id(Oid relid)
387444
{

0 commit comments

Comments
 (0)