diff --git a/cmd/jet/version.go b/cmd/jet/version.go index cfdb257..3f152c2 100644 --- a/cmd/jet/version.go +++ b/cmd/jet/version.go @@ -1,3 +1,3 @@ package main -const version = "v2.14.0" +const version = "v2.14.1" diff --git a/internal/jet/alias.go b/internal/jet/alias.go index d096a65..031dae1 100644 --- a/internal/jet/alias.go +++ b/internal/jet/alias.go @@ -27,8 +27,7 @@ func (a *alias) fromImpl(subQuery SelectTable) Projection { // This function is used to create dummy columns when exporting sub-query columns using subQuery.AllColumns() // In most case we don't care about type of the column, except when sub-query columns are used as SELECT_JSON projection. -// We need to know type to encode value for json unmarshal. At the moment only bool, time and blob columns are of interest, -// so we don't have to support every column type. +// We need to know type to encode value for json unmarshal. func newDummyColumnForExpression(exp Expression, name string) ColumnExpression { switch exp.(type) { @@ -54,6 +53,41 @@ func newDummyColumnForExpression(exp Expression, name string) ColumnExpression { return IntervalColumn(name) case StringExpression: return StringColumn(name) + + case Array[BoolExpression]: + return ArrayColumn[BoolExpression](name) + case Array[IntegerExpression]: + return ArrayColumn[IntegerExpression](name) + case Array[FloatExpression]: + return ArrayColumn[FloatExpression](name) + case Array[BlobExpression]: + return ArrayColumn[BlobExpression](name) + case Array[DateExpression]: + return ArrayColumn[DateExpression](name) + case Array[TimeExpression]: + return ArrayColumn[TimeExpression](name) + case Array[TimezExpression]: + return ArrayColumn[TimezExpression](name) + case Array[TimestampExpression]: + return ArrayColumn[TimestampExpression](name) + case Array[TimestampzExpression]: + return ArrayColumn[TimestampzExpression](name) + case Array[IntervalExpression]: + return ArrayColumn[IntervalExpression](name) + case Array[StringExpression]: + return ArrayColumn[StringExpression](name) + + case Range[Int4Expression], Range[Int8Expression]: + return RangeColumn[IntegerExpression](name) + case Range[NumericExpression]: + return RangeColumn[NumericExpression](name) + case Range[DateExpression]: + return RangeColumn[DateExpression](name) + case Range[TimestampExpression]: + return RangeColumn[TimestampExpression](name) + case Range[TimestampzExpression]: + return RangeColumn[TimestampzExpression](name) + } return StringColumn(name) diff --git a/internal/jet/array_expression.go b/internal/jet/array_expression.go index e0ab8dc..b054baa 100644 --- a/internal/jet/array_expression.go +++ b/internal/jet/array_expression.go @@ -80,9 +80,10 @@ type arrayExpressionWrapper[E Expression] struct { } func newArrayExpressionWrap[E Expression](expression Expression) Array[E] { - arrayExpressionWrapper := arrayExpressionWrapper[E]{Expression: expression} - arrayExpressionWrapper.arrayInterfaceImpl.parent = &arrayExpressionWrapper - return &arrayExpressionWrapper + arrayExpressionWrapper := &arrayExpressionWrapper[E]{Expression: expression} + arrayExpressionWrapper.arrayInterfaceImpl.parent = arrayExpressionWrapper + expression.setRoot(arrayExpressionWrapper) + return arrayExpressionWrapper } // ArrayExp is array expression wrapper around arbitrary expression. diff --git a/tests/postgres/alltypes_test.go b/tests/postgres/alltypes_test.go index d7338fe..1f00520 100644 --- a/tests/postgres/alltypes_test.go +++ b/tests/postgres/alltypes_test.go @@ -2,12 +2,12 @@ package postgres import ( "encoding/base64" + "fmt" "github.com/go-jet/jet/v2/internal/utils/ptr" - "github.com/stretchr/testify/assert" - "math" - "github.com/go-jet/jet/v2/qrm" "github.com/lib/pq" + "github.com/stretchr/testify/assert" + "math" "testing" "time" @@ -1728,6 +1728,135 @@ SELECT ROW($1::integer, $2::real, $3::text) AS "row", }) } +func TestSubQueryAllExpTypes(t *testing.T) { + skipForCockroachDB(t) + + subquery := SELECT( + Bool(true).AS("bool"), + Int32(11).AS("int"), + Text("doe").AS("text"), + Date(2000, 2, 2).AS("date"), + Time(11, 20, 40).AS("time"), + Timez(11, 20, 40, 200, "UTC").AS("timez"), + Timestamp(2030, 3, 4, 11, 20, 40).AS("timestamp"), + Timestampz(2023, 1, 2, 11, 20, 40, 200, "UTC").AS("timestampz"), + INTERVAL(100, HOUR).AS("interval"), + Bytea("bytes").AS("bytea"), + + ARRAY(Bool(true)).AS("bool_arr"), + ARRAY(Int32(11)).AS("int_arr"), + ARRAY(Text("doe")).AS("text_arr"), + ARRAY(Date(2000, 2, 2)).AS("date_arr"), + ARRAY(Time(11, 20, 40)).AS("time_arr"), + ARRAY(Timez(11, 20, 40, 200, "UTC")).AS("timez_arr"), + ARRAY(Timestamp(2030, 3, 4, 11, 20, 40)).AS("timestamp_arr"), + ARRAY(Timestampz(2023, 1, 2, 11, 20, 40, 200, "UTC")).AS("timestampz_arr"), + ARRAY(INTERVAL(100, HOUR)).AS("interval_arr"), + ARRAY(Bytea("bytes")).AS("bytea_arr"), + + INT4_RANGE(Int(1), Int(200)).AS("int4_range"), + DATE_RANGE(Date(2000, 2, 2), Date(2010, 3, 3)).AS("date_range"), + NUM_RANGE(Float(0.22), Float(22.1)).AS("num_range"), + TS_RANGE(LOCALTIMESTAMP(), LOCALTIMESTAMP().ADD(INTERVAL(1, HOUR))).AS("ts_range"), + TSTZ_RANGE(NOW(), NOW().ADD(INTERVAL(3, MONTH))).AS("tstz_range"), + ).AsTable("sub") + + var result = "\n" + for _, projection := range subquery.AllColumns() { + result += fmt.Sprintf("Column type: %T\n", projection) + } + + require.Equal(t, result, ` +Column type: *jet.boolColumnImpl +Column type: *jet.integerColumnImpl +Column type: *jet.stringColumnImpl +Column type: *jet.dateColumnImpl +Column type: *jet.timeColumnImpl +Column type: *jet.timezColumnImpl +Column type: *jet.timestampColumnImpl +Column type: *jet.timestampzColumnImpl +Column type: *jet.intervalColumnImpl +Column type: *jet.blobColumnImpl +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.BoolExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.IntegerExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.StringExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.DateExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.TimeExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.TimezExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.TimestampExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.TimestampzExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.IntervalExpression] +Column type: *jet.arrayColumnImpl[github.com/go-jet/jet/v2/internal/jet.BlobExpression] +Column type: *jet.rangeColumnImpl[github.com/go-jet/jet/v2/internal/jet.IntegerExpression] +Column type: *jet.rangeColumnImpl[github.com/go-jet/jet/v2/internal/jet.DateExpression] +Column type: *jet.rangeColumnImpl[github.com/go-jet/jet/v2/internal/jet.NumericExpression] +Column type: *jet.rangeColumnImpl[github.com/go-jet/jet/v2/internal/jet.TimestampExpression] +Column type: *jet.rangeColumnImpl[github.com/go-jet/jet/v2/internal/jet.TimestampzExpression] +`) + + stmt := SELECT( + subquery.AllColumns(), + ).FROM(subquery) + + testutils.AssertStatementSql(t, stmt, ` +SELECT sub.bool AS "bool", + sub.int AS "int", + sub.text AS "text", + sub.date AS "date", + sub.time AS "time", + sub.timez AS "timez", + sub.timestamp AS "timestamp", + sub.timestampz AS "timestampz", + sub.interval AS "interval", + sub.bytea AS "bytea", + sub.bool_arr AS "bool_arr", + sub.int_arr AS "int_arr", + sub.text_arr AS "text_arr", + sub.date_arr AS "date_arr", + sub.time_arr AS "time_arr", + sub.timez_arr AS "timez_arr", + sub.timestamp_arr AS "timestamp_arr", + sub.timestampz_arr AS "timestampz_arr", + sub.interval_arr AS "interval_arr", + sub.bytea_arr AS "bytea_arr", + sub.int4_range AS "int4_range", + sub.date_range AS "date_range", + sub.num_range AS "num_range", + sub.ts_range AS "ts_range", + sub.tstz_range AS "tstz_range" +FROM ( + SELECT $1::boolean AS "bool", + $2::integer AS "int", + $3::text AS "text", + $4::date AS "date", + $5::time without time zone AS "time", + $6::time with time zone AS "timez", + $7::timestamp without time zone AS "timestamp", + $8::timestamp with time zone AS "timestampz", + INTERVAL '100 HOUR' AS "interval", + $9::bytea AS "bytea", + ARRAY[$10::boolean] AS "bool_arr", + ARRAY[$11::integer] AS "int_arr", + ARRAY[$12::text] AS "text_arr", + ARRAY[$13::date] AS "date_arr", + ARRAY[$14::time without time zone] AS "time_arr", + ARRAY[$15::time with time zone] AS "timez_arr", + ARRAY[$16::timestamp without time zone] AS "timestamp_arr", + ARRAY[$17::timestamp with time zone] AS "timestampz_arr", + ARRAY[INTERVAL '100 HOUR'] AS "interval_arr", + ARRAY[$18::bytea] AS "bytea_arr", + int4range($19, $20) AS "int4_range", + daterange($21::date, $22::date) AS "date_range", + numrange($23, $24) AS "num_range", + tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + INTERVAL '1 HOUR') AS "ts_range", + tstzrange(NOW(), NOW() + INTERVAL '3 MONTH') AS "tstz_range" + ) AS sub; +`) + + _, err := stmt.Exec(db) + require.NoError(t, err) +} + func TestAllTypesSubQueryFrom(t *testing.T) { subQuery := SELECT( AllTypes.Boolean,