mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-18 03:23:37 +08:00
Previously, an ALTER TABLE ADD COLUMN IF NOT EXISTS on a hypertable caused an error if the column already did exist. The reversed problem with DROP COLUMN is also fixed. Resolves issue #42.
286 lines
11 KiB
PL/PgSQL
286 lines
11 KiB
PL/PgSQL
/*
|
|
This file creates function to intercept ddl commands executed on the
|
|
main tables of hypertables and convert them to commands that alter
|
|
the coresponding schema structure of hypertables.
|
|
|
|
The basic idea is that the main table is a regular sql tables that represents
|
|
the hypertable. DDL changes to that table have corresponding changes on the hypertable.
|
|
|
|
The intercepted schema changes work as follows:
|
|
1) a ddl command is intercepted and it is determined whether it's on a hypertable, if not exit.
|
|
2) it is determined whether this is a user or trigger initate command. If it is trigger initiated, exit.
|
|
3) send the corresponding hypertable ddl command to the meta node.
|
|
|
|
Because ddl commands cannot be "canceled" without an error, all comands are allowed to succeed. This is
|
|
why the command sent to the meta node always contains a parameter for the node the ddl command was executed on
|
|
(so that the meta node can know that the main table on that node was already modified and does not needed to be
|
|
modified again).
|
|
|
|
*/
|
|
|
|
--Handles ddl create index commands on hypertables
|
|
CREATE OR REPLACE FUNCTION _timescaledb_internal.ddl_process_create_index()
|
|
RETURNS event_trigger LANGUAGE plpgsql AS
|
|
$BODY$
|
|
DECLARE
|
|
info record;
|
|
table_oid regclass;
|
|
def TEXT;
|
|
hypertable_row _timescaledb_catalog.hypertable;
|
|
BEGIN
|
|
FOR info IN SELECT * FROM pg_event_trigger_ddl_commands()
|
|
LOOP
|
|
--get table oid
|
|
SELECT indrelid INTO STRICT table_oid
|
|
FROM pg_catalog.pg_index
|
|
WHERE indexrelid = info.objid;
|
|
|
|
IF NOT _timescaledb_internal.is_main_table(table_oid) OR current_setting('io.ignore_ddl_in_trigger', true) = 'true' THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
hypertable_row := _timescaledb_internal.hypertable_from_main_table(table_oid);
|
|
def = _timescaledb_internal.get_general_index_definition(info.objid, table_oid, hypertable_row);
|
|
|
|
PERFORM _timescaledb_meta_api.add_index(
|
|
hypertable_row.id,
|
|
hypertable_row.schema_name,
|
|
(SELECT relname FROM pg_class WHERE oid = info.objid::regclass),
|
|
def
|
|
);
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|
|
|
|
--Handles ddl create trigger commands on hypertables
|
|
CREATE OR REPLACE FUNCTION _timescaledb_internal.ddl_process_create_trigger()
|
|
RETURNS event_trigger LANGUAGE plpgsql AS
|
|
$BODY$
|
|
DECLARE
|
|
info record;
|
|
table_oid regclass;
|
|
trigger_name TEXT;
|
|
BEGIN
|
|
FOR info IN SELECT * FROM pg_event_trigger_ddl_commands()
|
|
LOOP
|
|
SELECT tgrelid, tgname INTO STRICT table_oid, trigger_name
|
|
FROM pg_catalog.pg_trigger
|
|
WHERE oid = info.objid;
|
|
|
|
IF _timescaledb_internal.is_main_table(table_oid)
|
|
AND trigger_name != '_timescaledb_main_insert_trigger'
|
|
AND trigger_name != '_timescaledb_main_after_insert_trigger'
|
|
AND trigger_name != '_timescaledb_modify_trigger'
|
|
THEN
|
|
RAISE EXCEPTION 'CREATE TRIGGER not supported on hypertable %', table_oid
|
|
USING ERRCODE = 'IO101';
|
|
END IF;
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|
|
|
|
--Handles ddl alter index commands on hypertables
|
|
CREATE OR REPLACE FUNCTION _timescaledb_internal.ddl_process_alter_index()
|
|
RETURNS event_trigger LANGUAGE plpgsql AS
|
|
$BODY$
|
|
DECLARE
|
|
info record;
|
|
table_oid regclass;
|
|
BEGIN
|
|
FOR info IN SELECT * FROM pg_event_trigger_ddl_commands()
|
|
LOOP
|
|
SELECT indrelid INTO STRICT table_oid
|
|
FROM pg_catalog.pg_index
|
|
WHERE indexrelid = info.objid;
|
|
|
|
IF NOT _timescaledb_internal.is_main_table(table_oid) THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
RAISE EXCEPTION 'ALTER INDEX not supported on hypertable %', table_oid
|
|
USING ERRCODE = 'IO101';
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|
|
|
|
--Handles ddl drop index commands on hypertables
|
|
CREATE OR REPLACE FUNCTION _timescaledb_internal.ddl_process_drop_index()
|
|
RETURNS event_trigger LANGUAGE plpgsql AS
|
|
$BODY$
|
|
DECLARE
|
|
info record;
|
|
table_oid regclass;
|
|
hypertable_row _timescaledb_catalog.hypertable;
|
|
BEGIN
|
|
FOR info IN SELECT * FROM pg_event_trigger_dropped_objects()
|
|
LOOP
|
|
SELECT format('%I.%I', h.schema_name, h.table_name) INTO table_oid
|
|
FROM _timescaledb_catalog.hypertable h
|
|
INNER JOIN _timescaledb_catalog.hypertable_index i ON (i.hypertable_id = h.id)
|
|
WHERE i.main_schema_name = info.schema_name AND i.main_index_name = info.object_name;
|
|
|
|
--if table_oid is not null, it is a main table
|
|
IF table_oid IS NULL OR current_setting('io.ignore_ddl_in_trigger', true) = 'true' THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
--TODO: this ignores the concurrently and cascade/restrict modifiers
|
|
PERFORM _timescaledb_meta_api.drop_index(info.schema_name, info.object_name);
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|
|
|
|
--Handles the following ddl alter table commands on hypertables:
|
|
-- ADD COLUMN, DROP COLUMN, ALTER COLUMN SET DEFAULT, ALTER COLUMN DROP DEFAULT, ALTER COLUMN SET/DROP NOT NULL
|
|
-- RENAME COLUMN
|
|
-- not supported (explicit):
|
|
-- ALTER COLUMN SET DATA TYPE
|
|
-- Other alter commands also not supported
|
|
CREATE OR REPLACE FUNCTION _timescaledb_internal.ddl_process_alter_table()
|
|
RETURNS event_trigger LANGUAGE plpgsql AS
|
|
$BODY$
|
|
DECLARE
|
|
info record;
|
|
hypertable_row _timescaledb_catalog.hypertable;
|
|
found_action BOOLEAN;
|
|
att_row pg_attribute;
|
|
rec record;
|
|
BEGIN
|
|
FOR info IN SELECT * FROM pg_event_trigger_ddl_commands()
|
|
LOOP
|
|
--exit if not hypertable or inside trigger
|
|
IF NOT _timescaledb_internal.is_main_table(info.objid) OR current_setting('io.ignore_ddl_in_trigger', true) = 'true' THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
hypertable_row := _timescaledb_internal.hypertable_from_main_table(info.objid);
|
|
--Try to find what was done. If you can't find it error out.
|
|
found_action = FALSE;
|
|
|
|
--was a column added?
|
|
|
|
IF ddl_is_add_column(info.command) THEN
|
|
FOR att_row IN
|
|
SELECT *
|
|
FROM pg_attribute att
|
|
WHERE attrelid = info.objid AND
|
|
attnum > 0 AND
|
|
NOT attisdropped AND
|
|
att.attnum NOT IN (SELECT c.attnum FROM _timescaledb_catalog.hypertable_column c WHERE hypertable_id = hypertable_row.id)
|
|
LOOP
|
|
PERFORM _timescaledb_internal.create_column_from_attribute(hypertable_row.id, att_row);
|
|
END LOOP;
|
|
|
|
found_action = TRUE;
|
|
END IF;
|
|
|
|
--was a column deleted
|
|
IF ddl_is_drop_column(info.command) THEN
|
|
FOR rec IN
|
|
SELECT name
|
|
FROM _timescaledb_catalog.hypertable_column c
|
|
INNER JOIN pg_attribute att ON (attrelid = info.objid AND att.attnum = c.attnum AND attisdropped) --do not match on att.attname here. it gets mangled
|
|
WHERE hypertable_id = hypertable_row.id
|
|
LOOP
|
|
PERFORM _timescaledb_meta_api.drop_column(hypertable_row.id, rec.name);
|
|
END LOOP;
|
|
found_action = TRUE;
|
|
END IF;
|
|
|
|
--was a column renamed
|
|
FOR rec IN
|
|
SELECT c.name old_name, att.attname new_name
|
|
FROM _timescaledb_catalog.hypertable_column c
|
|
LEFT JOIN pg_attribute att ON (attrelid = info.objid AND att.attnum = c.attnum AND NOT attisdropped)
|
|
WHERE hypertable_id = hypertable_row.id AND c.name IS DISTINCT FROM att.attname
|
|
LOOP
|
|
PERFORM _timescaledb_meta_api.alter_table_rename_column(
|
|
hypertable_row.id,
|
|
rec.old_name,
|
|
rec.new_name
|
|
);
|
|
found_action = TRUE;
|
|
END LOOP;
|
|
|
|
--was a column default changed
|
|
FOR rec IN
|
|
SELECT name, _timescaledb_internal.get_default_value_for_attribute(att) AS new_default_value
|
|
FROM _timescaledb_catalog.hypertable_column c
|
|
LEFT JOIN pg_attribute att ON (attrelid = info.objid AND attname = c.name AND att.attnum = c.attnum AND NOT attisdropped)
|
|
WHERE hypertable_id = hypertable_row.id AND _timescaledb_internal.get_default_value_for_attribute(att) IS DISTINCT FROM c.default_value
|
|
LOOP
|
|
PERFORM _timescaledb_meta_api.alter_column_set_default(
|
|
hypertable_row.id,
|
|
rec.name,
|
|
rec.new_default_value
|
|
);
|
|
found_action = TRUE;
|
|
END LOOP;
|
|
|
|
--was the not null flag changed?
|
|
FOR rec IN
|
|
SELECT name, attnotnull AS new_not_null
|
|
FROM _timescaledb_catalog.hypertable_column c
|
|
LEFT JOIN pg_attribute att ON (attrelid = info.objid AND attname = c.name AND att.attnum = c.attnum AND NOT attisdropped)
|
|
WHERE hypertable_id = hypertable_row.id AND attnotnull != c.not_null
|
|
LOOP
|
|
PERFORM _timescaledb_meta_api.alter_column_set_not_null(
|
|
hypertable_row.id,
|
|
rec.name,
|
|
rec.new_not_null
|
|
);
|
|
found_action = TRUE;
|
|
END LOOP;
|
|
|
|
--type changed
|
|
FOR rec IN
|
|
SELECT name
|
|
FROM _timescaledb_catalog.hypertable_column c
|
|
INNER JOIN pg_attribute att ON (attrelid = info.objid AND attname = c.name AND att.attnum = c.attnum AND NOT attisdropped)
|
|
WHERE hypertable_id = hypertable_row.id AND att.atttypid IS DISTINCT FROM c.data_type
|
|
LOOP
|
|
RAISE EXCEPTION 'ALTER TABLE ... ALTER COLUMN SET DATA TYPE not supported on hypertable %', info.objid::regclass
|
|
USING ERRCODE = 'IO101';
|
|
END LOOP;
|
|
|
|
IF NOT found_action THEN
|
|
RAISE EXCEPTION 'Unknown alter table action on %', info.objid::regclass
|
|
USING ERRCODE = 'IO101';
|
|
END IF;
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|
|
|
|
CREATE OR REPLACE FUNCTION _timescaledb_internal.is_hypertable(
|
|
schema_name NAME,
|
|
table_name NAME
|
|
)
|
|
RETURNS BOOLEAN LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
BEGIN
|
|
RETURN EXISTS(
|
|
SELECT 1 FROM _timescaledb_catalog.hypertable h
|
|
WHERE h.schema_name = is_hypertable.schema_name AND h.table_name = is_hypertable.table_name
|
|
);
|
|
END
|
|
$BODY$;
|
|
|
|
--Handles drop table command
|
|
CREATE OR REPLACE FUNCTION _timescaledb_internal.ddl_process_drop_table()
|
|
RETURNS event_trigger LANGUAGE plpgsql AS $BODY$
|
|
DECLARE
|
|
obj record;
|
|
BEGIN
|
|
IF current_setting('io.ignore_ddl_in_trigger', true) = 'true' THEN
|
|
RETURN;
|
|
END IF;
|
|
|
|
FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
|
|
LOOP
|
|
IF tg_tag = 'DROP TABLE' AND _timescaledb_internal.is_hypertable(obj.schema_name, obj.object_name) THEN
|
|
PERFORM _timescaledb_meta_api.drop_hypertable(obj.schema_name, obj.object_name);
|
|
END IF;
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|