Merge remote-tracking branch 'upstream/master' into feat/strict-field-mapping

This commit is contained in:
k4n4ry 2026-01-30 22:44:12 +09:00
commit 323c3a0597
11 changed files with 382 additions and 59 deletions

View file

@ -2,8 +2,8 @@ package mysql
import (
"context"
"fmt"
"github.com/go-jet/jet/v2/qrm"
"slices"
"strings"
"testing"
@ -127,32 +127,91 @@ WHERE actor.actor_id = ?;
}
func TestSelectJsonArr(t *testing.T) {
stmt := SELECT_JSON_ARR(Actor.AllColumns).
FROM(Actor).
ORDER_BY(Actor.ActorID)
onlyMariaDB(t)
testutils.AssertDebugStatementSql(t, stmt, `
var savedActors []model.Actor
testutils.ReadJSONFile(t, "./testdata/results/mysql/all_actors.json", &savedActors)
t.Run("order by", func(t *testing.T) {
stmt := SELECT_JSON_ARR(Actor.AllColumns).
FROM(Actor).
ORDER_BY(
Actor.LastName.DESC(),
Actor.FirstName.ASC(),
Actor.ActorID.ASC(),
)
testutils.AssertDebugStatementSql(t, stmt, `
SELECT JSON_ARRAYAGG(JSON_OBJECT(
'actorID', actor.actor_id,
'firstName', actor.first_name,
'lastName', actor.last_name,
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
)) AS "json"
FROM dvds.actor
ORDER BY actor.actor_id;
)
ORDER BY actor.last_name DESC, actor.first_name ASC, actor.actor_id ASC) AS "json"
FROM dvds.actor;
`)
var dest []model.Actor
var dest []model.Actor
err := stmt.Query(db, &dest)
require.Nil(t, err)
err := stmt.Query(db, &dest)
require.Nil(t, err)
// sort by actor.LastName desc
slices.SortFunc(savedActors, func(a, b model.Actor) int {
if l := strings.Compare(b.LastName, a.LastName); l != 0 {
return l
}
if f := strings.Compare(a.FirstName, b.FirstName); f != 0 {
return f
}
return int(a.ActorID) - int(b.ActorID)
})
require.Equal(t, dest, savedActors)
requireLogged(t, stmt)
requireQueryLogged(t, stmt, 1)
})
t.Run("order by, limit, offset", func(t *testing.T) {
stmt := SELECT_JSON_ARR(Actor.AllColumns).
FROM(Actor).
ORDER_BY(Actor.ActorID.DESC()).
LIMIT(5).
OFFSET(10)
testutils.AssertStatementSql(t, stmt, `
SELECT JSON_ARRAYAGG(JSON_OBJECT(
'actorID', actor.actor_id,
'firstName', actor.first_name,
'lastName', actor.last_name,
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
)
ORDER BY actor.actor_id DESC
LIMIT ?
OFFSET ?) AS "json"
FROM dvds.actor;
`)
var dest []model.Actor
err := stmt.Query(db, &dest)
require.Nil(t, err)
slices.SortFunc(savedActors, func(a, b model.Actor) int {
return int(b.ActorID) - int(a.ActorID)
})
require.Equal(t, dest, savedActors[10:15])
})
testutils.AssertJSONFile(t, dest, "./testdata/results/mysql/all_actors.json")
requireLogged(t, stmt)
requireQueryLogged(t, stmt, 1)
}
func TestSelectJsonArr_NestedArr(t *testing.T) {
onlyMariaDB(t)
stmt := SELECT_JSON_ARR(
Actor.AllColumns,
@ -198,16 +257,16 @@ SELECT JSON_ARRAYAGG(JSON_OBJECT(
'rating', film.rating,
'specialFeatures', film.special_features,
'lastUpdate', DATE_FORMAT(film.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
)) AS "json"
)
ORDER BY film.length DESC) AS "json"
FROM dvds.film_actor
INNER JOIN dvds.film ON ((film.film_id = film_actor.film_id) AND (actor.actor_id = film_actor.actor_id))
WHERE (film.film_id % 17) = 0
ORDER BY film.length DESC
)
)) AS "json"
)
ORDER BY actor.actor_id) AS "json"
FROM dvds.actor
WHERE actor.actor_id BETWEEN 1 AND 3
ORDER BY actor.actor_id;
WHERE actor.actor_id BETWEEN 1 AND 3;
`)
var dest []struct {
@ -217,8 +276,8 @@ ORDER BY actor.actor_id;
}
err := stmt.QueryContext(ctx, db, &dest)
fmt.Println(err)
require.Nil(t, err)
require.NoError(t, err)
testutils.AssertJSON(t, dest, `
[
{
@ -234,21 +293,6 @@ ORDER BY actor.actor_id;
"LastName": "WAHLBERG",
"LastUpdate": "2006-02-15T04:34:33Z",
"Films": [
{
"FilmID": 357,
"Title": "GILBERT PELICAN",
"Description": "A Fateful Tale of a Man And a Feminist who must Conquer a Crocodile in A Manhattan Penthouse",
"ReleaseYear": 2006,
"LanguageID": 1,
"OriginalLanguageID": null,
"RentalDuration": 7,
"RentalRate": 0.99,
"Length": 114,
"ReplacementCost": 13.99,
"Rating": "G",
"SpecialFeatures": "Trailers,Commentaries",
"LastUpdate": "2006-02-15T05:03:42Z"
},
{
"FilmID": 561,
"Title": "MASK PEACH",
@ -263,6 +307,21 @@ ORDER BY actor.actor_id;
"Rating": "NC-17",
"SpecialFeatures": "Commentaries,Deleted Scenes",
"LastUpdate": "2006-02-15T05:03:42Z"
},
{
"FilmID": 357,
"Title": "GILBERT PELICAN",
"Description": "A Fateful Tale of a Man And a Feminist who must Conquer a Crocodile in A Manhattan Penthouse",
"ReleaseYear": 2006,
"LanguageID": 1,
"OriginalLanguageID": null,
"RentalDuration": 7,
"RentalRate": 0.99,
"Length": 114,
"ReplacementCost": 13.99,
"Rating": "G",
"SpecialFeatures": "Trailers,Commentaries",
"LastUpdate": "2006-02-15T05:03:42Z"
}
]
},

View file

@ -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,

View file

@ -841,6 +841,42 @@ func TestRowsScan(t *testing.T) {
requireQueryLogged(t, stmt, 0)
}
func TestRowsNonStrictScan(t *testing.T) {
stmt := SELECT(
Inventory.AllColumns,
Store.AllColumns,
).FROM(
Inventory.INNER_JOIN(Store, Store.StoreID.EQ(Inventory.StoreID)),
).ORDER_BY(
Inventory.InventoryID.ASC(),
)
require.PanicsWithValue(t, "jet: columns never used: 'store.store_id', 'store.manager_staff_id', 'store.address_id', 'store.last_update'", func() {
rows, err := stmt.Rows(context.Background(), db)
var dest model.Inventory
for rows.Next() {
err = rows.Scan(&dest)
require.NoError(t, err)
}
})
allowUnusedColumns(func() {
rows, err := stmt.Rows(context.Background(), db)
var dest model.Inventory
for rows.Next() {
err = rows.Scan(&dest)
require.NoError(t, err)
}
require.NoError(t, rows.Close())
require.NoError(t, rows.Err())
})
}
func TestScanNullColumn(t *testing.T) {
stmt := SELECT(
Address.AllColumns,