#!/bin/sh

# Copyright (C) 2026 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/pkg"

# 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}" = "XXX-tests-builddir-XXX/src/share/database/scripts/pgsql"; then
    # shellcheck source=./src/bin/admin/admin-utils.sh.in
    . "XXX-tests-builddir-XXX/src/bin/admin/admin-utils.sh"
else
    # shellcheck source=./src/bin/admin/admin-utils.sh.in
    . "${prefix}/share/kea/scripts/admin-utils.sh"
fi

# Check only major version to allow for intermediary backported schema changes.
version=$(pgsql_version "${@}" | cut -d '.' -f 1)
if test "${version}" != '32'; then
    printf 'This script upgrades 32.* to 33.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 33.0.

-- Delete obsolete isJsonSupported function.
DROP FUNCTION isJsonSupported;

CREATE OR REPLACE FUNCTION func_lease4_AINS()
RETURNS trigger AS \$lease4_AINS\$
BEGIN
    PERFORM lease4_AINS_lease4_stat_by_client_class(NEW.state, NEW.user_context);
    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
    PERFORM lease4_AUPD_lease4_stat_by_client_class(OLD.state, OLD.user_context, NEW.state, NEW.user_context);
    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
    PERFORM lease4_ADEL_lease4_stat_by_client_class(OLD.state, OLD.user_context);
    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
    PERFORM lease6_AINS_lease6_stat_by_client_class(NEW.state, NEW.user_context, NEW.lease_type);
    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
    PERFORM lease6_AUPD_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type, NEW.state, NEW.user_context, NEW.lease_type);
    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
    PERFORM lease6_ADEL_lease6_stat_by_client_class(OLD.state, OLD.user_context, OLD.lease_type);
    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;

-- Create the SFLQ tables.

