/*
  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$;
