Handle changing the type of dimension columns correctly.

Update the type in the dimension metadata table and recreate
the check constraints.
This commit is contained in:
Matvey Arye 2017-09-07 17:58:30 -04:00 committed by Matvey Arye
parent 17c4ba9ec3
commit 19d3d8981b
9 changed files with 415 additions and 2 deletions

View File

@ -33,6 +33,26 @@ BEGIN
END
$BODY$;
CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_constraint_drop_table_constraint(
chunk_constraint_row _timescaledb_catalog.chunk_constraint
)
RETURNS VOID LANGUAGE PLPGSQL AS
$BODY$
DECLARE
sql_code TEXT;
chunk_row _timescaledb_catalog.chunk;
BEGIN
SELECT * INTO STRICT chunk_row FROM _timescaledb_catalog.chunk c WHERE c.id = chunk_constraint_row.chunk_id;
sql_code := format(
$$ ALTER TABLE %I.%I DROP CONSTRAINT %I $$,
chunk_row.schema_name, chunk_row.table_name, chunk_constraint_row.constraint_name
);
EXECUTE sql_code;
END
$BODY$;
CREATE OR REPLACE FUNCTION _timescaledb_internal.create_chunk_constraint(
chunk_id INTEGER,
constraint_oid OID

View File

@ -509,6 +509,42 @@ $BODY$
SELECT ''::void; --don't return NULL
$BODY$;
CREATE OR REPLACE FUNCTION _timescaledb_internal.change_column_type(
hypertable_id INT,
column_name NAME,
new_type REGTYPE
)
RETURNS VOID
LANGUAGE PLPGSQL VOLATILE
SECURITY DEFINER SET search_path = ''
AS
$BODY$
DECLARE
chunk_row _timescaledb_catalog.chunk;
dimension_row _timescaledb_catalog.dimension;
BEGIN
UPDATE _timescaledb_catalog.dimension d
SET column_type = new_type
WHERE d.column_name = change_column_type.column_name AND d.hypertable_id = change_column_type.hypertable_id
RETURNING * INTO dimension_row;
IF FOUND THEN
PERFORM _timescaledb_internal.chunk_constraint_drop_table_constraint(cc)
FROM _timescaledb_catalog.dimension d
INNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = d.id)
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id)
WHERE d.id = dimension_row.id;
PERFORM _timescaledb_internal.chunk_constraint_add_table_constraint(cc)
FROM _timescaledb_catalog.dimension d
INNER JOIN _timescaledb_catalog.dimension_slice ds ON (ds.dimension_id = d.id)
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = ds.id)
WHERE d.id = dimension_row.id;
END IF;
END
$BODY$;
CREATE OR REPLACE FUNCTION _timescaledb_internal.truncate_hypertable(
schema_name NAME,

View File

@ -33,3 +33,5 @@ ADD CONSTRAINT chunk_constraint_chunk_id_constraint_name_key UNIQUE (chunk_id, c
CREATE SEQUENCE _timescaledb_catalog.chunk_constraint_name;
SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_constraint_name', '');
DROP FUNCTION _timescaledb_internal.rename_hypertable(name, name, text, text);

View File

@ -105,6 +105,10 @@ const static InternalFunctionDef internal_function_definitions[_MAX_INTERNAL_FUN
.name = "rename_column",
.args = 3
},
[DDL_CHANGE_COLUMN_TYPE] = {
.name = "change_column_type",
.args = 3
},
[CHUNK_CREATE] = {
.name = "chunk_create",
.args = 4

View File

@ -50,6 +50,7 @@ typedef enum InternalFunction
DDL_DROP_HYPERTABLE,
DDL_RENAME_HYPERTABLE,
DDL_RENAME_COLUMN,
DDL_CHANGE_COLUMN_TYPE,
CHUNK_CREATE,
_MAX_INTERNAL_FUNCTIONS,
} InternalFunction;

View File

@ -514,6 +514,34 @@ process_altertable_plain_table(Node *parsetree)
}
}
static inline const char *
typename_get_unqual_name(TypeName *tn)
{
Value *name = llast(tn->names);
return name->val.str;
}
static void
process_alter_column_type(Cache *hcache, AlterTableCmd *cmd, Oid relid)
{
Hypertable *ht;
ColumnDef *coldef = (ColumnDef *) cmd->def;
Oid new_type;
NameData column_name;
ht = hypertable_cache_get_entry(hcache, relid);
if (NULL == ht)
return;
namestrcpy(&column_name, cmd->name);
new_type = TypenameGetTypid(typename_get_unqual_name(coldef->typeName));
CatalogInternalCall3(DDL_CHANGE_COLUMN_TYPE, Int32GetDatum(ht->fd.id),
NameGetDatum(&column_name), ObjectIdGetDatum(new_type));
}
static void
process_altertable(Node *parsetree)
{
@ -528,6 +556,8 @@ process_altertable(Node *parsetree)
hcache = hypertable_cache_pin();
/* TODO: forbid all alter_table on chunk table */
ht = hypertable_cache_get_entry(hcache, relid);
if (NULL == ht)
{
@ -574,6 +604,9 @@ process_altertable(Node *parsetree)
case AT_DropConstraintRecurse:
process_altertable_drop_constraint(ht, cmd, relid);
break;
case AT_AlterColumnType:
process_alter_column_type(hcache, cmd, relid);
break;
default:
break;
}

View File

@ -0,0 +1,275 @@
\ir include/create_single_db.sql
SET client_min_messages = WARNING;
DROP DATABASE IF EXISTS single;
SET client_min_messages = NOTICE;
CREATE DATABASE single;
\c single
CREATE EXTENSION IF NOT EXISTS timescaledb;
SET timescaledb.disable_optimizations = :DISABLE_OPTIMIZATIONS;
CREATE EXTENSION IF NOT EXISTS timescaledb;
NOTICE: extension "timescaledb" already exists, skipping
CREATE TABLE alter_test(time timestamptz, temp float, color varchar(10));
-- create hypertable with two chunks
SELECT create_hypertable('alter_test', 'time', 'color', 2, chunk_time_interval => 2628000000000);
create_hypertable
-------------------
(1 row)
INSERT INTO alter_test VALUES ('2017-01-20T09:00:01', 17.5, 'blue'),
('2017-01-21T09:00:01', 19.1, 'yellow'),
('2017-04-20T09:00:01', 89.5, 'green'),
('2017-04-21T09:00:01', 17.1, 'black');
\d+ alter_test
Table "public.alter_test"
Column | Type | Modifiers | Storage | Stats target | Description
--------+--------------------------+-----------+----------+--------------+-------------
time | timestamp with time zone | | plain | |
temp | double precision | | plain | |
color | character varying(10) | | extended | |
Indexes:
"alter_test_color_time_idx" btree (color, "time" DESC)
"alter_test_time_idx" btree ("time" DESC)
Child tables: _timescaledb_internal._hyper_1_1_chunk,
_timescaledb_internal._hyper_1_2_chunk,
_timescaledb_internal._hyper_1_3_chunk
\d+ _timescaledb_internal.*
Index "_timescaledb_internal.1-alter_test_time_idx"
Column | Type | Definition | Storage
--------+--------------------------+------------+---------
time | timestamp with time zone | "time" | plain
btree, for table "_timescaledb_internal._hyper_1_1_chunk"
Index "_timescaledb_internal.2-alter_test_color_time_idx"
Column | Type | Definition | Storage
--------+--------------------------+------------+----------
color | character varying(10) | color | extended
time | timestamp with time zone | "time" | plain
btree, for table "_timescaledb_internal._hyper_1_1_chunk"
Index "_timescaledb_internal.3-alter_test_time_idx"
Column | Type | Definition | Storage
--------+--------------------------+------------+---------
time | timestamp with time zone | "time" | plain
btree, for table "_timescaledb_internal._hyper_1_2_chunk"
Index "_timescaledb_internal.4-alter_test_color_time_idx"
Column | Type | Definition | Storage
--------+--------------------------+------------+----------
color | character varying(10) | color | extended
time | timestamp with time zone | "time" | plain
btree, for table "_timescaledb_internal._hyper_1_2_chunk"
Index "_timescaledb_internal.5-alter_test_time_idx"
Column | Type | Definition | Storage
--------+--------------------------+------------+---------
time | timestamp with time zone | "time" | plain
btree, for table "_timescaledb_internal._hyper_1_3_chunk"
Index "_timescaledb_internal.6-alter_test_color_time_idx"
Column | Type | Definition | Storage
--------+--------------------------+------------+----------
color | character varying(10) | color | extended
time | timestamp with time zone | "time" | plain
btree, for table "_timescaledb_internal._hyper_1_3_chunk"
Table "_timescaledb_internal._hyper_1_1_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
--------+--------------------------+-----------+----------+--------------+-------------
time | timestamp with time zone | | plain | |
temp | double precision | | plain | |
color | character varying(10) | | extended | |
Indexes:
"1-alter_test_time_idx" btree ("time" DESC)
"2-alter_test_color_time_idx" btree (color, "time" DESC)
Check constraints:
"constraint_1" CHECK ("time" >= 'Thu Jan 19 02:00:00 2017 PST'::timestamp with time zone AND "time" < 'Sat Feb 18 12:00:00 2017 PST'::timestamp with time zone)
"constraint_2" CHECK (_timescaledb_internal.get_partition_for_key(color::text) >= 0 AND _timescaledb_internal.get_partition_for_key(color::text) < 1073741823)
Inherits: alter_test
Table "_timescaledb_internal._hyper_1_2_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
--------+--------------------------+-----------+----------+--------------+-------------
time | timestamp with time zone | | plain | |
temp | double precision | | plain | |
color | character varying(10) | | extended | |
Indexes:
"3-alter_test_time_idx" btree ("time" DESC)
"4-alter_test_color_time_idx" btree (color, "time" DESC)
Check constraints:
"constraint_3" CHECK ("time" >= 'Thu Apr 20 09:00:00 2017 PDT'::timestamp with time zone AND "time" < 'Sat May 20 19:00:00 2017 PDT'::timestamp with time zone)
"constraint_4" CHECK (_timescaledb_internal.get_partition_for_key(color::text) >= 1073741823 AND _timescaledb_internal.get_partition_for_key(color::text) < 2147483647)
Inherits: alter_test
Table "_timescaledb_internal._hyper_1_3_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
--------+--------------------------+-----------+----------+--------------+-------------
time | timestamp with time zone | | plain | |
temp | double precision | | plain | |
color | character varying(10) | | extended | |
Indexes:
"5-alter_test_time_idx" btree ("time" DESC)
"6-alter_test_color_time_idx" btree (color, "time" DESC)
Check constraints:
"constraint_2" CHECK (_timescaledb_internal.get_partition_for_key(color::text) >= 0 AND _timescaledb_internal.get_partition_for_key(color::text) < 1073741823)
"constraint_3" CHECK ("time" >= 'Thu Apr 20 09:00:00 2017 PDT'::timestamp with time zone AND "time" < 'Sat May 20 19:00:00 2017 PDT'::timestamp with time zone)
Inherits: alter_test
-- show the column name and type of the partitioning dimension in the
-- metadata table
SELECT * FROM _timescaledb_catalog.dimension;
id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length
----+---------------+-------------+--------------------------+---------+------------+--------------------------+-----------------------+-----------------
1 | 1 | time | timestamp with time zone | t | | | | 2628000000000
2 | 1 | color | character varying | f | 2 | _timescaledb_internal | get_partition_for_key |
(2 rows)
EXPLAIN (costs off)
SELECT * FROM alter_test WHERE time > '2017-05-20T10:00:01';
QUERY PLAN
-----------------------------------------------------------------------------------------------
Append
-> Seq Scan on alter_test
Filter: ("time" > 'Sat May 20 10:00:01 2017 PDT'::timestamp with time zone)
-> Bitmap Heap Scan on _hyper_1_2_chunk
Recheck Cond: ("time" > 'Sat May 20 10:00:01 2017 PDT'::timestamp with time zone)
-> Bitmap Index Scan on "3-alter_test_time_idx"
Index Cond: ("time" > 'Sat May 20 10:00:01 2017 PDT'::timestamp with time zone)
-> Bitmap Heap Scan on _hyper_1_3_chunk
Recheck Cond: ("time" > 'Sat May 20 10:00:01 2017 PDT'::timestamp with time zone)
-> Bitmap Index Scan on "5-alter_test_time_idx"
Index Cond: ("time" > 'Sat May 20 10:00:01 2017 PDT'::timestamp with time zone)
(11 rows)
-- rename column and change its type
ALTER TABLE alter_test RENAME COLUMN time TO time_us;
ALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamp;
ALTER TABLE alter_test RENAME COLUMN color TO colorname;
ALTER TABLE alter_test ALTER COLUMN colorname TYPE text;
\d+ alter_test
Table "public.alter_test"
Column | Type | Modifiers | Storage | Stats target | Description
-----------+-----------------------------+-----------+----------+--------------+-------------
time_us | timestamp without time zone | | plain | |
temp | double precision | | plain | |
colorname | text | | extended | |
Indexes:
"alter_test_color_time_idx" btree (colorname, time_us DESC)
"alter_test_time_idx" btree (time_us DESC)
Child tables: _timescaledb_internal._hyper_1_1_chunk,
_timescaledb_internal._hyper_1_2_chunk,
_timescaledb_internal._hyper_1_3_chunk
\d+ _timescaledb_internal.*
Index "_timescaledb_internal.1-alter_test_time_idx"
Column | Type | Definition | Storage
---------+-----------------------------+------------+---------
time_us | timestamp without time zone | time_us | plain
btree, for table "_timescaledb_internal._hyper_1_1_chunk"
Index "_timescaledb_internal.2-alter_test_color_time_idx"
Column | Type | Definition | Storage
-----------+-----------------------------+------------+----------
colorname | text | colorname | extended
time_us | timestamp without time zone | time_us | plain
btree, for table "_timescaledb_internal._hyper_1_1_chunk"
Index "_timescaledb_internal.3-alter_test_time_idx"
Column | Type | Definition | Storage
---------+-----------------------------+------------+---------
time_us | timestamp without time zone | time_us | plain
btree, for table "_timescaledb_internal._hyper_1_2_chunk"
Index "_timescaledb_internal.4-alter_test_color_time_idx"
Column | Type | Definition | Storage
-----------+-----------------------------+------------+----------
colorname | text | colorname | extended
time_us | timestamp without time zone | time_us | plain
btree, for table "_timescaledb_internal._hyper_1_2_chunk"
Index "_timescaledb_internal.5-alter_test_time_idx"
Column | Type | Definition | Storage
---------+-----------------------------+------------+---------
time_us | timestamp without time zone | time_us | plain
btree, for table "_timescaledb_internal._hyper_1_3_chunk"
Index "_timescaledb_internal.6-alter_test_color_time_idx"
Column | Type | Definition | Storage
-----------+-----------------------------+------------+----------
colorname | text | colorname | extended
time_us | timestamp without time zone | time_us | plain
btree, for table "_timescaledb_internal._hyper_1_3_chunk"
Table "_timescaledb_internal._hyper_1_1_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
-----------+-----------------------------+-----------+----------+--------------+-------------
time_us | timestamp without time zone | | plain | |
temp | double precision | | plain | |
colorname | text | | extended | |
Indexes:
"1-alter_test_time_idx" btree (time_us DESC)
"2-alter_test_color_time_idx" btree (colorname, time_us DESC)
Check constraints:
"constraint_1" CHECK (time_us >= 'Thu Jan 19 02:00:00 2017'::timestamp without time zone AND time_us < 'Sat Feb 18 12:00:00 2017'::timestamp without time zone)
"constraint_2" CHECK (_timescaledb_internal.get_partition_for_key(colorname) >= 0 AND _timescaledb_internal.get_partition_for_key(colorname) < 1073741823)
Inherits: alter_test
Table "_timescaledb_internal._hyper_1_2_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
-----------+-----------------------------+-----------+----------+--------------+-------------
time_us | timestamp without time zone | | plain | |
temp | double precision | | plain | |
colorname | text | | extended | |
Indexes:
"3-alter_test_time_idx" btree (time_us DESC)
"4-alter_test_color_time_idx" btree (colorname, time_us DESC)
Check constraints:
"constraint_3" CHECK (time_us >= 'Thu Apr 20 09:00:00 2017'::timestamp without time zone AND time_us < 'Sat May 20 19:00:00 2017'::timestamp without time zone)
"constraint_4" CHECK (_timescaledb_internal.get_partition_for_key(colorname) >= 1073741823 AND _timescaledb_internal.get_partition_for_key(colorname) < 2147483647)
Inherits: alter_test
Table "_timescaledb_internal._hyper_1_3_chunk"
Column | Type | Modifiers | Storage | Stats target | Description
-----------+-----------------------------+-----------+----------+--------------+-------------
time_us | timestamp without time zone | | plain | |
temp | double precision | | plain | |
colorname | text | | extended | |
Indexes:
"5-alter_test_time_idx" btree (time_us DESC)
"6-alter_test_color_time_idx" btree (colorname, time_us DESC)
Check constraints:
"constraint_2" CHECK (_timescaledb_internal.get_partition_for_key(colorname) >= 0 AND _timescaledb_internal.get_partition_for_key(colorname) < 1073741823)
"constraint_3" CHECK (time_us >= 'Thu Apr 20 09:00:00 2017'::timestamp without time zone AND time_us < 'Sat May 20 19:00:00 2017'::timestamp without time zone)
Inherits: alter_test
-- show that the metadata has been updated
SELECT * FROM _timescaledb_catalog.dimension;
id | hypertable_id | column_name | column_type | aligned | num_slices | partitioning_func_schema | partitioning_func | interval_length
----+---------------+-------------+-----------------------------+---------+------------+--------------------------+-----------------------+-----------------
1 | 1 | time_us | timestamp without time zone | t | | | | 2628000000000
2 | 1 | colorname | text | f | 2 | _timescaledb_internal | get_partition_for_key |
(2 rows)
-- constraint exclusion should still work with updated column
EXPLAIN (costs off)
SELECT * FROM alter_test WHERE time_us > '2017-05-20T10:00:01';
QUERY PLAN
-------------------------------------------------------------------------------------
Append
-> Seq Scan on alter_test
Filter: (time_us > 'Sat May 20 10:00:01 2017'::timestamp without time zone)
-> Seq Scan on _hyper_1_2_chunk
Filter: (time_us > 'Sat May 20 10:00:01 2017'::timestamp without time zone)
-> Seq Scan on _hyper_1_3_chunk
Filter: (time_us > 'Sat May 20 10:00:01 2017'::timestamp without time zone)
(7 rows)
\set ON_ERROR_STOP 0
-- verify that we cannot change the column type to something incompatible
ALTER TABLE alter_test ALTER COLUMN colorname TYPE varchar(3);
ERROR: value too long for type character varying(3)
-- conversion that messes up partitioning fails
ALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamptz USING time_us::timestamptz+INTERVAL '1 year';
ERROR: check constraint "constraint_1" is violated by some row
\set ON_ERROR_STOP 1

View File

@ -43,7 +43,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
129
132
(1 row)
\c postgres
@ -67,7 +67,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
129
132
(1 row)
\c single

View File

@ -0,0 +1,42 @@
\ir include/create_single_db.sql
CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE TABLE alter_test(time timestamptz, temp float, color varchar(10));
-- create hypertable with two chunks
SELECT create_hypertable('alter_test', 'time', 'color', 2, chunk_time_interval => 2628000000000);
INSERT INTO alter_test VALUES ('2017-01-20T09:00:01', 17.5, 'blue'),
('2017-01-21T09:00:01', 19.1, 'yellow'),
('2017-04-20T09:00:01', 89.5, 'green'),
('2017-04-21T09:00:01', 17.1, 'black');
\d+ alter_test
\d+ _timescaledb_internal.*
-- show the column name and type of the partitioning dimension in the
-- metadata table
SELECT * FROM _timescaledb_catalog.dimension;
EXPLAIN (costs off)
SELECT * FROM alter_test WHERE time > '2017-05-20T10:00:01';
-- rename column and change its type
ALTER TABLE alter_test RENAME COLUMN time TO time_us;
ALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamp;
ALTER TABLE alter_test RENAME COLUMN color TO colorname;
ALTER TABLE alter_test ALTER COLUMN colorname TYPE text;
\d+ alter_test
\d+ _timescaledb_internal.*
-- show that the metadata has been updated
SELECT * FROM _timescaledb_catalog.dimension;
-- constraint exclusion should still work with updated column
EXPLAIN (costs off)
SELECT * FROM alter_test WHERE time_us > '2017-05-20T10:00:01';
\set ON_ERROR_STOP 0
-- verify that we cannot change the column type to something incompatible
ALTER TABLE alter_test ALTER COLUMN colorname TYPE varchar(3);
-- conversion that messes up partitioning fails
ALTER TABLE alter_test ALTER COLUMN time_us TYPE timestamptz USING time_us::timestamptz+INTERVAL '1 year';
\set ON_ERROR_STOP 1