Repository: go-jet/jet
Pull request #326 (2024-02-26 10:41:34)

Develop


Patch:
diff --git a/README.md b/README.md
index 15fd887..c84dd4e 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ https://medium.com/@go.jet/jet-5f3667efa0cc
 
 To install Jet package, you need to install Go and set your Go workspace first.
 
-[Go](https://golang.org/) **version 1.9+ is required**
+[Go](https://golang.org/) **version 1.18+ is required**
 
 ### Installation
 
diff --git a/generator/template/model_template.go b/generator/template/model_template.go
index 47f0339..f89ebd1 100644
--- a/generator/template/model_template.go
+++ b/generator/template/model_template.go
@@ -5,6 +5,7 @@ import (
 	"github.com/go-jet/jet/v2/generator/metadata"
 	"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
 	"github.com/google/uuid"
+	"github.com/jackc/pgtype"
 	"path"
 	"reflect"
 	"strings"
@@ -320,6 +321,18 @@ func toGoType(column metadata.Column) interface{} {
 		return float64(0.0)
 	case "uuid":
 		return uuid.UUID{}
+	case "daterange":
+		return pgtype.Daterange{}
+	case "tsrange":
+		return pgtype.Tsrange{}
+	case "tstzrange":
+		return pgtype.Tstzrange{}
+	case "int4range":
+		return pgtype.Int4range{}
+	case "int8range":
+		return pgtype.Int8range{}
+	case "numrange":
+		return pgtype.Numrange{}
 	default:
 		fmt.Println("- [Model      ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
 		return ""
diff --git a/generator/template/sql_builder_template.go b/generator/template/sql_builder_template.go
index cf7e121..fe7fba5 100644
--- a/generator/template/sql_builder_template.go
+++ b/generator/template/sql_builder_template.go
@@ -177,6 +177,18 @@ func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
 	case "real", "numeric", "decimal", "double precision", "float", "float4", "float8",
 		"double": // MySQL
 		return "Float"
+	case "daterange":
+		return "DateRange"
+	case "tsrange":
+		return "TimestampRange"
+	case "tstzrange":
+		return "TimestampzRange"
+	case "int4range":
+		return "Int4Range"
+	case "int8range":
+		return "Int8Range"
+	case "numrange":
+		return "NumericRange"
 	default:
 		fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
 		return "String"
diff --git a/go.mod b/go.mod
index 16c19fa..27cae16 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/go-jet/jet/v2
 
-go 1.11
+go 1.18
 
 require (
 	github.com/go-sql-driver/mysql v1.7.1
@@ -10,14 +10,34 @@ require (
 	github.com/mattn/go-sqlite3 v1.14.17
 )
 
-// test dependencies
 require (
 	github.com/google/go-cmp v0.5.9
+	github.com/jackc/pgtype v1.14.0
 	github.com/jackc/pgx/v4 v4.18.1
 	github.com/pkg/profile v1.7.0
 	github.com/shopspring/decimal v1.3.1
 	github.com/stretchr/testify v1.8.2
 	github.com/volatiletech/null/v8 v8.1.2
-	golang.org/x/sync v0.3.0
 	gopkg.in/guregu/null.v4 v4.0.0
 )
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/felixge/fgprof v0.9.3 // indirect
+	github.com/friendsofgo/errors v0.9.2 // indirect
+	github.com/gofrs/uuid v4.0.0+incompatible // indirect
+	github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
+	github.com/jackc/chunkreader/v2 v2.0.1 // indirect
+	github.com/jackc/pgio v1.0.0 // indirect
+	github.com/jackc/pgpassfile v1.0.0 // indirect
+	github.com/jackc/pgproto3/v2 v2.3.2 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/volatiletech/inflect v0.0.1 // indirect
+	github.com/volatiletech/randomize v0.0.1 // indirect
+	github.com/volatiletech/strmangle v0.0.1 // indirect
+	golang.org/x/crypto v0.6.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/go.sum b/go.sum
index deb45b8..2e79473 100644
--- a/go.sum
+++ b/go.sum
@@ -32,7 +32,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
-github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
 github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
 github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
 github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
@@ -54,7 +53,6 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W
 github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
 github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
 github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
 github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
@@ -183,8 +181,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/internal/jet/column_types.go b/internal/jet/column_types.go
index 4748b2c..a732061 100644
--- a/internal/jet/column_types.go
+++ b/internal/jet/column_types.go
@@ -358,3 +358,44 @@ func DateColumn(name string) ColumnDate {
 	dateColumn.ColumnExpressionImpl = NewColumnImpl(name, "", dateColumn)
 	return dateColumn
 }
+
+//------------------------------------------------------//
+
+// ColumnRange is interface for range columns which can be int range, string range
+// timestamp range or date range.
+type ColumnRange[T Expression] interface {
+	Range[T]
+	Column
+
+	From(subQuery SelectTable) ColumnRange[T]
+	SET(rangeExp Range[T]) ColumnAssigment
+}
+
+type rangeColumnImpl[T Expression] struct {
+	rangeInterfaceImpl[T]
+	ColumnExpressionImpl
+}
+
+func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] {
+	newRangeColumn := RangeColumn[T](i.name)
+	newRangeColumn.setTableName(i.tableName)
+	newRangeColumn.setSubQuery(subQuery)
+
+	return newRangeColumn
+}
+
+func (i *rangeColumnImpl[T]) SET(rangeExp Range[T]) ColumnAssigment {
+	return columnAssigmentImpl{
+		column:     i,
+		expression: rangeExp,
+	}
+}
+
+// RangeColumn creates named range column.
+func RangeColumn[T Expression](name string) ColumnRange[T] {
+	rangeColumn := &rangeColumnImpl[T]{}
+	rangeColumn.rangeInterfaceImpl.parent = rangeColumn
+	rangeColumn.ColumnExpressionImpl = NewColumnImpl(name, "", rangeColumn)
+
+	return rangeColumn
+}
diff --git a/internal/jet/func_expression.go b/internal/jet/func_expression.go
index 4ff3791..c56053f 100644
--- a/internal/jet/func_expression.go
+++ b/internal/jet/func_expression.go
@@ -468,6 +468,33 @@ func REGEXP_LIKE(stringExp StringExpression, pattern StringExpression, matchType
 	return newBoolFunc("REGEXP_LIKE", stringExp, pattern)
 }
 
+//----------Range Type Functions ----------------------//
+
+// LOWER_BOUND returns range expressions lower bound
+func LOWER_BOUND[T Expression](rangeExpression Range[T]) T {
+	return rangeTypeCaster[T](rangeExpression, NewFunc("LOWER", []Expression{rangeExpression}, nil))
+}
+
+// UPPER_BOUND returns range expressions upper bound
+func UPPER_BOUND[T Expression](rangeExpression Range[T]) T {
+	return rangeTypeCaster[T](rangeExpression, NewFunc("UPPER", []Expression{rangeExpression}, nil))
+}
+
+func rangeTypeCaster[T Expression](rangeExpression Range[T], exp Expression) T {
+	var i Expression
+	switch rangeExpression.(type) {
+	case Range[DateExpression]:
+		i = DateExp(exp)
+	case Range[IntegerExpression]:
+		i = IntExp(exp)
+	case Range[TimestampzExpression]:
+		i = TimestampzExp(exp)
+	case Range[TimestampExpression]:
+		i = TimestampExp(exp)
+	}
+	return i.(T)
+}
+
 //----------Data Type Formatting Functions ----------------------//
 
 // TO_CHAR converts expression to string with format
@@ -843,3 +870,35 @@ func newTimestampzFunc(name string, expressions ...Expression) *timestampzFunc {
 func Func(name string, expressions ...Expression) Expression {
 	return NewFunc(name, expressions, nil)
 }
+
+func NumRange(lowNum, highNum NumericExpression, bounds ...StringExpression) Range[NumericExpression] {
+	return RangeExp[NumericExpression](NewFunc("numrange", rangeFuncParamCombiner[NumericExpression](lowNum, highNum, bounds...), nil))
+}
+
+func Int4Range(lowNum, highNum IntegerExpression, bounds ...StringExpression) Range[IntegerExpression] {
+	return RangeExp[IntegerExpression](NewFunc("int4range", rangeFuncParamCombiner[IntegerExpression](lowNum, highNum, bounds...), nil))
+}
+
+func Int8Range(lowNum, highNum IntegerExpression, bounds ...StringExpression) Range[IntegerExpression] {
+	return RangeExp[IntegerExpression](NewFunc("int8range", rangeFuncParamCombiner[IntegerExpression](lowNum, highNum, bounds...), nil))
+}
+
+func TimestampRange(lowTs, highTs TimestampExpression, bounds ...StringExpression) Range[TimestampExpression] {
+	return RangeExp[TimestampExpression](NewFunc("tsrange", rangeFuncParamCombiner[TimestampExpression](lowTs, highTs, bounds...), nil))
+}
+
+func TimestampzRange(lowTs, highTs TimestampzExpression, bounds ...StringExpression) Range[TimestampzExpression] {
+	return RangeExp[TimestampzExpression](NewFunc("tstzrange", rangeFuncParamCombiner[TimestampzExpression](lowTs, highTs, bounds...), nil))
+}
+
+func DateRange(lowTs, highTs DateExpression, bounds ...StringExpression) Range[DateExpression] {
+	return RangeExp[DateExpression](NewFunc("daterange", rangeFuncParamCombiner[DateExpression](lowTs, highTs, bounds...), nil))
+}
+
+func rangeFuncParamCombiner[T Expression](low, high T, bounds ...StringExpression) []Expression {
+	exp := []Expression{low, high}
+	if len(bounds) != 0 {
+		exp = append(exp, bounds[0])
+	}
+	return exp
+}
diff --git a/internal/jet/literal_expression.go b/internal/jet/literal_expression.go
index 8c28b44..d6f0b41 100644
--- a/internal/jet/literal_expression.go
+++ b/internal/jet/literal_expression.go
@@ -333,6 +333,10 @@ var (
 	NULL = newNullLiteral()
 	// STAR is jet equivalent of SQL *
 	STAR = newStarLiteral()
+	// PLUS_INFINITY is jet equivalent for sql infinity
+	PLUS_INFINITY = String("infinity")
+	// MINUS_INFINITY is jet equivalent for sql -infinity
+	MINUS_INFINITY = String("-infinity")
 )
 
 type nullLiteral struct {
@@ -490,6 +494,11 @@ func RawDate(raw string, namedArgs ...map[string]interface{}) DateExpression {
 	return DateExp(Raw(raw, namedArgs...))
 }
 
+// RawRange helper that for range expressions
+func RawRange[T Expression](raw string, namedArgs ...map[string]interface{}) Range[T] {
+	return RangeExp[T](Raw(raw, namedArgs...))
+}
+
 // UUID is a helper function to create string literal expression from uuid object
 // value can be any uuid type with a String method
 func UUID(value fmt.Stringer) StringExpression {
diff --git a/internal/jet/operators.go b/internal/jet/operators.go
index b73a451..bf1dedf 100644
--- a/internal/jet/operators.go
+++ b/internal/jet/operators.go
@@ -69,6 +69,16 @@ func GtEq(lhs, rhs Expression) BoolExpression {
 	return newBinaryBoolOperatorExpression(lhs, rhs, ">=")
 }
 
+// Contains returns a representation of "a @> b"
+func Contains(lhs Expression, rhs Expression) BoolExpression {
+	return newBinaryBoolOperatorExpression(lhs, rhs, "@>")
+}
+
+// Overlap returns a representation of "a && b"
+func Overlap(lhs, rhs Expression) BoolExpression {
+	return newBinaryBoolOperatorExpression(lhs, rhs, "&&")
+}
+
 // Add notEq returns a representation of "a + b"
 func Add(lhs, rhs Serializer) Expression {
 	return NewBinaryOperatorExpression(lhs, rhs, "+")
diff --git a/internal/jet/range_expression.go b/internal/jet/range_expression.go
new file mode 100644
index 0000000..f05fdea
--- /dev/null
+++ b/internal/jet/range_expression.go
@@ -0,0 +1,95 @@
+package jet
+
+// Range Expression is interface for date range types
+type Range[T Expression] interface {
+	Expression
+
+	EQ(rhs Range[T]) BoolExpression
+	NOT_EQ(rhs Range[T]) BoolExpression
+
+	LT(rhs Range[T]) BoolExpression
+	LT_EQ(rhs Range[T]) BoolExpression
+	GT(rhs Range[T]) BoolExpression
+	GT_EQ(rhs Range[T]) BoolExpression
+
+	CONTAINS(rhs T) BoolExpression
+	CONTAINS_RANGE(rhs Range[T]) BoolExpression
+	OVERLAP(rhs Range[T]) BoolExpression
+	UNION(rhs Range[T]) Range[T]
+	INTERSECTION(rhs Range[T]) Range[T]
+	DIFFERENCE(rhs Range[T]) Range[T]
+}
+
+type rangeInterfaceImpl[T Expression] struct {
+	parent Expression
+}
+
+func (r *rangeInterfaceImpl[T]) EQ(rhs Range[T]) BoolExpression {
+	return Eq(r.parent, rhs)
+}
+
+func (r *rangeInterfaceImpl[T]) NOT_EQ(rhs Range[T]) BoolExpression {
+	return NotEq(r.parent, rhs)
+}
+
+func (r *rangeInterfaceImpl[T]) LT(rhs Range[T]) BoolExpression {
+	return Lt(r.parent, rhs)
+}
+
+func (r *rangeInterfaceImpl[T]) LT_EQ(rhs Range[T]) BoolExpression {
+	return LtEq(r.parent, rhs)
+
+}
+
+func (r *rangeInterfaceImpl[T]) GT(rhs Range[T]) BoolExpression {
+	return Gt(r.parent, rhs)
+
+}
+
+func (r *rangeInterfaceImpl[T]) GT_EQ(rhs Range[T]) BoolExpression {
+	return GtEq(r.parent, rhs)
+}
+
+func (r *rangeInterfaceImpl[T]) CONTAINS(rhs T) BoolExpression {
+	return Contains(r.parent, rhs)
+}
+
+func (r *rangeInterfaceImpl[T]) CONTAINS_RANGE(rhs Range[T]) BoolExpression {
+	return Contains(r.parent, rhs)
+}
+
+func (r *rangeInterfaceImpl[T]) OVERLAP(rhs Range[T]) BoolExpression {
+	return Overlap(r.parent, rhs)
+}
+
+func (r *rangeInterfaceImpl[T]) UNION(rhs Range[T]) Range[T] {
+	return RangeExp[T](Add(r.parent, rhs))
+}
+
+func (r *rangeInterfaceImpl[T]) INTERSECTION(rhs Range[T]) Range[T] {
+	return RangeExp[T](Mul(r.parent, rhs))
+}
+
+func (r *rangeInterfaceImpl[T]) DIFFERENCE(rhs Range[T]) Range[T] {
+	return RangeExp[T](Sub(r.parent, rhs))
+}
+
+//---------------------------------------------------//
+
+type rangeExpressionWrapper[T Expression] struct {
+	rangeInterfaceImpl[T]
+	Expression
+}
+
+func newRangeExpressionWrap[T Expression](expression Expression) Range[T] {
+	rangeExpressionWrap := rangeExpressionWrapper[T]{Expression: expression}
+	rangeExpressionWrap.rangeInterfaceImpl.parent = &rangeExpressionWrap
+	return &rangeExpressionWrap
+}
+
+// RangeExp is range expression wrapper around arbitrary expression.
+// Allows go compiler to see any expression as range expression.
+// Does not add sql cast to generated sql builder output.
+func RangeExp[T Expression](expression Expression) Range[T] {
+	return newRangeExpressionWrap[T](expression)
+}
diff --git a/postgres/columns.go b/postgres/columns.go
index a25a88c..aee2896 100644
--- a/postgres/columns.go
+++ b/postgres/columns.go
@@ -65,6 +65,42 @@ type ColumnTimestampz = jet.ColumnTimestampz
 // TimestampzColumn creates named timestamp with time zone column.
 var TimestampzColumn = jet.TimestampzColumn
 
+// ColumnDateRange is interface of SQL date range column
+type ColumnDateRange = jet.ColumnRange[DateExpression]
+
+// DateRangeColumn creates named range with range column
+var DateRangeColumn = jet.RangeColumn[DateExpression]
+
+// ColumnNumericRange is interface of SQL numeric range column
+type ColumnNumericRange = jet.ColumnRange[NumericExpression]
+
+// NumericRangeColumn creates named range with range column
+var NumericRangeColumn = jet.RangeColumn[NumericExpression]
+
+// ColumnTimestampRange is interface of SQL timestamp range column
+type ColumnTimestampRange = jet.ColumnRange[TimestampExpression]
+
+// TimestampRangeColumn creates named range with range column
+var TimestampRangeColumn = jet.RangeColumn[TimestampExpression]
+
+// ColumnTimestampzRange is interface of SQL timestamp range column
+type ColumnTimestampzRange = jet.ColumnRange[TimestampzExpression]
+
+// TimestampzRangeColumn creates named range with range column
+var TimestampzRangeColumn = jet.RangeColumn[TimestampzExpression]
+
+// ColumnInt4Range is interface of SQL int range column
+type ColumnInt4Range = jet.ColumnRange[IntegerExpression]
+
+// Int4RangeColumn creates named range with range column
+var Int4RangeColumn = jet.RangeColumn[IntegerExpression]
+
+// ColumnInt8Range is interface of SQL int range column
+type ColumnInt8Range = jet.ColumnRange[IntegerExpression]
+
+// Int8RangeColumn creates named range with range column
+var Int8RangeColumn = jet.RangeColumn[IntegerExpression]
+
 //------------------------------------------------------//
 
 // ColumnInterval is interface of PostgreSQL interval columns.
diff --git a/postgres/expressions.go b/postgres/expressions.go
index faf153d..a903860 100644
--- a/postgres/expressions.go
+++ b/postgres/expressions.go
@@ -36,6 +36,24 @@ type TimestampExpression = jet.TimestampExpression
 // TimestampzExpression interface
 type TimestampzExpression = jet.TimestampzExpression
 
+// DateRange Expression interface
+type DateRange = jet.Range[DateExpression]
+
+// TimestampRange Expression interface
+type TimestampRange = jet.Range[TimestampExpression]
+
+// TimestampzRange Expression interface
+type TimestampzRange = jet.Range[TimestampzExpression]
+
+// NumericRange Expression interface
+type NumericRange = jet.Range[NumericExpression]
+
+// Int4Range Expression interface
+type Int4Range = jet.Range[IntegerExpression]
+
+// Int8Range Expression interface
+type Int8Range = jet.Range[IntegerExpression]
+
 // BoolExp is bool expression wrapper around arbitrary expression.
 // Allows go compiler to see any expression as bool expression.
 // Does not add sql cast to generated sql builder output.
@@ -81,6 +99,13 @@ var TimestampExp = jet.TimestampExp
 // Does not add sql cast to generated sql builder output.
 var TimestampzExp = jet.TimestampzExp
 
+// RangeExp is range expression wrapper around arbitrary expression.
+// Allows go compiler to see any expression as range expression.
+// Does not add sql cast to generated sql builder output.
+func RangeExp[T Expression](expression T) jet.Range[T] {
+	return jet.RangeExp[T](expression)
+}
+
 // RawArgs is type used to pass optional arguments to Raw method
 type RawArgs = map[string]interface{}
 
@@ -90,15 +115,21 @@ type RawArgs = map[string]interface{}
 var (
 	Raw = jet.Raw
 
-	RawBool       = jet.RawBool
-	RawInt        = jet.RawInt
-	RawFloat      = jet.RawFloat
-	RawString     = jet.RawString
-	RawTime       = jet.RawTime
-	RawTimez      = jet.RawTimez
-	RawTimestamp  = jet.RawTimestamp
-	RawTimestampz = jet.RawTimestampz
-	RawDate       = jet.RawDate
+	RawBool            = jet.RawBool
+	RawInt             = jet.RawInt
+	RawFloat           = jet.RawFloat
+	RawString          = jet.RawString
+	RawTime            = jet.RawTime
+	RawTimez           = jet.RawTimez
+	RawTimestamp       = jet.RawTimestamp
+	RawTimestampz      = jet.RawTimestampz
+	RawDate            = jet.RawDate
+	RawNumRange        = jet.RawRange[jet.NumericExpression]
+	RawInt4Range       = jet.RawRange[jet.IntegerExpression]
+	RawInt8Range       = jet.RawRange[jet.IntegerExpression]
+	RawTimestampRange  = jet.RawRange[jet.TimestampExpression]
+	RawTimestampzRange = jet.RawRange[jet.TimestampzExpression]
+	RawDateRange       = jet.RawRange[jet.DateExpression]
 )
 
 // Func can be used to call custom or unsupported database functions.
diff --git a/postgres/functions.go b/postgres/functions.go
index 43a5f39..eddc930 100644
--- a/postgres/functions.go
+++ b/postgres/functions.go
@@ -267,6 +267,18 @@ var TO_HEX = jet.TO_HEX
 
 //----------Data Type Formatting Functions ----------------------//
 
+// LOWER_BOUND returns range expressions lower bound
+func LOWER_BOUND[T Expression](expression jet.Range[T]) T {
+	return jet.LOWER_BOUND[T](expression)
+}
+
+// UPPER_BOUND returns range expressions upper bound
+func UPPER_BOUND[T Expression](expression jet.Range[T]) T {
+	return jet.UPPER_BOUND[T](expression)
+}
+
+//----------Data Type Formatting Functions ----------------------//
+
 // TO_CHAR converts expression to string with format
 var TO_CHAR = jet.TO_CHAR
 
@@ -421,3 +433,18 @@ var CUBE = jet.CUBE
 // It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result
 // of the GROUPING function would then be an integer bit mask having 1’s for the arguments which have GROUPING(argument) as 1.
 var GROUPING = jet.GROUPING
+
+var (
+	// DATE_RANGE constructor function to create a date range
+	DATE_RANGE = jet.DateRange
+	// NUM_Range constructor function to create a numeric range
+	NUM_Range = jet.NumRange
+	// TIMESTAMP_RANGE constructor function to create a timestamp range
+	TIMESTAMP_RANGE = jet.TimestampRange
+	// TIMESTAMPTZ_RANGE constructor function to create a timestampz range
+	TIMESTAMPTZ_RANGE = jet.TimestampzRange
+	// INT4_RANGE constructor function to create a int4 range
+	INT4_RANGE = jet.Int4Range
+	// INT8_RANGE constructor function to create a int8 range
+	INT8_RANGE = jet.Int8Range
+)
diff --git a/postgres/keywords.go b/postgres/keywords.go
index cfc90a2..a468cc5 100644
--- a/postgres/keywords.go
+++ b/postgres/keywords.go
@@ -12,4 +12,8 @@ var (
 	NULL = jet.NULL
 	// STAR is jet equivalent of SQL *
 	STAR = jet.STAR
+	// PLUS_INFINITY is jet equivalent for sql infinity
+	PLUS_INFINITY = jet.PLUS_INFINITY
+	// MINUS_INFINITY is jet equivalent for sql -infinity
+	MINUS_INFINITY = jet.MINUS_INFINITY
 )


Test patch:
diff --git a/internal/jet/func_expression_test.go b/internal/jet/func_expression_test.go
index 048ade2..ca32589 100644
--- a/internal/jet/func_expression_test.go
+++ b/internal/jet/func_expression_test.go
@@ -202,3 +202,14 @@ func TestTO_ASCII(t *testing.T) {
 func TestFunc(t *testing.T) {
 	assertClauseSerialize(t, Func("FOO", String("test"), NULL, MAX(Int(1))), "FOO($1, NULL, MAX($2))", "test", int64(1))
 }
+
+func Test_rangePointCaster(t *testing.T) {
+	mainRange := Int8Range(Int8(10), Int8(12))
+	exp := NewFunc("UPPER", []Expression{mainRange}, nil)
+
+	got := rangeTypeCaster(mainRange, exp)
+	_, ok := got.(IntegerExpression)
+	if !ok {
+		t.Errorf("expecting to get IntegerExpression but got %v", got)
+	}
+}
diff --git a/internal/jet/range_expression_test.go b/internal/jet/range_expression_test.go
new file mode 100644
index 0000000..e5e8b75
--- /dev/null
+++ b/internal/jet/range_expression_test.go
@@ -0,0 +1,63 @@
+package jet
+
+import "testing"
+
+func TestRangeExpressionEQ(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.EQ(table2ColRange), "(table1.col_range = table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range = int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionNOT_EQ(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.NOT_EQ(table2ColRange), "(table1.col_range != table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.NOT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range != int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionLT(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.LT(table2ColRange), "(table1.col_range < table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.LT(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range < int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionLT_EQ(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.LT_EQ(table2ColRange), "(table1.col_range <= table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.LT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range <= int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionGT(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.GT(table2ColRange), "(table1.col_range > table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.GT(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range > int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionGT_EQ(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.GT_EQ(table2ColRange), "(table1.col_range >= table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.GT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range >= int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionCONTAINS_RANGE(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.CONTAINS_RANGE(table2ColRange), "(table1.col_range @> table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.CONTAINS_RANGE(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range @> int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionCONTAINS(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.CONTAINS(table2Col3), "(table1.col_range @> table2.col3)")
+	assertClauseSerialize(t, table1ColRange.CONTAINS(Int8(1)), "(table1.col_range @> $1)", int8(1))
+}
+
+func TestRangeExpressionOVERLAP(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.OVERLAP(table2ColRange), "(table1.col_range && table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.OVERLAP(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range && int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionUNION(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.UNION(table2ColRange), "(table1.col_range + table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.UNION(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range + int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionINTERSECTION(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.INTERSECTION(table2ColRange), "(table1.col_range * table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.INTERSECTION(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range * int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
+
+func TestRangeExpressionDIFFERENCE(t *testing.T) {
+	assertClauseSerialize(t, table1ColRange.DIFFERENCE(table2ColRange), "(table1.col_range - table2.col_range)")
+	assertClauseSerialize(t, table1ColRange.DIFFERENCE(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range - int8range($1, $2, $3))", int8(1), int8(4), "[)")
+}
diff --git a/internal/jet/testutils.go b/internal/jet/testutils.go
index 43f1f5d..d91e30f 100644
--- a/internal/jet/testutils.go
+++ b/internal/jet/testutils.go
@@ -25,8 +25,9 @@ var (
 	table1ColTimestampz = TimestampzColumn("col_timestampz")
 	table1ColBool       = BoolColumn("col_bool")
 	table1ColDate       = DateColumn("col_date")
+	table1ColRange      = RangeColumn[IntegerExpression]("col_range")
 )
-var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColTimestamp, table1ColTimestampz)
+var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColRange, table1ColTimestamp, table1ColTimestampz)
 
 var (
 	table2Col3          = IntegerColumn("col3")
@@ -40,8 +41,9 @@ var (
 	table2ColTimestamp  = TimestampColumn("col_timestamp")
 	table2ColTimestampz = TimestampzColumn("col_timestampz")
 	table2ColDate       = DateColumn("col_date")
+	table2ColRange      = RangeColumn[IntegerExpression]("col_range")
 )
-var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz)
+var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColRange, table2ColTimestamp, table2ColTimestampz)
 
 var (
 	table3Col1   = IntegerColumn("col1")
diff --git a/postgres/utils_test.go b/postgres/utils_test.go
index 292d7e4..96bb13b 100644
--- a/postgres/utils_test.go
+++ b/postgres/utils_test.go
@@ -17,6 +17,7 @@ var table1ColTimestampz = TimestampzColumn("col_timestampz")
 var table1ColBool = BoolColumn("col_bool")
 var table1ColDate = DateColumn("col_date")
 var table1ColInterval = IntervalColumn("col_interval")
+var table1ColRange = Int8RangeColumn("col_range")
 
 var table1 = NewTable(
 	"db",
@@ -32,6 +33,7 @@ var table1 = NewTable(
 	table1ColTimestamp,
 	table1ColTimestampz,
 	table1ColInterval,
+	table1ColRange,
 )
 
 var table2Col3 = IntegerColumn("col3")
@@ -46,8 +48,9 @@ var table2ColTimestamp = TimestampColumn("col_timestamp")
 var table2ColTimestampz = TimestampzColumn("col_timestampz")
 var table2ColDate = DateColumn("col_date")
 var table2ColInterval = IntervalColumn("col_interval")
+var table2ColRange = Int8RangeColumn("col_range")
 
-var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval)
+var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval, table2ColRange)
 
 var table3Col1 = IntegerColumn("col1")
 var table3ColInt = IntegerColumn("col_int")
diff --git a/tests/postgres/generator_test.go b/tests/postgres/generator_test.go
index 5aaaa31..479d82f 100644
--- a/tests/postgres/generator_test.go
+++ b/tests/postgres/generator_test.go
@@ -561,13 +561,14 @@ func TestGeneratedAllTypesSQLBuilderFiles(t *testing.T) {
 
 	testutils.AssertFileNamesEqual(t, modelDir, "all_types.go", "all_types_view.go", "employee.go", "link.go",
 		"mood.go", "person.go", "person_phone.go", "weird_names_table.go", "level.go", "user.go", "floats.go", "people.go",
-		"components.go", "vulnerabilities.go", "all_types_materialized_view.go")
+		"components.go", "vulnerabilities.go", "all_types_materialized_view.go", "sample_ranges.go")
 	testutils.AssertFileContent(t, modelDir+"/all_types.go", allTypesModelContent)
 
 	testutils.AssertFileNamesEqual(t, tableDir, "all_types.go", "employee.go", "link.go",
 		"person.go", "person_phone.go", "weird_names_table.go", "user.go", "floats.go", "people.go", "table_use_schema.go",
-		"components.go", "vulnerabilities.go")
+		"components.go", "vulnerabilities.go", "sample_ranges.go")
 	testutils.AssertFileContent(t, tableDir+"/all_types.go", allTypesTableContent)
+	testutils.AssertFileContent(t, tableDir+"/sample_ranges.go", sampleRangeTableContent)
 
 	testutils.AssertFileNamesEqual(t, viewDir, "all_types_materialized_view.go", "all_types_view.go",
 		"view_use_schema.go")
@@ -968,3 +969,96 @@ func newAllTypesTableImpl(schemaName, tableName, alias string) allTypesTable {
 	}
 }
 `
+
+var sampleRangeTableContent = `
+//
+// Code generated by go-jet DO NOT EDIT.
+//
+// WARNING: Changes to this file may cause incorrect behavior
+// and will be lost if the code is regenerated
+//
+
+package table
+
+import (
+	"github.com/go-jet/jet/v2/postgres"
+)
+
+var SampleRanges = newSampleRangesTable("test_sample", "sample_ranges", "")
+
+type sampleRangesTable struct {
+	postgres.Table
+
+	// Columns
+	DateRange       postgres.ColumnDateRange
+	TimestampRange  postgres.ColumnTimestampRange
+	TimestampzRange postgres.ColumnTimestampzRange
+	Int4Range       postgres.ColumnInt4Range
+	Int8Range       postgres.ColumnInt8Range
+	NumRange        postgres.ColumnNumericRange
+
+	AllColumns     postgres.ColumnList
+	MutableColumns postgres.ColumnList
+}
+
+type SampleRangesTable struct {
+	sampleRangesTable
+
+	EXCLUDED sampleRangesTable
+}
+
+// AS creates new SampleRangesTable with assigned alias
+func (a SampleRangesTable) AS(alias string) *SampleRangesTable {
+	return newSampleRangesTable(a.SchemaName(), a.TableName(), alias)
+}
+
+// Schema creates new SampleRangesTable with assigned schema name
+func (a SampleRangesTable) FromSchema(schemaName string) *SampleRangesTable {
+	return newSampleRangesTable(schemaName, a.TableName(), a.Alias())
+}
+
+// WithPrefix creates new SampleRangesTable with assigned table prefix
+func (a SampleRangesTable) WithPrefix(prefix string) *SampleRangesTable {
+	return newSampleRangesTable(a.SchemaName(), prefix+a.TableName(), a.TableName())
+}
+
+// WithSuffix creates new SampleRangesTable with assigned table suffix
+func (a SampleRangesTable) WithSuffix(suffix string) *SampleRangesTable {
+	return newSampleRangesTable(a.SchemaName(), a.TableName()+suffix, a.TableName())
+}
+
+func newSampleRangesTable(schemaName, tableName, alias string) *SampleRangesTable {
+	return &SampleRangesTable{
+		sampleRangesTable: newSampleRangesTableImpl(schemaName, tableName, alias),
+		EXCLUDED:          newSampleRangesTableImpl("", "excluded", ""),
+	}
+}
+
+func newSampleRangesTableImpl(schemaName, tableName, alias string) sampleRangesTable {
+	var (
+		DateRangeColumn       = postgres.DateRangeColumn("date_range")
+		TimestampRangeColumn  = postgres.TimestampRangeColumn("timestamp_range")
+		TimestampzRangeColumn = postgres.TimestampzRangeColumn("timestampz_range")
+		Int4RangeColumn       = postgres.Int4RangeColumn("int4_range")
+		Int8RangeColumn       = postgres.Int8RangeColumn("int8_range")
+		NumRangeColumn        = postgres.NumericRangeColumn("num_range")
+		allColumns            = postgres.ColumnList{DateRangeColumn, TimestampRangeColumn, TimestampzRangeColumn, Int4RangeColumn, Int8RangeColumn, NumRangeColumn}
+		mutableColumns        = postgres.ColumnList{DateRangeColumn, TimestampRangeColumn, TimestampzRangeColumn, Int4RangeColumn, Int8RangeColumn, NumRangeColumn}
+	)
+
+	return sampleRangesTable{
+		Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
+
+		//Columns
+		DateRange:       DateRangeColumn,
+		TimestampRange:  TimestampRangeColumn,
+		TimestampzRange: TimestampzRangeColumn,
+		Int4Range:       Int4RangeColumn,
+		Int8Range:       Int8RangeColumn,
+		NumRange:        NumRangeColumn,
+
+		AllColumns:     allColumns,
+		MutableColumns: mutableColumns,
+	}
+}
+`
diff --git a/tests/postgres/range_test.go b/tests/postgres/range_test.go
new file mode 100644
index 0000000..63498eb
--- /dev/null
+++ b/tests/postgres/range_test.go
@@ -0,0 +1,514 @@
+package postgres
+
+import (
+	"github.com/go-jet/jet/v2/internal/testutils"
+	"github.com/go-jet/jet/v2/qrm"
+	"github.com/google/go-cmp/cmp"
+	"github.com/jackc/pgtype"
+	"github.com/stretchr/testify/require"
+	"math/big"
+	"testing"
+	"time"
+
+	. "github.com/go-jet/jet/v2/postgres"
+	"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model"
+	. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/table"
+)
+
+func TestRangeTable_DateContainsSingle(t *testing.T) {
+	skipForCockroachDB(t)
+	expectedSQL := `
+SELECT DISTINCT sample_ranges.date_range AS "sample_ranges.date_range",
+     sample_ranges.timestamp_range AS "sample_ranges.timestamp_range",
+     sample_ranges.timestampz_range AS "sample_ranges.timestampz_range",
+     sample_ranges.int4_range AS "sample_ranges.int4_range",
+     sample_ranges.int8_range AS "sample_ranges.int8_range",
+     sample_ranges.num_range AS "sample_ranges.num_range"
+FROM test_sample.sample_ranges
+WHERE sample_ranges.date_range @> '2023-12-12'::date;
+`
+
+	query := SELECT(SampleRanges.AllColumns).
+		DISTINCT().
+		FROM(SampleRanges).
+		WHERE(SampleRanges.DateRange.CONTAINS(Date(2023, 12, 12)))
+
+	testutils.AssertDebugStatementSql(t, query, expectedSQL, "2023-12-12")
+
+	sample := model.SampleRanges{}
+	err := query.Query(db, &sample)
+
+	require.NoError(t, err)
+
+	expectedRow := model.SampleRanges{
+		DateRange: pgtype.Daterange{
+			Lower: pgtype.Date{
+				Time:   time.Date(2023, 9, 25, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Date{
+				Time:   time.Date(2024, 2, 10, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		TimestampRange: pgtype.Tsrange{
+			Lower: pgtype.Timestamp{
+				Time:   time.Date(2020, 01, 01, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Timestamp{
+				Time:   time.Date(2021, 01, 01, 15, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Inclusive,
+			Status:    pgtype.Present,
+		},
+		TimestampzRange: pgtype.Tstzrange{
+			Lower: pgtype.Timestamptz{
+				Time:   time.Date(2024, 05, 07, 15, 0, 0, 0, time.FixedZone("", 0)),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Timestamptz{
+				Time:   time.Date(2024, 10, 11, 14, 0, 0, 0, time.FixedZone("", 0)),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		Int4Range: pgtype.Int4range{
+			Lower: pgtype.Int4{
+				Int:    11,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Int4{
+				Int:    20,
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		Int8Range: pgtype.Int8range{
+			Lower: pgtype.Int8{
+				Int:    200,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Int8{
+				Int:    2450,
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		NumRange: pgtype.Numrange{
+			Lower: pgtype.Numeric{
+				Int:    big.NewInt(2),
+				Exp:    3,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Numeric{
+				Int:    big.NewInt(5),
+				Status: pgtype.Present,
+				Exp:    3,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+	}
+
+	testutils.AssertDeepEqual(t, sample, expectedRow, cmp.AllowUnexported(big.Int{}))
+	requireLogged(t, query)
+}
+
+func TestRangeTable_IntContainsRange(t *testing.T) {
+	skipForCockroachDB(t)
+	expectedSQL := `
+SELECT DISTINCT sample_ranges.date_range AS "sample_ranges.date_range",
+     sample_ranges.timestamp_range AS "sample_ranges.timestamp_range",
+     sample_ranges.timestampz_range AS "sample_ranges.timestampz_range",
+     sample_ranges.int4_range AS "sample_ranges.int4_range",
+     sample_ranges.int8_range AS "sample_ranges.int8_range",
+     sample_ranges.num_range AS "sample_ranges.num_range"
+FROM test_sample.sample_ranges
+WHERE sample_ranges.int4_range @> int4range(12, 18, '[)'::text);
+`
+
+	query := SELECT(SampleRanges.AllColumns).
+		DISTINCT().
+		FROM(SampleRanges).
+		WHERE(SampleRanges.Int4Range.CONTAINS_RANGE(INT4_RANGE(Int(12), Int(18), String("[)"))))
+
+	testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(12), int64(18), "[)")
+
+	sample := model.SampleRanges{}
+	err := query.Query(db, &sample)
+
+	require.NoError(t, err)
+
+	expectedRow := model.SampleRanges{
+		DateRange: pgtype.Daterange{
+			Lower: pgtype.Date{
+				Time:   time.Date(2023, 9, 25, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Date{
+				Time:   time.Date(2024, 2, 10, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		TimestampRange: pgtype.Tsrange{
+			Lower: pgtype.Timestamp{
+				Time:   time.Date(2020, 01, 01, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Timestamp{
+				Time:   time.Date(2021, 01, 01, 15, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Inclusive,
+			Status:    pgtype.Present,
+		},
+		TimestampzRange: pgtype.Tstzrange{
+			Lower: pgtype.Timestamptz{
+				Time:   time.Date(2024, 05, 07, 15, 0, 0, 0, time.FixedZone("", 0)),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Timestamptz{
+				Time:   time.Date(2024, 10, 11, 14, 0, 0, 0, time.FixedZone("", 0)),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		Int4Range: pgtype.Int4range{
+			Lower: pgtype.Int4{
+				Int:    11,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Int4{
+				Int:    20,
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		Int8Range: pgtype.Int8range{
+			Lower: pgtype.Int8{
+				Int:    200,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Int8{
+				Int:    2450,
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		NumRange: pgtype.Numrange{
+			Lower: pgtype.Numeric{
+				Int:    big.NewInt(2),
+				Exp:    3,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Numeric{
+				Int:    big.NewInt(5),
+				Status: pgtype.Present,
+				Exp:    3,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+	}
+
+	testutils.AssertDeepEqual(t, sample, expectedRow, cmp.AllowUnexported(big.Int{}))
+	requireLogged(t, query)
+}
+
+func TestRangeTable_TimestampContainsRange(t *testing.T) {
+	skipForCockroachDB(t)
+	expectedSQL := `
+SELECT DISTINCT sample_ranges.date_range AS "sample_ranges.date_range",
+     sample_ranges.timestamp_range AS "sample_ranges.timestamp_range",
+     sample_ranges.timestampz_range AS "sample_ranges.timestampz_range",
+     sample_ranges.int4_range AS "sample_ranges.int4_range",
+     sample_ranges.int8_range AS "sample_ranges.int8_range",
+     sample_ranges.num_range AS "sample_ranges.num_range"
+FROM test_sample.sample_ranges
+WHERE sample_ranges.timestamp_range @> tsrange('2020-02-01 00:00:00'::timestamp without time zone, '2020-10-01 00:00:00'::timestamp without time zone, '[)'::text);
+`
+
+	query := SELECT(SampleRanges.AllColumns).
+		DISTINCT().
+		FROM(SampleRanges).
+		WHERE(SampleRanges.TimestampRange.CONTAINS_RANGE(TIMESTAMP_RANGE(Timestamp(2020, 02, 01, 0, 0, 0), Timestamp(2020, 10, 01, 0, 0, 0), String("[)"))))
+
+	testutils.AssertDebugStatementSql(t, query, expectedSQL, "2020-02-01 00:00:00", "2020-10-01 00:00:00", "[)")
+
+	sample := model.SampleRanges{}
+	err := query.Query(db, &sample)
+
+	require.NoError(t, err)
+
+	expectedRow := model.SampleRanges{
+		DateRange: pgtype.Daterange{
+			Lower: pgtype.Date{
+				Time:   time.Date(2023, 9, 25, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Date{
+				Time:   time.Date(2024, 2, 10, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		TimestampRange: pgtype.Tsrange{
+			Lower: pgtype.Timestamp{
+				Time:   time.Date(2020, 01, 01, 0, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Timestamp{
+				Time:   time.Date(2021, 01, 01, 15, 0, 0, 0, time.UTC),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Inclusive,
+			Status:    pgtype.Present,
+		},
+		TimestampzRange: pgtype.Tstzrange{
+			Lower: pgtype.Timestamptz{
+				Time:   time.Date(2024, 05, 07, 15, 0, 0, 0, time.FixedZone("", 0)),
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Timestamptz{
+				Time:   time.Date(2024, 10, 11, 14, 0, 0, 0, time.FixedZone("", 0)),
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		Int4Range: pgtype.Int4range{
+			Lower: pgtype.Int4{
+				Int:    11,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Int4{
+				Int:    20,
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		Int8Range: pgtype.Int8range{
+			Lower: pgtype.Int8{
+				Int:    200,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Int8{
+				Int:    2450,
+				Status: pgtype.Present,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+		NumRange: pgtype.Numrange{
+			Lower: pgtype.Numeric{
+				Int:    big.NewInt(2),
+				Exp:    3,
+				Status: pgtype.Present,
+			},
+			Upper: pgtype.Numeric{
+				Int:    big.NewInt(5),
+				Status: pgtype.Present,
+				Exp:    3,
+			},
+			LowerType: pgtype.Inclusive,
+			UpperType: pgtype.Exclusive,
+			Status:    pgtype.Present,
+		},
+	}
+
+	testutils.AssertDeepEqual(t, sample, expectedRow, cmp.AllowUnexported(big.Int{}))
+	requireLogged(t, query)
+}
+
+func TestRangeTable_ContainsOutOfRange(t *testing.T) {
+	skipForCockroachDB(t)
+	expectedSQL := `
+SELECT DISTINCT sample_ranges.date_range AS "sample_ranges.date_range",
+     sample_ranges.timestamp_range AS "sample_ranges.timestamp_range",
+     sample_ranges.timestampz_range AS "sample_ranges.timestampz_range",
+     sample_ranges.int4_range AS "sample_ranges.int4_range",
+     sample_ranges.int8_range AS "sample_ranges.int8_range",
+     sample_ranges.num_range AS "sample_ranges.num_range"
+FROM test_sample.sample_ranges
+WHERE sample_ranges.int4_range @> int4range(12, 30, '[)'::text);
+`
+
+	query := SELECT(SampleRanges.AllColumns).
+		DISTINCT().
+		FROM(SampleRanges).
+		WHERE(SampleRanges.Int4Range.CONTAINS_RANGE(INT4_RANGE(Int(12), Int(30), String("[)"))))
+
+	testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(12), int64(30), "[)")
+
+	sample := model.SampleRanges{}
+	err := query.Query(db, &sample)
+
+	require.ErrorIs(t, err, qrm.ErrNoRows)
+	requireLogged(t, query)
+}
+
+func TestRangeTable_InsertColumn(t *testing.T) {
+	skipForCockroachDB(t)
+
+	insertQuery := SampleRanges.INSERT(SampleRanges.AllColumns).
+		VALUES(
+			DATE_RANGE(
+				Date(2010, 01, 01),
+				Date(2014, 01, 01),
+				String("[)"),
+			),
+			DEFAULT,
+			TIMESTAMPTZ_RANGE(
+				TimestampzT(time.Date(2010, 01, 01, 23, 0, 0, 0, time.UTC)),
+				TimestampzT(time.Date(2014, 01, 01, 15, 0, 0, 0, time.UTC)),
+				String("[)"),
+			),
+			INT4_RANGE(Int(64), Int(128), String("[]")),
+			INT8_RANGE(Int(1024), Int(2048), String("[]")),
+			DEFAULT,
+		).
+		RETURNING(SampleRanges.AllColumns)
+
+	expectedQuery := `
+INSERT INTO test_sample.sample_ranges (date_range, timestamp_range, timestampz_range, int4_range, int8_range, num_range)
+VALUES (daterange('2010-01-01'::date, '2014-01-01'::date, '[)'::text), DEFAULT, tstzrange('2010-01-01 23:00:00Z'::timestamp with time zone, '2014-01-01 15:00:00Z'::timestamp with time zone, '[)'::text), int4range(64, 128, '[]'::text), int8range(1024, 2048, '[]'::text), DEFAULT)
+RETURNING sample_ranges.date_range AS "sample_ranges.date_range",
+          sample_ranges.timestamp_range AS "sample_ranges.timestamp_range",
+          sample_ranges.timestampz_range AS "sample_ranges.timestampz_range",
+          sample_ranges.int4_range AS "sample_ranges.int4_range",
+          sample_ranges.int8_range AS "sample_ranges.int8_range",
+          sample_ranges.num_range AS "sample_ranges.num_range";
+`
+	testutils.AssertDebugStatementSql(t, insertQuery, expectedQuery,
+		"2010-01-01", "2014-01-01", "[)",
+		time.Date(2010, 01, 01, 23, 0, 0, 0, time.UTC), time.Date(2014, 01, 01, 15, 0, 0, 0, time.UTC), "[)",
+		int64(64), int64(128), "[]",
+		int64(1024), int64(2048), "[]",
+	)
+}
+
+func TestRangeTable_UpperBound(t *testing.T) {
+	skipForCockroachDB(t)
+
+	expectedSQL := `
+SELECT UPPER(sample_ranges.date_range)
+FROM test_sample.sample_ranges
+WHERE sample_ranges.date_range @> '2023-12-12'::date;
+`
+
+	query := SELECT(UPPER_BOUND[DateExpression](SampleRanges.DateRange)).
+		FROM(SampleRanges).
+		WHERE(SampleRanges.DateRange.CONTAINS(Date(2023, 12, 12)))
+
+	testutils.AssertDebugStatementSql(t, query, expectedSQL, "2023-12-12")
+
+	var date time.Time
+	err := query.Query(db, &date)
+	require.NoError(t, err)
+
+	expectedYear := 2024
+	expectedMonth := time.February
+	expectedDay := 10
+	if expectedYear != date.Year() || expectedMonth != date.Month() || expectedDay != date.Day() {
+		t.Errorf("expected: 2024-02-10 got: %s", date.Format("2006-01-02"))
+	}
+}
+
+func TestRangeTable_LowerBound(t *testing.T) {
+	skipForCockroachDB(t)
+
+	expectedSQL := `
+SELECT LOWER(sample_ranges.date_range)
+FROM test_sample.sample_ranges
+WHERE sample_ranges.date_range @> '2023-12-12'::date;
+`
+
+	query := SELECT(LOWER_BOUND[DateExpression](SampleRanges.DateRange)).
+		FROM(SampleRanges).
+		WHERE(SampleRanges.DateRange.CONTAINS(Date(2023, 12, 12)))
+
+	testutils.AssertDebugStatementSql(t, query, expectedSQL, "2023-12-12")
+
+	var date time.Time
+	err := query.Query(db, &date)
+	require.NoError(t, err)
+
+	expectedYear := 2023
+	expectedMonth := time.September
+	expectedDay := 25
+	if expectedYear != date.Year() || expectedMonth != date.Month() || expectedDay != date.Day() {
+		t.Errorf("expected: 2023-09-25 got: %s", date.Format("2006-01-02"))
+	}
+}
+
+func TestRangeTable_InsertInfinite(t *testing.T) {
+	skipForCockroachDB(t)
+
+	insertQuery := SampleRanges.INSERT(SampleRanges.AllColumns).
+		VALUES(
+			DATE_RANGE(
+				Date(2010, 01, 01),
+				DateExp(PLUS_INFINITY),
+				String("[)"),
+			),
+			DEFAULT,
+			TIMESTAMPTZ_RANGE(
+				TimestampzExp(MINUS_INFINITY),
+				TimestampzT(time.Date(2014, 01, 01, 15, 0, 0, 0, time.UTC)),
+				String("[)"),
+			),
+			INT4_RANGE(Int(64), Int(128), String("[]")),
+			INT8_RANGE(Int(1024), Int(2048), String("[]")),
+			DEFAULT,
+		).
+		RETURNING(SampleRanges.AllColumns)
+
+	expectedQuery := `
+INSERT INTO test_sample.sample_ranges (date_range, timestamp_range, timestampz_range, int4_range, int8_range, num_range)
+VALUES (daterange('2010-01-01'::date, 'infinity', '[)'::text), DEFAULT, tstzrange('-infinity', '2014-01-01 15:00:00Z'::timestamp with time zone, '[)'::text), int4range(64, 128, '[]'::text), int8range(1024, 2048, '[]'::text), DEFAULT)
+RETURNING sample_ranges.date_range AS "sample_ranges.date_range",
+          sample_ranges.timestamp_range AS "sample_ranges.timestamp_range",
+          sample_ranges.timestampz_range AS "sample_ranges.timestampz_range",
+          sample_ranges.int4_range AS "sample_ranges.int4_range",
+          sample_ranges.int8_range AS "sample_ranges.int8_range",
+          sample_ranges.num_range AS "sample_ranges.num_range";
+`
+
+	testutils.AssertDebugStatementSql(t, insertQuery, expectedQuery,
+		"2010-01-01", "infinity", "[)",
+		"-infinity", time.Date(2014, 01, 01, 15, 0, 0, 0, time.UTC), "[)",
+		int64(64), int64(128), "[]",
+		int64(1024), int64(2048), "[]",
+	)
+}
diff --git a/tests/testdata b/tests/testdata
index 08bcfcb..915bdc1 160000
--- a/tests/testdata
+++ b/tests/testdata
@@ -1,1 +1,1 @@
-Subproject commit 08bcfcbb2e1eadfca54c4522802fc65f1fee865c
+Subproject commit 915bdc16b723d89becc577c780949baef861a6ae