From 0f773b26d6910934c31250fef56ef594524fe6f8 Mon Sep 17 00:00:00 2001 From: go-jet Date: Mon, 3 May 2021 19:31:04 +0200 Subject: [PATCH] Add LATERAL query support --- mysql/lateral.go | 23 ++++++++ postgres/lateral.go | 14 ++++- postgres/lateral_test.go | 7 ++- tests/mysql/main_test.go | 6 ++ tests/mysql/select_test.go | 104 +++++++++++++++++++++++++++++++++- tests/postgres/select_test.go | 82 ++++++++++++++++++++++++--- 6 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 mysql/lateral.go diff --git a/mysql/lateral.go b/mysql/lateral.go new file mode 100644 index 0000000..8ba974b --- /dev/null +++ b/mysql/lateral.go @@ -0,0 +1,23 @@ +package mysql + +import "github.com/go-jet/jet/v2/internal/jet" + +func LATERAL(selectStmt SelectStatement) lateralImpl { + return lateralImpl{ + selectStmt: selectStmt, + } +} + +type lateralImpl struct { + selectStmt SelectStatement +} + +func (l lateralImpl) AS(alias string) SelectTable { + subQuery := &selectTableImpl{ + SelectTable: jet.NewLateral(l.selectStmt, alias), + } + + subQuery.readableTableInterfaceImpl.parent = subQuery + + return subQuery +} diff --git a/postgres/lateral.go b/postgres/lateral.go index fbbe953..8d2d4f8 100644 --- a/postgres/lateral.go +++ b/postgres/lateral.go @@ -2,9 +2,19 @@ package postgres import "github.com/go-jet/jet/v2/internal/jet" -func LATERAL(selectStmt SelectStatement, alias string) SelectTable { +func LATERAL(selectStmt SelectStatement) lateralImpl { + return lateralImpl{ + selectStmt: selectStmt, + } +} + +type lateralImpl struct { + selectStmt SelectStatement +} + +func (l lateralImpl) AS(alias string) SelectTable { subQuery := &selectTableImpl{ - SelectTable: jet.NewLateral(selectStmt, alias), + SelectTable: jet.NewLateral(l.selectStmt, alias), } subQuery.readableTableInterfaceImpl.parent = subQuery diff --git a/postgres/lateral_test.go b/postgres/lateral_test.go index a401a1e..35a0429 100644 --- a/postgres/lateral_test.go +++ b/postgres/lateral_test.go @@ -3,7 +3,12 @@ package postgres import "testing" func TestLATERAL(t *testing.T) { - assertSerialize(t, LATERAL(SELECT(Int(1)), "lat1"), `LATERAL ( + assertSerialize(t, + LATERAL( + SELECT(Int(1)), + ).AS("lat1"), + + `LATERAL ( SELECT $1 ) AS lat1`) } diff --git a/tests/mysql/main_test.go b/tests/mysql/main_test.go index 4f9268c..e2be933 100644 --- a/tests/mysql/main_test.go +++ b/tests/mysql/main_test.go @@ -64,3 +64,9 @@ func requireLogged(t *testing.T, statement postgres.Statement) { require.Equal(t, loggedSQLArgs, args) require.Equal(t, loggedDebugSQL, statement.DebugSql()) } + +func skipForMariaDB(t *testing.T) { + if sourceIsMariaDB() { + t.SkipNow() + } +} diff --git a/tests/mysql/select_test.go b/tests/mysql/select_test.go index f447218..1a60a42 100644 --- a/tests/mysql/select_test.go +++ b/tests/mysql/select_test.go @@ -1,15 +1,17 @@ package mysql import ( + "strings" + "testing" + "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/mysql" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/enum" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/view" - "github.com/stretchr/testify/require" - "testing" + "github.com/stretchr/testify/require" ) func TestSelect_ScanToStruct(t *testing.T) { @@ -787,3 +789,101 @@ LIMIT 5; require.Equal(t, dest.Films[1].Title, "ACE GOLDFINGER") require.Equal(t, dest.Films[4].Title, "AFRICAN EGG") } + +func TestLateral(t *testing.T) { + skipForMariaDB(t) // MariaDB does not implement LATERAL + + languages := LATERAL( + SELECT( + Language.AllColumns, + ).FROM( + Language, + ).WHERE( + Language.Name.NOT_IN(String("spanish")). + AND(Film.LanguageID.EQ(Language.LanguageID)), + ), + ).AS("films") + + stmt := SELECT( + Film.FilmID, + Film.Title, + languages.AllColumns(), + ).FROM( + Film.CROSS_JOIN(languages), + ).WHERE( + Film.FilmID.EQ(Int(1)), + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) + + testutils.AssertDebugStatementSql(t, stmt, strings.Replace(` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films.''language.language_id'' AS "language.language_id", + films.''language.name'' AS "language.name", + films.''language.last_update'' AS "language.last_update" +FROM dvds.film + CROSS JOIN LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`, "''", "`", -1)) + + type FilmLanguage struct { + model.Film + model.Language + } + + var dest []FilmLanguage + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Equal(t, dest[0].Film.Title, "ACADEMY DINOSAUR") + require.Equal(t, dest[0].Language.Name, "English") + + t.Run("implicit cross join", func(t *testing.T) { + stmt2 := SELECT( + Film.FilmID, + Film.Title, + languages.AllColumns(), + ).FROM( + Film, + languages, + ).WHERE( + Film.FilmID.EQ(Int(1)), + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) + + testutils.AssertDebugStatementSql(t, stmt2, strings.Replace(` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films.''language.language_id'' AS "language.language_id", + films.''language.name'' AS "language.name", + films.''language.last_update'' AS "language.last_update" +FROM dvds.film, + LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`, "''", "`", -1)) + + var dest2 []FilmLanguage + + err2 := stmt2.Query(db, &dest2) + require.NoError(t, err2) + require.Equal(t, dest, dest2) + }) +} diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index afa7230..59fc44a 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -1,15 +1,17 @@ package postgres import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/enum" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/table" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/view" - "github.com/stretchr/testify/require" - "testing" - "time" ) func TestSelect_ScanToStruct(t *testing.T) { @@ -1905,22 +1907,88 @@ func TestLateral(t *testing.T) { Language.Name.NOT_IN(String("spanish")). AND(Film.LanguageID.EQ(Language.LanguageID)), ), - "films") + ).AS("films") stmt := SELECT( - Film.AllColumns, + Film.FilmID, + Film.Title, languages.AllColumns(), ).FROM( Film.CROSS_JOIN(languages), ).WHERE( Film.FilmID.EQ(Int(1)), - ) + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) - var dest []struct { + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films."language.language_id" AS "language.language_id", + films."language.name" AS "language.name", + films."language.last_update" AS "language.last_update" +FROM dvds.film + CROSS JOIN LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`) + + type FilmLanguage struct { model.Film model.Language } + var dest []FilmLanguage + err := stmt.Query(db, &dest) require.NoError(t, err) + require.Equal(t, dest[0].Film.Title, "Academy Dinosaur") + require.Equal(t, dest[0].Language.Name, "English ") + + t.Run("implicit cross join", func(t *testing.T) { + stmt2 := SELECT( + Film.FilmID, + Film.Title, + languages.AllColumns(), + ).FROM( + Film, + languages, + ).WHERE( + Film.FilmID.EQ(Int(1)), + ).ORDER_BY( + Film.FilmID, + ).LIMIT(1) + + testutils.AssertDebugStatementSql(t, stmt2, ` +SELECT film.film_id AS "film.film_id", + film.title AS "film.title", + films."language.language_id" AS "language.language_id", + films."language.name" AS "language.name", + films."language.last_update" AS "language.last_update" +FROM dvds.film, + LATERAL ( + SELECT language.language_id AS "language.language_id", + language.name AS "language.name", + language.last_update AS "language.last_update" + FROM dvds.language + WHERE (language.name NOT IN ('spanish')) AND (film.language_id = language.language_id) + ) AS films +WHERE film.film_id = 1 +ORDER BY film.film_id +LIMIT 1; +`) + + var dest2 []FilmLanguage + + err2 := stmt2.Query(db, &dest2) + require.NoError(t, err2) + require.Equal(t, dest, dest2) + }) }