diff --git a/README.md b/README.md index b53dfe1a..a6288f30 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ https://medium.com/@go.jet/jet-5f3667efa0cc * [WITH](https://github.com/go-jet/jet/wiki/WITH) 2) Auto-generated Data Model types - Go types mapped to database type (table, view or enum), used to store - result of database queries. Can be combined to create desired query result destination. + result of database queries. Can be combined to create complex query result destination. 3) Query execution with result mapping to arbitrary destination. ## Getting Started @@ -164,11 +164,11 @@ import ( ``` Let's say we want to retrieve the list of all _actors_ that acted in _films_ longer than 180 minutes, _film language_ is 'English' and _film category_ is not 'Action'. -```java +```golang stmt := SELECT( Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, // or just Actor.AllColumns Film.AllColumns, - Language.AllColumns, + Language.AllColumns.Except(Language.LastUpdate), Category.AllColumns, ).FROM( Actor. @@ -358,7 +358,7 @@ fmt.Println(string(jsonText)) "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -393,7 +393,7 @@ fmt.Println(string(jsonText)) "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -580,5 +580,5 @@ To run the tests, additional dependencies are required: ## License -Copyright 2019-2021 Goran Bjelanovic +Copyright 2019-2022 Goran Bjelanovic Licensed under the Apache License, Version 2.0. diff --git a/examples/quick-start/dest.json b/examples/quick-start/dest.json index 050a6fb4..d23e5881 100644 --- a/examples/quick-start/dest.json +++ b/examples/quick-start/dest.json @@ -22,7 +22,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -57,7 +57,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -92,7 +92,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -127,7 +127,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -154,7 +154,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -181,7 +181,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -208,7 +208,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -243,7 +243,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -270,7 +270,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -305,7 +305,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -340,7 +340,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -367,7 +367,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -402,7 +402,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -429,7 +429,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -464,7 +464,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -499,7 +499,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -526,7 +526,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -553,7 +553,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -588,7 +588,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -623,7 +623,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -658,7 +658,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -693,7 +693,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -720,7 +720,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -755,7 +755,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -782,7 +782,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -817,7 +817,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -852,7 +852,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -879,7 +879,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -914,7 +914,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -949,7 +949,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -984,7 +984,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1019,7 +1019,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1046,7 +1046,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1081,7 +1081,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1116,7 +1116,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1151,7 +1151,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1186,7 +1186,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1221,7 +1221,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1248,7 +1248,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1283,7 +1283,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1318,7 +1318,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1345,7 +1345,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1380,7 +1380,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1407,7 +1407,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1434,7 +1434,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1469,7 +1469,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1504,7 +1504,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1539,7 +1539,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1574,7 +1574,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1609,7 +1609,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1644,7 +1644,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1679,7 +1679,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1714,7 +1714,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1741,7 +1741,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1776,7 +1776,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1803,7 +1803,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1838,7 +1838,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1865,7 +1865,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1900,7 +1900,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1927,7 +1927,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1954,7 +1954,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -1989,7 +1989,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2024,7 +2024,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2059,7 +2059,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2094,7 +2094,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2129,7 +2129,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2164,7 +2164,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2199,7 +2199,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2234,7 +2234,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2269,7 +2269,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2304,7 +2304,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2331,7 +2331,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2366,7 +2366,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2393,7 +2393,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2428,7 +2428,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2463,7 +2463,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2498,7 +2498,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2533,7 +2533,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2568,7 +2568,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2595,7 +2595,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2622,7 +2622,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2657,7 +2657,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2684,7 +2684,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2719,7 +2719,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2754,7 +2754,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2789,7 +2789,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2816,7 +2816,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2851,7 +2851,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2878,7 +2878,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2913,7 +2913,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2948,7 +2948,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -2975,7 +2975,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3010,7 +3010,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3045,7 +3045,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3080,7 +3080,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3107,7 +3107,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3134,7 +3134,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3169,7 +3169,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3204,7 +3204,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3239,7 +3239,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3274,7 +3274,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3309,7 +3309,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3344,7 +3344,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3379,7 +3379,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3414,7 +3414,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3441,7 +3441,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3476,7 +3476,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3511,7 +3511,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3538,7 +3538,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3573,7 +3573,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3608,7 +3608,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3643,7 +3643,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3678,7 +3678,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3713,7 +3713,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3748,7 +3748,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3783,7 +3783,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3818,7 +3818,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3853,7 +3853,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3880,7 +3880,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3915,7 +3915,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3942,7 +3942,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3969,7 +3969,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -3996,7 +3996,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4031,7 +4031,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4066,7 +4066,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4101,7 +4101,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4136,7 +4136,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4163,7 +4163,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4198,7 +4198,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4225,7 +4225,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4260,7 +4260,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4295,7 +4295,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4322,7 +4322,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4349,7 +4349,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4384,7 +4384,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4411,7 +4411,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4438,7 +4438,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4465,7 +4465,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4500,7 +4500,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4535,7 +4535,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4570,7 +4570,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4605,7 +4605,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4632,7 +4632,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4659,7 +4659,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4694,7 +4694,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4729,7 +4729,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4764,7 +4764,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4799,7 +4799,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4826,7 +4826,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4861,7 +4861,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4896,7 +4896,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4923,7 +4923,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4950,7 +4950,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -4985,7 +4985,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5020,7 +5020,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5055,7 +5055,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5082,7 +5082,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5117,7 +5117,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5152,7 +5152,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5187,7 +5187,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5222,7 +5222,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5249,7 +5249,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5284,7 +5284,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5311,7 +5311,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5346,7 +5346,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5381,7 +5381,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5416,7 +5416,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5443,7 +5443,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5478,7 +5478,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5505,7 +5505,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5540,7 +5540,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5575,7 +5575,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5610,7 +5610,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5645,7 +5645,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5680,7 +5680,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5715,7 +5715,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5742,7 +5742,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5777,7 +5777,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5804,7 +5804,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5839,7 +5839,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5874,7 +5874,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5909,7 +5909,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5936,7 +5936,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5963,7 +5963,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -5990,7 +5990,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -6017,7 +6017,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { @@ -6052,7 +6052,7 @@ "Language": { "LanguageID": 1, "Name": "English ", - "LastUpdate": "2006-02-15T10:02:19Z" + "LastUpdate": "0001-01-01T00:00:00Z" }, "Categories": [ { diff --git a/examples/quick-start/quick-start.go b/examples/quick-start/quick-start.go index 5bdc424c..e453f51b 100644 --- a/examples/quick-start/quick-start.go +++ b/examples/quick-start/quick-start.go @@ -36,7 +36,7 @@ func main() { stmt := SELECT( Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate, Film.AllColumns, - Language.AllColumns, + Language.AllColumns.Except(Language.LastUpdate), Category.AllColumns, ).FROM( Actor. diff --git a/internal/jet/clause.go b/internal/jet/clause.go index ce99a6b5..aa450055 100644 --- a/internal/jet/clause.go +++ b/internal/jet/clause.go @@ -98,9 +98,9 @@ func (c *ClauseWhere) Serialize(statementType StatementType, out *SQLBuilder, op } out.WriteString("WHERE") - out.IncreaseIdent() + out.IncreaseIdent(6) c.Condition.serialize(statementType, out, NoWrap.WithFallTrough(options)...) - out.DecreaseIdent() + out.DecreaseIdent(6) } // ClauseGroupBy struct diff --git a/internal/jet/expression.go b/internal/jet/expression.go index e657f30a..0fe78df0 100644 --- a/internal/jet/expression.go +++ b/internal/jet/expression.go @@ -123,6 +123,65 @@ func (c *binaryOperatorExpression) serialize(statement StatementType, out *SQLBu } } +type expressionListOperator struct { + ExpressionInterfaceImpl + + operator string + expressions []Expression +} + +func newExpressionListOperator(operator string, expressions ...Expression) *expressionListOperator { + ret := &expressionListOperator{ + operator: operator, + expressions: expressions, + } + + ret.ExpressionInterfaceImpl.Parent = ret + + return ret +} + +func newBoolExpressionListOperator(operator string, expressions ...BoolExpression) BoolExpression { + return BoolExp(newExpressionListOperator(operator, BoolExpressionListToExpressionList(expressions)...)) +} + +func (elo *expressionListOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { + if len(elo.expressions) == 0 { + panic("jet: syntax error, expression list empty") + } + + shouldWrap := len(elo.expressions) > 1 + if shouldWrap { + out.WriteByte('(') + out.IncreaseIdent(tabSize) + out.NewLine() + } + + for i, expression := range elo.expressions { + if i == 1 { + out.IncreaseIdent(tabSize) + } + if i > 0 { + out.NewLine() + out.WriteString(elo.operator) + } + + out.IncreaseIdent(len(elo.operator) + 1) + expression.serialize(statement, out, FallTrough(options)...) + out.DecreaseIdent(len(elo.operator) + 1) + } + + if len(elo.expressions) > 1 { + out.DecreaseIdent(tabSize) + } + + if shouldWrap { + out.DecreaseIdent(tabSize) + out.NewLine() + out.WriteByte(')') + } +} + // A prefix operator Expression type prefixExpression struct { ExpressionInterfaceImpl @@ -209,8 +268,8 @@ type complexExpression struct { expressions Expression } -func complexExpr(expressions Expression) Expression { - complexExpression := &complexExpression{expressions: expressions} +func complexExpr(expression Expression) Expression { + complexExpression := &complexExpression{expressions: expression} complexExpression.ExpressionInterfaceImpl.Parent = complexExpression return complexExpression diff --git a/internal/jet/func_expression.go b/internal/jet/func_expression.go index 3e40edfe..cfac71f8 100644 --- a/internal/jet/func_expression.go +++ b/internal/jet/func_expression.go @@ -1,5 +1,17 @@ package jet +// AND function adds AND operator between expressions. This function can be used, instead of method AND, +// to have a better inlining of a complex condition in the Go code and in the generated SQL. +func AND(expressions ...BoolExpression) BoolExpression { + return newBoolExpressionListOperator("AND", expressions...) +} + +// OR function adds OR operator between expressions. This function can be used, instead of method OR, +// to have a better inlining of a complex condition in the Go code and in the generated SQL. +func OR(expressions ...BoolExpression) BoolExpression { + return newBoolExpressionListOperator("OR", expressions...) +} + // ROW is construct one table row from list of expressions. func ROW(expressions ...Expression) Expression { return NewFunc("ROW", expressions, nil) diff --git a/internal/jet/func_expression_test.go b/internal/jet/func_expression_test.go index 264be956..048ade29 100644 --- a/internal/jet/func_expression_test.go +++ b/internal/jet/func_expression_test.go @@ -4,6 +4,28 @@ import ( "testing" ) +func TestAND(t *testing.T) { + assertClauseSerializeErr(t, AND(), "jet: syntax error, expression list empty") + assertClauseSerialize(t, AND(table1ColInt.IS_NULL()), `table1.col_int IS NULL`) // IS NULL doesn't add parenthesis + assertClauseSerialize(t, AND(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11)) + assertClauseSerialize(t, AND(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))), + `( + (table1.col_int > $1) + AND (table1.col_float = $2) +)`, int64(11), 0.0) +} + +func TestOR(t *testing.T) { + assertClauseSerializeErr(t, OR(), "jet: syntax error, expression list empty") + assertClauseSerialize(t, OR(table1ColInt.IS_NULL()), `table1.col_int IS NULL`) // IS NULL doesn't add parenthesis + assertClauseSerialize(t, OR(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11)) + assertClauseSerialize(t, OR(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))), + `( + (table1.col_int > $1) + OR (table1.col_float = $2) +)`, int64(11), 0.0) +} + func TestFuncAVG(t *testing.T) { assertClauseSerialize(t, AVG(table1ColFloat), "AVG(table1.col_float)") assertClauseSerialize(t, AVG(table1ColInt), "AVG(table1.col_int)") diff --git a/internal/jet/sql_builder.go b/internal/jet/sql_builder.go index 6241feec..e3fb61b2 100644 --- a/internal/jet/sql_builder.go +++ b/internal/jet/sql_builder.go @@ -26,6 +26,7 @@ type SQLBuilder struct { Debug bool } +const tabSize = 4 const defaultIdent = 5 // IncreaseIdent adds ident or defaultIdent number of spaces to each new line diff --git a/internal/jet/statement.go b/internal/jet/statement.go index b2058017..183aaae8 100644 --- a/internal/jet/statement.go +++ b/internal/jet/statement.go @@ -33,11 +33,13 @@ type Statement interface { // Rows wraps sql.Rows type to add query result mapping for Scan method type Rows struct { *sql.Rows + + scanContext *qrm.ScanContext } // Scan will map the Row values into struct destination func (r *Rows) Scan(destination interface{}) error { - return qrm.ScanOneRowToDest(r.Rows, destination) + return qrm.ScanOneRowToDest(r.scanContext, r.Rows, destination) } // SerializerStatement interface @@ -161,7 +163,16 @@ func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.DB) return nil, err } - return &Rows{rows}, nil + scanContext, err := qrm.NewScanContext(rows) + + if err != nil { + return nil, err + } + + return &Rows{ + Rows: rows, + scanContext: scanContext, + }, nil } func duration(f func()) time.Duration { diff --git a/internal/jet/utils.go b/internal/jet/utils.go index 524c2c53..4ab3fae9 100644 --- a/internal/jet/utils.go +++ b/internal/jet/utils.go @@ -113,6 +113,17 @@ func ExpressionListToSerializerList(expressions []Expression) []Serializer { return ret } +// BoolExpressionListToExpressionList converts list of bool expressions to list of expressions +func BoolExpressionListToExpressionList(expressions []BoolExpression) []Expression { + var ret []Expression + + for _, expression := range expressions { + ret = append(ret, expression) + } + + return ret +} + // ColumnListToProjectionList func func ColumnListToProjectionList(columns []ColumnExpression) []Projection { var ret []Projection diff --git a/internal/testutils/test_utils.go b/internal/testutils/test_utils.go index cac4a623..37c1665a 100644 --- a/internal/testutils/test_utils.go +++ b/internal/testutils/test_utils.go @@ -67,7 +67,8 @@ func AssertJSON(t *testing.T, data interface{}, expectedJSON string) { jsonData, err := json.MarshalIndent(data, "", "\t") require.NoError(t, err) - require.Equal(t, "\n"+string(jsonData)+"\n", expectedJSON) + dataJson := "\n" + string(jsonData) + "\n" + require.Equal(t, dataJson, expectedJSON) } // SaveJSONFile saves v as json at testRelativePath diff --git a/mysql/functions.go b/mysql/functions.go index 6d8193d9..b794ef70 100644 --- a/mysql/functions.go +++ b/mysql/functions.go @@ -2,6 +2,15 @@ package mysql import "github.com/go-jet/jet/v2/internal/jet" +// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition +// in the Go code and in the generated SQL. +var ( + // AND function adds AND operator between expressions. + AND = jet.AND + // OR function adds OR operator between expressions. + OR = jet.OR +) + // ROW is construct one table row from list of expressions. var ROW = jet.ROW diff --git a/mysql/select_statement_test.go b/mysql/select_statement_test.go index 37827d5f..bd3a0e9c 100644 --- a/mysql/select_statement_test.go +++ b/mysql/select_statement_test.go @@ -148,9 +148,9 @@ func TestSelect_NOT_EXISTS(t *testing.T) { SELECT table1.col_int AS "table1.col_int" FROM db.table1 WHERE NOT (EXISTS ( - SELECT table2.col_int AS "table2.col_int" - FROM db.table2 - WHERE table1.col_int = table2.col_int - )); + SELECT table2.col_int AS "table2.col_int" + FROM db.table2 + WHERE table1.col_int = table2.col_int + )); `) } diff --git a/postgres/functions.go b/postgres/functions.go index a20d1e11..cd2c130a 100644 --- a/postgres/functions.go +++ b/postgres/functions.go @@ -2,6 +2,15 @@ package postgres import "github.com/go-jet/jet/v2/internal/jet" +// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition +// in the Go code and in the generated SQL. +var ( + // AND function adds AND operator between expressions. + AND = jet.AND + // OR function adds OR operator between expressions. + OR = jet.OR +) + // ROW is construct one table row from list of expressions. var ROW = jet.ROW diff --git a/qrm/qrm.go b/qrm/qrm.go index 3731c687..50597cd9 100644 --- a/qrm/qrm.go +++ b/qrm/qrm.go @@ -63,46 +63,26 @@ func Query(ctx context.Context, db DB, query string, args []interface{}, destPtr } // ScanOneRowToDest will scan one row into struct destination -func ScanOneRowToDest(rows *sql.Rows, destPtr interface{}) error { +func ScanOneRowToDest(scanContext *ScanContext, rows *sql.Rows, destPtr interface{}) error { utils.MustBeInitializedPtr(destPtr, "jet: destination is nil") utils.MustBe(destPtr, reflect.Ptr, "jet: destination has to be a pointer to slice or pointer to struct") - scanContext, err := newScanContext(rows) - - if err != nil { - return fmt.Errorf("failed to create scan context, %w", err) - } - if len(scanContext.row) == 0 { return errors.New("empty row slice") } - err = rows.Scan(scanContext.row...) + err := rows.Scan(scanContext.row...) if err != nil { - return fmt.Errorf("rows scan error, %w", err) + return fmt.Errorf("jet: rows scan error, %w", err) } - destinationPtrType := reflect.TypeOf(destPtr) - tempSlicePtrValue := reflect.New(reflect.SliceOf(destinationPtrType)) - tempSliceValue := tempSlicePtrValue.Elem() + destValuePtr := reflect.ValueOf(destPtr) - _, err = mapRowToSlice(scanContext, "", newTypeStack(), tempSlicePtrValue, nil) + _, err = mapRowToStruct(scanContext, "", destValuePtr, nil) if err != nil { - return fmt.Errorf("failed to map a row, %w", err) - } - - // edge case when row result set contains only NULLs. - if tempSliceValue.Len() == 0 { - return nil - } - - destValue := reflect.ValueOf(destPtr).Elem() - firstTempSliceValue := tempSliceValue.Index(0).Elem() - - if destValue.Type().AssignableTo(firstTempSliceValue.Type()) { - destValue.Set(tempSliceValue.Index(0).Elem()) + return fmt.Errorf("jet: failed to scan a row into destination, %w", err) } return nil @@ -120,7 +100,7 @@ func queryToSlice(ctx context.Context, db DB, query string, args []interface{}, } defer rows.Close() - scanContext, err := newScanContext(rows) + scanContext, err := NewScanContext(rows) if err != nil { return @@ -141,7 +121,7 @@ func queryToSlice(ctx context.Context, db DB, query string, args []interface{}, scanContext.rowNum++ - _, err = mapRowToSlice(scanContext, "", newTypeStack(), slicePtrValue, nil) + _, err = mapRowToSlice(scanContext, "", slicePtrValue, nil) if err != nil { return scanContext.rowNum, err @@ -157,9 +137,8 @@ func queryToSlice(ctx context.Context, db DB, query string, args []interface{}, } func mapRowToSlice( - scanContext *scanContext, + scanContext *ScanContext, groupKey string, - typesVisited *typeStack, slicePtrValue reflect.Value, field *reflect.StructField) (updated bool, err error) { @@ -174,19 +153,19 @@ func mapRowToSlice( structGroupKey := scanContext.getGroupKey(sliceElemType, field) - groupKey = groupKey + "," + structGroupKey + groupKey = concat(groupKey, ",", structGroupKey) index, ok := scanContext.uniqueDestObjectsMap[groupKey] if ok { structPtrValue := getSliceElemPtrAt(slicePtrValue, index) - return mapRowToStruct(scanContext, groupKey, typesVisited, structPtrValue, field, true) + return mapRowToStruct(scanContext, groupKey, structPtrValue, field, true) } destinationStructPtr := newElemPtrValueForSlice(slicePtrValue) - updated, err = mapRowToStruct(scanContext, groupKey, typesVisited, destinationStructPtr, field) + updated, err = mapRowToStruct(scanContext, groupKey, destinationStructPtr, field) if err != nil { return @@ -204,7 +183,7 @@ func mapRowToSlice( return } -func mapRowToBaseTypeSlice(scanContext *scanContext, slicePtrValue reflect.Value, field *reflect.StructField) (updated bool, err error) { +func mapRowToBaseTypeSlice(scanContext *ScanContext, slicePtrValue reflect.Value, field *reflect.StructField) (updated bool, err error) { index := 0 if field != nil { typeName, columnName := getTypeAndFieldName("", *field) @@ -212,7 +191,7 @@ func mapRowToBaseTypeSlice(scanContext *scanContext, slicePtrValue reflect.Value return } } - rowElemPtr := scanContext.rowElemValuePtr(index) + rowElemPtr := scanContext.rowElemValueClonePtr(index) if rowElemPtr.IsValid() && !rowElemPtr.IsNil() { updated = true @@ -226,9 +205,8 @@ func mapRowToBaseTypeSlice(scanContext *scanContext, slicePtrValue reflect.Value } func mapRowToStruct( - scanContext *scanContext, + scanContext *ScanContext, groupKey string, - typesVisited *typeStack, // to prevent circular dependency scan structPtrValue reflect.Value, parentField *reflect.StructField, onlySlices ...bool, // small optimization, not to assign to already assigned struct fields @@ -237,12 +215,12 @@ func mapRowToStruct( mapOnlySlices := len(onlySlices) > 0 structType := structPtrValue.Type().Elem() - if typesVisited.contains(&structType) { + if scanContext.typesVisited.contains(&structType) { return false, nil } - typesVisited.push(&structType) - defer typesVisited.pop() + scanContext.typesVisited.push(&structType) + defer scanContext.typesVisited.pop() typeInf := scanContext.getTypeInfo(structType, parentField) @@ -260,7 +238,7 @@ func mapRowToStruct( if fieldMap.complexType { var changed bool - changed, err = mapRowToDestinationValue(scanContext, groupKey, typesVisited, fieldValue, &field) + changed, err = mapRowToDestinationValue(scanContext, groupKey, fieldValue, &field) if err != nil { return @@ -271,34 +249,36 @@ func mapRowToStruct( } } else { - if mapOnlySlices || fieldMap.columnIndex == -1 { + if mapOnlySlices || fieldMap.rowIndex == -1 { continue } - cellValue := scanContext.rowElem(fieldMap.columnIndex) + scannedValue := scanContext.rowElemValue(fieldMap.rowIndex) - if cellValue == nil { + if !scannedValue.IsValid() { + setZeroValue(fieldValue) // scannedValue is nil, destination should be set to zero value continue } - initializeValueIfNilPtr(fieldValue) updated = true if fieldMap.implementsScanner { - scanner := getScanner(fieldValue) + initializeValueIfNilPtr(fieldValue) + fieldScanner := getScanner(fieldValue) + + value := scannedValue.Interface() - err = scanner.Scan(cellValue) + err := fieldScanner.Scan(value) if err != nil { - err = fmt.Errorf(`can't scan %T(%q) to '%s %s': %w`, cellValue, cellValue, field.Name, field.Type.String(), err) - return + return updated, fmt.Errorf(`can't scan %T(%q) to '%s %s': %w`, value, value, field.Name, field.Type.String(), err) } } else { - err = setReflectValue(reflect.ValueOf(cellValue), fieldValue) + err := assign(scannedValue, fieldValue) if err != nil { - err = fmt.Errorf(`can't assign %T(%q) to '%s %s': %w`, cellValue, cellValue, field.Name, field.Type.String(), err) - return + return updated, fmt.Errorf(`can't assign %T(%q) to '%s %s': %w`, scannedValue.Interface(), scannedValue.Interface(), + field.Name, field.Type.String(), err) } } } @@ -308,9 +288,8 @@ func mapRowToStruct( } func mapRowToDestinationValue( - scanContext *scanContext, + scanContext *ScanContext, groupKey string, - typesVisited *typeStack, dest reflect.Value, structField *reflect.StructField) (updated bool, err error) { @@ -326,7 +305,7 @@ func mapRowToDestinationValue( } } - updated, err = mapRowToDestinationPtr(scanContext, groupKey, typesVisited, destPtrValue, structField) + updated, err = mapRowToDestinationPtr(scanContext, groupKey, destPtrValue, structField) if err != nil { return @@ -340,9 +319,8 @@ func mapRowToDestinationValue( } func mapRowToDestinationPtr( - scanContext *scanContext, + scanContext *ScanContext, groupKey string, - typesVisited *typeStack, destPtrValue reflect.Value, structField *reflect.StructField) (updated bool, err error) { @@ -351,9 +329,9 @@ func mapRowToDestinationPtr( destValueKind := destPtrValue.Elem().Kind() if destValueKind == reflect.Struct { - return mapRowToStruct(scanContext, groupKey, typesVisited, destPtrValue, structField) + return mapRowToStruct(scanContext, groupKey, destPtrValue, structField) } else if destValueKind == reflect.Slice { - return mapRowToSlice(scanContext, groupKey, typesVisited, destPtrValue, structField) + return mapRowToSlice(scanContext, groupKey, destPtrValue, structField) } else { panic("jet: unsupported dest type: " + structField.Name + " " + structField.Type.String()) } diff --git a/qrm/scan_context.go b/qrm/scan_context.go index 61feb759..fa99b5ad 100644 --- a/qrm/scan_context.go +++ b/qrm/scan_context.go @@ -7,16 +7,21 @@ import ( "strings" ) -type scanContext struct { +// ScanContext contains information about current row processed, mapping from the row to the +// destination types and type grouping information. +type ScanContext struct { rowNum int64 row []interface{} uniqueDestObjectsMap map[string]int commonIdentToColumnIndex map[string]int groupKeyInfoCache map[string]groupKeyInfo typeInfoMap map[string]typeInfo + + typesVisited typeStack // to prevent circular dependency scan } -func newScanContext(rows *sql.Rows) (*scanContext, error) { +// NewScanContext creates new ScanContext from rows +func NewScanContext(rows *sql.Rows) (*ScanContext, error) { aliases, err := rows.Columns() if err != nil { @@ -36,13 +41,13 @@ func newScanContext(rows *sql.Rows) (*scanContext, error) { commonIdentifier := toCommonIdentifier(names[0]) if len(names) > 1 { - commonIdentifier += "." + toCommonIdentifier(names[1]) + commonIdentifier = concat(commonIdentifier, ".", toCommonIdentifier(names[1])) } commonIdentToColumnIndex[commonIdentifier] = i } - return &scanContext{ + return &ScanContext{ row: createScanSlice(len(columnTypes)), uniqueDestObjectsMap: make(map[string]int), @@ -50,15 +55,17 @@ func newScanContext(rows *sql.Rows) (*scanContext, error) { commonIdentToColumnIndex: commonIdentToColumnIndex, typeInfoMap: make(map[string]typeInfo), + + typesVisited: newTypeStack(), }, nil } func createScanSlice(columnCount int) []interface{} { - scanSlice := make([]interface{}, columnCount) scanPtrSlice := make([]interface{}, columnCount) for i := range scanPtrSlice { - scanPtrSlice[i] = &scanSlice[i] // if destination is pointer to interface sql.Scan will just forward driver value + var a interface{} + scanPtrSlice[i] = &a // if destination is pointer to interface sql.Scan will just forward driver value } return scanPtrSlice @@ -69,17 +76,17 @@ type typeInfo struct { } type fieldMapping struct { - complexType bool // slice or struct - columnIndex int + complexType bool // slice and struct are complex types + rowIndex int // index in ScanContext.row implementsScanner bool } -func (s *scanContext) getTypeInfo(structType reflect.Type, parentField *reflect.StructField) typeInfo { +func (s *ScanContext) getTypeInfo(structType reflect.Type, parentField *reflect.StructField) typeInfo { typeMapKey := structType.String() if parentField != nil { - typeMapKey += string(parentField.Tag) + typeMapKey = concat(typeMapKey, string(parentField.Tag)) } if typeInfo, ok := s.typeInfoMap[typeMapKey]; ok { @@ -97,7 +104,7 @@ func (s *scanContext) getTypeInfo(structType reflect.Type, parentField *reflect. columnIndex := s.typeToColumnIndex(newTypeName, fieldName) fieldMap := fieldMapping{ - columnIndex: columnIndex, + rowIndex: columnIndex, } if implementsScannerType(field.Type) { @@ -120,26 +127,27 @@ type groupKeyInfo struct { subTypes []groupKeyInfo } -func (s *scanContext) getGroupKey(structType reflect.Type, structField *reflect.StructField) string { +func (s *ScanContext) getGroupKey(structType reflect.Type, structField *reflect.StructField) string { mapKey := structType.Name() if structField != nil { - mapKey += structField.Type.String() + mapKey = concat(mapKey, structField.Type.String()) } if groupKeyInfo, ok := s.groupKeyInfoCache[mapKey]; ok { return s.constructGroupKey(groupKeyInfo) } - groupKeyInfo := s.getGroupKeyInfo(structType, structField, newTypeStack()) + tempTypeStack := newTypeStack() + groupKeyInfo := s.getGroupKeyInfo(structType, structField, &tempTypeStack) s.groupKeyInfoCache[mapKey] = groupKeyInfo return s.constructGroupKey(groupKeyInfo) } -func (s *scanContext) constructGroupKey(groupKeyInfo groupKeyInfo) string { +func (s *ScanContext) constructGroupKey(groupKeyInfo groupKeyInfo) string { if len(groupKeyInfo.indexes) == 0 && len(groupKeyInfo.subTypes) == 0 { return fmt.Sprintf("|ROW:%d|", s.rowNum) } @@ -147,10 +155,7 @@ func (s *scanContext) constructGroupKey(groupKeyInfo groupKeyInfo) string { var groupKeys []string for _, index := range groupKeyInfo.indexes { - cellValue := s.rowElem(index) - subKey := valueToString(reflect.ValueOf(cellValue)) - - groupKeys = append(groupKeys, subKey) + groupKeys = append(groupKeys, s.rowElemToString(index)) } var subTypesGroupKeys []string @@ -158,10 +163,10 @@ func (s *scanContext) constructGroupKey(groupKeyInfo groupKeyInfo) string { subTypesGroupKeys = append(subTypesGroupKeys, s.constructGroupKey(subType)) } - return groupKeyInfo.typeName + "(" + strings.Join(groupKeys, ",") + strings.Join(subTypesGroupKeys, ",") + ")" + return concat(groupKeyInfo.typeName, "(", strings.Join(groupKeys, ","), strings.Join(subTypesGroupKeys, ","), ")") } -func (s *scanContext) getGroupKeyInfo( +func (s *ScanContext) getGroupKeyInfo( structType reflect.Type, parentField *reflect.StructField, typeVisited *typeStack) groupKeyInfo { @@ -210,7 +215,7 @@ func (s *scanContext) getGroupKeyInfo( return ret } -func (s *scanContext) typeToColumnIndex(typeName, fieldName string) int { +func (s *ScanContext) typeToColumnIndex(typeName, fieldName string) int { var key string if typeName != "" { @@ -228,32 +233,36 @@ func (s *scanContext) typeToColumnIndex(typeName, fieldName string) int { return index } -func (s *scanContext) rowElem(index int) interface{} { - cellValue := reflect.ValueOf(s.row[index]) +// rowElemValue always returns non-ptr value, +// invalid value is nil +func (s *ScanContext) rowElemValue(index int) reflect.Value { + scannedValue := reflect.ValueOf(s.row[index]) + return scannedValue.Elem().Elem() // no need to check validity of Elem, because s.row[index] always contains interface in interface +} + +func (s *ScanContext) rowElemToString(index int) string { + value := s.rowElemValue(index) + + if !value.IsValid() { + return "nil" + } + + valueInterface := value.Interface() - if cellValue.IsValid() && !cellValue.IsNil() { - return cellValue.Elem().Interface() + if t, ok := valueInterface.(fmt.Stringer); ok { + return t.String() } - return nil + return fmt.Sprintf("%#v", valueInterface) } -func (s *scanContext) rowElemValuePtr(index int) reflect.Value { - rowElem := s.rowElem(index) - rowElemValue := reflect.ValueOf(rowElem) +func (s *ScanContext) rowElemValueClonePtr(index int) reflect.Value { + rowElemValue := s.rowElemValue(index) if !rowElemValue.IsValid() { return reflect.Value{} } - if rowElemValue.Kind() == reflect.Ptr { - return rowElemValue - } - - if rowElemValue.CanAddr() { - return rowElemValue.Addr() - } - newElem := reflect.New(rowElemValue.Type()) newElem.Elem().Set(rowElemValue) return newElem diff --git a/qrm/type_stack.go b/qrm/type_stack.go index 235c06ea..2bdf799b 100644 --- a/qrm/type_stack.go +++ b/qrm/type_stack.go @@ -4,9 +4,9 @@ import "reflect" type typeStack []*reflect.Type -func newTypeStack() *typeStack { +func newTypeStack() typeStack { stack := make(typeStack, 0, 20) - return &stack + return stack } func (s *typeStack) isEmpty() bool { diff --git a/qrm/utill.go b/qrm/utill.go index 6926c423..dfb9a694 100644 --- a/qrm/utill.go +++ b/qrm/utill.go @@ -18,9 +18,9 @@ func implementsScannerType(fieldType reflect.Type) bool { return true } - typePtr := reflect.New(fieldType).Type() + fieldTypePtr := reflect.New(fieldType).Type() - return typePtr.Implements(scannerInterfaceType) + return fieldTypePtr.Implements(scannerInterfaceType) } func getScanner(value reflect.Value) sql.Scanner { @@ -68,9 +68,9 @@ func appendElemToSlice(slicePtrValue reflect.Value, objPtrValue reflect.Value) e if newSliceElemValue.Kind() == reflect.Ptr { newSliceElemValue.Set(reflect.New(newSliceElemValue.Type().Elem())) - err = tryAssign(objPtrValue.Elem(), newSliceElemValue.Elem()) + err = assign(objPtrValue.Elem(), newSliceElemValue.Elem()) } else { - err = tryAssign(objPtrValue.Elem(), newSliceElemValue) + err = assign(objPtrValue.Elem(), newSliceElemValue) } if err != nil { @@ -138,29 +138,6 @@ func initializeValueIfNilPtr(value reflect.Value) { } } -func valueToString(value reflect.Value) string { - - if !value.IsValid() { - return "nil" - } - - var valueInterface interface{} - if value.Kind() == reflect.Ptr { - if value.IsNil() { - return "nil" - } - valueInterface = value.Elem().Interface() - } else { - valueInterface = value.Interface() - } - - if t, ok := valueInterface.(fmt.Stringer); ok { - return t.String() - } - - return fmt.Sprintf("%#v", valueInterface) -} - var timeType = reflect.TypeOf(time.Now()) var uuidType = reflect.TypeOf(uuid.New()) var byteArrayType = reflect.TypeOf([]byte("")) @@ -180,51 +157,57 @@ func isSimpleModelType(objType reflect.Type) bool { return objType == timeType || objType == uuidType || objType == byteArrayType } -func isIntegerType(objType reflect.Type) bool { - objType = indirectType(objType) +// source can't be pointer +// destination can be pointer +func assign(source, destination reflect.Value) error { + if destination.Kind() == reflect.Ptr { + if destination.IsNil() { + initializeValueIfNilPtr(destination) + } - switch objType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return true + destination = destination.Elem() } - return false + err := tryAssign(source, destination) + + if err != nil { + // needs for the type conversions are rare, so we leave conversion as a last assign step if everything else fails + if tryConvert(source, destination) { + return nil + } + + return err + } + + return nil } -func isFloatType(value reflect.Type) bool { - switch value.Kind() { - case reflect.Float32, reflect.Float64: +func assignIfAssignable(source, destination reflect.Value) bool { + sourceType := source.Type() + if sourceType.AssignableTo(destination.Type()) { + switch sourceType { + case byteArrayType: + destination.SetBytes(cloneBytes(source.Interface().([]byte))) + default: + destination.Set(source) + } return true } return false } +// source and destination are non-ptr values func tryAssign(source, destination reflect.Value) error { - if source.Type() != destination.Type() && - !isFloatType(destination.Type()) && // to preserve precision during conversion - !(isIntegerType(source.Type()) && destination.Kind() == reflect.String) && // default conversion will convert int to 1 rune string - source.Type().ConvertibleTo(destination.Type()) { - - source = source.Convert(destination.Type()) - } - - if source.Type().AssignableTo(destination.Type()) { - switch b := source.Interface().(type) { - case []byte: - destination.SetBytes(cloneBytes(b)) - default: - destination.Set(source) - } + if assignIfAssignable(source, destination) { return nil } sourceInterface := source.Interface() - switch destination.Interface().(type) { - case bool: + switch destination.Type().Kind() { + case reflect.Bool: var nullBool internal.NullBool err := nullBool.Scan(sourceInterface) @@ -235,7 +218,7 @@ func tryAssign(source, destination reflect.Value) error { destination.SetBool(nullBool.Bool) - case float32, float64: + case reflect.Float32, reflect.Float64: var nullFloat sql.NullFloat64 err := nullFloat.Scan(sourceInterface) @@ -246,7 +229,7 @@ func tryAssign(source, destination reflect.Value) error { if nullFloat.Valid { destination.SetFloat(nullFloat.Float64) } - case int, int8, int16, int32, int64: + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var integer sql.NullInt64 err := integer.Scan(sourceInterface) @@ -258,7 +241,7 @@ func tryAssign(source, destination reflect.Value) error { destination.SetInt(integer.Int64) } - case uint, uint8, uint16, uint32, uint64: + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var uInt internal.NullUInt64 err := uInt.Scan(sourceInterface) @@ -271,7 +254,7 @@ func tryAssign(source, destination reflect.Value) error { destination.SetUint(uInt.UInt64) } - case string: + case reflect.String: var str sql.NullString err := str.Scan(sourceInterface) @@ -283,57 +266,42 @@ func tryAssign(source, destination reflect.Value) error { destination.SetString(str.String) } - case time.Time: - var nullTime internal.NullTime + default: + switch destination.Interface().(type) { + case time.Time: + var nullTime internal.NullTime - err := nullTime.Scan(sourceInterface) - if err != nil { - return err - } + err := nullTime.Scan(sourceInterface) + if err != nil { + return err + } - if nullTime.Valid { - destination.Set(reflect.ValueOf(nullTime.Time)) + if nullTime.Valid { + destination.Set(reflect.ValueOf(nullTime.Time)) + } + default: + return fmt.Errorf("can't assign %T to %T", sourceInterface, destination.Interface()) } - - default: - return fmt.Errorf("can't assign %T to %T", sourceInterface, destination.Interface()) } return nil } -func setReflectValue(source, destination reflect.Value) error { - - if destination.Kind() == reflect.Ptr { - if destination.IsNil() { - initializeValueIfNilPtr(destination) - } - - if source.Kind() == reflect.Ptr { - if source.IsNil() { - return nil // source is nil, destination should keep its zero value - } - source = source.Elem() - } +func tryConvert(source, destination reflect.Value) bool { + destinationType := destination.Type() - if err := tryAssign(source, destination.Elem()); err != nil { - return err - } + if source.Type().ConvertibleTo(destinationType) { + source = source.Convert(destinationType) + return assignIfAssignable(source, destination) + } - } else { - if source.Kind() == reflect.Ptr { - if source.IsNil() { - return nil // source is nil, destination should keep its zero value - } - source = source.Elem() - } + return false +} - if err := tryAssign(source, destination); err != nil { - return err - } +func setZeroValue(value reflect.Value) { + if !value.IsZero() { + value.Set(reflect.Zero(value.Type())) } - - return nil } func isPrimaryKey(field reflect.StructField, primaryKeyOverwrites []string) bool { @@ -389,3 +357,11 @@ func cloneBytes(b []byte) []byte { copy(c, b) return c } + +func concat(stringList ...string) string { + var b strings.Builder + for _, str := range stringList { + b.WriteString(str) + } + return b.String() +} diff --git a/sqlite/functions.go b/sqlite/functions.go index 2b70714b..d7142747 100644 --- a/sqlite/functions.go +++ b/sqlite/functions.go @@ -6,6 +6,15 @@ import ( "time" ) +// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition +// in the Go code and in the generated SQL. +var ( + // AND function adds AND operator between expressions. + AND = jet.AND + // OR function adds OR operator between expressions. + OR = jet.OR +) + // ROW is construct one table row from list of expressions. func ROW(expressions ...Expression) Expression { return jet.NewFunc("", expressions, nil) diff --git a/sqlite/select_statement_test.go b/sqlite/select_statement_test.go index a42fe06d..5c4f9c3c 100644 --- a/sqlite/select_statement_test.go +++ b/sqlite/select_statement_test.go @@ -148,9 +148,9 @@ func TestSelect_NOT_EXISTS(t *testing.T) { SELECT table1.col_int AS "table1.col_int" FROM db.table1 WHERE NOT (EXISTS ( - SELECT table2.col_int AS "table2.col_int" - FROM db.table2 - WHERE table1.col_int = table2.col_int - )); + SELECT table2.col_int AS "table2.col_int" + FROM db.table2 + WHERE table1.col_int = table2.col_int + )); `) } diff --git a/tests/mysql/select_test.go b/tests/mysql/select_test.go index 39f0e431..024d9417 100644 --- a/tests/mysql/select_test.go +++ b/tests/mysql/select_test.go @@ -951,8 +951,12 @@ func TestRowsScan(t *testing.T) { stmt := SELECT( Inventory.AllColumns, + Film.AllColumns, + Store.AllColumns, ).FROM( - Inventory, + Inventory. + INNER_JOIN(Film, Film.FilmID.EQ(Inventory.FilmID)). + INNER_JOIN(Store, Store.StoreID.EQ(Inventory.StoreID)), ).ORDER_BY( Inventory.InventoryID.ASC(), ) @@ -960,20 +964,43 @@ func TestRowsScan(t *testing.T) { rows, err := stmt.Rows(context.Background(), db) require.NoError(t, err) + var inventory struct { + model.Inventory + + Film model.Film + Store model.Store + } + for rows.Next() { - var inventory model.Inventory err = rows.Scan(&inventory) require.NoError(t, err) - require.NotEqual(t, inventory.InventoryID, uint32(0)) - require.NotEqual(t, inventory.FilmID, uint16(0)) - require.NotEqual(t, inventory.StoreID, uint16(0)) - require.NotEqual(t, inventory.LastUpdate, time.Time{}) + require.NotEmpty(t, inventory.InventoryID) + require.NotEmpty(t, inventory.FilmID) + require.NotEmpty(t, inventory.StoreID) + require.NotEmpty(t, inventory.LastUpdate) + + require.NotEmpty(t, inventory.Film.FilmID) + require.NotEmpty(t, inventory.Film.Title) + require.NotEmpty(t, inventory.Film.Description) + + require.NotEmpty(t, inventory.Store.StoreID) + require.NotEmpty(t, inventory.Store.AddressID) + require.NotEmpty(t, inventory.Store.ManagerStaffID) if inventory.InventoryID == 2103 { require.Equal(t, inventory.FilmID, uint16(456)) require.Equal(t, inventory.StoreID, uint8(2)) require.Equal(t, inventory.LastUpdate.Format(time.RFC3339), "2006-02-15T05:09:17Z") + + require.Equal(t, inventory.Film.FilmID, uint16(456)) + require.Equal(t, inventory.Film.Title, "INCH JET") + require.Equal(t, *inventory.Film.Description, "A Fateful Saga of a Womanizer And a Student who must Defeat a Butler in A Monastery") + require.Equal(t, *inventory.Film.ReleaseYear, int16(2006)) + + require.Equal(t, inventory.Store.StoreID, uint8(2)) + require.Equal(t, inventory.Store.ManagerStaffID, uint8(2)) + require.Equal(t, inventory.Store.AddressID, uint16(2)) } } @@ -1029,3 +1056,50 @@ func TestScanNumericToNumber(t *testing.T) { require.Equal(t, number.Float32, float32(1.234568e+09)) require.Equal(t, number.Float64, float64(1.23456789e+09)) } + +// scan into custom base types should be equivalent to the scan into base go types +func TestScanIntoCustomBaseTypes(t *testing.T) { + + type MyUint8 uint8 + type MyUint16 uint16 + type MyUint32 uint32 + type MyInt16 int16 + type MyFloat32 float32 + type MyFloat64 float64 + type MyString string + type MyTime = time.Time + + type film struct { + FilmID MyUint16 `sql:"primary_key"` + Title MyString + Description *MyString + ReleaseYear *MyInt16 + LanguageID MyUint8 + OriginalLanguageID *MyUint8 + RentalDuration MyUint8 + RentalRate MyFloat32 + Length *MyUint32 + ReplacementCost MyFloat64 + Rating *model.FilmRating + SpecialFeatures *MyString + LastUpdate MyTime + } + + stmt := SELECT( + Film.AllColumns, + ).FROM( + Film, + ).ORDER_BY( + Film.FilmID.ASC(), + ).LIMIT(3) + + var films []model.Film + err := stmt.Query(db, &films) + require.NoError(t, err) + + var myFilms []film + err = stmt.Query(db, &myFilms) + require.NoError(t, err) + + require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms)) +} diff --git a/tests/mysql/with_test.go b/tests/mysql/with_test.go index cc8dfd6f..d7d8d3d7 100644 --- a/tests/mysql/with_test.go +++ b/tests/mysql/with_test.go @@ -165,9 +165,9 @@ WITH payments_to_delete AS ( ) DELETE FROM dvds.payment WHERE payment.payment_id IN ( - SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id" - FROM payments_to_delete - ); + SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id" + FROM payments_to_delete + ); `, "''", "`")) tx, err := db.Begin() diff --git a/tests/postgres/chinook_db_test.go b/tests/postgres/chinook_db_test.go index 684abc2c..2d9821cb 100644 --- a/tests/postgres/chinook_db_test.go +++ b/tests/postgres/chinook_db_test.go @@ -38,6 +38,152 @@ ORDER BY "Album"."AlbumId" ASC; requireQueryLogged(t, stmt, 347) } +func TestComplex_AND_OR(t *testing.T) { + stmt := SELECT( + Artist.AllColumns, + Album.AllColumns, + Track.AllColumns, + ).FROM( + Artist. + LEFT_JOIN(Album, Artist.ArtistId.EQ(Album.ArtistId)). + LEFT_JOIN(Track, Track.AlbumId.EQ(Album.AlbumId)), + ).WHERE( + AND( + Artist.ArtistId.BETWEEN(Int(5), Int(11)), + Album.AlbumId.GT_EQ(Int(7)), + Track.TrackId.GT(Int(74)), + OR( + Track.GenreId.EQ(Int(2)), + Track.UnitPrice.GT(Float(1.01)), + ), + Track.TrackId.LT(Int(125)), + ), + ).ORDER_BY( + Artist.ArtistId, + Album.AlbumId, + Track.TrackId, + ) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT "Artist"."ArtistId" AS "Artist.ArtistId", + "Artist"."Name" AS "Artist.Name", + "Album"."AlbumId" AS "Album.AlbumId", + "Album"."Title" AS "Album.Title", + "Album"."ArtistId" AS "Album.ArtistId", + "Track"."TrackId" AS "Track.TrackId", + "Track"."Name" AS "Track.Name", + "Track"."AlbumId" AS "Track.AlbumId", + "Track"."MediaTypeId" AS "Track.MediaTypeId", + "Track"."GenreId" AS "Track.GenreId", + "Track"."Composer" AS "Track.Composer", + "Track"."Milliseconds" AS "Track.Milliseconds", + "Track"."Bytes" AS "Track.Bytes", + "Track"."UnitPrice" AS "Track.UnitPrice" +FROM chinook."Artist" + LEFT JOIN chinook."Album" ON ("Artist"."ArtistId" = "Album"."ArtistId") + LEFT JOIN chinook."Track" ON ("Track"."AlbumId" = "Album"."AlbumId") +WHERE ( + ("Artist"."ArtistId" BETWEEN 5 AND 11) + AND ("Album"."AlbumId" >= 7) + AND ("Track"."TrackId" > 74) + AND ( + ("Track"."GenreId" = 2) + OR ("Track"."UnitPrice" > 1.01) + ) + AND ("Track"."TrackId" < 125) + ) +ORDER BY "Artist"."ArtistId", "Album"."AlbumId", "Track"."TrackId"; +`) + + var dest []struct { + model.Artist + + Albums []struct { + model.Album + + Tracks []model.Track + } + } + + err := stmt.Query(db, &dest) + require.NoError(t, err) + + testutils.AssertJSON(t, dest, ` +[ + { + "ArtistId": 6, + "Name": "Ant�nio Carlos Jobim", + "Albums": [ + { + "AlbumId": 8, + "Title": "Warner 25 Anos", + "ArtistId": 6, + "Tracks": [ + { + "TrackId": 75, + "Name": "O Boto (B�to)", + "AlbumId": 8, + "MediaTypeId": 1, + "GenreId": 2, + "Composer": null, + "Milliseconds": 366837, + "Bytes": 12089673, + "UnitPrice": 0.99 + }, + { + "TrackId": 76, + "Name": "Canta, Canta Mais", + "AlbumId": 8, + "MediaTypeId": 1, + "GenreId": 2, + "Composer": null, + "Milliseconds": 271856, + "Bytes": 8719426, + "UnitPrice": 0.99 + } + ] + } + ] + }, + { + "ArtistId": 10, + "Name": "Billy Cobham", + "Albums": [ + { + "AlbumId": 13, + "Title": "The Best Of Billy Cobham", + "ArtistId": 10, + "Tracks": [ + { + "TrackId": 123, + "Name": "Quadrant", + "AlbumId": 13, + "MediaTypeId": 1, + "GenreId": 2, + "Composer": "Billy Cobham", + "Milliseconds": 261851, + "Bytes": 8538199, + "UnitPrice": 0.99 + }, + { + "TrackId": 124, + "Name": "Snoopy's search-Red baron", + "AlbumId": 13, + "MediaTypeId": 1, + "GenreId": 2, + "Composer": "Billy Cobham", + "Milliseconds": 456071, + "Bytes": 15075616, + "UnitPrice": 0.99 + } + ] + } + ] + } +] +`) +} + func TestJoinEverything(t *testing.T) { manager := Employee.AS("Manager") diff --git a/tests/postgres/delete_test.go b/tests/postgres/delete_test.go index 47637e1e..abbb3449 100644 --- a/tests/postgres/delete_test.go +++ b/tests/postgres/delete_test.go @@ -124,9 +124,11 @@ func TestDeleteFrom(t *testing.T) { table.Actor, ). WHERE( - table.Staff.StaffID.EQ(table.Rental.StaffID). - AND(table.Staff.StaffID.EQ(Int(2))). - AND(table.Rental.RentalID.LT(Int(10))), + AND( + table.Staff.StaffID.EQ(table.Rental.StaffID), + table.Store.StoreID.EQ(Int(2)), + table.Rental.RentalID.LT(Int(10)), + ), ). RETURNING( table.Rental.AllColumns, @@ -138,7 +140,11 @@ DELETE FROM dvds.rental USING dvds.staff INNER JOIN dvds.store ON (store.store_id = staff.staff_id), dvds.actor -WHERE ((staff.staff_id = rental.staff_id) AND (staff.staff_id = $1)) AND (rental.rental_id < $2) +WHERE ( + (staff.staff_id = rental.staff_id) + AND (store.store_id = $1) + AND (rental.rental_id < $2) + ) RETURNING rental.rental_id AS "rental.rental_id", rental.rental_date AS "rental.rental_date", rental.inventory_id AS "rental.inventory_id", diff --git a/tests/postgres/scan_test.go b/tests/postgres/scan_test.go index 61b7becc..30787090 100644 --- a/tests/postgres/scan_test.go +++ b/tests/postgres/scan_test.go @@ -786,6 +786,123 @@ func TestRowsScan(t *testing.T) { requireQueryLogged(t, stmt, 0) } +func TestScanNullColumn(t *testing.T) { + stmt := SELECT( + Address.AllColumns, + ).FROM( + Address, + ).WHERE( + Address.Address2.IS_NULL(), + ) + + var dest []model.Address + + err := stmt.Query(db, &dest) + require.NoError(t, err) + testutils.AssertJSON(t, dest, ` +[ + { + "AddressID": 1, + "Address": "47 MySakila Drive", + "Address2": null, + "District": "Alberta", + "CityID": 300, + "PostalCode": "", + "Phone": "", + "LastUpdate": "2006-02-15T09:45:30Z" + }, + { + "AddressID": 2, + "Address": "28 MySQL Boulevard", + "Address2": null, + "District": "QLD", + "CityID": 576, + "PostalCode": "", + "Phone": "", + "LastUpdate": "2006-02-15T09:45:30Z" + }, + { + "AddressID": 3, + "Address": "23 Workhaven Lane", + "Address2": null, + "District": "Alberta", + "CityID": 300, + "PostalCode": "", + "Phone": "14033335568", + "LastUpdate": "2006-02-15T09:45:30Z" + }, + { + "AddressID": 4, + "Address": "1411 Lillydale Drive", + "Address2": null, + "District": "QLD", + "CityID": 576, + "PostalCode": "", + "Phone": "6172235589", + "LastUpdate": "2006-02-15T09:45:30Z" + } +] +`) +} + +func TestRowsScanSetZeroValue(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).WHERE( + Rental.RentalID.IN(Int(16049), Int(15966)), + ).ORDER_BY( + Rental.RentalID.DESC(), + ) + + rows, err := stmt.Rows(context.Background(), db) + require.NoError(t, err) + + defer rows.Close() + + // destination object is used as destination for all rows scan. + // this tests checks that ReturnedDate is set to nil with the second call + // check qrm.setZeroValue + var dest model.Rental + + for rows.Next() { + err := rows.Scan(&dest) + require.NoError(t, err) + + if dest.RentalID == 16049 { + testutils.AssertJSON(t, dest, ` +{ + "RentalID": 16049, + "RentalDate": "2005-08-23T22:50:12Z", + "InventoryID": 2666, + "CustomerID": 393, + "ReturnDate": "2005-08-30T01:01:12Z", + "StaffID": 2, + "LastUpdate": "2006-02-16T02:30:53Z" +} +`) + } else { + testutils.AssertJSON(t, dest, ` +{ + "RentalID": 15966, + "RentalDate": "2006-02-14T15:16:03Z", + "InventoryID": 4472, + "CustomerID": 374, + "ReturnDate": null, + "StaffID": 1, + "LastUpdate": "2006-02-16T02:30:53Z" +} +`) + } + } + + err = rows.Close() + require.NoError(t, err) + err = rows.Err() + require.NoError(t, err) +} + func TestScanNumericToFloat(t *testing.T) { type Number struct { Float32 float32 @@ -826,6 +943,54 @@ func TestScanNumericToIntegerError(t *testing.T) { } +func TestScanIntoCustomBaseTypes(t *testing.T) { + + type MyUint8 uint8 + type MyUint16 uint16 + type MyUint32 uint32 + type MyInt16 int16 + type MyFloat32 float32 + type MyFloat64 float64 + type MyString string + type MyTime = time.Time + + type film struct { + FilmID MyUint16 `sql:"primary_key"` + Title MyString + Description *MyString + ReleaseYear *MyInt16 + LanguageID MyUint8 + RentalDuration MyUint8 + RentalRate MyFloat32 + Length *MyUint32 + ReplacementCost MyFloat64 + Rating *model.MpaaRating + LastUpdate MyTime + SpecialFeatures *MyString + Fulltext MyString + } + + stmt := SELECT( + Film.AllColumns, + ).FROM( + Film, + ).ORDER_BY( + Film.FilmID.ASC(), + ).LIMIT(3) + + var films []model.Film + + err := stmt.Query(db, &films) + require.NoError(t, err) + + var myFilms []film + + err = stmt.Query(db, &myFilms) + require.NoError(t, err) + + require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms)) +} + // QueryContext panic when the scanned value is nil and the destination is a slice of primitive // https://github.com/go-jet/jet/issues/91 func TestScanToPrimitiveElementsSlice(t *testing.T) { diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index b3d3e63d..304acb8c 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -395,8 +395,15 @@ func TestExecution1(t *testing.T) { Customer.CustomerID, Customer.LastName, ). - WHERE(City.City.EQ(String("London")).OR(City.City.EQ(String("York")))). - ORDER_BY(City.CityID, Address.AddressID, Customer.CustomerID) + WHERE( + OR( + City.City.EQ(String("London")), + City.City.EQ(String("York")), + ), + ). + ORDER_BY( + City.CityID, Address.AddressID, Customer.CustomerID, + ) testutils.AssertDebugStatementSql(t, stmt, ` SELECT city.city_id AS "city.city_id", @@ -408,7 +415,10 @@ SELECT city.city_id AS "city.city_id", FROM dvds.city INNER JOIN dvds.address ON (address.city_id = city.city_id) INNER JOIN dvds.customer ON (customer.address_id = address.address_id) -WHERE (city.city = 'London') OR (city.city = 'York') +WHERE ( + (city.city = 'London') + OR (city.city = 'York') + ) ORDER BY city.city_id, address.address_id, customer.customer_id; `, "London", "York") @@ -1073,9 +1083,9 @@ SELECT film.film_id AS "film.film_id", film.fulltext AS "film.fulltext" FROM dvds.film WHERE film.rental_rate = ( - SELECT MAX(film.rental_rate) - FROM dvds.film - ) + SELECT MAX(film.rental_rate) + FROM dvds.film + ) ORDER BY film.film_id ASC; ` @@ -2521,6 +2531,79 @@ func TestRecursionScanNx1(t *testing.T) { }) } +type StoreInfo struct { + model.Store + + Staffs ManagerInfo +} + +type ManagerInfo struct { + model.Staff + Store *StoreInfo +} + +func TestRecursionScan1x1(t *testing.T) { + + stmt := SELECT( + Store.AllColumns, + Staff.AllColumns, + ).FROM( + Store. + INNER_JOIN(Staff, Staff.StaffID.EQ(Store.ManagerStaffID)), + ).ORDER_BY( + Store.StoreID, + ) + + var dest []StoreInfo + + err := stmt.Query(db, &dest) + require.NoError(t, err) + testutils.AssertJSON(t, dest, ` +[ + { + "StoreID": 1, + "ManagerStaffID": 1, + "AddressID": 1, + "LastUpdate": "2006-02-15T09:57:12Z", + "Staffs": { + "StaffID": 1, + "FirstName": "Mike", + "LastName": "Hillyer", + "AddressID": 3, + "Email": "Mike.Hillyer@sakilastaff.com", + "StoreID": 1, + "Active": true, + "Username": "Mike", + "Password": "8cb2237d0679ca88db6464eac60da96345513964", + "LastUpdate": "2006-05-16T16:13:11.79328Z", + "Picture": "iVBORw0KWgo=", + "Store": null + } + }, + { + "StoreID": 2, + "ManagerStaffID": 2, + "AddressID": 2, + "LastUpdate": "2006-02-15T09:57:12Z", + "Staffs": { + "StaffID": 2, + "FirstName": "Jon", + "LastName": "Stephens", + "AddressID": 4, + "Email": "Jon.Stephens@sakilastaff.com", + "StoreID": 2, + "Active": true, + "Username": "Jon", + "Password": "8cb2237d0679ca88db6464eac60da96345513964", + "LastUpdate": "2006-05-16T16:13:11.79328Z", + "Picture": null, + "Store": null + } + } +] +`) +} + // In parameterized statements integer literals, like Int(num), are replaced with a placeholders. For some expressions, // postgres interpreter will not have enough information to deduce the type. If this is the case postgres returns an error. // Int8, Int16, .... functions will add automatic type cast over placeholder, so type deduction is always possible. diff --git a/tests/postgres/with_test.go b/tests/postgres/with_test.go index 0b47e9aa..c78ca8a4 100644 --- a/tests/postgres/with_test.go +++ b/tests/postgres/with_test.go @@ -2,7 +2,6 @@ package postgres import ( "context" - "fmt" "github.com/go-jet/jet/v2/internal/testutils" . "github.com/go-jet/jet/v2/postgres" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/northwind/model" @@ -74,9 +73,9 @@ WITH regional_sales AS ( SELECT regional_sales."orders.ship_region" AS "orders.ship_region" FROM regional_sales WHERE regional_sales.total_sales > (( - SELECT SUM(regional_sales.total_sales) - FROM regional_sales - ) / 50) + SELECT SUM(regional_sales.total_sales) + FROM regional_sales + ) / 50) ) SELECT orders.ship_region AS "orders.ship_region", order_details.product_id AS "order_details.product_id", @@ -85,9 +84,9 @@ SELECT orders.ship_region AS "orders.ship_region", FROM northwind.orders INNER JOIN northwind.order_details ON (orders.order_id = order_details.order_id) WHERE orders.ship_region IN ( - SELECT top_region."orders.ship_region" AS "orders.ship_region" - FROM top_region - ) + SELECT top_region."orders.ship_region" AS "orders.ship_region" + FROM top_region + ) GROUP BY orders.ship_region, order_details.product_id ORDER BY SUM(order_details.quantity) DESC; `) @@ -151,18 +150,18 @@ func TestWithStatementDeleteAndInsert(t *testing.T) { WITH remove_discontinued_orders AS ( DELETE FROM northwind.order_details WHERE order_details.product_id IN ( - SELECT products.product_id AS "products.product_id" - FROM northwind.products - WHERE products.discontinued = $1 - ) + SELECT products.product_id AS "products.product_id" + FROM northwind.products + WHERE products.discontinued = $1 + ) RETURNING order_details.product_id AS "order_details.product_id" ),update_discontinued_price AS ( UPDATE northwind.products SET unit_price = $2 WHERE products.product_id IN ( - SELECT remove_discontinued_orders."order_details.product_id" AS "order_details.product_id" - FROM remove_discontinued_orders - ) + SELECT remove_discontinued_orders."order_details.product_id" AS "order_details.product_id" + FROM remove_discontinued_orders + ) RETURNING products.product_id AS "products.product_id", products.product_name AS "products.product_name", products.supplier_id AS "products.supplier_id", @@ -864,5 +863,4 @@ WHERE orders1."orders.order_id" < $1; err := stmt.Query(db, &dest) require.NoError(t, err) require.Len(t, dest, 72) - fmt.Println(len(dest)) } diff --git a/tests/sqlite/with_test.go b/tests/sqlite/with_test.go index 92cd331e..402df2f0 100644 --- a/tests/sqlite/with_test.go +++ b/tests/sqlite/with_test.go @@ -154,9 +154,9 @@ WITH payments_to_update AS ( UPDATE payment SET amount = 0 WHERE payment.payment_id IN ( - SELECT payments_to_update.''payment.payment_id'' AS "payment.payment_id" - FROM payments_to_update - ); + SELECT payments_to_update.''payment.payment_id'' AS "payment.payment_id" + FROM payments_to_update + ); `, "''", "`", -1)) tx := beginDBTx(t) @@ -206,9 +206,9 @@ WITH payments_to_delete AS ( ) DELETE FROM payment WHERE payment.payment_id IN ( - SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id" - FROM payments_to_delete - ); + SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id" + FROM payments_to_delete + ); `, "''", "`", -1)) tx := beginDBTx(t)