pgx is a pure Go driver and toolkit for PostgreSQL providing a native high-performance interface with PostgreSQL-specific features plus a database/sql compatibility adapter.
PostgreSQL range and multirange type support.
import "github.com/jackc/pgx/v5/pgtype"type Range[T any] struct {
Lower T
Upper T
LowerType BoundType
UpperType BoundType
Valid bool
}
func (r Range[T]) BoundTypes() (lower, upper BoundType)
func (r Range[T]) Bounds() (lower, upper any)
func (r Range[T]) IsNull() bool
func (r *Range[T]) ScanBounds() (lowerTarget, upperTarget any)
func (r *Range[T]) ScanNull() error
func (r *Range[T]) SetBoundTypes(lower, upper BoundType) error
type BoundType byte
const (
Inclusive BoundType = 'i'
Exclusive BoundType = 'e'
Unbounded BoundType = 'U'
Empty BoundType = 'E'
)
func (bt BoundType) String() stringPostgreSQL provides built-in range types:
int4range — int32 rangeint8range — int64 rangenumrange — numeric rangetsrange — timestamp without time zone rangetstzrange — timestamp with time zone rangedaterange — date rangevar dateRange pgtype.Range[pgtype.Date]
err := conn.QueryRow(ctx,
"SELECT availability FROM schedules WHERE id = $1", id).Scan(&dateRange)
if dateRange.Valid {
fmt.Printf("Lower: %v (%s), Upper: %v (%s)
",
dateRange.Lower, dateRange.LowerType,
dateRange.Upper, dateRange.UpperType)
}dateRange := pgtype.Range[pgtype.Date]{
Lower: pgtype.Date{Time: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true},
Upper: pgtype.Date{Time: time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC), Valid: true},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Inclusive,
Valid: true,
}
_, err := conn.Exec(ctx,
"INSERT INTO schedules (availability) VALUES ($1)", dateRange)var ageRange pgtype.Range[pgtype.Int4]
err := conn.QueryRow(ctx,
"SELECT age_range FROM demographics WHERE id = $1", id).Scan(&ageRange)
if ageRange.Valid {
if ageRange.LowerType != pgtype.Unbounded {
fmt.Println("Min age:", ageRange.Lower.Int32)
}
if ageRange.UpperType != pgtype.Unbounded {
fmt.Println("Max age:", ageRange.Upper.Int32)
}
}start := time.Now()
end := start.Add(24 * time.Hour)
tsRange := pgtype.Range[pgtype.Timestamptz]{
Lower: pgtype.Timestamptz{Time: start, Valid: true},
Upper: pgtype.Timestamptz{Time: end, Valid: true},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive,
Valid: true,
}
_, err := conn.Exec(ctx,
"INSERT INTO reservations (time_slot) VALUES ($1)", tsRange)// RangeValuer is a type that can be converted into a PostgreSQL range.
type RangeValuer interface {
IsNull() bool
BoundTypes() (lower, upper BoundType)
Bounds() (lower, upper any)
}
// RangeScanner is a type that can be scanned from a PostgreSQL range.
type RangeScanner interface {
ScanNull() error
ScanBounds() (lowerTarget, upperTarget any)
SetBoundTypes(lower, upper BoundType) error
}Implement these for custom range types.
type Multirange[T RangeValuer] []T
func (r Multirange[T]) Index(i int) any
func (r Multirange[T]) IndexType() any
func (r Multirange[T]) IsNull() bool
func (r Multirange[T]) Len() int
func (r Multirange[T]) ScanIndex(i int) any
func (r Multirange[T]) ScanIndexType() any
func (r *Multirange[T]) ScanNull() error
func (r *Multirange[T]) SetLen(n int) errorMultiranges represent discontinuous ranges (PostgreSQL 14+).
var availability pgtype.Multirange[pgtype.Range[pgtype.Timestamptz]]
err := conn.QueryRow(ctx,
"SELECT available_slots FROM schedules WHERE id = $1", id).Scan(&availability)
for i, slot := range availability {
fmt.Printf("Slot %d: %v to %v
", i+1, slot.Lower.Time, slot.Upper.Time)
}slots := pgtype.Multirange[pgtype.Range[pgtype.Timestamptz]]{
{
Lower: pgtype.Timestamptz{Time: time.Date(2024, 1, 1, 9, 0, 0, 0, time.UTC), Valid: true},
Upper: pgtype.Timestamptz{Time: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), Valid: true},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive,
Valid: true,
},
{
Lower: pgtype.Timestamptz{Time: time.Date(2024, 1, 1, 14, 0, 0, 0, time.UTC), Valid: true},
Upper: pgtype.Timestamptz{Time: time.Date(2024, 1, 1, 17, 0, 0, 0, time.UTC), Valid: true},
LowerType: pgtype.Inclusive,
UpperType: pgtype.Exclusive,
Valid: true,
},
}
_, err := conn.Exec(ctx,
"INSERT INTO schedules (available_slots) VALUES ($1)", slots)type MultirangeGetter interface {
IsNull() bool
Len() int
Index(i int) any
IndexType() any
}
type MultirangeSetter interface {
ScanNull() error
SetLen(n int) error
ScanIndex(i int) any
ScanIndexType() any
}// RangeCodec — codec for any range type
type RangeCodec struct {
ElementType *Type
}
// MultirangeCodec — codec for any multirange type
type MultirangeCodec struct {
ElementType *Type
}Used internally for custom range/multirange types.
// Empty range
emptyRange := pgtype.Range[pgtype.Int4]{
LowerType: pgtype.Empty,
UpperType: pgtype.Empty,
Valid: true,
}
// Unbounded on both sides
unboundedRange := pgtype.Range[pgtype.Int8]{
LowerType: pgtype.Unbounded,
UpperType: pgtype.Unbounded,
Valid: true,
}
// Unbounded lower, bounded upper
halfBoundedRange := pgtype.Range[pgtype.Int4]{
Upper: pgtype.Int4{Int32: 100, Valid: true},
LowerType: pgtype.Unbounded,
UpperType: pgtype.Exclusive,
Valid: true,
}Install with Tessl CLI
npx tessl i tessl/golang-github-com-jackc-pgx-v5@5.8.0