diff --git a/go.mod b/go.mod index f15e634..61abf09 100644 --- a/go.mod +++ b/go.mod @@ -18,4 +18,5 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/stretchr/testify v1.7.0 github.com/volatiletech/null/v8 v8.1.2 + gopkg.in/guregu/null.v4 v4.0.0 ) diff --git a/go.sum b/go.sum index cb1e8e5..7b7c8cb 100644 --- a/go.sum +++ b/go.sum @@ -199,6 +199,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg= +gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/qrm/scan_context.go b/qrm/scan_context.go index fa99b5a..9b1d3ee 100644 --- a/qrm/scan_context.go +++ b/qrm/scan_context.go @@ -187,28 +187,24 @@ func (s *ScanContext) getGroupKeyInfo( field := structType.Field(i) fieldType := indirectType(field.Type) - if !isSimpleModelType(fieldType) { - if fieldType.Kind() != reflect.Struct { + if isPrimaryKey(field, primaryKeyOverwrites) { + newTypeName, fieldName := getTypeAndFieldName(typeName, field) + + index := s.typeToColumnIndex(newTypeName, fieldName) + + if index < 0 { continue } + ret.indexes = append(ret.indexes, index) + + } else if fieldType.Kind() == reflect.Struct { + subType := s.getGroupKeyInfo(fieldType, &field, typeVisited) if len(subType.indexes) != 0 || len(subType.subTypes) != 0 { ret.subTypes = append(ret.subTypes, subType) } - } else { - if isPrimaryKey(field, primaryKeyOverwrites) { - newTypeName, fieldName := getTypeAndFieldName(typeName, field) - - index := s.typeToColumnIndex(newTypeName, fieldName) - - if index < 0 { - continue - } - - ret.indexes = append(ret.indexes, index) - } } } diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index 5f34daa..96311a0 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -2,10 +2,13 @@ package postgres import ( "context" - "github.com/go-jet/jet/v2/qrm" + "database/sql" "testing" "time" + "github.com/go-jet/jet/v2/qrm" + "gopkg.in/guregu/null.v4" + "github.com/stretchr/testify/require" "github.com/go-jet/jet/v2/internal/testutils" @@ -658,6 +661,214 @@ ORDER BY city.city_id, address.address_id, customer.customer_id; `) } +// Test join with custom primary keys (sql.NullInt64) +func TestExecutionCustomPKTypes1(t *testing.T) { + + var dest []struct { + CityID sql.NullInt64 `sql:"primary_key" alias:"city.city_id"` + CityName string `alias:"city.city"` + + Customers []struct { + CustomerID sql.NullInt64 `sql:"primary_key" alias:"customer_id"` + LastName *string `alias:"last_name"` + + Address struct { + AddressID sql.NullInt64 `sql:"primary_key" alias:"AddressId"` + AddressLine string `alias:"address.address"` + } `alias:"address.*"` + } `alias:"customer"` + } + + stmt := City. + INNER_JOIN(Address, Address.CityID.EQ(City.CityID)). + INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)). + SELECT( + City.CityID, + City.City, + Customer.CustomerID, + Customer.LastName, + Address.AddressID, + Address.Address, + ). + WHERE(City.City.EQ(String("London")).OR(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", + city.city AS "city.city", + customer.customer_id AS "customer.customer_id", + customer.last_name AS "customer.last_name", + address.address_id AS "address.address_id", + address.address AS "address.address" +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'::text) OR (city.city = 'York'::text) +ORDER BY city.city_id, address.address_id, customer.customer_id; +`, "London", "York") + + err := stmt.Query(db, &dest) + + require.NoError(t, err) + require.Equal(t, len(dest), 2) + testutils.AssertJSON(t, dest, ` +[ + { + "CityID": { + "Int64": 312, + "Valid": true + }, + "CityName": "London", + "Customers": [ + { + "CustomerID": { + "Int64": 252, + "Valid": true + }, + "LastName": "Hoffman", + "Address": { + "AddressID": { + "Int64": 256, + "Valid": true + }, + "AddressLine": "1497 Yuzhou Drive" + } + }, + { + "CustomerID": { + "Int64": 512, + "Valid": true + }, + "LastName": "Vines", + "Address": { + "AddressID": { + "Int64": 517, + "Valid": true + }, + "AddressLine": "548 Uruapan Street" + } + } + ] + }, + { + "CityID": { + "Int64": 589, + "Valid": true + }, + "CityName": "York", + "Customers": [ + { + "CustomerID": { + "Int64": 497, + "Valid": true + }, + "LastName": "Sledge", + "Address": { + "AddressID": { + "Int64": 502, + "Valid": true + }, + "AddressLine": "1515 Korla Way" + } + } + ] + } +] +`) +} + +// Test join with custom primary keys (null.Int) +func TestExecutionCustomPKTypes2(t *testing.T) { + + var dest []struct { + CityID null.Int `sql:"primary_key" alias:"city.city_id"` + CityName string `alias:"city.city"` + + Customers []struct { + CustomerID null.Int `sql:"primary_key" alias:"customer_id"` + LastName *string `alias:"last_name"` + + Address struct { + AddressID null.Int `sql:"primary_key" alias:"AddressId"` + AddressLine string `alias:"address.address"` + } `alias:"address.*"` + } `alias:"customer"` + } + + stmt := City. + INNER_JOIN(Address, Address.CityID.EQ(City.CityID)). + INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)). + SELECT( + City.CityID, + City.City, + Customer.CustomerID, + Customer.LastName, + Address.AddressID, + Address.Address, + ). + WHERE(City.City.EQ(String("London")).OR(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", + city.city AS "city.city", + customer.customer_id AS "customer.customer_id", + customer.last_name AS "customer.last_name", + address.address_id AS "address.address_id", + address.address AS "address.address" +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'::text) OR (city.city = 'York'::text) +ORDER BY city.city_id, address.address_id, customer.customer_id; +`, "London", "York") + + err := stmt.Query(db, &dest) + + require.NoError(t, err) + require.Equal(t, len(dest), 2) + testutils.AssertJSON(t, dest, ` +[ + { + "CityID": 312, + "CityName": "London", + "Customers": [ + { + "CustomerID": 252, + "LastName": "Hoffman", + "Address": { + "AddressID": 256, + "AddressLine": "1497 Yuzhou Drive" + } + }, + { + "CustomerID": 512, + "LastName": "Vines", + "Address": { + "AddressID": 517, + "AddressLine": "548 Uruapan Street" + } + } + ] + }, + { + "CityID": 589, + "CityName": "York", + "Customers": [ + { + "CustomerID": 497, + "LastName": "Sledge", + "Address": { + "AddressID": 502, + "AddressLine": "1515 Korla Way" + } + } + ] + } +] +`) +} + func TestJoinQuerySliceWithPtrs(t *testing.T) { type FilmsPerLanguage struct { Language model.Language