Add support for additional array types.

This commit is contained in:
go-jet 2025-10-16 15:09:07 +02:00
parent 45d4ced9b0
commit 4ee047a675
47 changed files with 1994 additions and 4277 deletions

33
postgres/array_columns.go Executable file
View file

@ -0,0 +1,33 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// Interfaces for different postgres array column types
type (
ColumnBoolArray jet.ColumnArray[BoolExpression]
ColumnStringArray jet.ColumnArray[StringExpression]
ColumnIntegerArray jet.ColumnArray[IntegerExpression]
ColumnFloatArray jet.ColumnArray[FloatExpression]
ColumnByteaArray jet.ColumnArray[ByteaExpression]
ColumnDateArray jet.ColumnArray[DateExpression]
ColumnTimestampArray jet.ColumnArray[TimestampExpression]
ColumnTimestampzArray jet.ColumnArray[TimestampzExpression]
ColumnTimeArray jet.ColumnArray[TimeExpression]
ColumnTimezArray jet.ColumnArray[TimezExpression]
ColumnIntervalArray jet.ColumnArray[IntervalExpression]
)
// Column constructors for different postgres array column types
var (
BoolArrayColumn = jet.ArrayColumn[BoolExpression]
StringArrayColumn = jet.ArrayColumn[StringExpression]
IntegerArrayColumn = jet.ArrayColumn[IntegerExpression]
FloatArrayColumn = jet.ArrayColumn[FloatExpression]
ByteaArrayColumn = jet.ArrayColumn[ByteaExpression]
DateArrayColumn = jet.ArrayColumn[DateExpression]
TimestampArrayColumn = jet.ArrayColumn[TimestampExpression]
TimestampzArrayColumn = jet.ArrayColumn[TimestampzExpression]
TimeArrayColumn = jet.ArrayColumn[TimeExpression]
TimezArrayColumn = jet.ArrayColumn[TimezExpression]
IntervalArrayColumn = jet.ArrayColumn[IntervalExpression]
)

View file