DROP TABLE IF EXISTS flq_pool4;
CREATE TABLE IF NOT EXISTS flq_pool4 (
    id SERIAL PRIMARY KEY NOT NULL,
    start_address BIGINT NOT NULL,
    end_address BIGINT NOT NULL,
    last_returned_address BIGINT NOT NULL DEFAULT 0,
    subnet_id BIGINT NOT NULL,
    created_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX flq_pool4_start_end_address ON flq_pool4 (start_address, end_address);
CREATE INDEX flq_pool4_by_subnet_id ON flq_pool4 (subnet_id);

DROP TABLE IF EXISTS free_lease4;
CREATE TABLE IF NOT EXISTS free_lease4 (
    address BIGINT PRIMARY KEY NOT NULL,
    modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);

DROP TABLE IF EXISTS flq_pool6;
CREATE TABLE IF NOT EXISTS flq_pool6 (
    id SERIAL PRIMARY KEY NOT NULL,
    start_address INET NOT NULL,
    end_address INET NOT NULL,
    lease_type SMALLINT NOT NULL,
    delegated_len SMALLINT NOT NULL,
    last_returned_address INET NOT NULL DEFAULT ('::'),
    subnet_id BIGINT NOT NULL,
    created_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX flq_pool6_start_end_address ON flq_pool6 (start_address, end_address);
CREATE INDEX flq_pool6_by_subnet_id ON flq_pool6 (subnet_id);

DROP TABLE IF EXISTS free_lease6;
CREATE TABLE IF NOT EXISTS free_lease6 (
    address INET NOT NULL,
    bin_address BYTEA NOT NULL,
    modification_ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (address)
);

CREATE UNIQUE INDEX free_lease6_bin_address ON free_lease6 (bin_address);

-- Populate flq_pool4 and free_lease4 based on an address range.
CREATE OR REPLACE FUNCTION sflqCreateFlqPool4(p_start_address BIGINT,
                                               p_end_address BIGINT,
                                               p_subnet_id BIGINT,
                                               p_recreate BOOLEAN)
RETURNS VOID
LANGUAGE plpgsql AS \$\$
BEGIN
    IF (p_start_address = 0 OR p_start_address > p_end_address)
    THEN
        RAISE EXCEPTION 'Invalid range: % - %', ('0.0.0.0'::inet + p_start_address),
                        ('0.0.0.0'::inet + p_end_address);
    END IF;

    -- Create the flq_pool4 row. Concurrent attempts will hang until
    -- this one commits and then they will fail with duplicate key
    -- error. Callers should treat the duplicate error as success.
    IF (p_recreate = true)
    THEN
        -- (Re)creating should ignore duplicate on insert
        INSERT INTO flq_pool4 (start_address, end_address, subnet_id, last_returned_address)
           VALUES (p_start_address, p_end_address, p_subnet_id, 0)
           ON CONFLICT DO NOTHING;
    ELSE
        INSERT INTO flq_pool4 (start_address, end_address, subnet_id, last_returned_address)
           VALUES (p_start_address, p_end_address, p_subnet_id, 0);
    END IF;

    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
    DELETE FROM free_lease4 WHERE address >= p_start_address and address <= p_end_address;

    INSERT INTO free_lease4 (address)
        SELECT avail FROM generate_series(p_start_address, p_end_address, 1) AS avail
            LEFT JOIN lease4 on avail = lease4.address
            WHERE (lease4.address IS NULL OR lease4.state = 2)
            ON CONFLICT DO NOTHING;

    -- Update the modification time in the flq_pool row.
    UPDATE flq_pool4 SET modification_ts = now()
        WHERE (start_address = p_start_address AND end_address = p_end_address);
END;
\$\$;

-- Select a free address from an address range.
CREATE OR REPLACE FUNCTION sflqPickFreeLease4(p_start_address BIGINT,
                                              p_end_address BIGINT)
RETURNS BIGINT
AS \$\$
DECLARE
    -- Declarations
    pool_id BIGINT;
    last_address BIGINT;
    free_address BIGINT;
BEGIN
    free_address = 0;

    -- Find the pool, get the id and last addressed picked.
    SELECT id, last_returned_address INTO pool_id, last_address
        FROM flq_pool4
        WHERE start_address = p_start_address
        AND end_address = p_end_address;

    IF (pool_id IS NULL)
    THEN
        -- No matching pool, return empty address.
        RETURN free_address;
    END IF;

    IF (last_address < p_start_address or last_address >= p_end_address)
    THEN
        -- Set last pick to start address less one.
       last_address = p_start_address - 1;
    END IF;

    -- Find first address greater than the last pick.
    SELECT f.address INTO free_address FROM free_lease4 f
        WHERE (f.address > last_address and f.address <= p_end_address)
        ORDER BY f.address
        LIMIT 1;

    -- Didn't find one, so try front half of the range, including
    -- the last pick. This avoids returning nothing if there is only
    -- one address free.
    IF (free_address IS NULL)
    THEN
        SELECT f.address INTO free_address FROM free_lease4 f
            WHERE (f.address >= p_start_address AND f.address <= last_address)
            ORDER BY f.address
            LIMIT 1;
    END IF;

    IF (free_address IS NULL)
    THEN
        free_address = 0;
    END IF;

    -- Update the pool table with the pick.
    UPDATE flq_pool4 SET last_returned_address = free_address
        WHERE id = pool_id;

    RETURN free_address;
END;
\$\$ LANGUAGE plpgsql;

-- Function to convert a hex ascii digit value to an integer.
CREATE OR REPLACE FUNCTION hexToBin(digit SMALLINT)
RETURNS SMALLINT
AS \$\$
DECLARE
BEGIN
    IF (digit >= 48 and digit <= 57)
    THEN
        return (digit - 48);
    END IF;

    IF (digit >= 65 and digit <= 70)
    THEN
        return (digit - 55);
    END IF;

    IF (digit >= 97 and digit <= 102)
    THEN
        return (digit - 87);
    END IF;

    RETURN (-1);
END;
\$\$ LANGUAGE plpgsql;

-- Function to convert INET to BYTEA (16 byte binary).
CREATE OR REPLACE FUNCTION inetToBytea(inet_address inet)
RETURNS bytea
AS \$\$
DECLARE
    hextets TEXT[];
    num_hextets SMALLINT;
    hextet_idx SMALLINT;
    hextet_len SMALLINT;
    hextet TEXT;
    dummy VARCHAR(256);
    bin_address bytea;
    bin_idx SMALLINT;
    digit_idx SMALLINT;
    digit SMALLINT;
    digit_place SMALLINT;
    cur_byte SMALLINT;
    num_pad SMALLINT;
    first_hextet_zero BOOLEAN := false;
BEGIN
    bin_address = '\x00000000000000000000000000000000'::bytea;

    -- Avoid v4 mapped output of host()
    IF (inet_address < '0000:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF'::inet)
    THEN
        inet_address = inet_address | ('FFFF::'::inet);
        first_hextet_zero = true;
    END IF;

    hextets = string_to_array(host(inet_address), ':');
    num_hextets = array_length(hextets, 1);

    -- Skip first hextet if we faked above.
    IF (first_hextet_zero = true)
    THEN
        hextet_idx = 2;
        bin_idx = 2;
    ELSE
        hextet_idx = 1;
        bin_idx = 0;
    END IF;

    dummy = '';
    WHILE (hextet_idx <= num_hextets) LOOP
        hextet = hextets[hextet_idx];
        hextet_len = char_length(hextet);
        IF (hextet_len = 0)
        THEN
            -- Skip over omitted hextets.
            num_pad = 8 - num_hextets + 1;
            bin_idx = bin_idx + (num_pad * 2);
        ELSE
            num_pad = 4 - hextet_len;
            digit_idx = 1;
            digit_place = 1;
            WHILE (digit_place <= 4) LOOP
                IF (num_pad > 0)
                THEN
                    digit = 0;
                    num_pad = num_pad - 1;
                ELSE
                    digit = hexToBin(ascii(substring(hextet from digit_idx for 1))::smallint);
                    digit_idx = digit_idx + 1;
                END IF;

                IF (digit_place = 1 or digit_place = 3)
                THEN
                    cur_byte = digit << 4;
                ELSE
                    cur_byte = cur_byte + digit;
                    select set_byte(bin_address, bin_idx, cur_byte) into bin_address;
                    bin_idx = bin_idx + 1;
                    cur_byte = 0;
                END IF;
                digit_place = digit_place + 1;
            END LOOP;
            dummy = dummy || hextet || '+';
        END IF;
        hextet_idx = hextet_idx + 1;
    END LOOP;

    RETURN (bin_address);
END;
\$\$ LANGUAGE plpgsql;

-- Function to convert BYTEA (16 byte binary) to INET.
CREATE OR REPLACE FUNCTION byteaToInet(bin_address bytea)
RETURNS inet
AS \$\$
DECLARE
    new_prefix varchar(100);
    hex_string varchar(45);
    pos SMALLINT;
BEGIN
    IF (octet_length(bin_address) != 16)
    THEN
        RETURN ('::');
    END IF;

    pos = 1;
    new_prefix = '';
    hex_string = encode(bin_address, 'hex');

    FOR i IN 0..7 LOOP
        IF (i > 0)
        THEN
            new_prefix = new_prefix || ':';
        END IF;

        new_prefix = new_prefix || substring(hex_string, pos, 4);
        pos = pos + 4;
    END LOOP;

    RETURN (new_prefix::inet);
END;
\$\$ LANGUAGE plpgsql;

-- Function to increment a V6 prefix by one added at prefix_len.
CREATE OR REPLACE FUNCTION incrementV6Prefix(prefix bytea, prefix_len SMALLINT)
RETURNS bytea
AS \$\$
DECLARE
    new_prefix bytea;
    n_bytes SMALLINT;
    n_bits SMALLINT;
    mask SMALLINT;
    start_idx SMALLINT;
    tmp SMALLINT;
    i SMALLINT;
BEGIN
    IF (prefix_len < 1 OR prefix_len > 128)
    THEN
        return (prefix);
    END IF;

    new_prefix = prefix;

    -- Explanation: n_bytes specifies number of full bytes that are in-prefix.
    -- They can also be used as an offset for the first byte that is not in
    -- prefix. n_bits specifies number of bits on the last byte that is
    -- (often partially) in prefix. For example for a /125 prefix, the values
    -- are 15 and 3, respectively. Mask is a bitmask that has the least
    -- significant bit from the prefix set.

    n_bytes = (prefix_len - 1) / 8;
    n_bits = 8 - (prefix_len - (n_bytes * 8));
    mask = 1 << n_bits;

    -- Index of first byte to increment (0..15)
    start_idx = n_bytes;

    -- Get the starting byte as an int and increment it.
    tmp = get_byte(new_prefix, start_idx) + mask;

    -- If the new value is less than 256, we safely increase just the starting byte.
    IF (tmp < 256)
    THEN
        new_prefix = set_byte(new_prefix, start_idx, tmp);
        RETURN (new_prefix);
    END IF;

    -- Overflow, set the byte to the remainder.
    tmp = tmp - 256;
    new_prefix = set_byte(new_prefix, start_idx, tmp);

    --  Now propagate the overflow.
    i = start_idx - 1;
    WHILE i >= 0 LOOP
        tmp = get_byte(new_prefix, i) + 1;
        IF (tmp != 256)
        THEN
            -- No more overflow, store the byte and we're done.
            new_prefix = set_byte(new_prefix, i, tmp);
            RETURN (new_prefix);
        END IF;

        -- Store zero in the current byte, and move to the next one.
        new_prefix = set_byte(new_prefix, i, 0);
        i = i - 1;
    END LOOP;

    -- All done.
    RETURN (new_prefix);
END;
\$\$ LANGUAGE plpgsql;

-- Populate flq_pool6 and free_lease6 based on an address
-- range and delegated len. Use 128 for NA addresses.
CREATE OR REPLACE FUNCTION sflqCreateFlqPool6(p_start_address INET,
                                               p_end_address INET,
                                               p_lease_type SMALLINT,
                                               p_delegated_len SMALLINT,
                                               p_subnet_id BIGINT,
                                               p_recreate BOOLEAN)
RETURNS VOID
LANGUAGE plpgsql AS \$\$
DECLARE
    current_ts TIMESTAMP WITH TIME ZONE := now();
    p_end_bin BYTEA := inetToBytea(p_end_address);
    batch_start_address INET := p_start_address;
    batch_start_bin BYTEA := inetToBytea(p_start_address);
    max_prefixes_per_batch INTEGER := 10000;
    last_bin BYTEA;
    next_bin BYTEA;
BEGIN
    IF ((p_start_address = '::'::inet) OR (p_start_address > p_end_address))
    THEN
        RAISE EXCEPTION 'Invalid range: % - %', host(p_start_address), host(p_end_address);
    END IF;

    IF (p_delegated_len < 1 OR p_delegated_len > 128)
    THEN
        RAISE EXCEPTION 'Invalid p_delegated_len: %', p_delegated_len;
    END IF;

    -- Create the flq_pool6 row. Concurrent attempts will hang until
    -- this one commits and then they will fail with duplicate key
    -- error. Callers should treat the duplicate error as success.
    IF (p_recreate = true)
    THEN
        -- (Re)creating should ignore duplicate on insert
        INSERT INTO flq_pool6
            (start_address, end_address, lease_type, delegated_len, subnet_id)
            VALUES
            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id)
            ON CONFLICT DO NOTHING;
    ELSE
        INSERT INTO flq_pool6
            (start_address, end_address, lease_type, delegated_len, subnet_id)
            VALUES
            (p_start_address, p_end_address, p_lease_type, p_delegated_len, p_subnet_id);
    END IF;

    -- Wipe out existing free addresses. This ensures we fix any mixed allocation entries.
    DELETE FROM free_lease6 WHERE address >= p_start_address and address <= p_end_address;

    -- Enumerate prefixes in bounded chunks and insert free entries in bulk.
    WHILE batch_start_address <= p_end_address
    LOOP
        WITH RECURSIVE prefixes(depth, bin_address, address) AS (
            SELECT 1, batch_start_bin, batch_start_address
            UNION ALL
            SELECT prefixes.depth + 1,
                   next_prefix.bin_address,
                   byteaToInet(next_prefix.bin_address)
            FROM prefixes,
                LATERAL (SELECT incrementV6Prefix(prefixes.bin_address, p_delegated_len)
                    AS bin_address) AS next_prefix
            WHERE prefixes.address < p_end_address
              AND prefixes.depth < max_prefixes_per_batch
        ),
        ins AS (
            INSERT INTO free_lease6(address, bin_address)
                SELECT p.address, p.bin_address
                FROM prefixes p
                LEFT JOIN lease6 l
                    ON l.address = p.address AND l.state != 2
                WHERE l.address IS NULL
                ON CONFLICT DO NOTHING
                RETURNING 1
        )
        SELECT last_prefix.bin_address INTO STRICT last_bin
            FROM (
                SELECT bin_address
                FROM prefixes
                ORDER BY depth DESC
                LIMIT 1
            ) AS last_prefix;

        next_bin := incrementV6Prefix(last_bin, p_delegated_len);

        -- Stepped past the pool end (binary order), or increment wrapped at
        -- all-ones IPv6 (:: after FFFF:...:FFFF) so inet compare would loop forever.
        IF next_bin > p_end_bin OR next_bin < last_bin
        THEN
            EXIT;
        END IF;

        batch_start_bin := next_bin;
        batch_start_address := byteaToInet(next_bin);
    END LOOP;

    -- Update the modification time in the flq_pool row.
    UPDATE flq_pool6 SET modification_ts = now()
        WHERE (start_address = p_start_address AND end_address = p_end_address);
END;
\$\$;

-- Find a free lease with an address range.
CREATE OR REPLACE FUNCTION sflqPickFreeLease6(alloc_start_address INET,
                                              alloc_end_address INET)
RETURNS INET
AS \$\$
DECLARE
    -- Declarations
    pool_id BIGINT;
    last_address INET;
    free_address INET;

    bin_start_address BYTEA;
    bin_end_address BYTEA;
    bin_last_address BYTEA;
BEGIN

    -- Find the pool, get the id and last addressed picked.
    SELECT id, last_returned_address INTO pool_id, last_address
        FROM flq_pool6
        WHERE start_address = alloc_start_address
        AND end_address = alloc_end_address;

    IF (pool_id IS NULL)
    THEN
        -- No matching pool, return empty address.
        RETURN ("::");
    END IF;

    bin_start_address = inetToBytea(alloc_start_address);
    bin_end_address = inetToBytea(alloc_end_address);
    IF (last_address < alloc_start_address OR last_address >= alloc_end_address)
    THEN
        -- Set last pick to start address - 1.
        last_address = alloc_start_address - 1;
    END IF;

    bin_last_address = inetToBytea(last_address);
    -- Find first address greater than the last pick.
    SELECT f.address INTO free_address FROM free_lease6 f
        WHERE (f.bin_address > bin_last_address AND
               f.bin_address <= bin_end_address)
        ORDER BY f.bin_address
        LIMIT 1;

    -- Didn't find one, so try front half of the range, including
    -- the last pick. This avoids returning nothing if there is only
    -- one address free.
    IF (free_address IS NULL)
    THEN
        SELECT f.address INTO free_address FROM free_lease6 f
            WHERE (f.bin_address >= bin_start_address AND
                   f.bin_address <= bin_last_address)
            ORDER BY f.bin_address
            LIMIT 1;
    END IF;

    IF (free_address IS NULL)
    THEN
        free_address = '::'::inet;
    END IF;

    -- Update the pool table with the pick.
    UPDATE flq_pool6 SET last_returned_address = free_address
        WHERE id = pool_id;

    RETURN free_address;
END;
\$\$ LANGUAGE plpgsql;

-- Procedure to insert a v4 lease and update SFLQ data.
CREATE OR REPLACE FUNCTION sflqInsertLease4(p_address BIGINT,
                                            p_hwaddr  BYTEA,
                                            p_client_id BYTEA,
                                            p_valid_lifetime BIGINT,
                                            p_expire TIMESTAMP WITH TIME ZONE,
                                            p_subnet_id BIGINT,
                                            p_fqdn_fwd BOOLEAN,
                                            p_fqdn_rev BOOLEAN,
                                            p_hostname VARCHAR,
                                            p_state BIGINT,
                                            p_user_context TEXT,
                                            p_relay_id BYTEA,
                                            p_remote_id BYTEA,
                                            p_pool_id BIGINT)
RETURNS INTEGER
LANGUAGE plpgsql AS \$\$
DECLARE
    lease_row_count INTEGER;
BEGIN
    INSERT INTO lease4 (address, hwaddr, client_id, valid_lifetime, expire,
                        subnet_id, fqdn_fwd, fqdn_rev, hostname, state,
                        user_context, relay_id, remote_id, pool_id)
         VALUES ( p_address, p_hwaddr, p_client_id, p_valid_lifetime, p_expire,
                  p_subnet_id, p_fqdn_fwd, p_fqdn_rev, p_hostname, p_state,
                  p_user_context, p_relay_id, p_remote_id, p_pool_id);

    -- Remember the lease affected row count.
    GET DIAGNOSTICS lease_row_count = ROW_COUNT;

    IF (lease_row_count > 0)
    THEN
        -- Update SFLQ data.
        -- If it is expired-reclaimed delete add it to the
        -- free queue otherwise delete it from the free queue.
        IF (p_state = 2)
        THEN
            -- Insert into free queue. Ignore duplicates.
            INSERT INTO free_lease4 (address) VALUES (p_address)
                ON CONFLICT DO NOTHING;
        ELSE
            -- Delete it from the free queue. Ignore nothing deleted.
            DELETE FROM free_lease4 WHERE address = p_address;
        END IF;
    END IF;

    RETURN (lease_row_count);
END;
\$\$;

-- Procedure to update a v4 lease and update SFLQ data.
CREATE OR REPLACE FUNCTION sflqUpdateLease4(p_address BIGINT,
                                             p_hwaddr  BYTEA,
                                             p_client_id BYTEA,
                                             p_valid_lifetime BIGINT,
                                             p_expire TIMESTAMP WITH TIME ZONE,
                                             p_subnet_id BIGINT,
                                             p_fqdn_fwd BOOLEAN,
                                             p_fqdn_rev BOOLEAN,
                                             p_hostname VARCHAR,
                                             p_state BIGINT,
                                             p_user_context TEXT,
                                             p_relay_id BYTEA,
                                             p_remote_id BYTEA,
                                             p_pool_id BIGINT,
                                             p_where_address BIGINT,
                                             p_where_expire TIMESTAMP WITH TIME ZONE)
RETURNS INTEGER
LANGUAGE plpgsql AS \$\$
DECLARE
    lease_row_count INTEGER;
BEGIN
    UPDATE lease4 SET address = p_address, hwaddr = p_hwaddr, client_id = p_client_id,
                      valid_lifetime = p_valid_lifetime, expire = p_expire,
                      subnet_id = p_subnet_id, fqdn_fwd = p_fqdn_fwd,
                      fqdn_rev = p_fqdn_rev, hostname = p_hostname, state = p_state,
                      user_context = p_user_context, relay_id = p_relay_id,
                      remote_id = p_remote_id, pool_id = p_pool_id
        WHERE (address = p_where_address and expire = p_where_expire);

    -- Remember the lease affected row count.
    GET DIAGNOSTICS lease_row_count = ROW_COUNT;

    IF (lease_row_count > 0)
    THEN
        -- Update SFLQ data.
        -- If it is expired-reclaimed delete add it to the
        -- free queue otherwise delete it from the free queue.
        IF (p_state = 2)
        THEN
            -- Insert into free queue. Ignore duplicates.
            INSERT INTO free_lease4 (address) VALUES (p_address)
                ON CONFLICT DO NOTHING;
        ELSE
            -- Delete it from the free queue. Ignore nothing deleted.
            DELETE FROM free_lease4 WHERE address = p_address;
        END IF;
    END IF;

    RETURN (lease_row_count);
END;
\$\$;

-- Procedure to delete a v4 lease by address and update SFLQ data.
CREATE OR REPLACE FUNCTION sflqDeleteLease4(p_address BIGINT,
                                             p_expire TIMESTAMP WITH TIME ZONE)
RETURNS INTEGER
LANGUAGE plpgsql AS \$\$
DECLARE
    lease_row_count INTEGER;
BEGIN
    -- Delete the lease from lease4.
    DELETE FROM lease4 WHERE address = p_address AND expire = p_expire;

    -- Remember the lease affected row count.
    GET DIAGNOSTICS lease_row_count = ROW_COUNT;

    -- If we didn't delete it either it was already gone or expire
    -- didn't match. In case of the former it should already be in
    -- the flq and in the latter, it should not be.
    IF (lease_row_count > 0)
    THEN
        -- Insert into free queue. Ignore duplicates.
        INSERT INTO free_lease4 (address) VALUES (p_address)
            ON CONFLICT DO NOTHING;
    END IF;

    RETURN (lease_row_count);
END;
\$\$;

-- Procedure to insert a v6 lease and update SFLQ data.
CREATE OR REPLACE FUNCTION sflqInsertLease6(p_address INET,
                                             p_duid BYTEA,
                                             p_valid_lifetime BIGINT,
                                             p_expire TIMESTAMP WITH TIME ZONE,
                                             p_subnet_id BIGINT,
                                             p_pref_lifetime BIGINT,
                                             p_lease_type SMALLINT,
                                             p_iaid INTEGER,
                                             p_prefix_len SMALLINT,
                                             p_fqdn_fwd BOOLEAN,
                                             p_fqdn_rev BOOLEAN,
                                             p_hostname VARCHAR,
                                             p_hwaddr BYTEA,
                                             p_hwtype SMALLINT,
                                             p_hwaddr_source SMALLINT,
                                             p_state BIGINT,
                                             p_user_context TEXT,
                                             p_pool_id BIGINT)
RETURNS INTEGER
LANGUAGE plpgsql AS \$\$
DECLARE
    lease_row_count INTEGER;
BEGIN
    INSERT INTO lease6 (address, duid, valid_lifetime, expire, subnet_id,
                        pref_lifetime, lease_type, iaid, prefix_len, fqdn_fwd,
                        fqdn_rev, hostname, hwaddr, hwtype, hwaddr_source,
                        state, user_context, pool_id)
         VALUES (p_address, p_duid, p_valid_lifetime, p_expire, p_subnet_id,
                 p_pref_lifetime, p_lease_type, p_iaid, p_prefix_len, p_fqdn_fwd,
                 p_fqdn_rev, p_hostname, p_hwaddr, p_hwtype, p_hwaddr_source,
                 p_state, p_user_context, p_pool_id);

    -- Remember the lease affected row count.
    GET DIAGNOSTICS lease_row_count = ROW_COUNT;

    IF (lease_row_count > 0)
    THEN
        -- Update SFLQ data.
        -- If it is expired-reclaimed delete add it to the
        -- free queue otherwise delete it from the free queue.
        IF (p_state = 2)
        THEN
            -- Insert into free queue. Ignore duplicates.
            INSERT INTO free_lease6 (address, bin_address)
                VALUES (p_address, inetToBytea(p_address))
                ON CONFLICT DO NOTHING;
        ELSE
            -- Delete it from the free queue. Ignore nothing deleted.
            DELETE FROM free_lease6 WHERE address = p_address;
        END IF;
    END IF;

    RETURN (lease_row_count);
END;
\$\$;

-- Procedure to update a v6 lease and update SFLQ data.
CREATE OR REPLACE FUNCTION sflqUpdateLease6(p_address INET,
                                            p_duid BYTEA,
                                            p_valid_lifetime BIGINT,
                                            p_expire TIMESTAMP WITH TIME ZONE,
                                            p_subnet_id BIGINT,
                                            p_pref_lifetime BIGINT,
                                            p_lease_type SMALLINT,
                                            p_iaid INTEGER,
                                            p_prefix_len SMALLINT,
                                            p_fqdn_fwd BOOLEAN,
                                            p_fqdn_rev BOOLEAN,
                                            p_hostname VARCHAR,
                                            p_hwaddr BYTEA,
                                            p_hwtype SMALLINT,
                                            p_hwaddr_source SMALLINT,
                                            p_state BIGINT,
                                            p_user_context TEXT,
                                            p_pool_id BIGINT,
                                            p_where_address INET,
                                            p_where_expire TIMESTAMP WITH TIME ZONE)
RETURNS INTEGER
LANGUAGE plpgsql AS \$\$
DECLARE
    lease_row_count INTEGER;
BEGIN
    UPDATE lease6
        SET address = p_address, duid = p_duid, valid_lifetime = p_valid_lifetime,
            expire = p_expire, subnet_id = p_subnet_id, pref_lifetime = p_pref_lifetime,
            lease_type = p_lease_type, iaid = p_iaid, prefix_len = p_prefix_len,
            fqdn_fwd = p_fqdn_fwd, fqdn_rev = p_fqdn_rev, hostname = p_hostname,
            hwaddr =  p_hwaddr, hwtype = p_hwtype, hwaddr_source = p_hwaddr_source,
            state = p_state, user_context = p_user_context, pool_id = p_pool_id
        WHERE (address = p_where_address and expire = p_where_expire);

    -- Remember the lease affected row count.
    GET DIAGNOSTICS lease_row_count = ROW_COUNT;

    IF (lease_row_count > 0)
    THEN
        -- Update SFLQ data.
        -- If it is expired-reclaimed delete add it to the
        -- free queue otherwise delete it from the free queue.
        IF (p_state = 2)
        THEN
            -- Insert into free queue. Ignore duplicates.
            INSERT INTO free_lease6 (address, bin_address)
                VALUES (p_address, inetToBytea(p_address))
                ON CONFLICT DO NOTHING;
        ELSE
            -- Delete it from the free queue. Ignore nothing deleted.
            DELETE FROM free_lease6 WHERE address = p_address;
        END IF;
    END IF;

    RETURN (lease_row_count);
END;
\$\$;

-- Procedure to delete a v6 lease by address and update SFLQ data.
CREATE OR REPLACE FUNCTION sflqDeleteLease6(p_address INET,
                                             p_expire TIMESTAMP WITH TIME ZONE)
RETURNS INTEGER
LANGUAGE plpgsql AS \$\$
DECLARE
    lease_row_count INTEGER;
BEGIN
    -- Delete the lease from lease6.
    DELETE FROM lease6 WHERE address = p_address AND expire = p_expire;

    -- Remember the lease affected row count.
    GET DIAGNOSTICS lease_row_count = ROW_COUNT;

    -- If we didn't delete it either it was already gone or expire
    -- didn't match. In case of the former it should already be in
    -- the flq and in the latter, it should not be.
    IF (lease_row_count > 0)
    THEN
        -- Insert into free queue. Ignore duplicates.
        INSERT INTO free_lease6 (address, bin_address)
            VALUES (p_address, inetToBytea(p_address))
            ON CONFLICT DO NOTHING;
    END IF;

    RETURN (lease_row_count);
END;
\$\$;

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

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

-- Commit the script transaction.
COMMIT;

EOF
