PostgreSQL customize inet_aton, inet_ntoa function in text type like MySQL.

Postgres2015全国用户大会将于11月20至21日在北京丽亭华苑酒店召开。本次大会嘉宾阵容强大,国内顶级PostgreSQL数据库专家将悉数到场,并特邀欧洲、俄罗斯、日本、美国等国家和地区的数据库方面专家助阵:

  • Postgres-XC项目的发起人铃木市一(SUZUKI Koichi)
  • Postgres-XL的项目发起人Mason Sharp
  • pgpool的作者石井达夫(Tatsuo Ishii)
  • PG-Strom的作者海外浩平(Kaigai Kohei)
  • Greenplum研发总监姚延栋
  • 周正中(德哥), PostgreSQL中国用户会创始人之一
  • 汪洋,平安科技数据库技术部经理
  • ……
 
2015年度PG大象会报名地址:http://postgres2015.eventdove.com/PostgreSQL中国社区: http://postgres.cn/PostgreSQL专业1群: 3336901(已满)PostgreSQL专业2群: 100910388PostgreSQL专业3群: 150657323



PostgreSQL的类型支持比较强大, 除了主流的文本, 日期, 时间, 数字, 还支持JSON, 货币, 网络, 几何, 枚举, 数组, 范围, XML, 布尔, 全文检索, 比特, 复合等数据类型. 
MySQL的类型支持比较薄弱, MySQL里面没有专门的网络数据类型, 如果要存储网络信息, 一般的做法是转换成INT类型来处理.
PostgreSQL当然没有必要这么做, 因为已经支持inet和cidr类型了, 操作极为方便. 开发人员使用PostgreSQL会比较愉快.
例如
>> contains inet '192.168.1/24' >> inet '192.168.1.5'
 
   

digoal=# select inet '192.168.1/24' >> inet '192.168.2.1'
digoal-# ;
 ?column? 
----------
 f
(1 row)

digoal=# select inet '192.168.1/24' >> inet '192.168.2.1';
 ?column? 
----------
 f
(1 row)

digoal=# select inet '192.168.1/24' >> inet '192.168.1.100';
 ?column? 
----------
 t
(1 row)

digoal=# select inet '192.168.1.0/24' >> inet '192.168.1.100';
 ?column? 
----------
 t
(1 row)

digoal=# select inet '192.168.1.128/25' >> inet '192.168.1.100';
 ?column? 
----------
 f
(1 row)

digoal=# select inet '192.168.1.128/25' >> inet '192.168.1.129';
 ?column? 
----------
 t
(1 row)

本文模拟在没有INET或者cidr类型的时候的使用方法.

那么我这里要说的是如果你是从mysql转到pg的, 并且前期不想改代码的话, 那么就需要写一些和MySQL兼容的函数.
例如MySQL里面的inet_aton , inet_ntoa.
  •  INET_ATON(expr)

    Given the dotted-quad representation of an IPv4 network address as a string, returns an integer that represents the numeric value of the address in network byte order (big endian). INET_ATON() returns NULL if it does not understand its argument.

    mysql> SELECT INET_ATON('10.0.5.9');
            -> 167773449
    

    For this example, the return value is calculated as 10×2563 + 0×2562 + 5×256 + 9.

    INET_ATON() may or may not return a non-NULL result for short-form IP addresses (such as '127.1' as a representation of '127.0.0.1'). Because of this, INET_ATON()a should not be used for such addresses.

    Note

    To store values generated by INET_ATON(), use an INT UNSIGNED column rather than INT, which is signed. If you use a signed column, values corresponding to IP addresses for which the first octet is greater than 127 cannot be stored correctly. See Section 11.2.6, “Out-of-Range and Overflow Handling”.

  •  INET_NTOA(expr)

    Given a numeric IPv4 network address in network byte order, returns the dotted-quad string representation of the address as a nonbinary string in the connection character set. INET_NTOA() returns NULL if it does not understand its argument.

    mysql> SELECT INET_NTOA(167773449);
            -> '10.0.5.9'
在PostgreSQL可以通过如下自定义函数来实现 : 
 
   

create or replace function inet_aton(ip text) returns int8 as $$
declare
  v int;
  res int8 := 0;
  i int := 3;
