From 56d25cde7db14bd2f8628e8f074ea00cafcf2887 Mon Sep 17 00:00:00 2001 From: Fs02 Date: Sun, 26 Jun 2022 13:16:28 +0900 Subject: [PATCH 1/3] Add join assoc query builder --- query.go | 27 +++++++++++++++++++++++++++ query_test.go | 23 +++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/query.go b/query.go index 726bc40e..4de73fe2 100644 --- a/query.go +++ b/query.go @@ -182,6 +182,18 @@ func (q Query) Joinf(expr string, args ...interface{}) Query { return q } +// JoinAssoc current table with other table based on association field. +func (q Query) JoinAssoc(assoc string, filter ...FilterQuery) Query { + return q.JoinAssocWith("JOIN", assoc, filter...) +} + +// JoinAssocWith current table with other table based on association field. +func (q Query) JoinAssocWith(mode string, assoc string, filter ...FilterQuery) Query { + NewJoinAssocWith(mode, assoc, filter...).Build(&q) + + return q +} + // Where query. func (q Query) Where(filters ...FilterQuery) Query { q.WhereQuery = q.WhereQuery.And(filters...) @@ -467,6 +479,21 @@ func JoinWith(mode string, table string, from string, to string, filter ...Filte return query } +// JoinAssoc create a query with chainable syntax, using join as the starting point. +func JoinAssoc(assoc string, filter ...FilterQuery) Query { + return JoinAssocWith("JOIN", assoc, filter...) +} + +// JoinAssocWith create a query with chainable syntax, using join as the starting point. +func JoinAssocWith(mode string, assoc string, filter ...FilterQuery) Query { + query := newQuery() + query.JoinQuery = []JoinQuery{ + NewJoinAssocWith(mode, assoc, filter...), + } + query.AddPopulator(&query.JoinQuery[0]) + return query +} + // Joinf create a query with chainable syntax, using join as the starting point. func Joinf(expr string, args ...interface{}) Query { query := newQuery() diff --git a/query_test.go b/query_test.go index af0d8376..3164780e 100644 --- a/query_test.go +++ b/query_test.go @@ -377,6 +377,29 @@ func TestQuery_Joinf(t *testing.T) { shallowAssertQuery(t, result, rel.Joinf("JOIN transactions ON transacations.id=?", 1).From("users")) } +func TestQuery_JoinAssoc(t *testing.T) { + result := rel.Query{ + Table: "users", + JoinQuery: []rel.JoinQuery{ + { + Mode: "JOIN", + Table: "transactions", + To: "transactions.user_id", + From: "users.id", + Assoc: "transactions", + }, + }, + CascadeQuery: true, + } + + shallowAssertQuery(t, result, + rel.Build("", rel.From("users").JoinAssoc("transactions")). + Populate(rel.NewDocument(&rel.User{}, false).Meta())) + shallowAssertQuery(t, result, + rel.Build("users", rel.JoinAssoc("transactions")). + Populate(rel.NewDocument(&rel.User{}, false).Meta())) +} + func TestQuery_Where(t *testing.T) { tests := []struct { Case string From 0cab30692c9b2de7e765a6234f2ba1f204fb9500 Mon Sep 17 00:00:00 2001 From: Fs02 Date: Sat, 2 Jul 2022 23:36:07 +0900 Subject: [PATCH 2/3] support loading assoc using join --- join_query.go | 31 ++++++++++++++++++++++++++++--- join_query_test.go | 34 +++++++++++++++++++--------------- query.go | 4 ++-- query_test.go | 2 +- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/join_query.go b/join_query.go index 95af9754..49712a1e 100644 --- a/join_query.go +++ b/join_query.go @@ -20,15 +20,40 @@ func (jq JoinQuery) Build(query *Query) { } } -func (jq *JoinQuery) Populate(docMeta DocumentMeta) { +func (jq *JoinQuery) Populate(query *Query, docMeta DocumentMeta) { var ( assocMeta = docMeta.Association(jq.Assoc) assocDocMeta = assocMeta.DocumentMeta() ) - jq.Table = assocDocMeta.Table() - jq.To = jq.Table + "." + assocMeta.ForeignField() + jq.Table = assocDocMeta.Table() + " as " + jq.Assoc + jq.To = jq.Assoc + "." + assocMeta.ForeignField() jq.From = docMeta.Table() + "." + assocMeta.ReferenceField() + + // load association if defined and supported + if assocMeta.Type() == HasOne || assocMeta.Type() == BelongsTo { + var ( + load = false + selectField = jq.Assoc + ".*" + ) + + for i := range query.SelectQuery.Fields { + if load && i > 0 { + query.SelectQuery.Fields[i-1] = query.SelectQuery.Fields[i] + } + if query.SelectQuery.Fields[i] == selectField { + load = true + } + } + + if load { + fields := make([]string, len(assocDocMeta.Fields())) + for i, f := range assocDocMeta.Fields() { + fields[i] = jq.Assoc + "." + f + } + query.SelectQuery.Fields = append(query.SelectQuery.Fields[:(len(query.SelectQuery.Fields)-1)], fields...) + } + } } // NewJoinWith query with custom join mode, table, field and additional filters with AND condition. diff --git a/join_query_test.go b/join_query_test.go index b87ab6c9..5ec335f0 100644 --- a/join_query_test.go +++ b/join_query_test.go @@ -73,34 +73,38 @@ func TestJoin(t *testing.T) { func TestJoinAssoc_hasOne(t *testing.T) { var ( - populated = rel.Build("", rel.NewJoinAssoc("address")). - Populate(rel.NewDocument(&rel.User{}, false).Meta()). - JoinQuery[0] + populated = rel.Build("", rel.Select("*", "address.*"), rel.NewJoinAssoc("address")). + Populate(rel.NewDocument(&rel.User{}, false).Meta()) ) assert.Equal(t, rel.JoinQuery{ Mode: "JOIN", - Table: "user_addresses", - To: "user_addresses.user_id", + Table: "user_addresses as address", + To: "address.user_id", From: "users.id", Assoc: "address", - }, populated) + }, populated.JoinQuery[0]) + assert.Equal(t, []string{ + "*", "address.id", "address.user_id", "address.street", "address.notes", "address.deleted_at", + }, populated.SelectQuery.Fields) } func TestJoinPopulate_hasOnePtr(t *testing.T) { var ( - populated = rel.Build("", rel.NewJoinAssoc("work_address")). - Populate(rel.NewDocument(&rel.User{}, false).Meta()). - JoinQuery[0] + populated = rel.Build("", rel.Select("id", "work_address.*", "name"), rel.NewJoinAssoc("work_address")). + Populate(rel.NewDocument(&rel.User{}, false).Meta()) ) assert.Equal(t, rel.JoinQuery{ Mode: "JOIN", - Table: "user_addresses", - To: "user_addresses.user_id", + Table: "user_addresses as work_address", + To: "work_address.user_id", From: "users.id", Assoc: "work_address", - }, populated) + }, populated.JoinQuery[0]) + assert.Equal(t, []string{ + "id", "name", "work_address.id", "work_address.user_id", "work_address.street", "work_address.notes", "work_address.deleted_at", + }, populated.SelectQuery.Fields) } func TestJoinPopulate_hasMany(t *testing.T) { @@ -112,7 +116,7 @@ func TestJoinPopulate_hasMany(t *testing.T) { assert.Equal(t, rel.JoinQuery{ Mode: "JOIN", - Table: "transactions", + Table: "transactions as transactions", To: "transactions.user_id", From: "users.id", Assoc: "transactions", @@ -128,8 +132,8 @@ func TestJoinAssoc_belongsTo(t *testing.T) { assert.Equal(t, rel.JoinQuery{ Mode: "JOIN", - Table: "users", - To: "users.id", + Table: "users as user", + To: "user.id", From: "user_addresses.user_id", Assoc: "user", }, populated) diff --git a/query.go b/query.go index 4de73fe2..23a40b73 100644 --- a/query.go +++ b/query.go @@ -11,7 +11,7 @@ type Querier interface { } type QueryPopulator interface { - Populate(DocumentMeta) + Populate(*Query, DocumentMeta) } // Build for given table using given queriers. @@ -130,7 +130,7 @@ func (q Query) Build(query *Query) { func (q Query) Populate(documentMeta DocumentMeta) Query { for i := range q.queryPopulators { - q.queryPopulators[i].Populate(documentMeta) + q.queryPopulators[i].Populate(&q, documentMeta) } return q diff --git a/query_test.go b/query_test.go index 3164780e..d95d3e76 100644 --- a/query_test.go +++ b/query_test.go @@ -383,7 +383,7 @@ func TestQuery_JoinAssoc(t *testing.T) { JoinQuery: []rel.JoinQuery{ { Mode: "JOIN", - Table: "transactions", + Table: "transactions as transactions", To: "transactions.user_id", From: "users.id", Assoc: "transactions", From 075ffcb0fd10d20c29b59bf07aba2eb72976a4e9 Mon Sep 17 00:00:00 2001 From: Fs02 Date: Sun, 3 Jul 2022 00:09:46 +0900 Subject: [PATCH 3/3] fix select alias --- join_query.go | 2 +- join_query_test.go | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/join_query.go b/join_query.go index 49712a1e..61c6e43b 100644 --- a/join_query.go +++ b/join_query.go @@ -49,7 +49,7 @@ func (jq *JoinQuery) Populate(query *Query, docMeta DocumentMeta) { if load { fields := make([]string, len(assocDocMeta.Fields())) for i, f := range assocDocMeta.Fields() { - fields[i] = jq.Assoc + "." + f + fields[i] = jq.Assoc + "." + f + " as " + jq.Assoc + "." + f } query.SelectQuery.Fields = append(query.SelectQuery.Fields[:(len(query.SelectQuery.Fields)-1)], fields...) } diff --git a/join_query_test.go b/join_query_test.go index 5ec335f0..7a6e748c 100644 --- a/join_query_test.go +++ b/join_query_test.go @@ -85,7 +85,12 @@ func TestJoinAssoc_hasOne(t *testing.T) { Assoc: "address", }, populated.JoinQuery[0]) assert.Equal(t, []string{ - "*", "address.id", "address.user_id", "address.street", "address.notes", "address.deleted_at", + "*", + "address.id as address.id", + "address.user_id as address.user_id", + "address.street as address.street", + "address.notes as address.notes", + "address.deleted_at as address.deleted_at", }, populated.SelectQuery.Fields) } @@ -103,7 +108,13 @@ func TestJoinPopulate_hasOnePtr(t *testing.T) { Assoc: "work_address", }, populated.JoinQuery[0]) assert.Equal(t, []string{ - "id", "name", "work_address.id", "work_address.user_id", "work_address.street", "work_address.notes", "work_address.deleted_at", + "id", + "name", + "work_address.id as work_address.id", + "work_address.user_id as work_address.user_id", + "work_address.street as work_address.street", + "work_address.notes as work_address.notes", + "work_address.deleted_at as work_address.deleted_at", }, populated.SelectQuery.Fields) }