Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
Finding all rows where an IP address falls within a subnet requires comparing numeric IP values against a range. MySQL’s standard approach uses bitwise arithmetic with INET_ATON(). VillageSQL’s INET type provides network functions that make the same query more readable and work for both IPv4 and IPv6.

The Standard Approach: Bitwise Range Checks

For IPv4, INET_ATON() converts an address to an unsigned 32-bit integer. A subnet membership check is a bitwise AND: if (ip & netmask) == network_address, the IP is in the subnet.
Both work for IPv4. The BETWEEN approach is readable. The bitwise form is more composable when subnet parameters are variables. Neither approach works for IPv6 — INET_ATON() only handles IPv4.

With VillageSQL: Network Functions on INET Columns

VillageSQL’s vsql_network_address extension provides containment predicates that answer “is this address inside that subnet” directly — inet_contained_by(), inet_contains(), and inet_overlaps() — plus inet_network(), inet_set_masklen(), and inet_compare() for building the check by hand. All of them work on both IPv4 and IPv6 INET columns.

Subnet containment query

To check whether a stored IP belongs to a subnet, mask the address to the subnet’s prefix length and compare the result to the target network:
For a parameterized version where the subnet comes from a variable:

Containment predicates

The extension ships PostgreSQL’s five containment predicates as functions. They take two INET values and return 1, 0, or NULL: The subnet query above becomes a single call:
Only the network part of each value is compared, through the shorter of the two prefix lengths — so 192.168.1.5/24 and 192.168.1.9/24 are the same network, and the strict forms return 0 for them while the _or_equals forms return 1. Two addresses of different families never match: comparing an IPv4 value with an IPv6 one returns 0 rather than raising an error, matching PostgreSQL. Both arguments must be INET. A CIDR value is not coerced:
Pass inet_from_string() on the subnet instead, as the example above does. These functions were added in vsql_network_address 0.0.5.

Range-based containment with inet_compare

An alternative that avoids recomputing the network on every row — useful when you have the broadcast address available:

IPv6 subnet queries (same pattern)

The same inet_network() approach works for IPv6 without any changes:

Networks table pattern

A common use case: a networks table with authorized CIDRs, and checking whether an incoming IP is in any of them.

Standard vs VillageSQL

Frequently Asked Questions

Does VillageSQL have PostgreSQL’s << containment operator?

The behaviour is there, as functions rather than operators: inet_contained_by() for <<, inet_contains() for >>, the _or_equals variants for <<= and >>=, and inet_overlaps() for &&. See Containment predicates.

Does the subnet query use the index?

The cidr_to_string(inet_network(inet_set_masklen(...))) pattern does not use an index scan — it recomputes the network address for every row. For large tables, the inet_compare range approach is more index-friendly since it expresses the query as a range on the ip_address column.

Can I store subnets and query containment at the same time?

Yes. Store both the host address and the subnet in INET columns and compare them with inet_contained_by(host_ip, subnet). If the subnet is in a CIDR column, convert it first — the containment predicates take INET on both sides and do not coerce CIDR.

Troubleshooting

See also