mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-19 20:24:46 +08:00
333 lines
12 KiB
PL/PgSQL
333 lines
12 KiB
PL/PgSQL
-- This file has functions that implement changes to hypertable columns as
|
|
-- change to the underlying chunk tables.
|
|
|
|
-- TODO(mat) - Doc this? Not sure I can do it justice.
|
|
CREATE OR REPLACE FUNCTION _sysinternal.create_partition_constraint_for_field(
|
|
hypertable_name NAME,
|
|
field_name NAME
|
|
)
|
|
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
BEGIN
|
|
PERFORM _sysinternal.add_partition_constraint(pr.schema_name, pr.table_name, p.keyspace_start, p.keyspace_end,
|
|
p.epoch_id)
|
|
FROM partition_epoch pe
|
|
INNER JOIN partition p ON (p.epoch_id = pe.id)
|
|
INNER JOIN partition_replica pr ON (pr.partition_id = p.id)
|
|
WHERE pe.hypertable_name = create_partition_constraint_for_field.hypertable_name
|
|
AND pe.partitioning_field = create_partition_constraint_for_field.field_name;
|
|
END
|
|
$BODY$;
|
|
|
|
-- Adds a field to a table (e.g. main table or root table)
|
|
CREATE OR REPLACE FUNCTION _sysinternal.create_field_on_table(
|
|
schema_name NAME,
|
|
table_name NAME,
|
|
field NAME,
|
|
attnum int2,
|
|
data_type_oid REGTYPE,
|
|
default_value TEXT,
|
|
not_null BOOLEAN
|
|
)
|
|
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
DECLARE
|
|
null_constraint TEXT := 'NOT NULL';
|
|
default_constraint TEXT := '';
|
|
created_columns_att_num INT2;
|
|
BEGIN
|
|
IF NOT not_null THEN
|
|
null_constraint = 'NULL';
|
|
END IF;
|
|
|
|
default_constraint = 'DEFAULT '|| default_value;
|
|
|
|
EXECUTE format(
|
|
$$
|
|
ALTER TABLE %1$I.%2$I ADD COLUMN %3$I %4$s %5$s %6$s
|
|
$$,
|
|
schema_name, table_name, field, data_type_oid, default_constraint, null_constraint);
|
|
|
|
SELECT att.attnum INTO STRICT created_columns_att_num
|
|
FROM pg_attribute att
|
|
WHERE att.attrelid = format('%I.%I', schema_name, table_name)::regclass AND att.attname = field
|
|
AND NOT attisdropped;
|
|
|
|
IF created_columns_att_num IS DISTINCT FROM attnum THEN
|
|
RAISE EXCEPTION 'Inconsistent state: the attnum of newly created colum does not match (% vs %)', attnum, created_columns_att_num
|
|
USING ERRCODE = 'IO501';
|
|
END IF;
|
|
END
|
|
$BODY$;
|
|
|
|
|
|
-- Removes a field from a table (e.g. main table or root table)
|
|
CREATE OR REPLACE FUNCTION _sysinternal.drop_field_on_table(
|
|
schema_name NAME,
|
|
table_name NAME,
|
|
field NAME
|
|
) RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
BEGIN
|
|
EXECUTE format(
|
|
$$
|
|
ALTER TABLE %1$I.%2$I DROP COLUMN %3$I
|
|
$$,
|
|
schema_name, table_name, field);
|
|
END
|
|
$BODY$;
|
|
|
|
-- Changes the default of a field in a table (e.g. main table or root table)
|
|
CREATE OR REPLACE FUNCTION _sysinternal.exec_alter_column_set_default(
|
|
schema_name NAME,
|
|
table_name NAME,
|
|
field NAME,
|
|
new_default_value TEXT
|
|
) RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
BEGIN
|
|
EXECUTE format(
|
|
$$
|
|
ALTER TABLE %1$I.%2$I ALTER COLUMN %3$I SET DEFAULT %4$L
|
|
$$,
|
|
schema_name, table_name, field, new_default_value);
|
|
END
|
|
$BODY$;
|
|
|
|
-- Renames a field of a table (e.g. main table or root table)
|
|
CREATE OR REPLACE FUNCTION _sysinternal.exec_alter_table_rename_column(
|
|
schema_name NAME,
|
|
table_name NAME,
|
|
old_field NAME,
|
|
new_field NAME
|
|
) RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
BEGIN
|
|
EXECUTE format(
|
|
$$
|
|
ALTER TABLE %1$I.%2$I RENAME COLUMN %3$I TO %4$I
|
|
$$,
|
|
schema_name, table_name, old_field, new_field);
|
|
END
|
|
$BODY$;
|
|
|
|
-- Sets a field of a table (e.g. main table or root table) to NOT NULL
|
|
CREATE OR REPLACE FUNCTION _sysinternal.exec_alter_column_set_not_null(
|
|
schema_name NAME,
|
|
table_name NAME,
|
|
field NAME,
|
|
new_not_null BOOLEAN
|
|
) RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
BEGIN
|
|
IF new_not_null THEN
|
|
EXECUTE format(
|
|
$$
|
|
ALTER TABLE %1$I.%2$I ALTER COLUMN %3$I SET NOT NULL
|
|
$$,
|
|
schema_name, table_name, field);
|
|
ELSE
|
|
EXECUTE format(
|
|
$$
|
|
ALTER TABLE %1$I.%2$I ALTER COLUMN %3$I DROP NOT NULL
|
|
$$,
|
|
schema_name, table_name, field);
|
|
END IF;
|
|
END
|
|
$BODY$;
|
|
|
|
-- Adds distinct values for a field to a hypertable's distinct table.
|
|
CREATE OR REPLACE FUNCTION _sysinternal.populate_distinct_table(
|
|
hypertable_name NAME,
|
|
field NAME
|
|
) RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
DECLARE
|
|
distinct_replica_node_row distinct_replica_node;
|
|
chunk_replica_node_row chunk_replica_node;
|
|
BEGIN
|
|
FOR distinct_replica_node_row IN
|
|
SELECT *
|
|
FROM distinct_replica_node drn
|
|
WHERE drn.hypertable_name = populate_distinct_table.hypertable_name AND
|
|
drn.database_name = current_database()
|
|
LOOP
|
|
FOR chunk_replica_node_row IN
|
|
SELECT crn.*
|
|
FROM chunk_replica_node crn
|
|
INNER JOIN partition_replica pr ON (pr.id = crn.partition_replica_id)
|
|
WHERE pr.hypertable_name = distinct_replica_node_row.hypertable_name AND
|
|
pr.replica_id = distinct_replica_node_row.replica_id AND
|
|
crn.database_name = current_database()
|
|
LOOP
|
|
EXECUTE format($$
|
|
INSERT INTO %I.%I(field, value)
|
|
SELECT DISTINCT %L, %I
|
|
FROM %I.%I
|
|
ON CONFLICT DO NOTHING
|
|
$$,
|
|
distinct_replica_node_row.schema_name, distinct_replica_node_row.table_name,
|
|
field, field,
|
|
chunk_replica_node_row.schema_name, chunk_replica_node_row.table_name
|
|
);
|
|
END LOOP;
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|
|
|
|
-- Removes distinct values for a field from a hypertable's distinct table.
|
|
CREATE OR REPLACE FUNCTION _sysinternal.unpopulate_distinct_table(
|
|
hypertable_name NAME,
|
|
field NAME
|
|
) RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
|
|
$BODY$
|
|
DECLARE
|
|
distinct_replica_node_row distinct_replica_node;
|
|
BEGIN
|
|
FOR distinct_replica_node_row IN
|
|
SELECT *
|
|
FROM distinct_replica_node drn
|
|
WHERE drn.hypertable_name = unpopulate_distinct_table.hypertable_name AND
|
|
drn.database_name = current_database()
|
|
LOOP
|
|
EXECUTE format($$
|
|
DELETE FROM %I.%I WHERE field = %L
|
|
$$,
|
|
distinct_replica_node_row.schema_name, distinct_replica_node_row.table_name, field
|
|
);
|
|
END LOOP;
|
|
END
|
|
$BODY$;
|
|
|
|
-- Trigger to modify a field from a hypertable.
|
|
-- Called when the user alters the main table by adding a field or changing
|
|
-- the properties of a field.
|
|
CREATE OR REPLACE FUNCTION _sysinternal.on_modify_field()
|
|
RETURNS TRIGGER LANGUAGE PLPGSQL AS
|
|
$BODY$
|
|
DECLARE
|
|
hypertable_row hypertable;
|
|
update_found BOOLEAN = false;
|
|
BEGIN
|
|
|
|
IF TG_OP = 'INSERT' THEN
|
|
SELECT *
|
|
INTO STRICT hypertable_row
|
|
FROM hypertable AS h
|
|
WHERE h.name = NEW.hypertable_name;
|
|
|
|
-- update root table
|
|
PERFORM _sysinternal.create_field_on_table(hypertable_row.root_schema_name, hypertable_row.root_table_name,
|
|
NEW.name, NEW.attnum, NEW.data_type, NEW.default_value, NEW.not_null);
|
|
IF new.created_on <> current_database() THEN
|
|
PERFORM set_config('io.ignore_ddl_in_trigger', 'true', true);
|
|
-- update main table on others
|
|
PERFORM _sysinternal.create_field_on_table(hypertable_row.main_schema_name, hypertable_row.main_table_name,
|
|
NEW.name, NEW.attnum, NEW.data_type, NEW.default_value, NEW.not_null);
|
|
END IF;
|
|
|
|
PERFORM _sysinternal.create_partition_constraint_for_field(NEW.hypertable_name, NEW.name);
|
|
RETURN NEW;
|
|
ELSIF TG_OP = 'UPDATE' THEN
|
|
SELECT *
|
|
INTO STRICT hypertable_row
|
|
FROM hypertable AS h
|
|
WHERE h.name = NEW.hypertable_name;
|
|
|
|
IF NEW.default_value IS DISTINCT FROM OLD.default_value THEN
|
|
update_found = TRUE;
|
|
-- update root table
|
|
PERFORM _sysinternal.exec_alter_column_set_default(hypertable_row.root_schema_name, hypertable_row.root_table_name,
|
|
NEW.name, NEW.default_value);
|
|
IF NEW.modified_on <> current_database() THEN
|
|
PERFORM set_config('io.ignore_ddl_in_trigger', 'true', true);
|
|
-- update main table on others
|
|
PERFORM _sysinternal.exec_alter_column_set_default(hypertable_row.main_schema_name, hypertable_row.main_table_name,
|
|
NEW.name, NEW.default_value);
|
|
END IF;
|
|
END IF;
|
|
IF NEW.not_null IS DISTINCT FROM OLD.not_null THEN
|
|
update_found = TRUE;
|
|
-- update root table
|
|
PERFORM _sysinternal.exec_alter_column_set_not_null(hypertable_row.root_schema_name, hypertable_row.root_table_name,
|
|
NEW.name, NEW.not_null);
|
|
IF NEW.modified_on <> current_database() THEN
|
|
PERFORM set_config('io.ignore_ddl_in_trigger', 'true', true);
|
|
-- update main table on others
|
|
PERFORM _sysinternal.exec_alter_column_set_not_null(hypertable_row.main_schema_name, hypertable_row.main_table_name,
|
|
NEW.name, NEW.not_null);
|
|
END IF;
|
|
END IF;
|
|
IF NEW.name IS DISTINCT FROM OLD.name THEN
|
|
update_found = TRUE;
|
|
-- update root table
|
|
PERFORM _sysinternal.exec_alter_table_rename_column(hypertable_row.root_schema_name, hypertable_row.root_table_name,
|
|
OLD.name, NEW.name);
|
|
IF NEW.modified_on <> current_database() THEN
|
|
PERFORM set_config('io.ignore_ddl_in_trigger', 'true', true);
|
|
-- update main table on others
|
|
PERFORM _sysinternal.exec_alter_table_rename_column(hypertable_row.main_schema_name, hypertable_row.main_table_name,
|
|
OLD.name, NEW.name);
|
|
END IF;
|
|
END IF;
|
|
|
|
IF NEW.is_distinct IS DISTINCT FROM OLD.is_distinct THEN
|
|
update_found = TRUE;
|
|
IF NEW.is_distinct THEN
|
|
PERFORM _sysinternal.populate_distinct_table(NEW.hypertable_name, NEW.name);
|
|
ELSE
|
|
PERFORM _sysinternal.unpopulate_distinct_table(NEW.hypertable_name, NEW.name);
|
|
END IF;
|
|
END IF;
|
|
|
|
IF NOT update_found THEN
|
|
RAISE EXCEPTION 'Invalid update type on %', TG_TABLE_NAME
|
|
USING ERRCODE = 'IO101';
|
|
END IF;
|
|
RETURN NEW;
|
|
ELSIF TG_OP = 'DELETE' THEN
|
|
--handled by deleted log
|
|
RETURN OLD;
|
|
END IF;
|
|
END
|
|
$BODY$
|
|
SET SEARCH_PATH = 'public';
|
|
|
|
-- Trigger to remove a field from a hypertable.
|
|
-- Called when the user alters the main table by deleting a field.
|
|
CREATE OR REPLACE FUNCTION _sysinternal.on_deleted_field()
|
|
RETURNS TRIGGER LANGUAGE PLPGSQL AS
|
|
$BODY$
|
|
DECLARE
|
|
hypertable_row hypertable;
|
|
BEGIN
|
|
IF TG_OP <> 'INSERT' THEN
|
|
RAISE EXCEPTION 'Only inserts supported on % table', TG_TABLE_NAME
|
|
USING ERRCODE = 'IO101';
|
|
END IF;
|
|
|
|
SELECT *
|
|
INTO hypertable_row
|
|
FROM hypertable AS h
|
|
WHERE h.name = NEW.hypertable_name;
|
|
|
|
IF hypertable_row IS NULL THEN
|
|
--presumably hypertable has been dropped and this is part of cascade
|
|
RETURN NEW;
|
|
END IF;
|
|
|
|
-- update root table
|
|
PERFORM _sysinternal.drop_field_on_table(hypertable_row.root_schema_name, hypertable_row.root_table_name,
|
|
NEW.name);
|
|
IF NEW.deleted_on <> current_database() THEN
|
|
PERFORM set_config('io.ignore_ddl_in_trigger', 'true', true);
|
|
-- update main table on others
|
|
PERFORM _sysinternal.drop_field_on_table(hypertable_row.main_schema_name, hypertable_row.main_table_name,
|
|
NEW.name);
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END
|
|
$BODY$
|
|
SET SEARCH_PATH = 'public';
|