sql >> Database teknologi >  >> RDS >> Oracle

Oracle PL/SQL versioner af INET6_ATON og NTOA funktioner?

Endte med at rulle mit eget. Indså også, at Oracles 126-bit INTEGER ikke er nok bits til IPv6's 128-bit adresser. Helt ærligt, jeg ved ikke, hvordan det originale C-biblioteks INET6_ATON (eller INET_PTON) gør det, i betragtning af at jeg aldrig har hørt om et 16-byte heltal.

Jeg endte med en 32-byte hex-streng, hvilket betyder, at jeg skal lave noget fancy "halvstrengs"-matematik på nettohex og bruge SUBSTR for at FBI'erne fungerer korrekt. (Blastet PL/SQL tillader ikke "RETURN CHAR(32)"...)

Generelt fungerer det dog godt, fungerer i alle formater og giver mulighed for indeksbaserede tegnsammenligninger for at finde ud af, om en IP-adresse er inden for et IP-interval.

Her er den fulde kode:

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!

OPDATERING: Oracle 11g tillader, at SUBSTR-indgangen sættes i en virtuel kolonne. Så du kunne have kolonner som denne:

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,

Og indekser som:

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

Brug af WHERE-sætninger som:

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. Hibernate - ClassNotFoundException:com.mysql.jdbc.Driver

  2. Konverter til datetime fra Oracle

  3. Mærkeligt sorteringsproblem i PHP, mysql med utf8 data

  4. Rangering baseret på rækkefølge af datoer