begin
  foreach v in array string_to_array(ip, '.') loop
    res := res+v*(256^i);
    i := i-1;
  end loop;
  return res;
end;
$$ language plpgsql;

create or replace function inet_ntoa(ip int8) returns text as $$
declare
  res text := '';
begin
  res := res || ((ip >> 24) & (2^8-1)::int);
  res := res || '.' || ((ip >> 16) & (2^8-1)::int);
  res := res || '.' || ((ip >> 8) & (2^8-1)::int);
  res := res || '.' || (ip & (2^8-1)::int);
  return res;
end;
$$ language plpgsql;


测试 : 
 
   

digoal=# select inet_aton('192.168.9.100');
 inet_aton  
------------
 3232237924
(1 row)

digoal=# select inet_ntoa(3232237924);
   inet_ntoa   
---------------
 192.168.9.100
(1 row)


在MySQL中假如存储了IP和子网掩码的值, 我们可以通过如下方法来判断IP是否在这个子网中.
1. 从IP和子网掩码运算得到net和broadcast, 然后转换成INT8.
2. 把要匹配的IP转换成INT8, 与第一步得到的值进行比较
例如 : 
 
   

192.168.9.0/24
192.168.9.128/25
192.168.9.0/25
192.168.0.0/16

转换成net和broadcast
 
    

192.168.9.0, 192.168.9.255
192.168.9.128, 192.168.9.255
192.168.9.0, 192.168.9.127
192.168.0.0, 192.168.255.255

net的转换方法如下 : 
 
   

digoal=# select substring('192.168.9.0/24','(.*)/\d*');
  substring  
-------------
 192.168.9.0
(1 row)

broadcast的转换方法如下 : 
 
   

digoal=# select inet_ntoa((inet_aton(substring('192.168.9.0/24','(.*)/\d*')) + 2^(32-substring('192.168.9.0/24','.*/(\d*)')::int) -1)::int8);
   inet_ntoa   
---------------
 192.168.9.255
(1 row)

转换成数字就可以做比较了.
 
   

digoal=# create table t1 (ip text, inet text);
CREATE TABLE
digoal=# insert into t1 values ('192.168.1.1','192.168.1.128/25');
INSERT 0 1
digoal=# insert into t1 values ('192.168.1.10','192.168.1.0/25');
INSERT 0 1
digoal=# insert into t1 values ('192.168.1.129','192.168.1.128/25');
INSERT 0 1
digoal=# select inet, substring(inet,'(.*)/\d*') as net, inet_ntoa((inet_aton(substring(inet,'(.*)/\d*')) + 2^(32-substring(inet,'.*/(\d*)')::int) -1)::int8) as broadcast from t1;
       inet       |      net      |   broadcast   
------------------+---------------+---------------
 192.168.1.128/25 | 192.168.1.128 | 192.168.1.255
 192.168.1.0/25   | 192.168.1.0   | 192.168.1.127
 192.168.1.128/25 | 192.168.1.128 | 192.168.1.255
(3 rows)

查看IP是否落在IPNET范围内.
 
   

digoal=# select * from (select ip, inet, substring(inet,'(.*)/\d*') as net, inet_ntoa((inet_aton(substring(inet,'(.*)/\d*')) + 2^(32-substring(inet,'.*/(\d*)')::int) -1)::int8) as broadcast from t1) t where inet_aton(ip) > inet_aton(net) and inet_aton(ip) < inet_aton(broadcast);
      ip       |       inet       |      net      |   broadcast   
---------------+------------------+---------------+---------------
 192.168.1.10  | 192.168.1.0/25   | 192.168.1.0   | 192.168.1.127
 192.168.1.129 | 192.168.1.128/25 | 192.168.1.128 | 192.168.1.255
(2 rows)


[参考]
1.  http://dev.mysql.com/doc/refman/5.5/en/miscellaneous-functions.html#function_inet-aton
2.  http://doxygen.postgresql.org/inet__aton_8c_source.html#l00054
3.  http://www.postgresql.org/docs/9.3/static/datatype-net-types.html
4.  http://www.postgresql.org/docs/9.3/static/functions-net.html
5.  http://doxygen.postgresql.org/inet__aton_8c_source.html#l00054

你可能感兴趣的:(转载,数据库应用开发)