我会用两个功能来解决这个问题。第一个函数接受一个 cidr 和一个异常地址,并返回一组 cidr,它们等价于原始 cidr 减去异常地址。该函数通过将 cidr 分成两半来工作,然后递归地从它所在的那一半中删除异常地址。更复杂的算法可以避免一些不必要的拆分。简单的函数如下所示:
CREATE OR REPLACE FUNCTION split_cidr(net cidr, exc inet) returns setof cidr language plpgsql AS $$
DECLARE
r cidr;
lower cidr;
upper cidr;
BEGIN
IF masklen(net) >= 32 THEN RETURN; END IF;
lower = set_masklen(net, masklen(net)+1);
upper = set_masklen( (lower | ~ netmask(lower)) + 1, masklen(lower));
IF exc << upper THEN
RETURN NEXT lower;
FOR r IN SELECT * from split_cidr(upper, exc)
LOOP RETURN NEXT r;
END LOOP;
ELSE
FOR r IN SELECT * from split_cidr(lower, exc)
LOOP RETURN NEXT r;
END LOOP;
RETURN NEXT upper;
END IF;
RETURN;
END $$;
有了这个功能,就可以遍历网络列表,将其应用于那些包含异常地址的网络。以下函数将网络地址列表拆分为包含异常的和不包含异常的。那些不返回的,那些应用了上述功能的。这不处理网络包含多个异常地址的情况。
CREATE OR REPLACE FUNCTION DOIT() RETURNS Setof cidr language plpgsql AS $$
DECLARE
r cidr;
x cidr;
z inet;
BEGIN
-- these are the rows where the network has no exceptions
FOR r in SELECT network FROM tmp_networks n WHERE NOT EXISTS (
SELECT address FROM tmp_except WHERE address << n.network )
LOOP RETURN NEXT r;
END LOOP;
-- these are the rows where the network has an exception
FOR r,z in SELECT network, address from tmp_networks full join tmp_except on true where address << network
LOOP
FOR x IN SELECT * FROM split_cidr(r, z)
LOOP RETURN NEXT x;
END LOOP;
END LOOP;
END $$;
我将通过修改 split_cidr 以获取异常地址数组而不是单个异常地址,然后将每个网络的异常聚合到一个数组中并为网络及其异常数组调用 split_cidr_array 来处理每个网络的多个异常地址的情况.