From e7eb8a3250fa1d6d4c9c57fc896e62b0d44247b0 Mon Sep 17 00:00:00 2001
From: Jack Christensen <jack@jackchristensen.com>
Date: Sun, 10 Jul 2022 14:29:44 -0500
Subject: [PATCH] Use netip package for representing inet and cidr types

---
 CHANGELOG.md               |   1 +
 README.md                  |   2 +-
 doc.go                     |   3 +-
 pgtype/builtin_wrappers.go |  81 +++++++++++++++----
 pgtype/inet.go             | 162 ++++++++++++++-----------------------
 pgtype/inet_test.go        |  52 +++++++++++-
 pgtype/pgtype.go           |  52 +++++++++++-
 pgtype/pgtype_test.go      |   2 +
 8 files changed, 231 insertions(+), 124 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e97434f..5e605f68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -102,6 +102,7 @@ This matches the convention set by `database/sql`. In addition, for comparable t
 * `Hstore` is now defined as `map[string]*string`.
 * `JSON` and `JSONB` types removed. Use `[]byte` or `string` directly.
 * `QChar` type removed. Use `rune` or `byte` directly.
+* `Inet` and `Cidr` types removed. Use `netip.Addr` and `netip.Prefix` directly. These types are more memory efficient than the previous `net.IPNet`.
 * `Macaddr` type removed. Use `net.HardwareAddr` directly.
 * Renamed `pgtype.ConnInfo` to `pgtype.Map`.
 * Renamed `pgtype.DataType` to `pgtype.Type`.
diff --git a/README.md b/README.md
index 3cc5a91b..c4f7239f 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ pgx supports many features beyond what is available through `database/sql`:
 * Conversion of PostgreSQL arrays to Go slice mappings for integers, floats, and strings
 * Hstore support
 * JSON and JSONB support
-* Maps `inet` and `cidr` PostgreSQL types to `net.IPNet` and `net.IP`
+* Maps `inet` and `cidr` PostgreSQL types to `netip.Addr` and `netip.Prefix`
 * Large object support
 * NULL mapping to Null* struct or pointer to pointer
 * Supports `database/sql.Scanner` and `database/sql/driver.Valuer` interfaces for custom types
diff --git a/doc.go b/doc.go
index 0fd3713b..cfc2af85 100644
--- a/doc.go
+++ b/doc.go
@@ -152,8 +152,7 @@ pgx includes built-in support to marshal and unmarshal between Go types and the
 
 Inet and CIDR Mapping
 
-pgx encodes from net.IPNet to and from inet and cidr PostgreSQL types. In addition, as a convenience pgx will encode
-from a net.IP; it will assume a /32 netmask for IPv4 and a /128 for IPv6.
+pgx converts netip.Prefix and netip.Addr to and from inet and cidr PostgreSQL types.
 
 Custom Type Support
 
diff --git a/pgtype/builtin_wrappers.go b/pgtype/builtin_wrappers.go
index da9cf0bb..7a992b09 100644
--- a/pgtype/builtin_wrappers.go
+++ b/pgtype/builtin_wrappers.go
@@ -1,9 +1,11 @@
 package pgtype
 
 import (
+	"errors"
 	"fmt"
 	"math"
 	"net"
+	"net/netip"
 	"reflect"
 	"time"
 )
