Skip to content

Commit

Permalink
Block TAM rewrites with incompatible GUC setting
Browse files Browse the repository at this point in the history
The GUC timescaledb.enable_transparent_decompression can be set to
'hypercore' when using Hypercore TAM in order to get a DecompressChunk
plan. This will make a scan read only non-compressed data from the
TAM, and is used for debugging. Howver, the setting can be dangerous
if a chunk is rewritten by decompression or vaccum full, leading to
data loss. Therefore, block rewrite operations on Hypercore TAM when
the GUC is set to 'hypercore'. Also remove the possibility to use this
GUC value entirely in release builds.

(cherry picked from commit c957433)
  • Loading branch information
erimatnor authored and timescale-automation committed Feb 21, 2025
1 parent 3092001 commit cc45afc
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 3 deletions.
1 change: 1 addition & 0 deletions .unreleased/pr_7746
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixes: #7747 Block TAM rewrites with incompatible GUC setting
3 changes: 3 additions & 0 deletions src/guc.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,16 @@ static const struct config_enum_entry loglevel_options[] = {
*
* (2) = hypercore, enabled for compressed tables and those using Hypercore
* TAM. This is useful mostly for debugging/testing and as a fallback.
* Only available in debug builds.
*/
static const struct config_enum_entry transparent_decompression_options[] = {
{ "on", 1, false },
{ "true", 1, false },
{ "off", 0, false },
{ "false", 0, false },
#ifdef TS_DEBUG
{ TS_HYPERCORE_TAM_NAME, 2, false },
#endif
{ NULL, 0, false }
};

Expand Down
14 changes: 14 additions & 0 deletions tsl/src/hypercore/hypercore_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ hypercore_set_truncate_compressed(bool onoff)
#define HYPERCORE_AM_INFO_SIZE(natts) \
(sizeof(HypercoreInfo) + (sizeof(ColumnCompressionSettings) * (natts)))

static void
check_guc_setting_compatible_with_scan()
{
if (ts_guc_enable_transparent_decompression == 2)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("operation not compatible with current setting of %s",
MAKE_EXTOPTION("enable_transparent_decompression")),
errhint("Set the GUC to true or false.")));
}

static int32
get_chunk_id_from_relid(Oid relid)
{
Expand Down Expand Up @@ -2303,6 +2314,8 @@ hypercore_relation_copy_for_cluster(Relation OldHypercore, Relation NewCompressi
if (ts_is_hypertable(RelationGetRelid(OldHypercore)))
return;

check_guc_setting_compatible_with_scan();

/* Error out if this is a CLUSTER. It would be possible to CLUSTER only
* the non-compressed relation, but utility of this is questionable as
* most of the data should be compressed (and ordered) anyway. */
Expand Down Expand Up @@ -3866,6 +3879,7 @@ convert_to_hypercore_finish(Oid relid)
static void
convert_from_hypercore(Oid relid)
{
check_guc_setting_compatible_with_scan();
int32 chunk_id = get_chunk_id_from_relid(relid);
ts_compression_chunk_size_delete(chunk_id);

Expand Down
23 changes: 23 additions & 0 deletions tsl/test/expected/hypercore_create.out
Original file line number Diff line number Diff line change
Expand Up @@ -1063,3 +1063,26 @@ select * from amrels where relparent = 'test5'::regclass;
_timescaledb_internal._hyper_10_47_chunk | heap | test5
(2 rows)

-- Check that operations that rewrite the relation are blocked with
-- invalid setting of transparent decompression GUC
\set ON_ERROR_STOP 0
select count(*) from :chunk;
count
-------
1
(1 row)

set timescaledb.enable_transparent_decompression='hypercore';
select decompress_chunk(:'chunk');
ERROR: operation not compatible with current setting of timescaledb.enable_transparent_decompression
alter table :chunk set access method heap;
ERROR: operation not compatible with current setting of timescaledb.enable_transparent_decompression
vacuum full :chunk;
ERROR: operation not compatible with current setting of timescaledb.enable_transparent_decompression
select count(*) from :chunk;
count
-------
1
(1 row)

\set ON_ERROR_STOP 1
5 changes: 2 additions & 3 deletions tsl/test/sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ endif(CMAKE_BUILD_TYPE MATCHES Debug)

if((${PG_VERSION_MAJOR} GREATER_EQUAL "15"))
if(CMAKE_BUILD_TYPE MATCHES Debug)
list(APPEND TEST_FILES bgw_scheduler_control.sql hypercore.sql)
list(APPEND TEST_FILES bgw_scheduler_control.sql hypercore.sql
hypercore_create.sql hypercore_scans.sql)
endif()
list(
APPEND
Expand All @@ -153,7 +154,6 @@ if((${PG_VERSION_MAJOR} GREATER_EQUAL "15"))
hypercore_columnar.sql
hypercore_constraints.sql
hypercore_copy.sql
hypercore_create.sql
hypercore_cursor.sql
hypercore_ddl.sql
hypercore_delete.sql
Expand All @@ -164,7 +164,6 @@ if((${PG_VERSION_MAJOR} GREATER_EQUAL "15"))
hypercore_join.sql
hypercore_merge.sql
hypercore_policy.sql
hypercore_scans.sql
hypercore_stats.sql
hypercore_trigger.sql
hypercore_types.sql
Expand Down
11 changes: 11 additions & 0 deletions tsl/test/sql/hypercore_create.sql
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,14 @@ select ch as chunk from show_chunks('test5') ch limit 1 \gset
alter table test5 set (timescaledb.compress);
select compress_chunk(:'chunk');
select * from amrels where relparent = 'test5'::regclass;

-- Check that operations that rewrite the relation are blocked with
-- invalid setting of transparent decompression GUC
\set ON_ERROR_STOP 0
select count(*) from :chunk;
set timescaledb.enable_transparent_decompression='hypercore';
select decompress_chunk(:'chunk');
alter table :chunk set access method heap;
vacuum full :chunk;
select count(*) from :chunk;
\set ON_ERROR_STOP 1

0 comments on commit cc45afc

Please sign in to comment.