Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Wersje Oracle PL/SQL funkcji INET6_ATON i NTOA?

Skończyło się na zwijaniu własnego. Zdałem sobie również sprawę, że 126-bitowa INTEGER Oracle to za mało bitów dla 128-bitowych adresów IPv6. Szczerze mówiąc, nie wiem, jak to robi INET6_ATON (lub INET_PTON) oryginalnej biblioteki C, biorąc pod uwagę, że nigdy nie słyszałem o 16-bajtowej liczbie całkowitej.

Skończyło się na 32-bajtowym łańcuchu szesnastkowym, co oznacza, że ​​muszę wykonać jakąś wymyślną matematykę „pół-łańcuchową” na nettohexie i użyć SUBSTR, aby FBI działały poprawnie. (Przeklęty PL/SQL nie pozwala na "RETURN CHAR(32)"...)

Ogólnie rzecz biorąc, działa dobrze, działa we wszystkich formatach i umożliwia porównywanie znaków na podstawie indeksu, aby dowiedzieć się, czy adres IP znajduje się w zakresie adresów IP.

Oto pełny kod:

CREATE OR REPLACE FUNCTION ipguess(
   ip_string IN VARCHAR2
) RETURN NATURAL
DETERMINISTIC
IS
BEGIN
   -- Short-circuit the most popular, and also catch the special case of IPv4 addresses in IPv6
   IF    REGEXP_LIKE(ip_string, '\d{1,3}(\.\d{1,3}){3}')                       THEN RETURN 4;
   ELSIF REGEXP_LIKE(ip_string, '[[:xdigit:]]{0,4}(\:[[:xdigit:]]{0,4}){0,7}') THEN RETURN 6;
   ELSE                                                                             RETURN NULL;
   END IF;
END ipguess;

CREATE OR REPLACE FUNCTION iptohex(
   ip_string IN VARCHAR2
) RETURN CHAR     -- INTEGER only holds 126 binary digits, IPv6 has 128
DETERMINISTIC
IS
   iptype NATURAL := ipguess(ip_string);
   ip     VARCHAR2(32);
   ipwork VARCHAR2(64);
   d      INTEGER;
   q      VARCHAR2(3);
BEGIN
   IF    iptype = 4 THEN
      -- Sanity check
      ipwork := REGEXP_SUBSTR(ip_string, '\d{1,3}(\.\d{1,3}){3}');
      IF ipwork IS NULL THEN RETURN NULL; END IF;

      -- Starting prefix
      -- NOTE: 2^48 - 2^32 = 281470681743360 = ::ffff:0.0.0.0
      --       (for compatibility with IPv4 addresses in IPv6)
      ip := '00000000000000000000ffff';

      -- Parse the input
      WHILE LENGTH(ipwork) IS NOT NULL
      LOOP
         d := INSTR(ipwork, '.');  -- find the dot
         IF d > 0 THEN             -- isolate the decimal octet
            q      := SUBSTR(ipwork, 1, d - 1);
            ipwork := SUBSTR(ipwork, d + 1);
         ELSE
            q      := ipwork;
            ipwork := '';
         END IF;

         -- convert to a hex string
         ip := ip || TO_CHAR(TO_NUMBER(q), 'FM0x');

      END LOOP;
   ELSIF iptype = 6 THEN
      -- Short-circuit "::" = 0
      IF ip_string = '::' THEN RETURN LPAD('0', 32, '0'); END IF;

      -- Sanity check
      ipwork := REGEXP_SUBSTR(ip_string, '[[:xdigit:]]{0,4}(\:[[:xdigit:]]{0,4}){0,7}');
      IF ipwork IS NULL THEN RETURN NULL; END IF;

      -- Replace leading zeros
      -- (add a bunch to all of the pairs, then remove only the required ones)
      ipwork := REGEXP_REPLACE(ipwork, '(^|\:)([[:xdigit:]]{1,4})', '\1000\2');
      ipwork := REGEXP_REPLACE(ipwork, '(^|\:)0+([[:xdigit:]]{4})',    '\1\2');

      -- Groups of zeroes
      -- (total length should be 32+Z, so the gap would be the zeroes)
      ipwork := REPLACE(ipwork, '::', 'Z');
      ipwork := REPLACE(ipwork, ':');
      ipwork := REPLACE(ipwork, 'Z', LPAD('0', 33 - LENGTH(ipwork), '0'));
      ip     := LOWER(ipwork);
   ELSE
      RETURN NULL;
   END IF;

   RETURN ip;

END iptohex;

