sql >> データベース >  >> RDS >> Oracle

Oracle PL /SQLバージョンのINET6_ATONおよびNTOA関数?

    自分で転がしてしまいました。また、Oracleの126ビットINTEGERは、IPv6の128ビットアドレスには十分なビットではないことも認識しました。率直に言って、16バイトの整数について聞いたことがないことを考えると、元のCライブラリのINET6_ATON(またはINET_PTON)がどのようにそれを行うのかわかりません。

    最終的に32バイトの16進文字列になりました。つまり、nettohexでいくつかの凝った「半文字列」計算を実行し、FBIが正しく機能するためにSUBSTRを使用する必要があります。 (ブラストされたPL / SQLでは「RETURNCHAR(32)」は使用できません...)

    ただし、全体としてはうまく機能し、すべての形式で機能し、インデックスベースの文字比較でIPアドレスがIP範囲内にあるかどうかを確認できます。

    完全なコードは次のとおりです:

    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!
    

    更新: Oracle 11gでは、SUBSTRエントリを仮想列に配置できます。したがって、次のような列を作成できます:

    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,
    

    そして次のようなインデックス:

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

    次のようなWHERE句の使用:

    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. SQLServerでALL論理演算子を使用する方法-SQLServer/TSQLチュートリアルパート126

    2. 特定のデータベーステーブルにレコードが追加/更新/削除された場合、プログラムに警告するにはどうすればよいですか?

    3. 繰り返されるテーブル内のレコードを削除するにはどうすればよいですか?

    4. mysqlのデータベースで最大行数のテーブルを検索します