Ваша функция кажется очень сложной, и я не уверен, что она вообще выполняет свою работу.
Это не будет работать, если ips
пусто, и вы никогда не получите адрес ниже, чем самый низкий адрес в таблице, поэтому вам нужно убедиться, что этот самый низкий адрес никогда не удаляется из таблицы .
В любом случае, на ваш вопрос:
Полагаю, вы хотите избежать того, чтобы один и тот же адрес возвращался одновременно вызывающим функциям.
Для этого было бы достаточно создать ограничение UNIQUE
для ips
, которое запрещает добавление одного и того же IP-адреса дважды.
Затем вы должны перехватить эту ошибку во время INSERT
и повторить всю операцию в случае ошибки.
Вот моя версия вашей функции.
CREATE TABLE IF NOT EXISTS ips(
ip inet UNIQUE NOT NULL,
id character(9) PRIMARY KEY
);
CREATE OR REPLACE FUNCTION get_ip(inp_id character(9)) RETURNS inet
LANGUAGE plpgsql STRICT AS
$$DECLARE
min_ip inet := '192.168.0.0';
max_ip inet := '192.168.255.255';
new_ip inet;
BEGIN
/* loop until we find and can insert a new address */
LOOP
BEGIN
/* don't do anything if the entry already exists */
SELECT ip INTO new_ip
FROM ips
WHERE id = inp_id;
IF new_ip IS NOT NULL THEN
RETURN new_ip;
END IF;
/* see if the lowest IP address is free */
IF NOT EXISTS (SELECT 1 FROM ips
WHERE ip = min_ip)
THEN
/* attempt to insert the new row */
INSERT INTO ips (ip, id)
VALUES (min_ip, inp_id);
/* return if that was successful */
RETURN min_ip;
END IF;
/* else, get the lowest IP address gap in "ips" */
SELECT ip + 1 INTO new_ip
FROM (SELECT ip,
CASE WHEN lead(ip) OVER (ORDER BY ip) = ip + 1
THEN FALSE
ELSE TRUE
END AS followed_by_gap
FROM ips) subq
WHERE followed_by_gap
ORDER BY ip
LIMIT 1;
/* must not exceed maximum */
IF new_ip > max_ip THEN
RAISE EXCEPTION 'no free IP address found';
END IF;
/* if the table is still empty, use the minimum */
IF new_ip IS NULL THEN
new_ip := min_ip;
END IF;
/* attempt to insert the new row */
INSERT INTO ips (ip, id)
VALUES (new_ip, inp_id);
/* return if that was successful */
RETURN new_ip;
EXCEPTION
WHEN unique_violation THEN
/* retry in another loop execution */
NULL;
END;
END LOOP;
END;$$;
Даже если вам не нравится мой подход, вы можете понять, что я имею в виду, используя цикл.