#!/bin/sh

# Copyright (C) 2023-2025 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Exit with error if commands exit with non-zero and if undefined variables are
# used.
set -eu

# shellcheck disable=SC2034
# SC2034: ... appears unused. Verify use (or export if used externally).
prefix="/usr"

# Include utilities based on location of this script. Check for sources first,
# so that the unexpected situations with weird paths fall on the default
# case of installed.
script_path=$(cd "$(dirname "${0}")" && pwd)
if test "${script_path}" = "/build/kea/src/build/src/share/database/scripts/pgsql"; then
    # shellcheck source=./src/bin/admin/admin-utils.sh.in
    . "/build/kea/src/build/src/bin/admin/admin-utils.sh"
else
    # shellcheck source=./src/bin/admin/admin-utils.sh.in
    . "${prefix}/share/kea/scripts/admin-utils.sh"
fi

VERSION=$(pgsql_version "$@")

if [ "$VERSION" != "16.0" ]; then
    printf 'This script upgrades 16.0 to 17.0. '
    printf 'Reported version is %s. Skipping upgrade.\n' "${VERSION}"
    exit 0
fi

psql "$@" >/dev/null <<EOF
START TRANSACTION;

-- This line starts the schema upgrade to version 17.0.

UPDATE lease6 SET duid = E'\\\\x000000' WHERE duid = E'\\\\x00';

-- Add pool_id column to the lease4 table.
ALTER TABLE lease4
    ADD COLUMN pool_id BIGINT NOT NULL DEFAULT 0;

-- Add pool_id column to the lease6 table.
ALTER TABLE lease6
    ADD COLUMN pool_id BIGINT NOT NULL DEFAULT 0;

-- Create v4 lease statistics table
CREATE TABLE lease4_pool_stat (
    subnet_id BIGINT NOT NULL,
    pool_id BIGINT NOT NULL,
    state INT8 NOT NULL,
    leases BIGINT,
    PRIMARY KEY (subnet_id, pool_id, state)
);

-- Create v6 lease statistics table
CREATE TABLE lease6_pool_stat (
    subnet_id BIGINT NOT NULL,
    pool_id BIGINT NOT NULL,
    lease_type SMALLINT NOT NULL,
    state INT8 NOT NULL,
    leases BIGINT,
    PRIMARY KEY (subnet_id, pool_id, lease_type, state)
);

CREATE OR REPLACE FUNCTION lease4_AINS_lease4_pool_stat(IN new_state BIGINT,
                                                        IN new_subnet_id BIGINT,
                                                        IN new_pool_id BIGINT)
RETURNS VOID
AS \$\$
BEGIN
    IF new_state = 0 OR new_state = 1 THEN
        -- Update the state count if it exists.
        UPDATE lease4_pool_stat SET leases = leases + 1
            WHERE subnet_id = new_subnet_id AND pool_id = new_pool_id
            AND state = new_state;

        -- Insert the state count record if it does not exist.
        IF NOT FOUND THEN
            INSERT INTO lease4_pool_stat
            VALUES (new_subnet_id, new_pool_id, new_state, 1);
        END IF;
    END IF;
END;
\$\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION lease4_AUPD_lease4_pool_stat(IN old_state BIGINT,
                                                        IN old_subnet_id BIGINT,
                                                        IN old_pool_id BIGINT,
                                                        IN new_state BIGINT,
                                                        IN new_subnet_id BIGINT,
                                                        IN new_pool_id BIGINT)
RETURNS VOID
AS \$\$
BEGIN
    IF old_subnet_id != new_subnet_id OR
       old_pool_id != new_pool_id OR
       old_state != new_state THEN
        IF old_state = 0 OR old_state = 1 THEN
            -- Decrement the old state count if record exists.
            UPDATE lease4_pool_stat
                SET leases = GREATEST(leases - 1, 0)
                WHERE subnet_id = old_subnet_id AND pool_id = old_pool_id
                AND state = old_state;
        END IF;

        IF new_state = 0 OR new_state = 1 THEN
            -- Increment the new state count if record exists.
            UPDATE lease4_pool_stat SET leases = leases + 1
                WHERE subnet_id = new_subnet_id AND pool_id = new_pool_id
                AND state = new_state;

            -- Insert new state record if it does not exist.
            IF NOT FOUND THEN
                INSERT INTO lease4_pool_stat
                VALUES (new_subnet_id, new_pool_id, new_state, 1);
            END IF;
        END IF;
    END IF;