CREATE OR REPLACE FUNCTION nettohex(
   ip_string IN VARCHAR2,
   cidr      IN NATURALN,
   is_end    IN SIGNTYPE DEFAULT 0
) RETURN CHAR
DETERMINISTIC
IS
   iptype   NATURAL  := ipguess(ip_string);
   iphex    CHAR(32) := iptohex(ip_string);
   iphalf1  CHAR(16) := SUBSTR(iphex, 1, 16);
   iphalf2  CHAR(16) := SUBSTR(iphex, 17);
   ipwork   CHAR(16) := iphalf2;
   cidr_exp INTEGER  := 2 ** (iptype + 1) - cidr;
   ipint    INTEGER;
   subnet   INTEGER;
   is_big   SIGNTYPE := 0;
BEGIN
   -- Sanity checks
   IF    iptype IS NULL THEN RETURN NULL;
   ELSIF iphex  IS NULL THEN RETURN NULL;
   END IF;

   IF    cidr_exp >= 64  THEN is_big := 1;
   ELSIF cidr_exp = 0    THEN RETURN iphex;  -- the exact IP, such as /32 on IPv4
   ELSIF cidr_exp <  0   THEN RETURN NULL;
   ELSIF cidr_exp >  128 THEN RETURN NULL;
   END IF;

   -- Change some variables around if we are working with the first/largest half
   IF is_big = 1 THEN
      ipwork   := iphalf1;
      iphalf2  := TO_CHAR((2 ** 64 - 1) * is_end, 'FM0xxxxxxxxxxxxxxx');  -- either all 0 or all F
      cidr_exp := cidr_exp - 64;
   END IF;

   -- Normalize IP to divisions of CIDR
   subnet := 2 ** cidr_exp;
   ipint  := TO_NUMBER(ipwork, 'FM0xxxxxxxxxxxxxxx');
   -- if is_end = 1 then add one net range (then subtract one IP) to get the ending range
   ipwork := TO_CHAR(FLOOR(ipint / subnet + is_end) * subnet - is_end, 'FM0xxxxxxxxxxxxxxx');

   -- Re-integrate
   IF is_big = 0 THEN iphalf2 := ipwork;
   ELSE               iphalf1 := ipwork;
   END IF;

   RETURN SUBSTR(iphalf1 || iphalf2, 1, 32);

END nettohex;

-- WHERE clause:
-- 1. BETWEEN compare:
--    iptohex(a.ip_addy) BETWEEN nettohex(b.net_addy, b.cidr, 0) AND nettohex(b.net_addy, b.cidr, 1)
--
--    Requires three function-based indexes, but all of them would work, as they are all inside the tables.
--
-- 2. CIDR match:
--    nettohex(a.ip_addy, b.cidr) = nettohex(b.net_addy, b.cidr)
--
--    Only two functions and uses exact match, but first one requires an outside variable.  Last one would be only function-based index.
--    An FBI of iptohex(a.ip_addy) could be implemented, but it's questionable if nettohex would use that index.
--
-- Recommended FBIs:
--
-- (SUBSTR(iptohex(a.ip_addy), 1, 32))
-- (SUBSTR(nettohex(b.ip_addy, b.cidr, 0), 1, 32), SUBSTR(nettohex(b.ip_addy, b.cidr, 1), 1, 32))
--
-- NOTE: Will need to use the SUBSTR form for the above WHERE clauses!

AKTUALIZACJA: Oracle 11g pozwala na umieszczenie wpisu SUBSTR w wirtualnej kolumnie. Możesz więc mieć takie kolumny:

ip              VARCHAR2(39),
cidr            NUMBER(2),
ip_hex          AS (SUBSTR(iptohex(ip),           1, 32)) VIRTUAL,
ip_nethex_start AS (SUBSTR(nettohex(ip, cidr, 0), 1, 32)) VIRTUAL,
ip_nethex_end   AS (SUBSTR(nettohex(ip, cidr, 1), 1, 32)) VIRTUAL,

Oraz indeksy takie jak:

CREATE INDEX foobar_iphex_idx ON foobar (ip_hex);
CREATE INDEX foobar_ipnet_idx ON foobar (ip_nethex_start, ip_nethex_end);

Korzystanie z klauzul WHERE, takich jak:

a.ip_hex BETWEEN b.ip_nethex_start AND b.ip_nethex_end
nettohex(a.ip, b.cidr) = b.ip_nethex_start  -- not as effective



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Oracle Delete Statement:ile wierszy zostało usuniętych przez kaskadowe usuwanie

  2. Tworzenie JSON i edycja złożonego zapytania (oracle 11g)

  3. Upewnij się, że sesja php otrzymuje tę samą sesję oracle podczas korzystania z oci_pconnect

  4. Próba załadowania bibliotek klienta Oracle zgłosiła BadImageFormatException

  5. Używanie REGEXP_SUBSTR z kwalifikatorem ciągów