diff --git a/tsl/src/compression/create.c b/tsl/src/compression/create.c index e3ef8aa2f30..286e2db337d 100644 --- a/tsl/src/compression/create.c +++ b/tsl/src/compression/create.c @@ -1063,10 +1063,17 @@ tsl_process_compress_table_add_column(Hypertable *ht, ColumnDef *orig_def) Oid coloid; int32 orig_htid = ht->fd.id; char *colname = orig_def->colname; - TypeName *orig_typname = orig_def->typeName; + FormData_hypertable_compression *ht_comp = + ts_hypertable_compression_get_by_pkey(orig_htid, colname); + /* don't add column if it already exists */ + if (ht_comp) + return; + + TypeName *orig_typname = orig_def->typeName; coloid = LookupTypeNameOid(NULL, orig_typname, false); compresscolinfo_init_singlecolumn(&compress_cols, colname, coloid); + if (TS_HYPERTABLE_HAS_COMPRESSION_TABLE(ht)) { int32 compress_htid = ht->fd.compressed_hypertable_id; diff --git a/tsl/test/expected/compression_errors.out b/tsl/test/expected/compression_errors.out index fbaeda1e223..a1e33bf12d8 100644 --- a/tsl/test/expected/compression_errors.out +++ b/tsl/test/expected/compression_errors.out @@ -494,3 +494,35 @@ SELECT config FROM _timescaledb_config.bgw_job WHERE id = :compressjob_id; CALL run_job(:compressjob_id); ERROR: job 1001 has null config CONTEXT: PL/pgSQL function _timescaledb_internal.policy_compression(integer,jsonb) line 21 at RAISE +-- test ADD COLUMN IF NOT EXISTS +CREATE TABLE metric (time TIMESTAMPTZ NOT NULL, val FLOAT8 NOT NULL, dev_id INT4 NOT NULL); +SELECT create_hypertable('metric', 'time', 'dev_id', 10); + create_hypertable +---------------------- + (23,public,metric,t) +(1 row) + +ALTER TABLE metric SET ( +timescaledb.compress, +timescaledb.compress_segmentby = 'dev_id', +timescaledb.compress_orderby = 'time DESC' +); +INSERT INTO metric(time, val, dev_id) +SELECT s.*, 3.14+1, 1 +FROM generate_series('2021-08-17 00:00:00'::timestamp, + '2021-08-17 00:02:00'::timestamp, '1 s'::interval) s; +SELECT compress_chunk(show_chunks('metric')); + compress_chunk +------------------------------------------ + _timescaledb_internal._hyper_23_17_chunk +(1 row) + +-- column does not exist the first time +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" VARCHAR ; +-- column already exists the second time +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" VARCHAR ; +NOTICE: column "medium" of relation "metric" already exists, skipping +-- also add one without IF NOT EXISTS +ALTER TABLE metric ADD COLUMN "medium_1" VARCHAR ; +ALTER TABLE metric ADD COLUMN "medium_1" VARCHAR ; +ERROR: column "medium_1" of relation "metric" already exists diff --git a/tsl/test/expected/dist_compression.out b/tsl/test/expected/dist_compression.out index bf6edd0276a..5fe99788627 100644 --- a/tsl/test/expected/dist_compression.out +++ b/tsl/test/expected/dist_compression.out @@ -1388,3 +1388,88 @@ ORDER BY 1; c2 (1 row) +-- test ADD COLUMN IF NOT EXISTS on a distributed hypertable +CREATE TABLE metric (time TIMESTAMPTZ NOT NULL, val FLOAT8 NOT NULL, dev_id INT4 NOT NULL); + +SELECT create_distributed_hypertable('metric', 'time'); + create_distributed_hypertable +------------------------------- + (10,public,metric,t) +(1 row) + +ALTER TABLE metric SET ( +timescaledb.compress, +timescaledb.compress_segmentby = 'dev_id', +timescaledb.compress_orderby = 'time DESC' +); + +INSERT INTO metric(time, val, dev_id) +SELECT s.*, 3.14+1, 1 +FROM generate_series('2021-07-01 00:00:00'::timestamp, + '2021-08-17 00:02:00'::timestamp, '30 s'::interval) s; +SELECT compress_chunk(chunk) +FROM show_chunks('metric') AS chunk +ORDER BY chunk; + compress_chunk +----------------------------------------------- + _timescaledb_internal._dist_hyper_10_36_chunk + _timescaledb_internal._dist_hyper_10_37_chunk + _timescaledb_internal._dist_hyper_10_38_chunk + _timescaledb_internal._dist_hyper_10_39_chunk + _timescaledb_internal._dist_hyper_10_40_chunk + _timescaledb_internal._dist_hyper_10_41_chunk + _timescaledb_internal._dist_hyper_10_42_chunk +(7 rows) + +-- make sure we have chunks on all data nodes +select * from timescaledb_information.chunks where hypertable_name like 'metric'; + hypertable_schema | hypertable_name | chunk_schema | chunk_name | primary_dimension | primary_dimension_type | range_start | range_end | range_start_integer | range_end_integer | is_compressed | chunk_tablespace | data_nodes +-------------------+-----------------+-----------------------+-------------------------+-------------------+--------------------------+------------------------------+------------------------------+---------------------+-------------------+---------------+------------------+------------------------- + public | metric | _timescaledb_internal | _dist_hyper_10_36_chunk | time | timestamp with time zone | Wed Jun 30 17:00:00 2021 PDT | Wed Jul 07 17:00:00 2021 PDT | | | t | | {db_dist_compression_2} + public | metric | _timescaledb_internal | _dist_hyper_10_37_chunk | time | timestamp with time zone | Wed Jul 07 17:00:00 2021 PDT | Wed Jul 14 17:00:00 2021 PDT | | | t | | {db_dist_compression_3} + public | metric | _timescaledb_internal | _dist_hyper_10_38_chunk | time | timestamp with time zone | Wed Jul 14 17:00:00 2021 PDT | Wed Jul 21 17:00:00 2021 PDT | | | t | | {db_dist_compression_1} + public | metric | _timescaledb_internal | _dist_hyper_10_39_chunk | time | timestamp with time zone | Wed Jul 21 17:00:00 2021 PDT | Wed Jul 28 17:00:00 2021 PDT | | | t | | {db_dist_compression_2} + public | metric | _timescaledb_internal | _dist_hyper_10_40_chunk | time | timestamp with time zone | Wed Jul 28 17:00:00 2021 PDT | Wed Aug 04 17:00:00 2021 PDT | | | t | | {db_dist_compression_3} + public | metric | _timescaledb_internal | _dist_hyper_10_41_chunk | time | timestamp with time zone | Wed Aug 04 17:00:00 2021 PDT | Wed Aug 11 17:00:00 2021 PDT | | | t | | {db_dist_compression_1} + public | metric | _timescaledb_internal | _dist_hyper_10_42_chunk | time | timestamp with time zone | Wed Aug 11 17:00:00 2021 PDT | Wed Aug 18 17:00:00 2021 PDT | | | t | | {db_dist_compression_2} +(7 rows) + +-- perform all combinations +-- [IF NOT EXISTS] - [] +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" varchar; +-- [IF NOT EXISTS] - ["medium"] +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" varchar; +NOTICE: column "medium" of relation "metric" already exists, skipping +-- [] - [] +ALTER TABLE metric ADD COLUMN "medium_1" varchar; +-- [] - ["medium_1"] +\set ON_ERROR_STOP 0 +ALTER TABLE metric ADD COLUMN "medium_1" varchar; +ERROR: column "medium_1" of relation "metric" already exists + +SELECT * FROM metric limit 5; + time | val | dev_id | medium | medium_1 +------------------------------+------+--------+--------+---------- + Wed Jul 21 16:59:30 2021 PDT | 4.14 | 1 | | + Wed Jul 21 16:59:00 2021 PDT | 4.14 | 1 | | + Wed Jul 21 16:58:30 2021 PDT | 4.14 | 1 | | + Wed Jul 21 16:58:00 2021 PDT | 4.14 | 1 | | + Wed Jul 21 16:57:30 2021 PDT | 4.14 | 1 | | +(5 rows) + +SELECT * FROM metric where medium is not null; + time | val | dev_id | medium | medium_1 +------+-----+--------+--------+---------- +(0 rows) + +-- INSERTs operate normally on the added column +INSERT INTO metric (time, val, dev_id, medium) +SELECT s.*, 3.14+1, 1, 'medium_value_text' +FROM generate_series('2021-08-18 00:00:00'::timestamp, + '2021-08-19 00:02:00'::timestamp, '30 s'::interval) s; +SELECT * FROM metric where medium is not null ORDER BY time LIMIT 1; + time | val | dev_id | medium | medium_1 +------------------------------+------+--------+-------------------+---------- + Wed Aug 18 00:00:00 2021 PDT | 4.14 | 1 | medium_value_text | +(1 row) + diff --git a/tsl/test/sql/compression_errors.sql b/tsl/test/sql/compression_errors.sql index 76b791ca8b1..6c179312d2a 100644 --- a/tsl/test/sql/compression_errors.sql +++ b/tsl/test/sql/compression_errors.sql @@ -282,3 +282,25 @@ SELECT config FROM _timescaledb_config.bgw_job WHERE id = :compressjob_id; --should fail CALL run_job(:compressjob_id); + +-- test ADD COLUMN IF NOT EXISTS +CREATE TABLE metric (time TIMESTAMPTZ NOT NULL, val FLOAT8 NOT NULL, dev_id INT4 NOT NULL); +SELECT create_hypertable('metric', 'time', 'dev_id', 10); +ALTER TABLE metric SET ( +timescaledb.compress, +timescaledb.compress_segmentby = 'dev_id', +timescaledb.compress_orderby = 'time DESC' +); + +INSERT INTO metric(time, val, dev_id) +SELECT s.*, 3.14+1, 1 +FROM generate_series('2021-08-17 00:00:00'::timestamp, + '2021-08-17 00:02:00'::timestamp, '1 s'::interval) s; +SELECT compress_chunk(show_chunks('metric')); +-- column does not exist the first time +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" VARCHAR ; +-- column already exists the second time +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" VARCHAR ; +-- also add one without IF NOT EXISTS +ALTER TABLE metric ADD COLUMN "medium_1" VARCHAR ; +ALTER TABLE metric ADD COLUMN "medium_1" VARCHAR ; \ No newline at end of file diff --git a/tsl/test/sql/dist_compression.sql b/tsl/test/sql/dist_compression.sql index 21b722586d0..07bc3c65a25 100644 --- a/tsl/test/sql/dist_compression.sql +++ b/tsl/test/sql/dist_compression.sql @@ -610,3 +610,45 @@ INNER JOIN _timescaledb_catalog.hypertable ht WHERE attname NOT IN ('time','device','o1','o2') ORDER BY 1; +-- test ADD COLUMN IF NOT EXISTS on a distributed hypertable +CREATE TABLE metric (time TIMESTAMPTZ NOT NULL, val FLOAT8 NOT NULL, dev_id INT4 NOT NULL); + +SELECT create_distributed_hypertable('metric', 'time'); +ALTER TABLE metric SET ( +timescaledb.compress, +timescaledb.compress_segmentby = 'dev_id', +timescaledb.compress_orderby = 'time DESC' +); + +INSERT INTO metric(time, val, dev_id) +SELECT s.*, 3.14+1, 1 +FROM generate_series('2021-07-01 00:00:00'::timestamp, + '2021-08-17 00:02:00'::timestamp, '30 s'::interval) s; + +SELECT compress_chunk(chunk) +FROM show_chunks('metric') AS chunk +ORDER BY chunk; + +-- make sure we have chunks on all data nodes +select * from timescaledb_information.chunks where hypertable_name like 'metric'; +-- perform all combinations +-- [IF NOT EXISTS] - [] +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" varchar; +-- [IF NOT EXISTS] - ["medium"] +ALTER TABLE metric ADD COLUMN IF NOT EXISTS "medium" varchar; +-- [] - [] +ALTER TABLE metric ADD COLUMN "medium_1" varchar; +-- [] - ["medium_1"] +\set ON_ERROR_STOP 0 +ALTER TABLE metric ADD COLUMN "medium_1" varchar; + +SELECT * FROM metric limit 5; +SELECT * FROM metric where medium is not null; + +-- INSERTs operate normally on the added column +INSERT INTO metric (time, val, dev_id, medium) +SELECT s.*, 3.14+1, 1, 'medium_value_text' +FROM generate_series('2021-08-18 00:00:00'::timestamp, + '2021-08-19 00:02:00'::timestamp, '30 s'::interval) s; + +SELECT * FROM metric where medium is not null ORDER BY time LIMIT 1; \ No newline at end of file