/*
 * Copyright (c) 2020 Huawei Technologies Co.,Ltd.
 *
 * openGauss is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 *
 * purge.sql
 *    Purge DB4AI.Snapshot functionality.
 *
 * IDENTIFICATION
 *    src/gausskernel/dbmind/db4ai/snapshots/purge.sql
 *
 * -------------------------------------------------------------------------
 */

CREATE OR REPLACE FUNCTION db4ai.purge_snapshot_internal(
    IN i_schema NAME,    -- snapshot namespace
    IN i_name NAME       -- snapshot name
)
RETURNS VOID LANGUAGE plpgsql SECURITY INVOKER
AS $$
DECLARE
    s_id BIGINT;         -- snapshot id
    p_id BIGINT;         -- parent id
    m_id BIGINT;         -- matrix id
    o_id BIGINT[];       -- other snapshot ids in same backing table
    pushed_cmds TEXT[];  -- commands to be pushed to descendants
    pushed_comment TEXT; -- comments to be pushed to descendants
    drop_cols NAME[];    -- orphaned columns
    e_stack_act TEXT;    -- current stack for validation
    affected BIGINT;     -- number of affected rows;
BEGIN
    BEGIN
        RAISE EXCEPTION 'SECURITY_STACK_CHECK';
    EXCEPTION WHEN OTHERS THEN
        GET STACKED DIAGNOSTICS e_stack_act = PG_EXCEPTION_CONTEXT;

        IF CURRENT_SCHEMA = 'db4ai' THEN
            e_stack_act := pg_catalog.replace(e_stack_act, 'ion pur', 'ion db4ai.pur');
        END IF;

        IF e_stack_act NOT LIKE 'referenced column: purge_snapshot_internal
SQL statement "SELECT db4ai.purge_snapshot_internal(i_schema, i_name)"
PL/pgSQL function db4ai.purge_snapshot(name,name) line 87 at PERFORM%'
        THEN
            RAISE EXCEPTION 'direct call to db4ai.purge_snapshot_internal(name,name) is not allowed'
            USING HINT = 'call public interface db4ai.purge_snapshot instead';
        END IF;
    END;

    -- check if snapshot exists
    BEGIN
        SELECT commands, comment, id, parent_id, matrix_id FROM db4ai.snapshot WHERE schema = i_schema AND name = i_name
            INTO STRICT pushed_cmds, pushed_comment, s_id, p_id, m_id;
    EXCEPTION WHEN NO_DATA_FOUND THEN
        RAISE EXCEPTION 'snapshot %.% does not exist' , pg_catalog.quote_ident(i_schema), pg_catalog.quote_ident(i_name);
    END;

    -- update descendants, if any
    UPDATE db4ai.snapshot SET
        parent_id = p_id,
        commands = pushed_cmds || commands,
        comment = CASE WHEN pushed_comment IS NULL THEN comment
                       WHEN comment IS NULL THEN pushed_comment
                       ELSE pushed_comment || ' | ' || comment END
        WHERE parent_id = s_id;
    IF p_id IS NULL AND SQL%ROWCOUNT > 0 THEN
        RAISE EXCEPTION 'cannot purge root snapshot ''%.%'' having dependent snapshots', pg_catalog.quote_ident(i_schema), pg_catalog.quote_ident(i_name)
        USING HINT = 'purge all dependent snapshots first';
    END IF;

    IF m_id IS NULL THEN
        EXECUTE 'DROP VIEW db4ai.v' || s_id::TEXT;
        EXECUTE 'DROP TABLE db4ai.t' || s_id::TEXT;
        RAISE NOTICE 'PURGE_SNAPSHOT: MSS backing table dropped';
    ELSE
        SELECT pg_catalog.array_agg(id) FROM db4ai.snapshot WHERE matrix_id = m_id AND id <> s_id INTO STRICT o_id;

        IF o_id IS NULL OR pg_catalog.array_length(o_id, 1) = 0 OR pg_catalog.array_length(o_id, 1) IS NULL THEN
            EXECUTE 'DROP VIEW db4ai.v' || s_id::TEXT;
            EXECUTE 'DROP TABLE db4ai.t' || m_id::TEXT;
            RAISE NOTICE 'PURGE_SNAPSHOT: CSS backing table dropped';
        ELSE
            EXECUTE 'DELETE FROM db4ai.t' || m_id::TEXT || ' WHERE _' || s_id::TEXT || ' AND NOT (_' || pg_catalog.array_to_string(o_id, ' OR _') || ')';
            GET DIAGNOSTICS affected = ROW_COUNT;

            SELECT pg_catalog.array_agg(pg_catalog.quote_ident(column_name))
            FROM  ( SELECT column_name
                    FROM information_schema.columns
                    WHERE table_schema = 'db4ai' AND table_name = ANY ( ('{v' || pg_catalog.array_to_string(s_id || o_id, ',v') || '}')::NAME[] )
                    GROUP BY column_name
                    HAVING SUM(CASE table_name WHEN 'v' || s_id::TEXT THEN 0 ELSE 1 END) = 0 )
            INTO STRICT drop_cols;

            EXECUTE 'DROP VIEW db4ai.v' || s_id::TEXT;

            EXECUTE 'ALTER TABLE db4ai.t' || m_id::TEXT || ' DROP _' || s_id::TEXT;
            RAISE NOTICE 'PURGE_SNAPSHOT: orphaned rows dropped: %, orphaned columns dropped: none', affected;
        END IF;
    END IF;

    DELETE FROM db4ai.snapshot WHERE schema = i_schema AND name = i_name;
    IF SQL%ROWCOUNT = 0 THEN
        -- checked before, this should never happen
        RAISE INFO 'snapshot %.% does not exist' , pg_catalog.quote_ident(i_schema), pg_catalog.quote_ident(i_name);
    END IF;