@ -96,7 +96,7 @@ func (b *cast) AS_DATE() DateExpression {
return DateExp(b.AS("date"))
}
// AS_DECIMAL casts expression AS date type
// AS_DECIMAL casts expression AS decimal type
func (b *cast) AS_DECIMAL() FloatExpression {
return FloatExp(b.AS("decimal"))
}
@ -130,3 +130,68 @@ func (b *cast) AS_TIMESTAMPZ() TimestampzExpression {
func (b *cast) AS_INTERVAL() IntervalExpression {
return IntervalExp(b.AS("interval"))
}
// AS_UUID casts expression AS uuid type
func (b *cast) AS_UUID() StringExpression {
return StringExp(b.AS("uuid"))
}
// AS_BOOL_ARRAY casts expression as boolean array type
func (b *cast) AS_BOOL_ARRAY() Array[BoolExpression] {
return ArrayExp[BoolExpression](b.AS("boolean[]"))
}
// AS_INTEGER_ARRAY casts expression as integer array type
func (b *cast) AS_INTEGER_ARRAY() Array[IntegerExpression] {
return ArrayExp[IntegerExpression](b.AS("integer[]"))
}
// AS_BIGINT_ARRAY casts expression as bigint array type
func (b *cast) AS_BIGINT_ARRAY() Array[IntegerExpression] {
return ArrayExp[IntegerExpression](b.AS("bigint[]"))
}
// AS_REAL_ARRAY casts expression as real array
func (b *cast) AS_REAL_ARRAY() Array[FloatExpression] {
return ArrayExp[FloatExpression](b.AS("real[]"))
}
// AS_DOUBLE_ARRAY casts expression as double precision array
func (b *cast) AS_DOUBLE_ARRAY() Array[FloatExpression] {
return ArrayExp[FloatExpression](b.AS("double precision[]"))
}
// AS_TEXT_ARRAY casts expression as text array
func (b *cast) AS_TEXT_ARRAY() Array[StringExpression] {
return ArrayExp[StringExpression](b.AS("text[]"))
}
// AS_BYTEA_ARRAY casts expression as bytea array
func (b *cast) AS_BYTEA_ARRAY() Array[ByteaExpression] {
return ArrayExp[ByteaExpression](b.AS("bytea[]"))
}
// AS_DATE_ARRAY casts expression as date array
func (b *cast) AS_DATE_ARRAY() Array[DateExpression] {
return ArrayExp[DateExpression](b.AS("date[]"))
}
// AS_TIMESTAMP_ARRAY casts expression as timestamp array
func (b *cast) AS_TIMESTAMP_ARRAY() Array[TimestampExpression] {
return ArrayExp[TimestampExpression](b.AS("timestamp without time zone[]"))
}
// AS_TIMESTAMPZ_ARRAY casts expression as timestamp with time zone array
func (b *cast) AS_TIMESTAMPZ_ARRAY() Array[TimestampzExpression] {
return ArrayExp[TimestampzExpression](b.AS("timestamp with time zone[]"))
}
// AS_TIME_ARRAY casts expression as time array
func (b *cast) AS_TIME_ARRAY() Array[TimeExpression] {
return ArrayExp[TimeExpression](b.AS("time without time zone[]"))
}
// AS_TIMEZ_ARRAY casts expression as time with timezone array
func (b *cast) AS_TIMEZ_ARRAY() Array[TimezExpression] {
return ArrayExp[TimezExpression](b.AS("time with time zone[]"))
}

View file

@ -112,21 +112,3 @@ type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
// Int8RangeColumn creates named range with range column
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
// ColumnStringArray is interface of column
type ColumnStringArray jet.ColumnArray[jet.StringExpression]
// StringArrayColumn creates named string array column
var StringArrayColumn = jet.ArrayColumn[jet.StringExpression]
// ColumnIntegerArray is interface of column
type ColumnIntegerArray jet.ColumnArray[jet.IntegerExpression]
// IntegerArrayColumn creates named integer array column
var IntegerArrayColumn = jet.ArrayColumn[jet.IntegerExpression]
// ColumnBoolArray is interface of column
type ColumnBoolArray jet.ColumnArray[jet.BoolExpression]
// BoolArrayColumn creates named bool array column
var BoolArrayColumn = jet.ArrayColumn[jet.BoolExpression]

View file

@ -2,33 +2,24 @@ package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// Expression is common interface for all expressions.
// Expression is a common interface for all expressions.
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
type Expression = jet.Expression
// BoolExpression interface
type BoolExpression = jet.BoolExpression
// BoolArrayExpression interface
type BoolArrayExpression = jet.ArrayExpression[BoolExpression]
// StringExpression interface
type StringExpression = jet.StringExpression
type ByteaExpression = jet.BlobExpression
// StringArrayExpression interface
type StringArrayExpression = jet.ArrayExpression[StringExpression]
// NumericExpression interface
type NumericExpression = jet.NumericExpression
// IntegerExpression interface
type IntegerExpression = jet.IntegerExpression
// IntegerArrayExpression interface
type IntegerArrayExpression = jet.ArrayExpression[IntegerExpression]
// FloatExpression is interface
type FloatExpression = jet.FloatExpression
@ -185,3 +176,13 @@ var NewEnumValue = jet.NewEnumValue
// BinaryOperator can be used to use custom or unsupported operators that take two operands.
var BinaryOperator = jet.BinaryOperator
// Array is a common template interface for all array expressions.
type Array[T Expression] jet.Array[T]
// ArrayExp serves as a wrapper for an arbitrary expression, treating it as an array expression of type T.
// This enables the Go compiler to interpret any expression as an array expression of type T.
// Note: This does not modify the generated SQL builder output by adding an SQL CAST operation.
func ArrayExp[T Expression](exp Expression) Array[T] {
return jet.ArrayExp[T](exp)
}

View file

@ -335,7 +335,7 @@ var TO_ASCII = jet.TO_ASCII
// TO_HEX converts number to its equivalent hexadecimal representation
var TO_HEX = jet.TO_HEX
//----------Data Type Formatting Functions ----------------------//
//---------- Range Functions ----------------------//
// LOWER_BOUND returns range expressions lower bound
func LOWER_BOUND[T Expression](expression jet.Range[T]) T {
@ -347,7 +347,144 @@ func UPPER_BOUND[T Expression](expression jet.Range[T]) T {
return jet.UPPER_BOUND[T](expression)
}
//----------Data Type Formatting Functions ----------------------//
// ---------- Array Functions ----------------------//
// ANY should be used in combination with a boolean operator. The result of ANY is "true" if any true result is obtained
func ANY[E Expression](arr Array[E]) E {
return jet.CastToArrayElemType(arr, Func("ANY", arr))
}
// ALL should be used in combination with a boolean operator. The result of ALL is “true” if all comparisons yield true
func ALL[E Expression](arr Array[E]) E {
return jet.CastToArrayElemType(arr, Func("ALL", arr))
}
// ARRAY_APPEND appends an element to the end of an array
func ARRAY_APPEND[E Expression](arr Array[E], elem E) Array[E] {
return ArrayExp[E](Func("ARRAY_APPEND", arr, elem))
}
// ARRAY_CAT concatenates two arrays
func ARRAY_CAT[E Expression](arr1, arr2 Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_CAT", arr1, arr2))
}
// ARRAY_DIMS returns a text representation of the array's dimensions.
func ARRAY_DIMS[E Expression](arr Array[E]) StringExpression {
return StringExp(Func("ARRAY_DIMS", arr))
}
// ARRAY_LENGTH returns the length of the requested array dimension.
// Produces NULL instead of 0 for empty or missing array dimensions.
func ARRAY_LENGTH[E Expression](arr Array[E], elem IntegerExpression) IntegerExpression {
return IntExp(Func("ARRAY_LENGTH", arr, elem))
}
// ARRAY_LOWER returns the lower bound of the requested array dimension.
func ARRAY_LOWER[E Expression](arr Array[E]) IntegerExpression {
return IntExp(Func("ARRAY_LOWER", arr))
}
// ARRAY_NDIMS returns the number of dimensions of the array.
func ARRAY_NDIMS[E Expression](arr Array[E]) IntegerExpression {
return IntExp(Func("ARRAY_NDIMS", arr))
}
// ARRAY_POSITION returns the subscript of the first occurrence of the second argument in the array, or NULL if it's not present.
// If the third argument is given, the search begins at that subscript.
// The array must be one-dimensional.
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to search for NULL.
func ARRAY_POSITION[E Expression](arr Array[E], elem E, beginAt ...IntegerExpression) IntegerExpression {
return IntExp(Func("ARRAY_POSITION", optionalAppend([]Expression{arr, elem}, beginAt)...))
}
// ARRAY_POSITIONS returns an array of the subscripts of all occurrences of the second argument in the array given as first argument.
// The array must be one-dimensional.
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to search for NULL.
// NULL is returned only if the array is NULL; if the value is not found in the array, an empty array is returned.
func ARRAY_POSITIONS[E Expression](arr Array[E], elem E) Array[IntegerExpression] {
return ArrayExp[IntegerExpression](Func("ARRAY_POSITIONS", arr, elem))
}
// ARRAY_PREPEND prepends an element to the beginning of an array
func ARRAY_PREPEND[E Expression](el E, arr Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_PREPEND", el, arr))
}
// ARRAY_REMOVE removes all elements equal to the given value from the array. The array must be one-dimensional.
// Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible to remove NULLs.
func ARRAY_REMOVE[E Expression](arr Array[E], elem Expression) IntegerExpression {
return IntExp(Func("ARRAY_REMOVE", arr, elem))
}
// ARRAY_REPLACE replaces each array element equal to the second argument with the third argument.
func ARRAY_REPLACE[E Expression](arr Array[E], existing E, new E) Array[E] {
return ArrayExp[E](Func("ARRAY_REPLACE", arr, existing, new))
}
// ARRAY_REVERSE reverses the first dimension of the array.
func ARRAY_REVERSE[E Expression](arr Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_REVERSE", arr))
}
// ARRAY_SAMPLE returns an array of n items randomly selected from array.
// n may not exceed the length of array's first dimension.
// If array is multi-dimensional, an “item” is a slice having a given first subscript.
func ARRAY_SAMPLE[E Expression](arr Array[E], n IntegerExpression) Array[E] {
return ArrayExp[E](Func("ARRAY_SAMPLE", arr, n))
}
// ARRAY_SHUFFLE randomly shuffles the first dimension of the array.
func ARRAY_SHUFFLE[E Expression](arr Array[E]) Array[E] {
return ArrayExp[E](Func("ARRAY_SHUFFLE", arr))
}
// ARRAY_SORT sorts the first dimension of the array.
// The sort order is determined by the default sort ordering of the array's element type; however, if the element type is collatable, the collation to use can be specified by adding a COLLATE clause to the array argument.
//
// If descending is true then sort in descending order, otherwise ascending order.
// If omitted, the default is ascending order.
// If nulls_first is true then nulls appear before non-null values, otherwise nulls appear after non-null values.
// If omitted, nulls_first is taken to have the same value as descending.
func ARRAY_SORT[E Expression](arr Array[E], desc BoolExpression, nullFirst ...BoolExpression) Array[E] {
return ArrayExp[E](Func("ARRAY_SORT", optionalAppend([]Expression{arr, desc}, nullFirst)...))
}
// ARRAY_TO_STRING Converts each array element to its text representation, and concatenates those separated by the delimiter string.
// If null_string is given and is not NULL, then NULL array entries are represented by that string; otherwise, they are omitted.
func ARRAY_TO_STRING[T Expression](arr Array[T], delim StringExpression) StringExpression {
return StringExp(Func("ARRAY_TO_STRING", arr, delim))
}
// ARRAY_UPPER returns the upper bound of the requested array dimension.
func ARRAY_UPPER[E Expression](arr Array[E], dim IntegerExpression) IntegerExpression {
return IntExp(Func("ARRAY_UPPER", arr, dim))
}
// CARDINALITY returns the total number of elements in the array, or 0 if the array is empty.
func CARDINALITY[E Expression](arr Array[E]) IntegerExpression {
return IntExp(Func("CARDINALITY", arr))
}
// TRIM_ARRAY trims an array by removing the last n elements. If the array is multidimensional, only the first dimension is trimmed.
func TRIM_ARRAY[E Expression](arr Array[E], n IntegerExpression) Array[E] {
return ArrayExp[E](Func("TRIM_ARRAY", arr, n))
}
// ARRAY constructor is an expression that builds an array value using values for its member elements.
func ARRAY[T Expression](elems ...T) Array[T] {
return jet.ARRAY[T](elems...)
}
func optionalAppend[O Expression](elem []Expression, optional []O) []Expression {
if len(optional) == 0 {
return elem
}
return append(elem, optional[0])
}
//---------- Data Type Formatting Functions ----------------------//
// TO_CHAR converts expression to string with format
var TO_CHAR = jet.TO_CHAR

View file

@ -175,30 +175,27 @@ RETURNING table1.col1 AS "table1.col1",
}
func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
stmt := table1.INSERT(table1Col1, table1ColBool, table1ColStringArray).
VALUES("one", "two", "three").
VALUES("1", "2", "3").
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").DO_UPDATE(
SET(table1ColBool.SET(Bool(false)),
table2ColInt.SET(Int(1)),
table1ColStringArray.SET(StringArray([]string{"one"})),
ColumnList{table1Col1, table1ColBool, table1ColStringArray}.SET(jet.ROW(Int(2), String("two"), StringArray([]string{"two"}))),
).WHERE(table1Col1.GT(Int(2))),
).
RETURNING(table1Col1, table1ColBool, table1ColStringArray)
stmt := table1.INSERT(table1Col1, table1ColBool).
VALUES("one", "two").
VALUES("1", "2").
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").
DO_UPDATE(
SET(table1ColBool.SET(Bool(false)),
table2ColInt.SET(Int(1)),
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
).WHERE(table1Col1.GT(Int(2)))).
RETURNING(table1Col1, table1ColBool)
assertDebugStatementSql(t, stmt, `
INSERT INTO db.table1 (col1, col_bool, col_string_array)
VALUES ('one', 'two', 'three'),
('1', '2', '3')
INSERT INTO db.table1 (col1, col_bool)
VALUES ('one', 'two'),
('1', '2')
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
SET col_bool = FALSE::boolean,
col_int = 1,
col_string_array = '{"one"}',
(col1, col_bool, col_string_array) = ROW(2, 'two'::text, '{"two"}')
(col1, col_bool) = ROW(2, 'two'::text)
WHERE table1.col1 > 2
RETURNING table1.col1 AS "table1.col1",
table1.col_bool AS "table1.col_bool",
table1.col_string_array AS "table1.col_string_array";
table1.col_bool AS "table1.col_bool";
`)
}

