/
join_condition.go
245 lines (208 loc) · 6.25 KB
/
join_condition.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package condition
import (
"fmt"
"github.com/elliotchance/pie/v2"
"gorm.io/gorm/clause"
"github.com/FrancoLiberali/cql/model"
)
// Condition that joins T with any other model
type JoinCondition[T model.Model] interface {
Condition[T]
// Preload activates the preloading of the joined model.
Preload() JoinCondition[T]
// Returns true if this condition or any nested condition makes a preload
makesPreload() bool
// Returns true if the condition of nay nested condition applies a filter (has where conditions)
makesFilter() bool
}
// Condition that joins T with any other model
func NewJoinCondition[T1, T2 model.Model](
conditions []Condition[T2],
relationField string,
t1Field string,
t1PreloadCondition Condition[T1],
t2Field string,
t2PreloadCondition Condition[T2],
) JoinCondition[T1] {
return joinConditionImpl[T1, T2]{
Conditions: conditions,
RelationField: relationField,
T1Field: t1Field,
T2Field: t2Field,
T1PreloadCondition: t1PreloadCondition,
T2PreloadCondition: t2PreloadCondition,
T2Preload: false,
}
}
// Implementation of join condition
type joinConditionImpl[T1, T2 model.Model] struct {
T1Field string
T2Field string
RelationField string
Conditions []Condition[T2]
T1PreloadCondition Condition[T1] // Condition to preload T1 in case T2 any nested object is preloaded by user
T2PreloadCondition Condition[T2] // Condition to preload T2
T2Preload bool // Indicates if T2PreloadCondition must be applied
}
func (condition joinConditionImpl[T1, T2]) Preload() JoinCondition[T1] {
condition.T2Preload = true
return condition
}
//nolint:unused // is used
func (condition joinConditionImpl[T1, T2]) interfaceVerificationMethod(_ T1) {
// This method is necessary to get the compiler to verify
// that an object is of type Condition[T]
}
// Returns true if this condition or any nested condition makes a preload
//
//nolint:unused // is used
func (condition joinConditionImpl[T1, T2]) makesPreload() bool {
_, joinConditions := divideConditionsByType(condition.Conditions)
return condition.T2Preload || pie.Any(joinConditions, func(cond JoinCondition[T2]) bool {
return cond.makesPreload()
})
}
// Returns true if the condition of nay nested condition applies a filter (has where conditions)
//
//nolint:unused // is used
func (condition joinConditionImpl[T1, T2]) makesFilter() bool {
whereConditions, joinConditions := divideConditionsByType(condition.Conditions)
return len(whereConditions) != 0 || pie.Any(joinConditions, func(cond JoinCondition[T2]) bool {
return cond.makesFilter()
})
}
// Applies a join between the tables of T1 and T2
// previousTableName is the name of the table of T1
// It also applies the nested conditions
//
//nolint:unused // is used
func (condition joinConditionImpl[T1, T2]) applyTo(query *GormQuery, t1Table Table) error {
whereConditions, joinConditions := divideConditionsByType(condition.Conditions)
// get the sql to do the join with T2
t2Table, err := t1Table.DeliverTable(query, *new(T2), condition.RelationField)
if err != nil {
return err
}
err = condition.addJoin(query, t1Table, t2Table, whereConditions)
if err != nil {
return err
}
// apply T1 preload condition
// if this condition has a T2 preload condition
// or any nested join condition has a preload condition
// and this is not first level (T1 is the type of the repository)
// because T1 is always loaded in that case
if condition.makesPreload() && !t1Table.IsInitial() {
err = condition.T1PreloadCondition.applyTo(query, t1Table)
if err != nil {
return err
}
}
// apply T2 preload condition
if condition.T2Preload {
err = condition.T2PreloadCondition.applyTo(query, t2Table)
if err != nil {
return err
}
}
// apply nested joins
for _, joinCondition := range joinConditions {
err = joinCondition.applyTo(query, t2Table)
if err != nil {
return err
}
}
return nil
}
// Adds the join between t1Table and t2Table to the query and the whereConditions in the "ON"
//
//nolint:unused // is used
func (condition joinConditionImpl[T1, T2]) addJoin(query *GormQuery, t1Table, t2Table Table, whereConditions []WhereCondition[T2]) error {
joinQuery := condition.getSQLJoin(
query,
t1Table,
t2Table,
)
query.AddConcernedModel(
*new(T2),
t2Table,
)
// apply WhereConditions to the join in the "on" clause
connectionCondition := And(whereConditions...)
onQuery, onValues, err := connectionCondition.getSQL(query, t2Table)
if err != nil {
return err
}
if onQuery != "" {
joinQuery += clause.AndWithSpace + onQuery
}
if !connectionCondition.affectsDeletedAt() {
joinQuery += fmt.Sprintf(
clause.AndWithSpace+"%s.deleted_at IS NULL",
t2Table.Alias,
)
}
// add the join to the query
query.Joins(
joinQuery,
len(whereConditions) == 0 && condition.makesPreload(),
onValues...,
)
return nil
}
// Returns the SQL string to do a join between T1 and T2
// taking into account that the ID attribute necessary to do it
// can be either in T1's or T2's table.
//
//nolint:unused // is used
func (condition joinConditionImpl[T1, T2]) getSQLJoin(
query *GormQuery,
t1Table Table,
t2Table Table,
) string {
return fmt.Sprintf(
`%s %s ON %s
`,
t2Table.Name,
t2Table.Alias,
getSQLJoin(query, t1Table, condition.T1Field, t2Table, condition.T2Field),
)
}
// Returns the SQL string to verify a join between T1 and T2
//
//nolint:unused // is used
func getSQLJoin(
query *GormQuery,
t1Table Table,
t1Field string,
t2Table Table,
t2Field string,
) string {
return fmt.Sprintf(
"%s.%s = %s.%s",
t2Table.Alias,
query.ColumnName(t2Table, t2Field),
t1Table.Alias,
query.ColumnName(t1Table, t1Field),
)
}
// Divides a list of conditions by its type: WhereConditions and JoinConditions
//
//nolint:unused // is used
func divideConditionsByType[T model.Model](
conditions []Condition[T],
) (whereConditions []WhereCondition[T], joinConditions []JoinCondition[T]) {
for _, condition := range conditions {
possibleWhereCondition, ok := condition.(WhereCondition[T])
if ok {
whereConditions = append(whereConditions, possibleWhereCondition)
continue
}
possibleJoinCondition, ok := condition.(JoinCondition[T])
if ok {
joinConditions = append(joinConditions, possibleJoinCondition)
continue
}
}
return
}