PostgreSQL IP-пул - PullRequest
       7

PostgreSQL IP-пул

0 голосов
/ 26 апреля 2018

У меня есть следующая функция для получения бесплатного IP-адреса из пула:

CREATE OR REPLACE FUNCTION get_ip(inp_id CHARACTER(9)) RETURNS INET AS $$
    DECLARE ip_assigned INET;
    BEGIN
        ip_assigned := (COALESCE((SELECT ip FROM ips WHERE id = inp_id),
                                 (SELECT (a.ip + 1) AS ip
                                  FROM ips a LEFT JOIN LATERAL (SELECT * FROM ips b WHERE a.ip < b.ip ORDER BY b.ip ASC LIMIT 1) AS q ON true
                                  WHERE q.ip <> (a.ip + 1)
                                  ORDER BY ip ASC LIMIT 1)));

        IF NOT EXISTS (SELECT 1 FROM ips WHERE id = inp_id) AND NOT EXISTS (SELECT 1 FROM ips WHERE ip = ip_assigned) THEN
            INSERT INTO ips VALUES (ip_assigned, inp_id);
            RETURN ip_assigned;
        ELSEIF EXISTS (SELECT 1 FROM ips WHERE id = inp_id AND ip = ip_assigned) THEN
            RETURN ip_assigned;
        ELSE
            RETURN '0.0.0.0';
        END IF;

    END;
$$ LANGUAGE plpgsql;

Кажется, это работает, но я не уверен, что может возникнуть необходимость в блокировке таблицы при получении IP и вставке в таблицу.

Я проверяю, существует ли уже IP-адрес и требуется ли идентификатор и IP-адрес. 0.0.0.0 возвращается в случае сбоя.

inp_id - это клиент, запрашивающий IP, а таблица ips имеет 2 столбца; ip и id, для сопоставления идентификатора клиента и IP.

1 Ответ

0 голосов
/ 26 апреля 2018

Ваша функция кажется очень сложной, и я не уверен, что она вообще выполняет свою работу.

Это не будет работать, если 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;$$;

Даже если вам не нравится мой подход, вы можете понять, что я имею в виду, используя цикл.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...