View file

@ -1,6 +1,8 @@
package postgres
import (
"fmt"
"github.com/lib/pq"
"time"
"github.com/go-jet/jet/v2/internal/jet"
@ -11,11 +13,6 @@ func Bool(value bool) BoolExpression {
return CAST(jet.Bool(value)).AS_BOOL()
}
// BoolArray creates new bool array literal expression
func BoolArray(elements []bool) BoolArrayExpression {
return jet.BoolArray(elements)
}
// Int is constructor for 64 bit signed integer expressions literals.
var Int = jet.Int
@ -73,8 +70,11 @@ func Double(value float64) FloatExpression {
}
// Decimal creates new float literal expression
var Decimal = jet.Decimal
func Decimal(value string) FloatExpression {
return CAST(jet.Literal(value)).AS_DECIMAL()
}
// String creates new string literal expression
// String is a parameter constructor for the PostgreSQL text type. Using the `Text` constructor is
// generally preferable.
//
@ -85,11 +85,6 @@ func String(value string) StringExpression {
return CAST(jet.String(value)).AS_TEXT()
}
// StringArray creates new string array literal expression
func StringArray(elements []string) StringArrayExpression {
return jet.StringArray(elements)
}
// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an
// explicit placeholder type cast to text in the generated query, such as `$3::text`.
// Example usage:
@ -134,7 +129,9 @@ func Json(value interface{}) StringExpression {
// UUID is a helper function to create string literal expression from uuid object
// value can be any uuid type with a String method
var UUID = jet.UUID
func UUID(value fmt.Stringer) StringExpression {
return CAST(jet.Literal(value.String())).AS_UUID()
}
// Bytea creates new bytea literal expression
func Bytea(value interface{}) ByteaExpression {
@ -195,3 +192,63 @@ func Timestampz(year int, month time.Month, day, hour, minute, second int, milli
func TimestampzT(t time.Time) TimestampzExpression {
return CAST(jet.TimestampzT(t)).AS_TIMESTAMPZ()
}
// BoolArray creates new bool array literal expression from list of values
func BoolArray(values ...bool) Array[BoolExpression] {
return CAST(jet.Literal(pq.BoolArray(values))).AS_BOOL_ARRAY()
}
// Int32Array creates new integer array literal expression from list of values
func Int32Array(values ...int32) Array[IntegerExpression] {
return CAST(jet.Literal(pq.Int32Array(values))).AS_INTEGER_ARRAY()
}
// Int64Array creates new bigint array literal expression from list of values
func Int64Array(values ...int64) Array[IntegerExpression] {
return CAST(jet.Literal(pq.Int64Array(values))).AS_BIGINT_ARRAY()
}
// Float32Array creates new real array literal expression from list of values
func Float32Array(values ...float32) Array[FloatExpression] {
return CAST(jet.Literal(pq.Float32Array(values))).AS_REAL_ARRAY()
}
// Float64Array creates new double precision array literal expression from list of values
func Float64Array(values ...float64) Array[FloatExpression] {
return CAST(jet.Literal(pq.Float64Array(values))).AS_DOUBLE_ARRAY()
}
// StringArray creates new string array literal expression from list of values
func StringArray(values ...string) Array[StringExpression] {
return CAST(jet.Literal(pq.StringArray(values))).AS_TEXT_ARRAY()
}
// ByteaArray creates new bytea array literal expression from list of values
func ByteaArray(values ...[]byte) Array[ByteaExpression] {
return CAST(jet.Literal(pq.ByteaArray(values))).AS_BYTEA_ARRAY()
}
// DateArray creates new date array literal expression from list of values
func DateArray(values ...time.Time) Array[DateExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_DATE_ARRAY()
}
// TimestampArray creates new timestamp array literal expression from list of values
func TimestampArray(values ...time.Time) Array[TimestampExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIMESTAMP_ARRAY()
}
// TimestampzArray creates new timestampt with timezone array literal expression from list of values
func TimestampzArray(values ...time.Time) Array[TimestampzExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIMESTAMPZ_ARRAY()
}
// TimeArray creates new time array literal expression from list of values
func TimeArray(values ...time.Time) Array[TimeExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIME_ARRAY()
}
// TimezArray creates new time with timezone array literal expression from list of values
func TimezArray(values ...time.Time) Array[TimezExpression] {
return CAST(jet.Literal(pq.Array(values))).AS_TIMEZ_ARRAY()
}