END;
$$;

CREATE OR REPLACE FUNCTION db4ai.purge_snapshot(
    IN i_schema NAME,    -- snapshot namespace, default is CURRENT_USER or PUBLIC
    IN i_name NAME       -- snapshot name
)
RETURNS db4ai.snapshot_name LANGUAGE plpgsql SECURITY INVOKER SET client_min_messages TO ERROR
AS $$
DECLARE
    adminuser BOOLEAN;              -- current user privileges
    s_mode VARCHAR(3);              -- current snapshot mode
    s_vers_del CHAR;                -- snapshot version delimiter, default '@'
    s_vers_sep CHAR;                -- snapshot version separator, default '.'
    s_name_vers TEXT[];             -- split full name into name and version
    current_compatibility_mode TEXT;-- current compatibility mode
    none_represent INT;             -- 0 or NULL
    res db4ai.snapshot_name;        -- composite result
BEGIN

    -- obtain active message level
    BEGIN
        EXECUTE 'SET LOCAL client_min_messages TO ' || pg_catalog.quote_ident(pg_catalog.current_setting('db4ai.message_level'));
        RAISE INFO 'effective client_min_messages is ''%''', pg_catalog.upper(pg_catalog.current_setting('db4ai.message_level'));
    EXCEPTION WHEN OTHERS THEN
    END;

    BEGIN
        EXECUTE 'SELECT rolsystemadmin FROM pg_roles WHERE rolname=CURRENT_USER' INTO STRICT adminuser;
        IF adminuser IS FALSE THEN
            RAISE EXCEPTION 'In the current version, the DB4AI.SNAPSHOT feature is available only to administrators.';
        END IF;
    END;

    IF i_schema LIKE ANY(ARRAY['%;%', '%[%', '%]%', '%,%', '%(%', '%)%']) THEN
        RAISE EXCEPTION 'Please use specification input: schema name';
    END IF;

    IF i_name LIKE ANY(ARRAY['%;%', '%[%', '%]%', '%,%', '%(%', '%)%']) THEN
        RAISE EXCEPTION 'Please use specification input: snapshot name';
    END IF;

    -- obtain active snapshot mode
    BEGIN
        s_mode := pg_catalog.upper(pg_catalog.current_setting('db4ai_snapshot_mode'));
    EXCEPTION WHEN OTHERS THEN
        s_mode := 'MSS';
    END;

    IF s_mode NOT IN ('CSS', 'MSS') THEN
        RAISE EXCEPTION 'invalid snapshot mode: ''%''', s_mode;
    END IF;

    -- obtain relevant configuration parameters
    BEGIN
        s_vers_del := pg_catalog.current_setting('db4ai_snapshot_version_delimiter');
    EXCEPTION WHEN OTHERS THEN
        s_vers_del := '@';
    END;
    BEGIN
        s_vers_sep := pg_catalog.upper(pg_catalog.current_setting('db4ai_snapshot_version_separator'));
    EXCEPTION WHEN OTHERS THEN
        s_vers_sep := '.';
    END;

    -- check all input parameters
    IF i_schema IS NULL OR i_schema = '' THEN
        i_schema := CASE WHEN (SELECT 0=COUNT(*) FROM pg_catalog.pg_namespace WHERE nspname = CURRENT_USER) THEN 'public' ELSE CURRENT_USER END;
    END IF;

    current_compatibility_mode := pg_catalog.current_setting('sql_compatibility');
    IF current_compatibility_mode = 'ORA' OR current_compatibility_mode = 'A' THEN
        none_represent := 0;
    ELSE
        none_represent := NULL;
    END IF;

    IF i_name IS NULL OR i_name = '' THEN
        RAISE EXCEPTION 'i_name cannot be NULL or empty';
    ELSE
        i_name := pg_catalog.replace(i_name, pg_catalog.chr(1), s_vers_del);
        i_name := pg_catalog.replace(i_name, pg_catalog.chr(2), s_vers_sep);
        s_name_vers := pg_catalog.regexp_split_to_array(i_name, s_vers_del);
        IF pg_catalog.array_length(s_name_vers, 1) <> 2 OR pg_catalog.array_length(s_name_vers, 2) <> none_represent THEN
            RAISE EXCEPTION 'i_name must contain exactly one ''%'' character', s_vers_del
            USING HINT = 'reference a snapshot using the format: snapshot_name' || s_vers_del || 'version';
        END IF;
    END IF;

    BEGIN
        EXECUTE 'DROP VIEW ' || pg_catalog.quote_ident(i_schema) || '.' || pg_catalog.quote_ident(i_name);
    EXCEPTION WHEN OTHERS THEN
    END;

    PERFORM db4ai.purge_snapshot_internal(i_schema, i_name);

    -- return purged snapshot name
    res := ROW(i_schema, i_name);
    return res;

END;
$$;
COMMENT ON FUNCTION db4ai.purge_snapshot() IS 'Purge a snapshot and reclaim occupied storage';