END;
\$\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION lease4_ADEL_lease4_pool_stat(IN old_state BIGINT,
                                                        IN old_subnet_id BIGINT,
                                                        IN old_pool_id BIGINT)
RETURNS VOID
AS \$\$
BEGIN
    IF old_state = 0 OR old_state = 1 THEN
        -- Decrement the state count if record exists.
        UPDATE lease4_pool_stat
            SET leases = GREATEST(leases - 1, 0)
            WHERE subnet_id = old_subnet_id AND pool_id = old_pool_id
            AND state = old_state;
    END IF;
END;
\$\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION lease6_AINS_lease6_pool_stat(IN new_state BIGINT,
                                                        IN new_subnet_id BIGINT,
                                                        IN new_pool_id BIGINT,
                                                        IN new_lease_type SMALLINT)
RETURNS VOID
AS \$\$
BEGIN
    IF new_state = 0 OR new_state = 1 THEN
        -- Update the state count if it exists.
        UPDATE lease6_pool_stat SET leases = leases + 1
            WHERE subnet_id = new_subnet_id AND pool_id = new_pool_id
            AND lease_type = new_lease_type AND state = new_state;

        -- Insert the state count record if it does not exist.
        IF NOT FOUND THEN
            INSERT INTO lease6_pool_stat
            VALUES (new_subnet_id, new_pool_id, new_lease_type, new_state, 1);
        END IF;
    END IF;
END;
\$\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION lease6_AUPD_lease6_pool_stat(IN old_state BIGINT,
                                                        IN old_subnet_id BIGINT,
                                                        IN old_pool_id BIGINT,
                                                        IN old_lease_type SMALLINT,
                                                        IN new_state BIGINT,
                                                        IN new_subnet_id BIGINT,
                                                        IN new_pool_id BIGINT,
                                                        IN new_lease_type SMALLINT)
RETURNS VOID
AS \$\$
BEGIN
    IF old_subnet_id != new_subnet_id OR
       old_pool_id != new_pool_id OR
       old_lease_type != new_lease_type OR
       old_state != new_state THEN
        IF old_state = 0 OR old_state = 1 THEN
            -- Decrement the old state count if record exists.
            UPDATE lease6_pool_stat
                SET leases = GREATEST(leases - 1, 0)
                WHERE subnet_id = old_subnet_id AND pool_id = old_pool_id
                AND lease_type = old_lease_type AND state = old_state;
        END IF;

        IF new_state = 0 OR new_state = 1 THEN
            -- Increment the new state count if record exists
            UPDATE lease6_pool_stat SET leases = leases + 1
                WHERE subnet_id = new_subnet_id AND pool_id = new_pool_id
                AND lease_type = new_lease_type AND state = new_state;

            -- Insert new state record if it does not exist
            IF NOT FOUND THEN
                INSERT INTO lease6_pool_stat
                VALUES (new_subnet_id, new_pool_id, new_lease_type, new_state, 1);
            END IF;
        END IF;
    END IF;
END;
\$\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION lease6_ADEL_lease6_pool_stat(IN old_state BIGINT,
                                                        IN old_subnet_id BIGINT,
                                                        IN old_pool_id BIGINT,
                                                        IN old_lease_type SMALLINT)
RETURNS VOID
AS \$\$
BEGIN
    IF old_state = 0 OR old_state = 1 THEN
        -- Decrement the state count if record exists
        UPDATE lease6_pool_stat
            SET leases = GREATEST(leases - 1, 0)
            WHERE subnet_id = old_subnet_id AND pool_id = old_pool_id
            AND lease_type = old_lease_type AND state = old_state;
    END IF;