@@ -460,44 +462,95 @@ func (w durationWrapper) IntervalValue() (Interval, error) {
 
 type netIPNetWrapper net.IPNet
 
-func (w *netIPNetWrapper) ScanInet(v Inet) error {
-	if !v.Valid {
+func (w *netIPNetWrapper) ScanNetipPrefix(v netip.Prefix) error {
+	if !v.IsValid() {
 		return fmt.Errorf("cannot scan NULL into *net.IPNet")
 	}
 
-	*w = (netIPNetWrapper)(*v.IPNet)
+	*w = netIPNetWrapper{
+		IP:   v.Addr().AsSlice(),
+		Mask: net.CIDRMask(v.Bits(), v.Addr().BitLen()),
+	}
+
 	return nil
 }
+func (w netIPNetWrapper) NetipPrefixValue() (netip.Prefix, error) {
+	ip, ok := netip.AddrFromSlice(w.IP)
+	if !ok {
+		return netip.Prefix{}, errors.New("invalid net.IPNet")
+	}
 
-func (w netIPNetWrapper) InetValue() (Inet, error) {
-	return Inet{IPNet: (*net.IPNet)(&w), Valid: true}, nil
+	ones, _ := w.Mask.Size()
+
+	return netip.PrefixFrom(ip, ones), nil
 }
 
 type netIPWrapper net.IP
 
 func (w netIPWrapper) SkipUnderlyingTypePlan() {}
 
-func (w *netIPWrapper) ScanInet(v Inet) error {
-	if !v.Valid {
+func (w *netIPWrapper) ScanNetipPrefix(v netip.Prefix) error {
+	if !v.IsValid() {
 		*w = nil
 		return nil
 	}
 
-	if oneCount, bitCount := v.IPNet.Mask.Size(); oneCount != bitCount {
+	if v.Addr().BitLen() != v.Bits() {
 		return fmt.Errorf("cannot scan %v to *net.IP", v)
 	}
-	*w = netIPWrapper(v.IPNet.IP)
+
+	*w = netIPWrapper(v.Addr().AsSlice())
 	return nil
 }
 
-func (w netIPWrapper) InetValue() (Inet, error) {
+func (w netIPWrapper) NetipPrefixValue() (netip.Prefix, error) {
 	if w == nil {
-		return Inet{}, nil
+		return netip.Prefix{}, nil
 	}
 
-	bitCount := len(w) * 8
-	mask := net.CIDRMask(bitCount, bitCount)
-	return Inet{IPNet: &net.IPNet{Mask: mask, IP: net.IP(w)}, Valid: true}, nil
+	addr, ok := netip.AddrFromSlice([]byte(w))
+	if !ok {
+		return netip.Prefix{}, errors.New("invalid net.IP")
+	}
+
+	return netip.PrefixFrom(addr, addr.BitLen()), nil
+}
+
+type netipPrefixWrapper netip.Prefix
+
+func (w *netipPrefixWrapper) ScanNetipPrefix(v netip.Prefix) error {
+	*w = netipPrefixWrapper(v)
+	return nil
+}
+
+func (w netipPrefixWrapper) NetipPrefixValue() (netip.Prefix, error) {
+	return netip.Prefix(w), nil
+}
+
+type netipAddrWrapper netip.Addr
+
+func (w *netipAddrWrapper) ScanNetipPrefix(v netip.Prefix) error {
+	if !v.IsValid() {
+		*w = netipAddrWrapper(netip.Addr{})
+		return nil
+	}
+
+	if v.Addr().BitLen() != v.Bits() {
+		return fmt.Errorf("cannot scan %v to netip.Addr", v)
+	}
+
+	*w = netipAddrWrapper(v.Addr())
+
+	return nil
+}
+
+func (w netipAddrWrapper) NetipPrefixValue() (netip.Prefix, error) {
+	addr := (netip.Addr)(w)
+	if !addr.IsValid() {
+		return netip.Prefix{}, nil
+	}
+
+	return netip.PrefixFrom(addr, addr.BitLen()), nil
 }
 
 type mapStringToPointerStringWrapper map[string]*string
diff --git a/pgtype/inet.go b/pgtype/inet.go
index f8abeef8..f094ed2f 100644
--- a/pgtype/inet.go
+++ b/pgtype/inet.go
@@ -1,9 +1,11 @@
 package pgtype
 
 import (
+	"bytes"
 	"database/sql/driver"
+	"errors"
 	"fmt"
-	"net"
+	"net/netip"
 )
 
 // Network address family is dependent on server socket.h value for AF_INET.
@@ -14,57 +16,16 @@ const (
 	defaultAFInet6 = 3
 )
 
-type InetScanner interface {
-	ScanInet(v Inet) error
+type NetipPrefixScanner interface {
+	ScanNetipPrefix(v netip.Prefix) error
 }
 
-type InetValuer interface {
-	InetValue() (Inet, error)
-}
-
-// Inet represents both inet and cidr PostgreSQL types.
-type Inet struct {
-	IPNet *net.IPNet
-	Valid bool
-}
-
-func (inet *Inet) ScanInet(v Inet) error {
-	*inet = v
-	return nil
-}
-
-func (inet Inet) InetValue() (Inet, error) {
-	return inet, nil
-}
-
-// Scan implements the database/sql Scanner interface.
-func (dst *Inet) Scan(src any) error {
-	if src == nil {
-		*dst = Inet{}
-		return nil
-	}
-
-	switch src := src.(type) {
-	case string:
-		return scanPlanTextAnyToInetScanner{}.Scan([]byte(src), dst)
-	}
-
-	return fmt.Errorf("cannot scan %T", src)
-}
-
-// Value implements the database/sql/driver Valuer interface.
-func (src Inet) Value() (driver.Value, error) {
-	if !src.Valid {
-		return nil, nil
-	}
-
-	buf, err := InetCodec{}.PlanEncode(nil, 0, TextFormatCode, src).Encode(src, nil)
-	if err != nil {
-		return nil, err
-	}
-	return string(buf), err
+type NetipPrefixValuer interface {
+	NetipPrefixValue() (netip.Prefix, error)
 }
 
+// InetCodec handles both inet and cidr PostgreSQL types. The preferred Go types are netip.Prefix and netip.Addr. If
+// IsValid() is false then they are treated as SQL NULL.
 type InetCodec struct{}
 
 func (InetCodec) FormatSupported(format int16) bool {
@@ -76,7 +37,7 @@ func (InetCodec) PreferredFormat() int16 {
 }
 
 func (InetCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
-	if _, ok := value.(InetValuer); !ok {
+	if _, ok := value.(NetipPrefixValuer); !ok {
 		return nil
 	}
 
@@ -93,51 +54,56 @@ func (InetCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodeP
 type encodePlanInetCodecBinary struct{}
 
 func (encodePlanInetCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
-	inet, err := value.(InetValuer).InetValue()
+	prefix, err := value.(NetipPrefixValuer).NetipPrefixValue()
 	if err != nil {
 		return nil, err
 	}
 
-	if !inet.Valid {
+	if !prefix.IsValid() {
 		return nil, nil
 	}
 
 	var family byte
-	switch len(inet.IPNet.IP) {
-	case net.IPv4len:
+	if prefix.Addr().Is4() {
 		family = defaultAFInet
-	case net.IPv6len:
+	} else {
 		family = defaultAFInet6
-	default:
-		return nil, fmt.Errorf("Unexpected IP length: %v", len(inet.IPNet.IP))
 	}
 
 	buf = append(buf, family)
 
-	ones, _ := inet.IPNet.Mask.Size()
+	ones := prefix.Bits()
 	buf = append(buf, byte(ones))
 
 	// is_cidr is ignored on server
 	buf = append(buf, 0)
 
-	buf = append(buf, byte(len(inet.IPNet.IP)))
+	if family == defaultAFInet {
+		buf = append(buf, byte(4))
+		b := prefix.Addr().As4()
+		buf = append(buf, b[:]...)
+	} else {
+		buf = append(buf, byte(16))
+		b := prefix.Addr().As16()
+		buf = append(buf, b[:]...)
+	}
 
-	return append(buf, inet.IPNet.IP...), nil
+	return buf, nil
 }
 
 type encodePlanInetCodecText struct{}
 
 func (encodePlanInetCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
-	inet, err := value.(InetValuer).InetValue()
+	prefix, err := value.(NetipPrefixValuer).NetipPrefixValue()
 	if err != nil {
 		return nil, err
 	}
 
-	if !inet.Valid {
+	if !prefix.IsValid() {
 		return nil, nil
 	}
 
-	return append(buf, inet.IPNet.String()...), nil
+	return append(buf, prefix.String()...), nil
 }
 
 func (InetCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
@@ -145,13 +111,13 @@ func (InetCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan
 	switch format {
 	case BinaryFormatCode:
 		switch target.(type) {
-		case InetScanner:
-			return scanPlanBinaryInetToInetScanner{}
+		case NetipPrefixScanner:
+			return scanPlanBinaryInetToNetipPrefixScanner{}
 		}
 	case TextFormatCode:
 		switch target.(type) {
-		case InetScanner:
-			return scanPlanTextAnyToInetScanner{}
+		case NetipPrefixScanner:
+			return scanPlanTextAnyToNetipPrefixScanner{}
 		}
 	}
 
@@ -167,26 +133,26 @@ func (c InetCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (an
 		return nil, nil
 	}
 
-	var inet Inet
-	err := codecScan(c, m, oid, format, src, &inet)
+	var prefix netip.Prefix
+	err := codecScan(c, m, oid, format, src, &prefix)
 	if err != nil {
 		return nil, err
 	}
 
-	if !inet.Valid {
+	if !prefix.IsValid() {
 		return nil, nil
 	}
 
-	return inet.IPNet, nil
+	return prefix, nil
 }
 
-type scanPlanBinaryInetToInetScanner struct{}
+type scanPlanBinaryInetToNetipPrefixScanner struct{}
 
-func (scanPlanBinaryInetToInetScanner) Scan(src []byte, dst any) error {
-	scanner := (dst).(InetScanner)
+func (scanPlanBinaryInetToNetipPrefixScanner) Scan(src []byte, dst any) error {
+	scanner := (dst).(NetipPrefixScanner)
 
 	if src == nil {
-		return scanner.ScanInet(Inet{})
+		return scanner.ScanNetipPrefix(netip.Prefix{})
 	}
 
 	if len(src) != 8 && len(src) != 20 {
@@ -196,49 +162,39 @@ func (scanPlanBinaryInetToInetScanner) Scan(src []byte, dst any) error {
 	// ignore family
 	bits := src[1]
 	// ignore is_cidr
-	addressLength := src[3]
+	// ignore addressLength - implicit in length of message
 
-	var ipnet net.IPNet
-	ipnet.IP = make(net.IP, int(addressLength))
-	copy(ipnet.IP, src[4:])
-	if ipv4 := ipnet.IP.To4(); ipv4 != nil {
-		ipnet.IP = ipv4
+	addr, ok := netip.AddrFromSlice(src[4:])
+	if !ok {
+		return errors.New("netip.AddrFromSlice failed")
 	}
-	ipnet.Mask = net.CIDRMask(int(bits), len(ipnet.IP)*8)
 
-	return scanner.ScanInet(Inet{IPNet: &ipnet, Valid: true})
+	return scanner.ScanNetipPrefix(netip.PrefixFrom(addr, int(bits)))
 }
 
-type scanPlanTextAnyToInetScanner struct{}
+type scanPlanTextAnyToNetipPrefixScanner struct{}
 
-func (scanPlanTextAnyToInetScanner) Scan(src []byte, dst any) error {
-	scanner := (dst).(InetScanner)
+func (scanPlanTextAnyToNetipPrefixScanner) Scan(src []byte, dst any) error {
+	scanner := (dst).(NetipPrefixScanner)
 
 	if src == nil {
-		return scanner.ScanInet(Inet{})
+		return scanner.ScanNetipPrefix(netip.Prefix{})
 	}
 
-	var ipnet *net.IPNet
-	var err error
-
-	if ip := net.ParseIP(string(src)); ip != nil {
-		if ipv4 := ip.To4(); ipv4 != nil {
-			ip = ipv4
-		}
-		bitCount := len(ip) * 8
-		mask := net.CIDRMask(bitCount, bitCount)
-		ipnet = &net.IPNet{Mask: mask, IP: ip}
-	} else {
-		ip, ipnet, err = net.ParseCIDR(string(src))
+	var prefix netip.Prefix
+	if bytes.IndexByte(src, '/') == -1 {
+		addr, err := netip.ParseAddr(string(src))
 		if err != nil {
 			return err
 		}
-		if ipv4 := ip.To4(); ipv4 != nil {
-			ip = ipv4
+		prefix = netip.PrefixFrom(addr, addr.BitLen())
+	} else {
+		var err error
+		prefix, err = netip.ParsePrefix(string(src))
+		if err != nil {
+			return err
 		}
-		ones, _ := ipnet.Mask.Size()
-		*ipnet = net.IPNet{IP: ip, Mask: net.CIDRMask(ones, len(ip)*8)}
 	}
 
-	return scanner.ScanInet(Inet{IPNet: ipnet, Valid: true})
+	return scanner.ScanNetipPrefix(prefix)
 }
diff --git a/pgtype/inet_test.go b/pgtype/inet_test.go
index 0a174e1a..f4b43daf 100644
--- a/pgtype/inet_test.go
+++ b/pgtype/inet_test.go
@@ -3,9 +3,9 @@ package pgtype_test
 import (
 	"context"
 	"net"
+	"net/netip"
 	"testing"
 
-	"github.com/jackc/pgx/v5/pgtype"
 	"github.com/jackc/pgx/v5/pgxtest"
 )
 
@@ -31,7 +31,42 @@ func TestInetTranscode(t *testing.T) {
 		{mustParseInet(t, "::/0"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "::/0"))},
 		{mustParseInet(t, "::1/128"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "::1/128"))},
 		{mustParseInet(t, "2607:f8b0:4009:80b::200e/64"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "2607:f8b0:4009:80b::200e/64"))},
-		{nil, new(pgtype.Inet), isExpectedEq(pgtype.Inet{})},
+
+		{mustParseInet(t, "0.0.0.0/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("0.0.0.0/32"))},
+		{mustParseInet(t, "127.0.0.1/8"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("127.0.0.1/8"))},
+		{mustParseInet(t, "12.34.56.65/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("12.34.56.65/32"))},
+		{mustParseInet(t, "192.168.1.16/24"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("192.168.1.16/24"))},
+		{mustParseInet(t, "255.0.0.0/8"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("255.0.0.0/8"))},
+		{mustParseInet(t, "255.255.255.255/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("255.255.255.255/32"))},
+		{mustParseInet(t, "2607:f8b0:4009:80b::200e"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("2607:f8b0:4009:80b::200e/128"))},
+		{mustParseInet(t, "::1/64"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::1/64"))},
+		{mustParseInet(t, "::/0"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::/0"))},
+		{mustParseInet(t, "::1/128"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::1/128"))},
+		{mustParseInet(t, "2607:f8b0:4009:80b::200e/64"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("2607:f8b0:4009:80b::200e/64"))},
+
+		{netip.MustParsePrefix("0.0.0.0/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("0.0.0.0/32"))},
+		{netip.MustParsePrefix("127.0.0.1/8"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("127.0.0.1/8"))},
+		{netip.MustParsePrefix("12.34.56.65/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("12.34.56.65/32"))},
+		{netip.MustParsePrefix("192.168.1.16/24"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("192.168.1.16/24"))},
+		{netip.MustParsePrefix("255.0.0.0/8"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("255.0.0.0/8"))},
+		{netip.MustParsePrefix("255.255.255.255/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("255.255.255.255/32"))},
+		{netip.MustParsePrefix("::1/64"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::1/64"))},
+		{netip.MustParsePrefix("::/0"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::/0"))},
+		{netip.MustParsePrefix("::1/128"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::1/128"))},
+		{netip.MustParsePrefix("2607:f8b0:4009:80b::200e/64"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("2607:f8b0:4009:80b::200e/64"))},
+
+		{netip.MustParseAddr("0.0.0.0"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("0.0.0.0"))},
+		{netip.MustParseAddr("127.0.0.1"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("127.0.0.1"))},
+		{netip.MustParseAddr("12.34.56.65"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("12.34.56.65"))},
+		{netip.MustParseAddr("192.168.1.16"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("192.168.1.16"))},
+		{netip.MustParseAddr("255.0.0.0"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("255.0.0.0"))},
+		{netip.MustParseAddr("255.255.255.255"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("255.255.255.255"))},
+		{netip.MustParseAddr("2607:f8b0:4009:80b::200e"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("2607:f8b0:4009:80b::200e"))},
+		{netip.MustParseAddr("::1"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("::1"))},
+		{netip.MustParseAddr("::"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("::"))},
+		{netip.MustParseAddr("2607:f8b0:4009:80b::200e"), new(netip.Addr), isExpectedEq(netip.MustParseAddr("2607:f8b0:4009:80b::200e"))},
+
+		{nil, new(netip.Prefix), isExpectedEq(netip.Prefix{})},
 	})
 }
 
@@ -48,6 +83,17 @@ func TestCidrTranscode(t *testing.T) {
 		{mustParseInet(t, "::/0"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "::/0"))},
 		{mustParseInet(t, "::1/128"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "::1/128"))},
 		{mustParseInet(t, "2607:f8b0:4009:80b::200e/128"), new(net.IPNet), isExpectedEqIPNet(mustParseInet(t, "2607:f8b0:4009:80b::200e/128"))},
-		{nil, new(pgtype.Inet), isExpectedEq(pgtype.Inet{})},
+
+		{netip.MustParsePrefix("0.0.0.0/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("0.0.0.0/32"))},
+		{netip.MustParsePrefix("127.0.0.1/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("127.0.0.1/32"))},
+		{netip.MustParsePrefix("12.34.56.0/32"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("12.34.56.0/32"))},
+		{netip.MustParsePrefix("192.168.1.0/24"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("192.168.1.0/24"))},
+		{netip.MustParsePrefix("255.0.0.0/8"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("255.0.0.0/8"))},
+		{netip.MustParsePrefix("::/128"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::/128"))},
+		{netip.MustParsePrefix("::/0"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::/0"))},
+		{netip.MustParsePrefix("::1/128"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("::1/128"))},
+		{netip.MustParsePrefix("2607:f8b0:4009:80b::200e/128"), new(netip.Prefix), isExpectedEq(netip.MustParsePrefix("2607:f8b0:4009:80b::200e/128"))},
+
+		{nil, new(netip.Prefix), isExpectedEq(netip.Prefix{})},
 	})
 }
diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go
index 904c99fa..160f18af 100644
--- a/pgtype/pgtype.go
+++ b/pgtype/pgtype.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"net"
+	"net/netip"
 	"reflect"
 	"time"
 )
@@ -364,6 +365,8 @@ func NewMap() *Map {
 
 	registerDefaultPgTypeVariants[net.IP](m, "inet")
 	registerDefaultPgTypeVariants[net.IPNet](m, "cidr")
+	registerDefaultPgTypeVariants[netip.Addr](m, "inet")
+	registerDefaultPgTypeVariants[netip.Prefix](m, "cidr")
 
 	// pgtype provided structs
 	registerDefaultPgTypeVariants[Bits](m, "varbit")
@@ -377,7 +380,6 @@ func NewMap() *Map {
 	registerDefaultPgTypeVariants[Float8](m, "float8")
 	registerDefaultPgTypeVariants[Range[Float8]](m, "numrange")                  // There is no PostgreSQL builtin float8range so map it to numrange.
 	registerDefaultPgTypeVariants[Multirange[Range[Float8]]](m, "nummultirange") // There is no PostgreSQL builtin float8multirange so map it to nummultirange.
-	registerDefaultPgTypeVariants[Inet](m, "inet")
 	registerDefaultPgTypeVariants[Int2](m, "int2")
 	registerDefaultPgTypeVariants[Int4](m, "int4")
 	registerDefaultPgTypeVariants[Range[Int4]](m, "int4range")
@@ -751,6 +753,10 @@ func TryWrapBuiltinTypeScanPlan(target any) (plan WrappedScanPlanNextSetter, nex
 		return &wrapNetIPNetScanPlan{}, (*netIPNetWrapper)(target), true
 	case *net.IP:
 		return &wrapNetIPScanPlan{}, (*netIPWrapper)(target), true
+	case *netip.Prefix:
+		return &wrapNetipPrefixScanPlan{}, (*netipPrefixWrapper)(target), true
+	case *netip.Addr:
+		return &wrapNetipAddrScanPlan{}, (*netipAddrWrapper)(target), true
 	case *map[string]*string:
 		return &wrapMapStringToPointerStringScanPlan{}, (*mapStringToPointerStringWrapper)(target), true
 	case *map[string]string:
@@ -934,6 +940,26 @@ func (plan *wrapNetIPScanPlan) Scan(src []byte, dst any) error {
 	return plan.next.Scan(src, (*netIPWrapper)(dst.(*net.IP)))
 }
 
+type wrapNetipPrefixScanPlan struct {
+	next ScanPlan
+}
+
+func (plan *wrapNetipPrefixScanPlan) SetNext(next ScanPlan) { plan.next = next }
+
+func (plan *wrapNetipPrefixScanPlan) Scan(src []byte, dst any) error {
+	return plan.next.Scan(src, (*netipPrefixWrapper)(dst.(*netip.Prefix)))
+}
+
+type wrapNetipAddrScanPlan struct {
+	next ScanPlan
+}
+
+func (plan *wrapNetipAddrScanPlan) SetNext(next ScanPlan) { plan.next = next }
+
+func (plan *wrapNetipAddrScanPlan) Scan(src []byte, dst any) error {
+	return plan.next.Scan(src, (*netipAddrWrapper)(dst.(*netip.Addr)))
+}
+
 type wrapMapStringToPointerStringScanPlan struct {
 	next ScanPlan
 }
@@ -1454,6 +1480,10 @@ func TryWrapBuiltinTypeEncodePlan(value any) (plan WrappedEncodePlanNextSetter,
 		return &wrapNetIPNetEncodePlan{}, netIPNetWrapper(value), true
 	case net.IP:
 		return &wrapNetIPEncodePlan{}, netIPWrapper(value), true
+	case netip.Prefix:
+		return &wrapNetipPrefixEncodePlan{}, netipPrefixWrapper(value), true
+	case netip.Addr:
+		return &wrapNetipAddrEncodePlan{}, netipAddrWrapper(value), true
 	case map[string]*string:
 		return &wrapMapStringToPointerStringEncodePlan{}, mapStringToPointerStringWrapper(value), true
 	case map[string]string:
@@ -1639,6 +1669,26 @@ func (plan *wrapNetIPEncodePlan) Encode(value any, buf []byte) (newBuf []byte, e
 	return plan.next.Encode(netIPWrapper(value.(net.IP)), buf)
 }
 
+type wrapNetipPrefixEncodePlan struct {
+	next EncodePlan
+}
+
+func (plan *wrapNetipPrefixEncodePlan) SetNext(next EncodePlan) { plan.next = next }
+
+func (plan *wrapNetipPrefixEncodePlan) Encode(value any, buf []byte) (newBuf []byte, err error) {
+	return plan.next.Encode(netipPrefixWrapper(value.(netip.Prefix)), buf)
+}
+
+type wrapNetipAddrEncodePlan struct {
+	next EncodePlan
+}
+
+func (plan *wrapNetipAddrEncodePlan) SetNext(next EncodePlan) { plan.next = next }
+
+func (plan *wrapNetipAddrEncodePlan) Encode(value any, buf []byte) (newBuf []byte, err error) {
+	return plan.next.Encode(netipAddrWrapper(value.(netip.Addr)), buf)
+}
+
 type wrapMapStringToPointerStringEncodePlan struct {
 	next EncodePlan
 }
diff --git a/pgtype/pgtype_test.go b/pgtype/pgtype_test.go
index fa4c823b..e2465afc 100644
--- a/pgtype/pgtype_test.go
+++ b/pgtype/pgtype_test.go
@@ -55,6 +55,8 @@ func mustParseInet(t testing.TB, s string) *net.IPNet {
 	if err == nil {
 		if ipv4 := ip.To4(); ipv4 != nil {
 			ipnet.IP = ipv4
+		} else {
+			ipnet.IP = ip
 		}
 		return ipnet
 	}