From a5725d987558ec674bb7eba89dfcf47f23928ad5 Mon Sep 17 00:00:00 2001 From: Olof Rensfelt Date: Thu, 6 Jul 2017 13:15:48 +0200 Subject: [PATCH] Add support to rename and change schema on hypertable. --- sql/chunk_index_triggers.sql | 2 + sql/ddl_internal.sql | 28 ++++++ sql/hypertable_index_triggers.sql | 1 - sql/tables.sql | 6 +- src/metadata_queries.c | 54 +++++++++++ src/metadata_queries.h | 2 + src/process_utility.c | 42 ++++++-- test/expected/drop_rename_hypertable.out | 118 ++++++++++++++++++----- test/expected/pg_dump.out | 4 +- test/sql/drop_rename_hypertable.sql | 28 ++++-- 10 files changed, 242 insertions(+), 43 deletions(-) diff --git a/sql/chunk_index_triggers.sql b/sql/chunk_index_triggers.sql index 15a215f7e..7a9606b65 100644 --- a/sql/chunk_index_triggers.sql +++ b/sql/chunk_index_triggers.sql @@ -11,6 +11,8 @@ BEGIN ELSIF TG_OP = 'DELETE' THEN EXECUTE format('DROP INDEX IF EXISTS %I.%I', OLD.schema_name, OLD.index_name); RETURN OLD; + ELSIF TG_OP = 'UPDATE' THEN + RETURN NEW; END IF; PERFORM _timescaledb_internal.on_trigger_error(TG_OP, TG_TABLE_SCHEMA, TG_TABLE_NAME); diff --git a/sql/ddl_internal.sql b/sql/ddl_internal.sql index e8138b168..3338d8d96 100644 --- a/sql/ddl_internal.sql +++ b/sql/ddl_internal.sql @@ -358,3 +358,31 @@ BEGIN END IF; END $BODY$; + + +CREATE OR REPLACE FUNCTION _timescaledb_internal.rename_hypertable( + old_schema NAME, + old_table_name NAME, + new_schema TEXT, + new_table_name TEXT +) + RETURNS VOID + LANGUAGE PLPGSQL VOLATILE + SECURITY DEFINER SET search_path = '' + AS +$BODY$ +DECLARE + hypertable_row _timescaledb_catalog.hypertable; +BEGIN + SELECT * INTO STRICT hypertable_row + FROM _timescaledb_catalog.hypertable + WHERE schema_name = old_schema AND table_name = old_table_name; + + UPDATE _timescaledb_catalog.hypertable SET + schema_name = new_schema, + table_name = new_table_name + WHERE + schema_name = old_schema AND + table_name = old_table_name; +END +$BODY$; diff --git a/sql/hypertable_index_triggers.sql b/sql/hypertable_index_triggers.sql index cb502cf3c..d92b1c78c 100644 --- a/sql/hypertable_index_triggers.sql +++ b/sql/hypertable_index_triggers.sql @@ -38,7 +38,6 @@ DECLARE hypertable_row _timescaledb_catalog.hypertable; BEGIN IF TG_OP = 'UPDATE' THEN - PERFORM _timescaledb_internal.on_trigger_error(TG_OP, TG_TABLE_SCHEMA, TG_TABLE_NAME); RETURN NEW; ELSIF TG_OP = 'INSERT' THEN -- create index on all chunks diff --git a/sql/tables.sql b/sql/tables.sql index 47deb0557..b9939b16a 100644 --- a/sql/tables.sql +++ b/sql/tables.sql @@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable ( associated_schema_name NAME NOT NULL, associated_table_prefix NAME NOT NULL, num_dimensions SMALLINT NOT NULL CHECK (num_dimensions > 0), + UNIQUE (id, schema_name), -- So hypertable_index can use it as foreign key UNIQUE (schema_name, table_name), UNIQUE (associated_schema_name, associated_table_prefix) ); @@ -127,10 +128,11 @@ SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_constrain -- Represents an index on the hypertable CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable_index ( - hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE, + hypertable_id INTEGER NOT NULL, main_schema_name NAME NOT NULL, -- schema name of main table (needed for a uniqueness constraint) main_index_name NAME NOT NULL, -- index name on main table definition TEXT NOT NULL, -- def with /*INDEX_NAME*/ and /*TABLE_NAME*/ placeholders + FOREIGN KEY (hypertable_id, main_schema_name) REFERENCES _timescaledb_catalog.hypertable(id, schema_name) ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY (hypertable_id, main_index_name), UNIQUE(main_schema_name, main_index_name) -- globally unique since index names globally unique ); @@ -147,7 +149,7 @@ CREATE TABLE IF NOT EXISTS _timescaledb_catalog.chunk_index ( definition TEXT NOT NULL, UNIQUE (schema_name, table_name, index_name), FOREIGN KEY (schema_name, table_name) REFERENCES _timescaledb_catalog.chunk (schema_name, table_name) ON DELETE CASCADE, - FOREIGN KEY (main_schema_name, main_index_name) REFERENCES _timescaledb_catalog.hypertable_index (main_schema_name, main_index_name) ON DELETE CASCADE + FOREIGN KEY (main_schema_name, main_index_name) REFERENCES _timescaledb_catalog.hypertable_index (main_schema_name, main_index_name) ON DELETE CASCADE ON UPDATE CASCADE ); SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_index', ''); SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.chunk_index','id'), ''); diff --git a/src/metadata_queries.c b/src/metadata_queries.c index 81243f379..8068bfe84 100644 --- a/src/metadata_queries.c +++ b/src/metadata_queries.c @@ -2,10 +2,12 @@ #include #include #include +#include #include "metadata_queries.h" #include "chunk.h" #include "dimension.h" +#include "hypertable.h" /* Utility function to prepare an SPI plan */ static SPIPlanPtr @@ -47,9 +49,16 @@ prepare_plan(const char *src, int nargs, Oid *argtypes) #define CHUNK_CREATE_ARGS (Oid[]) {INT4ARRAYOID, INT8ARRAYOID} #define CHUNK_CREATE "SELECT * FROM _timescaledb_internal.chunk_create($1, $2)" +/* old_schema, old_name, new_schema, new_name */ +#define RENAME_HYPERTABLE_ARGS (Oid[]) {NAMEOID, NAMEOID, TEXTOID, TEXTOID} +#define RENAME_HYPERTABLE "SELECT * FROM _timescaledb_internal.rename_hypertable($1, $2, $3, $4)" + /* plan for creating a chunk via create_chunk(). */ DEFINE_PLAN(create_chunk_plan, CHUNK_CREATE, 2, CHUNK_CREATE_ARGS) +/* plan to rename hypertable */ +DEFINE_PLAN(rename_hypertable_plan, RENAME_HYPERTABLE, 4, RENAME_HYPERTABLE_ARGS) + static HeapTuple chunk_tuple_create_spi_connected(Hyperspace *hs, Point *p, SPIPlanPtr plan) { @@ -104,3 +113,48 @@ spi_chunk_create(Hyperspace *hs, Point *p) return chunk; } + + +static void +hypertable_rename_spi_connected(Hypertable *ht, char *new_schema_name, char *new_table_name, SPIPlanPtr plan) +{ + int ret; + Datum args[4]; + + args[0] = PointerGetDatum(NameStr(ht->fd.schema_name)); + args[1] = PointerGetDatum(NameStr(ht->fd.table_name)); + + args[2] = CStringGetTextDatum(new_schema_name); + args[3] = CStringGetTextDatum(new_table_name); + + ret = SPI_execute_plan(plan, args, NULL, false, 4); + + if (ret <= 0) + elog(ERROR, "Got an SPI error %d", ret); + + return; +} + +void +spi_hypertable_rename(Hypertable *ht, char *new_schema_name, char *new_table_name) +{ + SPIPlanPtr plan = rename_hypertable_plan(); + + + if (strlen(new_schema_name) > sizeof(NameData) - 1) { + elog(ERROR, "New schema name '%s' is too long", new_schema_name); + } + + if (strlen(new_table_name) > sizeof(NameData) - 1) { + elog(ERROR, "New schema name '%s' is too long", new_table_name); + } + + if (SPI_connect() < 0) + elog(ERROR, "Got an SPI connect error"); + + hypertable_rename_spi_connected(ht, new_schema_name, new_table_name, plan); + + SPI_finish(); + + return; +} diff --git a/src/metadata_queries.h b/src/metadata_queries.h index 98b92e738..70e5f8f59 100644 --- a/src/metadata_queries.h +++ b/src/metadata_queries.h @@ -6,7 +6,9 @@ typedef struct Chunk Chunk; typedef struct Hyperspace Hyperspace; typedef struct Point Point; +typedef struct Hypertable Hypertable; extern Chunk *spi_chunk_create(Hyperspace *hs, Point *p); +extern void spi_hypertable_rename(Hypertable *ht, char *new_schema_name, char *new_table_name); #endif /* TIMESCALEDB_METADATA_QUERIES_H */ diff --git a/src/process_utility.c b/src/process_utility.c index 752ac4b34..9a449c21f 100644 --- a/src/process_utility.c +++ b/src/process_utility.c @@ -8,6 +8,7 @@ #include "hypertable_cache.h" #include "extension.h" #include "executor.h" +#include "metadata_queries.h" void _process_utility_init(void); void _process_utility_fini(void); @@ -35,7 +36,8 @@ prev_ProcessUtility(Node *parsetree, } } -/* Hook-intercept for ProcessUtility. */ +/* Hook-intercept for ProcessUtility. Used to set COPY completion tag and */ +/* renaming of hypertables. */ static void timescaledb_ProcessUtility(Node *parsetree, const char *query_string, @@ -50,7 +52,31 @@ timescaledb_ProcessUtility(Node *parsetree, return; } - /* We don't support renaming hypertables yet so we need to block it */ + /* Change the schema of hypertable */ + if (IsA(parsetree, AlterObjectSchemaStmt)) + { + AlterObjectSchemaStmt *alterstmt = (AlterObjectSchemaStmt *) parsetree; + Oid relId = RangeVarGetRelid(alterstmt->relation, NoLock, true); + + if (OidIsValid(relId)) + { + Cache *hcache = hypertable_cache_pin(); + Hypertable *hentry = hypertable_cache_get_entry(hcache, relId); + + if (hentry != NULL && alterstmt->objectType == OBJECT_TABLE) { + executor_level_enter(); + spi_hypertable_rename(hentry, + alterstmt->newschema, + NameStr(hentry->fd.table_name)); + executor_level_exit(); + } + cache_release(hcache); + } + prev_ProcessUtility((Node *) alterstmt, query_string, context, params, dest, completion_tag); + return; + } + + /* Rename hypertable */ if (IsA(parsetree, RenameStmt)) { RenameStmt *renamestmt = (RenameStmt *) parsetree; @@ -61,15 +87,19 @@ timescaledb_ProcessUtility(Node *parsetree, Cache *hcache = hypertable_cache_pin(); Hypertable *hentry = hypertable_cache_get_entry(hcache, relid); - if (hentry != NULL && renamestmt->renameType == OBJECT_TABLE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Renaming hypertables is not yet supported"))); + if (hentry != NULL && renamestmt->renameType == OBJECT_TABLE) { + executor_level_enter(); + spi_hypertable_rename(hentry, + NameStr(hentry->fd.schema_name), + renamestmt->newname); + executor_level_exit(); + } cache_release(hcache); } prev_ProcessUtility((Node *) renamestmt, query_string, context, params, dest, completion_tag); return; } + if (IsA(parsetree, CopyStmt)) { /* diff --git a/test/expected/drop_rename_hypertable.out b/test/expected/drop_rename_hypertable.out index 10cbe1441..084b0d062 100644 --- a/test/expected/drop_rename_hypertable.out +++ b/test/expected/drop_rename_hypertable.out @@ -317,32 +317,97 @@ Check constraints: "constraint_8" CHECK (_timescaledb_internal.get_partition_for_key(device_id) >= 0 AND _timescaledb_internal.get_partition_for_key(device_id) < 1073741823) Inherits: "two_Partitions" --- Test that renaming hypertable is blocked -\set VERBOSITY default -\set ON_ERROR_STOP 0 +-- Test that renaming hypertable works +\d _timescaledb_internal._hyper_1_1_chunk +Table "_timescaledb_internal._hyper_1_1_chunk" + Column | Type | Modifiers +-------------+------------------+----------- + timeCustom | bigint | not null + device_id | text | not null + series_0 | double precision | + series_1 | double precision | + series_2 | double precision | + series_bool | boolean | +Indexes: + "1-two_Partitions_device_id_timeCustom_idx" btree (device_id, "timeCustom" DESC NULLS LAST) WHERE device_id IS NOT NULL + "2-two_Partitions_timeCustom_series_0_idx" btree ("timeCustom" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL + "3-two_Partitions_timeCustom_series_1_idx" btree ("timeCustom" DESC NULLS LAST, series_1) WHERE series_1 IS NOT NULL + "4-two_Partitions_timeCustom_series_2_idx" btree ("timeCustom" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL + "5-two_Partitions_timeCustom_series_bool_idx" btree ("timeCustom" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL + "6-two_Partitions_timeCustom_device_id_idx" btree ("timeCustom" DESC NULLS LAST, device_id) + "7-two_Partitions_timeCustom_idx" btree ("timeCustom" DESC) +Check constraints: + "constraint_1" CHECK ("timeCustom" >= '1257892416000000000'::bigint AND "timeCustom" < '1257895008000000000'::bigint) + "constraint_2" CHECK (_timescaledb_internal.get_partition_for_key(device_id) >= 1073741823 AND _timescaledb_internal.get_partition_for_key(device_id) < 2147483647) +Inherits: "two_Partitions" + ALTER TABLE "two_Partitions" RENAME TO "newname"; -ERROR: Renaming hypertables is not yet supported -\set ON_ERROR_STOP 1 --- Test that renaming ordinary table works -CREATE TABLE renametable (foo int); -ALTER TABLE "renametable" RENAME TO "newname"; SELECT * FROM "newname"; - foo ------ + timeCustom | device_id | series_0 | series_1 | series_2 | series_bool +---------------------+-----------+----------+----------+----------+------------- + 1257894000000000000 | dev1 | 1.5 | 1 | 2 | t + 1257894000000000000 | dev1 | 1.5 | 2 | | + 1257894000000001000 | dev1 | 2.5 | 3 | | + 1257894001000000000 | dev1 | 3.5 | 4 | | + 1257894002000000000 | dev1 | 5.5 | 6 | | t + 1257894002000000000 | dev1 | 5.5 | 7 | | f + 1257894002000000000 | dev1 | 2.5 | 3 | | + 1257897600000000000 | dev1 | 4.5 | 5 | | f + 1257987600000000000 | dev1 | 1.5 | 1 | | + 1257987600000000000 | dev1 | 1.5 | 2 | | + 1257894000000000000 | dev2 | 1.5 | 1 | | + 1257894000000000000 | dev2 | 1.5 | 2 | | +(12 rows) + +SELECT * FROM _timescaledb_catalog.hypertable; + id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions +----+-------------+------------+------------------------+-------------------------+---------------- + 1 | public | newname | _timescaledb_internal | _hyper_1 | 2 +(1 row) + +CREATE SCHEMA "newschema"; +ALTER TABLE "newname" SET SCHEMA "newschema"; +SELECT * FROM "newschema"."newname"; + timeCustom | device_id | series_0 | series_1 | series_2 | series_bool +---------------------+-----------+----------+----------+----------+------------- + 1257894000000000000 | dev1 | 1.5 | 1 | 2 | t + 1257894000000000000 | dev1 | 1.5 | 2 | | + 1257894000000001000 | dev1 | 2.5 | 3 | | + 1257894001000000000 | dev1 | 3.5 | 4 | | + 1257894002000000000 | dev1 | 5.5 | 6 | | t + 1257894002000000000 | dev1 | 5.5 | 7 | | f + 1257894002000000000 | dev1 | 2.5 | 3 | | + 1257897600000000000 | dev1 | 4.5 | 5 | | f + 1257987600000000000 | dev1 | 1.5 | 1 | | + 1257987600000000000 | dev1 | 1.5 | 2 | | + 1257894000000000000 | dev2 | 1.5 | 1 | | + 1257894000000000000 | dev2 | 1.5 | 2 | | +(12 rows) + +SELECT * FROM _timescaledb_catalog.hypertable; + id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions +----+-------------+------------+------------------------+-------------------------+---------------- + 1 | newschema | newname | _timescaledb_internal | _hyper_1 | 2 +(1 row) + +SELECT * FROM _timescaledb_catalog.hypertable_index WHERE main_schema_name <> 'newschema'; + hypertable_id | main_schema_name | main_index_name | definition +---------------+------------------+-----------------+------------ +(0 rows) + +SELECT * FROM _timescaledb_catalog.chunk_index WHERE main_schema_name <> 'newschema'; + id | schema_name | table_name | index_name | main_schema_name | main_index_name | definition +----+-------------+------------+------------+------------------+-----------------+------------ (0 rows) SELECT * FROM _timescaledb_catalog.hypertable; - id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions -----+-------------+----------------+------------------------+-------------------------+---------------- - 1 | public | two_Partitions | _timescaledb_internal | _hyper_1 | 2 + id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions +----+-------------+------------+------------------------+-------------------------+---------------- + 1 | newschema | newname | _timescaledb_internal | _hyper_1 | 2 (1 row) -DROP TABLE "two_Partitions" CASCADE; +DROP TABLE "newschema"."newname" CASCADE; NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to table _timescaledb_internal._hyper_1_1_chunk -drop cascades to table _timescaledb_internal._hyper_1_2_chunk -drop cascades to table _timescaledb_internal._hyper_1_3_chunk -drop cascades to table _timescaledb_internal._hyper_1_4_chunk NOTICE: index "1-two_Partitions_device_id_timeCustom_idx" does not exist, skipping NOTICE: index "2-two_Partitions_timeCustom_series_0_idx" does not exist, skipping NOTICE: index "3-two_Partitions_timeCustom_series_1_idx" does not exist, skipping @@ -377,11 +442,10 @@ SELECT * FROM _timescaledb_catalog.hypertable; (0 rows) \dt "public".* - List of relations - Schema | Name | Type | Owner ---------+---------+-------+---------- - public | newname | table | postgres -(1 row) + List of relations + Schema | Name | Type | Owner +--------+------+------+------- +(0 rows) \dt "_timescaledb_catalog".* List of relations @@ -403,3 +467,11 @@ SELECT * FROM _timescaledb_catalog.hypertable; --------+------+------+-------+------+------------- (0 rows) +-- Test that renaming ordinary table works +CREATE TABLE renametable (foo int); +ALTER TABLE "renametable" RENAME TO "newname_none_ht"; +SELECT * FROM "newname_none_ht"; + foo +----- +(0 rows) + diff --git a/test/expected/pg_dump.out b/test/expected/pg_dump.out index f9c12bee0..e3941b88a 100644 --- a/test/expected/pg_dump.out +++ b/test/expected/pg_dump.out @@ -42,7 +42,7 @@ SELECT count(*) AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb'); count ------- - 111 + 112 (1 row) \c postgres @@ -66,7 +66,7 @@ SELECT count(*) AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb'); count ------- - 111 + 112 (1 row) \c single diff --git a/test/sql/drop_rename_hypertable.sql b/test/sql/drop_rename_hypertable.sql index 04fcf6e6e..acd995b2b 100644 --- a/test/sql/drop_rename_hypertable.sql +++ b/test/sql/drop_rename_hypertable.sql @@ -4,22 +4,32 @@ \d+ "_timescaledb_internal".* --- Test that renaming hypertable is blocked -\set VERBOSITY default -\set ON_ERROR_STOP 0 -ALTER TABLE "two_Partitions" RENAME TO "newname"; -\set ON_ERROR_STOP 1 +-- Test that renaming hypertable works --- Test that renaming ordinary table works -CREATE TABLE renametable (foo int); -ALTER TABLE "renametable" RENAME TO "newname"; +\d _timescaledb_internal._hyper_1_1_chunk +ALTER TABLE "two_Partitions" RENAME TO "newname"; SELECT * FROM "newname"; +SELECT * FROM _timescaledb_catalog.hypertable; + +CREATE SCHEMA "newschema"; + +ALTER TABLE "newname" SET SCHEMA "newschema"; +SELECT * FROM "newschema"."newname"; +SELECT * FROM _timescaledb_catalog.hypertable; +SELECT * FROM _timescaledb_catalog.hypertable_index WHERE main_schema_name <> 'newschema'; +SELECT * FROM _timescaledb_catalog.chunk_index WHERE main_schema_name <> 'newschema'; SELECT * FROM _timescaledb_catalog.hypertable; -DROP TABLE "two_Partitions" CASCADE; +DROP TABLE "newschema"."newname" CASCADE; SELECT * FROM _timescaledb_catalog.hypertable; \dt "public".* \dt "_timescaledb_catalog".* \dt+ "_timescaledb_internal".* +-- Test that renaming ordinary table works + +CREATE TABLE renametable (foo int); +ALTER TABLE "renametable" RENAME TO "newname_none_ht"; +SELECT * FROM "newname_none_ht"; +