END;
\$\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION func_lease4_AINS()
RETURNS trigger AS \$lease4_AINS\$
BEGIN
    IF isJsonSupported() = true THEN
        PERFORM lease4_AINS_lease4_stat_by_client_class(NEW.state, NEW.user_context);
    END IF;
    PERFORM lease4_AINS_lease4_stat(NEW.state, NEW.subnet_id);
    PERFORM lease4_AINS_lease4_pool_stat(NEW.state, NEW.subnet_id, NEW.pool_id);
    RETURN NULL;
END;
\$lease4_AINS\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION func_lease4_AUPD()
RETURNS trigger AS \$lease4_AUPD\$
BEGIN
    IF isJsonSupported() = true THEN
        PERFORM lease4_AUPD_lease4_stat_by_client_class(OLD.state, OLD.user_context, NEW.state, NEW.user_context);
    END IF;
    PERFORM lease4_AUPD_lease4_stat(OLD.state, OLD.subnet_id, NEW.state, NEW.subnet_id);
    PERFORM lease4_AUPD_lease4_pool_stat(OLD.state, OLD.subnet_id, OLD.pool_id, NEW.state, NEW.subnet_id, NEW.pool_id);
    RETURN NULL;
END;
\$lease4_AUPD\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION func_lease4_ADEL()
RETURNS trigger AS \$lease4_ADEL\$
BEGIN
    IF isJsonSupported() = true THEN
        PERFORM lease4_ADEL_lease4_stat_by_client_class(OLD.state, OLD.user_context);
    END IF;
    PERFORM lease4_ADEL_lease4_stat(OLD.state, OLD.subnet_id);
    PERFORM lease4_ADEL_lease4_pool_stat(OLD.state, OLD.subnet_id, OLD.pool_id);
    RETURN NULL;
END;
\$lease4_ADEL\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION func_lease6_AINS()
RETURNS trigger AS \$lease6_AINS\$
BEGIN
    IF isJsonSupported() = true THEN
        PERFORM lease6_AINS_lease6_stat_by_client_class(NEW.state, NEW.user_context, NEW.lease_type);
    END IF;
    PERFORM lease6_AINS_lease6_stat(NEW.state, NEW.subnet_id, NEW.lease_type);
    PERFORM lease6_AINS_lease6_pool_stat(NEW.state, NEW.subnet_id, NEW.pool_id, NEW.lease_type);
    RETURN NULL;
END;
\$lease6_AINS\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION func_lease6_AUPD()
RETURNS trigger AS \$lease6_AUPD\$
BEGIN
    IF isJsonSupported() = true THEN
        PERFORM lease6_AUPD_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type, NEW.state, NEW.user_context, NEW.lease_type);
    END IF;
    PERFORM lease6_AUPD_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type, NEW.state, NEW.subnet_id, NEW.lease_type);
    PERFORM lease6_AUPD_lease6_pool_stat(OLD.state, OLD.subnet_id, OLD.pool_id, OLD.lease_type, NEW.state, NEW.subnet_id, NEW.pool_id, NEW.lease_type);
    RETURN NULL;
END;
\$lease6_AUPD\$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION func_lease6_ADEL()
RETURNS trigger AS \$lease6_ADEL\$
BEGIN
    IF isJsonSupported() = true THEN
        PERFORM lease6_ADEL_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type);
    END IF;
    PERFORM lease6_ADEL_lease6_stat(OLD.state, OLD.subnet_id, OLD.lease_type);
    PERFORM lease6_ADEL_lease6_pool_stat(OLD.state, OLD.subnet_id, OLD.pool_id, OLD.lease_type);
    RETURN NULL;
END;
\$lease6_ADEL\$ LANGUAGE plpgsql;

DROP FUNCTION IF EXISTS lease4DumpHeader();
CREATE OR REPLACE FUNCTION lease4DumpHeader()
RETURNS text AS \$\$
    select cast('address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id' as text) as result;
\$\$ LANGUAGE SQL;

