From 51085e0d9749df99ba3f7e65a0a302f649f1fae9 Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Tue, 24 Sep 2024 15:26:35 +0200 Subject: [PATCH] Add ALTER TABLE SET ACCESS METHOD support This commit allows `ALTER TABLE SET ACCESS METHOD` on a hypertable, which will set the access method of the hypertable to the given access method and also set the compression flag if changing to `hyperstore` access method. In order to set compression when setting the access method, it is necessary to allow several ALTER TABLE commands, which was previously not allowed. To support this, `ProcessUtility` will process each `ALTER TABLE` command in turn and remove it from the list if it is dealt with. If there are any remaining commands in the list after that, it will assume that these are not timescaledb-specific and continue running the normal command and also recurse the ALTER TABLE SET ACCESS METHOD command to the chunks. --- .unreleased/pr_7295 | 1 + src/process_utility.c | 62 +++++++++++++---- test/expected/reloptions.out | 5 +- test/expected/tableam_alter.out | 77 +++++++++++++++++++++ test/sql/CMakeLists.txt | 2 +- test/sql/reloptions.sql | 5 +- test/sql/tableam_alter.sql | 47 +++++++++++++ tsl/src/compression/create.h | 2 + tsl/test/expected/compression_errors-14.out | 3 - tsl/test/expected/compression_errors-15.out | 3 - tsl/test/expected/compression_errors-16.out | 3 - tsl/test/expected/compression_errors-17.out | 3 - tsl/test/sql/compression_errors.sql.in | 3 - 13 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 .unreleased/pr_7295 create mode 100644 test/expected/tableam_alter.out create mode 100644 test/sql/tableam_alter.sql diff --git a/.unreleased/pr_7295 b/.unreleased/pr_7295 new file mode 100644 index 00000000000..a2dc9541625 --- /dev/null +++ b/.unreleased/pr_7295 @@ -0,0 +1 @@ +Implements: #7295 Support ALTER TABLE SET ACCESS METHOD on hypertable diff --git a/src/process_utility.c b/src/process_utility.c index 7b8b4b23836..296f0d48a01 100644 --- a/src/process_utility.c +++ b/src/process_utility.c @@ -3374,6 +3374,41 @@ process_altertable_chunk_set_tablespace(AlterTableCmd *cmd, Oid relid) } } +/* + * Set the access method for a table. + * + * If called on a hypertable, this will set the compression flag on the + * hypertable in addition to running the set access method code. + */ +#if PG15_GE +static void +process_set_access_method(AlterTableCmd *cmd, ProcessUtilityArgs *args) +{ + AlterTableStmt *stmt = castNode(AlterTableStmt, args->parsetree); + Oid relid = AlterTableLookupRelation(stmt, NoLock); + Cache *hcache; + Hypertable *ht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache); + if (ht && (strcmp(cmd->name, "hyperstore") == 0)) + { + /* For hypertables, we automatically add command to set the + * compression flag if we are setting the access method to be a + * hyperstore. */ + DefElem *elem = makeDefElemExtended(EXTENSION_NAMESPACE, + "compress", + (Node *) makeInteger(1), + DEFELEM_UNSPEC, + -1); + + AlterTableCmd *cmd = makeNode(AlterTableCmd); + cmd->type = T_AlterTableCmd; + cmd->subtype = AT_SetRelOptions; + cmd->def = (Node *) list_make1(elem); + stmt->cmds = lappend(stmt->cmds, cmd); + } + ts_cache_release(hcache); +} +#endif + static DDLResult process_altertable_start_table(ProcessUtilityArgs *args) { @@ -3382,8 +3417,6 @@ process_altertable_start_table(ProcessUtilityArgs *args) Cache *hcache; Hypertable *ht; ListCell *lc; - DDLResult result = DDL_CONTINUE; - int num_cmds; if (!OidIsValid(relid)) return DDL_CONTINUE; @@ -3399,7 +3432,6 @@ process_altertable_start_table(ProcessUtilityArgs *args) relation_not_only(stmt->relation); add_hypertable_to_process_args(args, ht); } - num_cmds = list_length(stmt->cmds); foreach (lc, stmt->cmds) { AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lc); @@ -3477,19 +3509,17 @@ process_altertable_start_table(ProcessUtilityArgs *args) } break; } + case AT_SetRelOptions: { if (ht != NULL) { - if (num_cmds != 1) - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("ALTER TABLE SET does not support multiple " - "clauses"))); - } EventTriggerAlterTableStart(args->parsetree); - result = process_altertable_set_options(cmd, ht); + /* If we dealt with the option, we remove it from the + * list. We do not set the result variable since there + * could be other options that are not dealt with. */ + if (process_altertable_set_options(cmd, ht) == DDL_DONE) + stmt->cmds = foreach_delete_current(stmt->cmds, lc); } break; } @@ -3501,13 +3531,21 @@ process_altertable_start_table(ProcessUtilityArgs *args) if (NULL == ht) process_altertable_chunk_set_tablespace(cmd, relid); break; +#if PG15_GE + case AT_SetAccessMethod: + process_set_access_method(cmd, args); + break; +#endif default: break; } } ts_cache_release(hcache); - return result; + + /* If there are any commands remaining in the list, we need to deal with + * them. Otherwise, we just skip the rest. */ + return (list_length(stmt->cmds) > 0) ? DDL_CONTINUE : DDL_DONE; } static void diff --git a/test/expected/reloptions.out b/test/expected/reloptions.out index 621182d456d..dbbdb815bfd 100644 --- a/test/expected/reloptions.out +++ b/test/expected/reloptions.out @@ -20,12 +20,9 @@ WHERE relname ~ '^_hyper.*' AND relkind = 'r'; _hyper_1_2_chunk | {fillfactor=75,autovacuum_vacuum_threshold=100} (2 rows) --- Alter reloptions +-- Alter reloptions. We support multiple options for the ALTER TABLE ALTER TABLE reloptions_test SET (fillfactor=80, parallel_workers=8); -\set ON_ERROR_STOP 0 ALTER TABLE reloptions_test SET (fillfactor=80), SET (parallel_workers=8); -ERROR: ALTER TABLE SET does not support multiple clauses -\set ON_ERROR_STOP 1 SELECT relname, reloptions FROM pg_class WHERE relname ~ '^_hyper.*' AND relkind = 'r'; relname | reloptions diff --git a/test/expected/tableam_alter.out b/test/expected/tableam_alter.out new file mode 100644 index 00000000000..488bb6613bc --- /dev/null +++ b/test/expected/tableam_alter.out @@ -0,0 +1,77 @@ +-- This file and its contents are licensed under the Apache License 2.0. +-- Please see the included NOTICE for copyright information and +-- LICENSE-APACHE for a copy of the license. +-- Test support for setting table access method on hypertables using +-- ALTER TABLE. It should propagate to the chunks. +\c :TEST_DBNAME :ROLE_SUPERUSER +CREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler; +SET ROLE :ROLE_DEFAULT_PERM_USER; +CREATE VIEW chunk_info AS +SELECT hypertable_name AS hypertable, + chunk_name AS chunk, + amname + FROM timescaledb_information.chunks ch + JOIN pg_class cl ON (format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass = cl.oid) + JOIN pg_am am ON (am.oid = cl.relam); +CREATE TABLE test_table (time timestamptz not null, device int, temp float); +SELECT create_hypertable('test_table', by_range('time')); + create_hypertable +------------------- + (1,t) +(1 row) + +INSERT INTO test_table +SELECT ts, 10 * random(), 100 * random() +FROM generate_series('2001-01-01'::timestamp, '2001-02-01', '1d'::interval) as x(ts); +SELECT cl.relname, amname + FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid + WHERE cl.relname = 'test_table'; + relname | amname +------------+-------- + test_table | heap +(1 row) + +SELECT * FROM chunk_info WHERE hypertable = 'test_table'; + hypertable | chunk | amname +------------+------------------+-------- + test_table | _hyper_1_1_chunk | heap + test_table | _hyper_1_2_chunk | heap + test_table | _hyper_1_3_chunk | heap + test_table | _hyper_1_4_chunk | heap + test_table | _hyper_1_5_chunk | heap + test_table | _hyper_1_6_chunk | heap +(6 rows) + +-- Test setting the access method together with other options. This +-- should not generate an error. +ALTER TABLE test_table + SET ACCESS METHOD testam, + SET (autovacuum_vacuum_threshold = 100); +-- Create more chunks. These will use the new access method, but the +-- old chunks will use the old access method. +INSERT INTO test_table +SELECT ts, 10 * random(), 100 * random() +FROM generate_series('2001-02-01'::timestamp, '2001-03-01', '1d'::interval) as x(ts); +SELECT cl.relname, amname + FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid + WHERE cl.relname = 'test_table'; + relname | amname +------------+-------- + test_table | testam +(1 row) + +SELECT * FROM chunk_info WHERE hypertable = 'test_table'; + hypertable | chunk | amname +------------+-------------------+-------- + test_table | _hyper_1_1_chunk | heap + test_table | _hyper_1_2_chunk | heap + test_table | _hyper_1_3_chunk | heap + test_table | _hyper_1_4_chunk | heap + test_table | _hyper_1_5_chunk | heap + test_table | _hyper_1_6_chunk | heap + test_table | _hyper_1_7_chunk | testam + test_table | _hyper_1_8_chunk | testam + test_table | _hyper_1_9_chunk | testam + test_table | _hyper_1_10_chunk | testam +(10 rows) + diff --git a/test/sql/CMakeLists.txt b/test/sql/CMakeLists.txt index e4b8df6adac..5ed75b5307f 100644 --- a/test/sql/CMakeLists.txt +++ b/test/sql/CMakeLists.txt @@ -118,7 +118,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug) endif(CMAKE_BUILD_TYPE MATCHES Debug) if((${PG_VERSION_MAJOR} GREATER_EQUAL "15")) - list(APPEND TEST_FILES merge.sql) + list(APPEND TEST_FILES merge.sql tableam_alter.sql) list(APPEND TEST_TEMPLATES ts_merge.sql.in) endif() diff --git a/test/sql/reloptions.sql b/test/sql/reloptions.sql index aff63655376..21d4d048eb9 100644 --- a/test/sql/reloptions.sql +++ b/test/sql/reloptions.sql @@ -13,12 +13,9 @@ INSERT INTO reloptions_test VALUES (4, 24.3, 1), (9, 13.3, 2); SELECT relname, reloptions FROM pg_class WHERE relname ~ '^_hyper.*' AND relkind = 'r'; --- Alter reloptions +-- Alter reloptions. We support multiple options for the ALTER TABLE ALTER TABLE reloptions_test SET (fillfactor=80, parallel_workers=8); - -\set ON_ERROR_STOP 0 ALTER TABLE reloptions_test SET (fillfactor=80), SET (parallel_workers=8); -\set ON_ERROR_STOP 1 SELECT relname, reloptions FROM pg_class WHERE relname ~ '^_hyper.*' AND relkind = 'r'; diff --git a/test/sql/tableam_alter.sql b/test/sql/tableam_alter.sql new file mode 100644 index 00000000000..43c53fff570 --- /dev/null +++ b/test/sql/tableam_alter.sql @@ -0,0 +1,47 @@ +-- This file and its contents are licensed under the Apache License 2.0. +-- Please see the included NOTICE for copyright information and +-- LICENSE-APACHE for a copy of the license. + +-- Test support for setting table access method on hypertables using +-- ALTER TABLE. It should propagate to the chunks. +\c :TEST_DBNAME :ROLE_SUPERUSER +CREATE ACCESS METHOD testam TYPE TABLE HANDLER heap_tableam_handler; +SET ROLE :ROLE_DEFAULT_PERM_USER; + +CREATE VIEW chunk_info AS +SELECT hypertable_name AS hypertable, + chunk_name AS chunk, + amname + FROM timescaledb_information.chunks ch + JOIN pg_class cl ON (format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass = cl.oid) + JOIN pg_am am ON (am.oid = cl.relam); + +CREATE TABLE test_table (time timestamptz not null, device int, temp float); + +SELECT create_hypertable('test_table', by_range('time')); + +INSERT INTO test_table +SELECT ts, 10 * random(), 100 * random() +FROM generate_series('2001-01-01'::timestamp, '2001-02-01', '1d'::interval) as x(ts); + +SELECT cl.relname, amname + FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid + WHERE cl.relname = 'test_table'; +SELECT * FROM chunk_info WHERE hypertable = 'test_table'; + +-- Test setting the access method together with other options. This +-- should not generate an error. +ALTER TABLE test_table + SET ACCESS METHOD testam, + SET (autovacuum_vacuum_threshold = 100); + +-- Create more chunks. These will use the new access method, but the +-- old chunks will use the old access method. +INSERT INTO test_table +SELECT ts, 10 * random(), 100 * random() +FROM generate_series('2001-02-01'::timestamp, '2001-03-01', '1d'::interval) as x(ts); + +SELECT cl.relname, amname + FROM pg_class cl JOIN pg_am am ON cl.relam = am.oid + WHERE cl.relname = 'test_table'; +SELECT * FROM chunk_info WHERE hypertable = 'test_table'; diff --git a/tsl/src/compression/create.h b/tsl/src/compression/create.h index 8b3ea7845d8..3854796bd1a 100644 --- a/tsl/src/compression/create.h +++ b/tsl/src/compression/create.h @@ -23,6 +23,8 @@ bool tsl_process_compress_table(AlterTableCmd *cmd, Hypertable *ht, void tsl_process_compress_table_add_column(Hypertable *ht, ColumnDef *orig_def); void tsl_process_compress_table_drop_column(Hypertable *ht, char *name); void tsl_process_compress_table_rename_column(Hypertable *ht, const RenameStmt *stmt); +bool tsl_set_compression_options(Hypertable *ht, bool enable, + WithClauseResult *with_clause_options); Chunk *create_compress_chunk(Hypertable *compress_ht, Chunk *src_chunk, Oid table_id); char *column_segment_min_name(int16 column_index); diff --git a/tsl/test/expected/compression_errors-14.out b/tsl/test/expected/compression_errors-14.out index 2733b3aa874..0a87d7b5ae8 100644 --- a/tsl/test/expected/compression_errors-14.out +++ b/tsl/test/expected/compression_errors-14.out @@ -112,9 +112,6 @@ insert into foo values( 3 , 16 , 20); insert into foo values( 10 , 10 , 20); insert into foo values( 20 , 11 , 20); insert into foo values( 30 , 12 , 20); --- should error out -- -ALTER TABLE foo ALTER b SET NOT NULL, set (timescaledb.compress); -ERROR: ALTER TABLE SET does not support multiple clauses ALTER TABLE foo ALTER b SET NOT NULL; select attname, attnotnull from pg_attribute where attrelid = (select oid from pg_class where relname like 'foo') and attname like 'b'; attname | attnotnull diff --git a/tsl/test/expected/compression_errors-15.out b/tsl/test/expected/compression_errors-15.out index 2733b3aa874..0a87d7b5ae8 100644 --- a/tsl/test/expected/compression_errors-15.out +++ b/tsl/test/expected/compression_errors-15.out @@ -112,9 +112,6 @@ insert into foo values( 3 , 16 , 20); insert into foo values( 10 , 10 , 20); insert into foo values( 20 , 11 , 20); insert into foo values( 30 , 12 , 20); --- should error out -- -ALTER TABLE foo ALTER b SET NOT NULL, set (timescaledb.compress); -ERROR: ALTER TABLE SET does not support multiple clauses ALTER TABLE foo ALTER b SET NOT NULL; select attname, attnotnull from pg_attribute where attrelid = (select oid from pg_class where relname like 'foo') and attname like 'b'; attname | attnotnull diff --git a/tsl/test/expected/compression_errors-16.out b/tsl/test/expected/compression_errors-16.out index 29ec64940dc..6874d4f9128 100644 --- a/tsl/test/expected/compression_errors-16.out +++ b/tsl/test/expected/compression_errors-16.out @@ -112,9 +112,6 @@ insert into foo values( 3 , 16 , 20); insert into foo values( 10 , 10 , 20); insert into foo values( 20 , 11 , 20); insert into foo values( 30 , 12 , 20); --- should error out -- -ALTER TABLE foo ALTER b SET NOT NULL, set (timescaledb.compress); -ERROR: ALTER TABLE SET does not support multiple clauses ALTER TABLE foo ALTER b SET NOT NULL; select attname, attnotnull from pg_attribute where attrelid = (select oid from pg_class where relname like 'foo') and attname like 'b'; attname | attnotnull diff --git a/tsl/test/expected/compression_errors-17.out b/tsl/test/expected/compression_errors-17.out index 29ec64940dc..6874d4f9128 100644 --- a/tsl/test/expected/compression_errors-17.out +++ b/tsl/test/expected/compression_errors-17.out @@ -112,9 +112,6 @@ insert into foo values( 3 , 16 , 20); insert into foo values( 10 , 10 , 20); insert into foo values( 20 , 11 , 20); insert into foo values( 30 , 12 , 20); --- should error out -- -ALTER TABLE foo ALTER b SET NOT NULL, set (timescaledb.compress); -ERROR: ALTER TABLE SET does not support multiple clauses ALTER TABLE foo ALTER b SET NOT NULL; select attname, attnotnull from pg_attribute where attrelid = (select oid from pg_class where relname like 'foo') and attname like 'b'; attname | attnotnull diff --git a/tsl/test/sql/compression_errors.sql.in b/tsl/test/sql/compression_errors.sql.in index 191ec1bbec0..12ab925a161 100644 --- a/tsl/test/sql/compression_errors.sql.in +++ b/tsl/test/sql/compression_errors.sql.in @@ -69,9 +69,6 @@ insert into foo values( 10 , 10 , 20); insert into foo values( 20 , 11 , 20); insert into foo values( 30 , 12 , 20); --- should error out -- -ALTER TABLE foo ALTER b SET NOT NULL, set (timescaledb.compress); - ALTER TABLE foo ALTER b SET NOT NULL; select attname, attnotnull from pg_attribute where attrelid = (select oid from pg_class where relname like 'foo') and attname like 'b';