-- Adding support for pool ID in function to output a memfile-ready CSV file.
-- Some columns that are SMALLINT in the lease4 table have their type promoted
-- to INT in the declaration of this function for backwards compatibility with
-- PostgreSQL versions.
DROP FUNCTION IF EXISTS lease4DumpData();
CREATE OR REPLACE FUNCTION lease4DumpData()
RETURNS TABLE (
    address INET,
    hwaddr VARCHAR,
    client_id VARCHAR,
    valid_lifetime BIGINT,
    expire BIGINT,
    subnet_id BIGINT,
    fqdn_fwd INT,
    fqdn_rev INT,
    hostname VARCHAR,
    state INT8,
    user_context VARCHAR,
    pool_id BIGINT
) AS \$\$
    SELECT
        ('0.0.0.0'::inet + address),
        colonSeparatedHex(encode(hwaddr, 'hex')),
        colonSeparatedHex(encode(client_id, 'hex')),
        valid_lifetime,
        extract(epoch from expire)::bigint,
        subnet_id,
        fqdn_fwd::int,
        fqdn_rev::int,
        replace(hostname, ',', '&#x2c'),
        state,
        replace(user_context, ',', '&#x2c'),
        pool_id
    FROM lease4
    ORDER BY address;
\$\$ LANGUAGE SQL;

DROP FUNCTION IF EXISTS lease6DumpHeader();
CREATE OR REPLACE FUNCTION lease6DumpHeader()
RETURNS TEXT AS \$\$
    SELECT CAST('address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state,user_context,hwtype,hwaddr_source,pool_id' AS TEXT) AS result;
\$\$ LANGUAGE SQL;

-- Adding support for pool ID in function to output a memfile-ready CSV file.
-- Some columns that are SMALLINT in the lease6 table have their type promoted
-- to INT in the declaration of this function for backwards compatibility with
-- PostgreSQL versions.
DROP FUNCTION IF EXISTS lease6DumpData();
CREATE OR REPLACE FUNCTION lease6DumpData()
RETURNS TABLE (
    address VARCHAR,
    duid VARCHAR,
    valid_lifetime BIGINT,
    expire BIGINT,
    subnet_id BIGINT,
    pref_lifetime BIGINT,
    lease_type SMALLINT,
    iaid INT,
    prefix_len SMALLINT,
    fqdn_fwd INT,
    fqdn_rev INT,
    hostname VARCHAR,
    hwaddr VARCHAR,
    state INT8,
    user_context VARCHAR,
    hwtype SMALLINT,
    hwaddr_source SMALLINT,
    pool_id BIGINT
) AS \$\$
    SELECT
        address,
        colonSeparatedHex(encode(duid, 'hex')),
        valid_lifetime,
        extract(epoch from expire)::bigint,
        subnet_id,
        pref_lifetime,
        lease_type,
        iaid,
        prefix_len,
        fqdn_fwd::int,
        fqdn_rev::int,
        replace(hostname, ',', '&#x2c'),
        colonSeparatedHex(encode(hwaddr, 'hex')),
        state,
        replace(user_context, ',', '&#x2c'),
        hwtype,
        hwaddr_source,
        pool_id
    FROM lease6
    ORDER BY address;
\$\$ LANGUAGE SQL;

-- Adding support for pool id in function that inserts a v4 lease from memfile data.
-- Some columns that are SMALLINT in the lease4 table have their type promoted
-- to INT in the declaration of this function for backwards compatibility with
-- PostgreSQL versions.
CREATE OR REPLACE FUNCTION lease4Upload(
    IN address VARCHAR,
    IN hwaddr VARCHAR,
    IN client_id VARCHAR,
    IN valid_lifetime BIGINT,
    IN expire BIGINT,
    IN subnet_id BIGINT,
    IN fqdn_fwd INT,
    IN fqdn_rev INT,
    IN hostname VARCHAR,
    IN state INT8,
    IN user_context VARCHAR,
    IN pool_id BIGINT
) RETURNS VOID AS \$\$
BEGIN
    INSERT INTO lease4 (
        address,
        hwaddr,
        client_id,
        valid_lifetime,
        expire,
        subnet_id,
        fqdn_fwd,
        fqdn_rev,
        hostname,
        state,
        user_context,
        pool_id
    ) VALUES (
        address::inet - '0.0.0.0'::inet,
        decode(replace(hwaddr, ':', ''), 'hex'),
        decode(replace(client_id, ':', ''), 'hex'),
        valid_lifetime,
        to_timestamp(expire),
        subnet_id,
        fqdn_fwd::int::boolean,
        fqdn_rev::int::boolean,
        replace(hostname, '&#x2c', ','),
        state,
        replace(user_context, '&#x2c', ','),
        pool_id
    );
END
\$\$ LANGUAGE plpgsql;

-- Adding support for pool id in function that inserts a v6 lease from memfile data.
-- Some columns that are SMALLINT in the lease6 table have their type promoted
-- to INT in the declaration of this function for backwards compatibility with
-- PostgreSQL versions.
CREATE OR REPLACE FUNCTION lease6Upload(
    IN address VARCHAR,
    IN duid VARCHAR,
    IN valid_lifetime BIGINT,
    IN expire BIGINT,
    IN subnet_id BIGINT,
    IN pref_lifetime BIGINT,
    IN lease_type INT,
    IN iaid INT,
    IN prefix_len INT,
    IN fqdn_fwd INT,
    IN fqdn_rev INT,
    IN hostname VARCHAR,
    IN hwaddr VARCHAR,
    IN state INT8,
    IN user_context VARCHAR,
    IN hwtype INT,
    IN hwaddr_source INT,
    IN pool_id BIGINT
) RETURNS VOID AS \$\$
BEGIN
    INSERT INTO lease6 (
        address,
        duid,
        valid_lifetime,
        expire,
        subnet_id,
        pref_lifetime,
        lease_type,
        iaid,
        prefix_len,
        fqdn_fwd,
        fqdn_rev,
        hostname,
        hwaddr,
        state,
        user_context,
        hwtype,
        hwaddr_source,
        pool_id
    ) VALUES (
        address,
        decode(replace(duid, ':', ''), 'hex'),
        valid_lifetime,
        to_timestamp(expire),
        subnet_id,
        pref_lifetime,
        lease_type,
        iaid,
        prefix_len,
        fqdn_fwd::int::boolean,
        fqdn_rev::int::boolean,
        replace(hostname, '&#x2c', ','),
        decode(replace(hwaddr, ':', ''), 'hex'),
        state,
        replace(user_context, '&#x2c', ','),
        hwtype,
        hwaddr_source,
        pool_id
    );
END
\$\$ LANGUAGE plpgsql;

INSERT INTO lease4_pool_stat (subnet_id, pool_id, state, leases)
    SELECT subnet_id, pool_id, state, count(*) FROM lease4
    WHERE state = 0 OR state = 1 GROUP BY subnet_id, pool_id, state;

INSERT INTO lease6_pool_stat (subnet_id, pool_id, lease_type, state, leases)
    SELECT subnet_id, pool_id, lease_type, state, count(*) FROM lease6
    WHERE state = 0 OR state = 1 GROUP BY subnet_id, pool_id, lease_type, state;

-- Add the binary version of the IPv6 address for v6 BLQ prefix filter.
ALTER TABLE lease6
    ADD COLUMN binaddr BYTEA DEFAULT NULL;
CREATE INDEX lease6_by_binaddr ON lease6 (binaddr ASC);

-- Create table for v6 BLQ by-relay-id.
CREATE TABLE lease6_relay_id (
    extended_info_id SERIAL PRIMARY KEY NOT NULL,
    relay_id BYTEA NOT NULL,
    lease_addr BYTEA NOT NULL);
CREATE INDEX lease6_relay_id_by_id ON lease6_relay_id (relay_id, lease_addr ASC);
CREATE INDEX lease6_relay_id_by_address ON lease6_relay_id (lease_addr);

-- Create table for v6 BLQ by-remote-id.
CREATE TABLE lease6_remote_id (
    extended_info_id SERIAL PRIMARY KEY NOT NULL,
    remote_id BYTEA NOT NULL,
    lease_addr BYTEA NOT NULL);
CREATE INDEX lease6_remote_id_by_id ON lease6_remote_id (remote_id, lease_addr ASC);
CREATE INDEX lease6_remote_id_by_address ON lease6_remote_id (lease_addr);

-- Update the schema version number.
UPDATE schema_version
    SET version = '17', minor = '0';

-- This line concludes the schema upgrade to version 17.0.

-- Commit the script transaction.
COMMIT;

EOF
