@@ -175,3 +175,339 @@ func Test_Model_FieldsPrefix(t *testing.T) {
175175 t .Assert (r [0 ]["nickname" ], "name_1" )
176176 })
177177}
178+
179+ // Test_Model_Join_FiveTables tests complex join with 5+ tables
180+ func Test_Model_Join_FiveTables (t * testing.T ) {
181+ var (
182+ table1 = gtime .TimestampNanoStr () + "_table1"
183+ table2 = gtime .TimestampNanoStr () + "_table2"
184+ table3 = gtime .TimestampNanoStr () + "_table3"
185+ table4 = gtime .TimestampNanoStr () + "_table4"
186+ table5 = gtime .TimestampNanoStr () + "_table5"
187+ )
188+ createInitTable (table1 )
189+ defer dropTable (table1 )
190+ createInitTable (table2 )
191+ defer dropTable (table2 )
192+ createInitTable (table3 )
193+ defer dropTable (table3 )
194+ createInitTable (table4 )
195+ defer dropTable (table4 )
196+ createInitTable (table5 )
197+ defer dropTable (table5 )
198+
199+ gtest .C (t , func (t * gtest.T ) {
200+ r , err := db .Model (table1 ).As ("t1" ).
201+ FieldsPrefix ("t1" , "id" , "nickname" ).
202+ FieldsPrefix ("t2" , "passport" ).
203+ InnerJoin (table2 + " AS t2" , "t1.id = t2.id" ).
204+ InnerJoin (table3 + " AS t3" , "t2.id = t3.id" ).
205+ InnerJoin (table4 + " AS t4" , "t3.id = t4.id" ).
206+ InnerJoin (table5 + " AS t5" , "t4.id = t5.id" ).
207+ Where ("t1.id IN(?)" , g.Slice {1 , 2 , 3 }).
208+ Order ("t1.id asc" ).
209+ All ()
210+ t .AssertNil (err )
211+
212+ t .Assert (len (r ), 3 )
213+ t .Assert (r [0 ]["id" ], "1" )
214+ t .Assert (r [0 ]["nickname" ], "name_1" )
215+ t .Assert (r [0 ]["passport" ], "user_1" )
216+ t .Assert (r [2 ]["id" ], "3" )
217+ })
218+
219+ gtest .C (t , func (t * gtest.T ) {
220+ // 6 tables with mixed join types
221+ table6 := gtime .TimestampNanoStr () + "_table6"
222+ createInitTable (table6 )
223+ defer dropTable (table6 )
224+
225+ r , err := db .Model (table1 ).As ("t1" ).
226+ Fields ("t1.id" ).
227+ InnerJoin (table2 + " AS t2" , "t1.id = t2.id" ).
228+ LeftJoin (table3 + " AS t3" , "t2.id = t3.id" ).
229+ InnerJoin (table4 + " AS t4" , "t3.id = t4.id" ).
230+ RightJoin (table5 + " AS t5" , "t4.id = t5.id" ).
231+ LeftJoin (table6 + " AS t6" , "t5.id = t6.id" ).
232+ Where ("t1.id" , 5 ).
233+ One ()
234+ t .AssertNil (err )
235+
236+ t .Assert (r ["id" ], "5" )
237+ })
238+ }
239+
240+ // Test_Model_Join_SelfJoin tests self-join scenarios
241+ func Test_Model_Join_SelfJoin (t * testing.T ) {
242+ table := createInitTable ()
243+ defer dropTable (table )
244+
245+ gtest .C (t , func (t * gtest.T ) {
246+ // Self-join to find pairs where a.id < b.id
247+ r , err := db .Model (table ).As ("a" ).
248+ Fields ("a.id AS a_id" , "b.id AS b_id" ).
249+ InnerJoin (table + " AS b" , "a.id < b.id" ).
250+ Where ("a.id" , 1 ).
251+ Where ("b.id <=" , 3 ).
252+ Order ("b.id asc" ).
253+ All ()
254+ t .AssertNil (err )
255+
256+ t .Assert (len (r ), 2 )
257+ t .Assert (r [0 ]["a_id" ], "1" )
258+ t .Assert (r [0 ]["b_id" ], "2" )
259+ t .Assert (r [1 ]["b_id" ], "3" )
260+ })
261+
262+ gtest .C (t , func (t * gtest.T ) {
263+ // Self-join with multiple conditions
264+ r , err := db .Model (table ).As ("a" ).
265+ Fields ("a.id" , "a.nickname" , "b.nickname AS other_nickname" ).
266+ LeftJoin (table + " AS b" , "a.id = b.id - 1" ).
267+ Where ("a.id IN(?)" , g.Slice {1 , 2 }).
268+ Order ("a.id asc" ).
269+ All ()
270+ t .AssertNil (err )
271+
272+ t .Assert (len (r ), 2 )
273+ t .Assert (r [0 ]["id" ], "1" )
274+ t .Assert (r [0 ]["nickname" ], "name_1" )
275+ t .Assert (r [0 ]["other_nickname" ], "name_2" )
276+ t .Assert (r [1 ]["id" ], "2" )
277+ t .Assert (r [1 ]["other_nickname" ], "name_3" )
278+ })
279+ }
280+
281+ // Test_Model_Join_LeftJoinNull tests LEFT JOIN NULL handling
282+ func Test_Model_Join_LeftJoinNull (t * testing.T ) {
283+ var (
284+ table1 = gtime .TimestampNanoStr () + "_table1"
285+ table2 = gtime .TimestampNanoStr () + "_table2"
286+ )
287+ createInitTable (table1 )
288+ defer dropTable (table1 )
289+
290+ // Create table2 with only partial data
291+ createTable (table2 )
292+ defer dropTable (table2 )
293+ _ , err := db .Insert (ctx , table2 , g.List {
294+ {"id" : 1 , "passport" : "user_1" , "nickname" : "name_1" },
295+ {"id" : 2 , "passport" : "user_2" , "nickname" : "name_2" },
296+ })
297+ if err != nil {
298+ gtest .Fatal (err )
299+ }
300+
301+ gtest .C (t , func (t * gtest.T ) {
302+ // LEFT JOIN - table1 has all records, table2 only has id 1,2
303+ r , err := db .Model (table1 ).As ("t1" ).
304+ FieldsPrefix ("t1" , "id" ).
305+ FieldsPrefix ("t2" , "nickname" ).
306+ LeftJoin (table2 + " AS t2" , "t1.id = t2.id" ).
307+ Where ("t1.id IN(?)" , g.Slice {1 , 2 , 3 }).
308+ Order ("t1.id asc" ).
309+ All ()
310+ t .AssertNil (err )
311+
312+ t .Assert (len (r ), 3 )
313+ t .Assert (r [0 ]["id" ], "1" )
314+ t .Assert (r [0 ]["nickname" ], "name_1" ) // matched
315+ t .Assert (r [1 ]["id" ], "2" )
316+ t .Assert (r [1 ]["nickname" ], "name_2" ) // matched
317+ t .Assert (r [2 ]["id" ], "3" )
318+ // r[2]["nickname"] should be NULL/empty from t2
319+ })
320+
321+ gtest .C (t , func (t * gtest.T ) {
322+ // Find records where RIGHT table is NULL
323+ r , err := db .Model (table1 ).As ("t1" ).
324+ FieldsPrefix ("t1" , "id" , "nickname" ).
325+ LeftJoin (table2 + " AS t2" , "t1.id = t2.id" ).
326+ Where ("t2.id IS NULL" ).
327+ Where ("t1.id IN(?)" , g.Slice {1 , 2 , 3 , 4 }).
328+ Order ("t1.id asc" ).
329+ All ()
330+ t .AssertNil (err )
331+
332+ // Should return id 3,4 (not in table2)
333+ t .Assert (len (r ), 2 )
334+ t .Assert (r [0 ]["id" ], "3" )
335+ t .Assert (r [0 ]["nickname" ], "name_3" )
336+ t .Assert (r [1 ]["id" ], "4" )
337+ })
338+ }
339+
340+ // Test_Model_Join_RightJoinNull tests RIGHT JOIN NULL handling
341+ func Test_Model_Join_RightJoinNull (t * testing.T ) {
342+ var (
343+ table1 = gtime .TimestampNanoStr () + "_table1"
344+ table2 = gtime .TimestampNanoStr () + "_table2"
345+ )
346+ // table1 has partial data
347+ createTable (table1 )
348+ defer dropTable (table1 )
349+ _ , err := db .Insert (ctx , table1 , g.List {
350+ {"id" : 1 , "passport" : "user_1" , "nickname" : "name_1" },
351+ {"id" : 2 , "passport" : "user_2" , "nickname" : "name_2" },
352+ })
353+ if err != nil {
354+ gtest .Fatal (err )
355+ }
356+
357+ // table2 has all data
358+ createInitTable (table2 )
359+ defer dropTable (table2 )
360+
361+ gtest .C (t , func (t * gtest.T ) {
362+ // RIGHT JOIN - table1 only has id 1,2, table2 has all
363+ r , err := db .Model (table1 ).As ("t1" ).
364+ FieldsPrefix ("t2" , "id" ).
365+ FieldsPrefix ("t1" , "nickname" ).
366+ RightJoin (table2 + " AS t2" , "t1.id = t2.id" ).
367+ Where ("t2.id IN(?)" , g.Slice {1 , 2 , 3 }).
368+ Order ("t2.id asc" ).
369+ All ()
370+ t .AssertNil (err )
371+
372+ t .Assert (len (r ), 3 )
373+ t .Assert (r [0 ]["id" ], "1" )
374+ t .Assert (r [0 ]["nickname" ], "name_1" ) // matched
375+ t .Assert (r [1 ]["id" ], "2" )
376+ t .Assert (r [1 ]["nickname" ], "name_2" ) // matched
377+ t .Assert (r [2 ]["id" ], "3" )
378+ // r[2]["nickname"] should be NULL/empty from t1
379+ })
380+
381+ gtest .C (t , func (t * gtest.T ) {
382+ // Find records where LEFT table is NULL
383+ r , err := db .Model (table1 ).As ("t1" ).
384+ FieldsPrefix ("t2" , "id" , "nickname" ).
385+ RightJoin (table2 + " AS t2" , "t1.id = t2.id" ).
386+ Where ("t1.id IS NULL" ).
387+ Where ("t2.id IN(?)" , g.Slice {1 , 2 , 3 , 4 }).
388+ Order ("t2.id asc" ).
389+ All ()
390+ t .AssertNil (err )
391+
392+ // Should return id 3,4 (not in table1)
393+ t .Assert (len (r ), 2 )
394+ t .Assert (r [0 ]["id" ], "3" )
395+ t .Assert (r [0 ]["nickname" ], "name_3" )
396+ t .Assert (r [1 ]["id" ], "4" )
397+ })
398+ }
399+
400+ // Test_Model_Join_OnVsWhere tests difference between ON and WHERE conditions
401+ func Test_Model_Join_OnVsWhere (t * testing.T ) {
402+ var (
403+ table1 = gtime .TimestampNanoStr () + "_table1"
404+ table2 = gtime .TimestampNanoStr () + "_table2"
405+ )
406+ createInitTable (table1 )
407+ defer dropTable (table1 )
408+ createInitTable (table2 )
409+ defer dropTable (table2 )
410+
411+ gtest .C (t , func (t * gtest.T ) {
412+ // INNER JOIN: ON and WHERE behave the same
413+ r1 , err := db .Model (table1 ).As ("t1" ).
414+ Fields ("t1.id" ).
415+ InnerJoin (table2 + " AS t2" , "t1.id = t2.id AND t2.id <= 3" ).
416+ Order ("t1.id asc" ).
417+ All ()
418+ t .AssertNil (err )
419+
420+ r2 , err := db .Model (table1 ).As ("t1" ).
421+ Fields ("t1.id" ).
422+ InnerJoin (table2 + " AS t2" , "t1.id = t2.id" ).
423+ Where ("t2.id <=" , 3 ).
424+ Order ("t1.id asc" ).
425+ All ()
426+ t .AssertNil (err )
427+
428+ // For INNER JOIN, results should be identical
429+ t .Assert (len (r1 ), 3 )
430+ t .Assert (len (r2 ), 3 )
431+ t .Assert (r1 [0 ]["id" ], r2 [0 ]["id" ])
432+ })
433+
434+ gtest .C (t , func (t * gtest.T ) {
435+ // LEFT JOIN: ON filter in join condition vs WHERE filter after join
436+ // ON condition: filters t2 before join (keeps all t1 rows)
437+ r1 , err := db .Model (table1 ).As ("t1" ).
438+ FieldsPrefix ("t1" , "id" ).
439+ FieldsPrefix ("t2" , "nickname" ).
440+ LeftJoin (table2 + " AS t2" , "t1.id = t2.id AND t2.id <= 2" ).
441+ Where ("t1.id <=" , 4 ).
442+ Order ("t1.id asc" ).
443+ All ()
444+ t .AssertNil (err )
445+
446+ // WHERE condition: filters result after join (removes t1 rows where t2 is NULL)
447+ r2 , err := db .Model (table1 ).As ("t1" ).
448+ FieldsPrefix ("t1" , "id" ).
449+ FieldsPrefix ("t2" , "nickname" ).
450+ LeftJoin (table2 + " AS t2" , "t1.id = t2.id" ).
451+ Where ("t1.id <=" , 4 ).
452+ Where ("t2.id <=" , 2 ).
453+ Order ("t1.id asc" ).
454+ All ()
455+ t .AssertNil (err )
456+
457+ // r1: all t1 rows (1,2,3,4), t2 data only for id 1,2
458+ t .Assert (len (r1 ), 4 )
459+ t .Assert (r1 [0 ]["id" ], "1" )
460+ t .Assert (r1 [0 ]["nickname" ], "name_1" )
461+ t .Assert (r1 [2 ]["id" ], "3" )
462+ // r1[2]["nickname"] is NULL from t2
463+
464+ // r2: only rows where t2.id <= 2, so only id 1,2
465+ t .Assert (len (r2 ), 2 )
466+ t .Assert (r2 [0 ]["id" ], "1" )
467+ t .Assert (r2 [1 ]["id" ], "2" )
468+ })
469+ }
470+
471+ // Test_Model_Join_ComplexConditions tests joins with complex ON conditions
472+ func Test_Model_Join_ComplexConditions (t * testing.T ) {
473+ var (
474+ table1 = gtime .TimestampNanoStr () + "_table1"
475+ table2 = gtime .TimestampNanoStr () + "_table2"
476+ )
477+ createInitTable (table1 )
478+ defer dropTable (table1 )
479+ createInitTable (table2 )
480+ defer dropTable (table2 )
481+
482+ gtest .C (t , func (t * gtest.T ) {
483+ // Multiple AND conditions in ON clause
484+ r , err := db .Model (table1 ).As ("t1" ).
485+ Fields ("t1.id" , "t1.nickname" ).
486+ InnerJoin (
487+ table2 + " AS t2" ,
488+ "t1.id = t2.id AND t1.nickname = t2.nickname AND t1.id BETWEEN 2 AND 4" ,
489+ ).
490+ Order ("t1.id asc" ).
491+ All ()
492+ t .AssertNil (err )
493+
494+ t .Assert (len (r ), 3 )
495+ t .Assert (r [0 ]["id" ], "2" )
496+ t .Assert (r [2 ]["id" ], "4" )
497+ })
498+
499+ gtest .C (t , func (t * gtest.T ) {
500+ // OR conditions in ON clause (need to use Where for OR in join)
501+ r , err := db .Model (table1 ).As ("t1" ).
502+ Fields ("t1.id" ).
503+ InnerJoin (table2 + " AS t2" , "t1.id = t2.id" ).
504+ Where ("t2.id = 1 OR t2.id = 5" ).
505+ Order ("t1.id asc" ).
506+ All ()
507+ t .AssertNil (err )
508+
509+ t .Assert (len (r ), 2 )
510+ t .Assert (r [0 ]["id" ], "1" )
511+ t .Assert (r [1 ]["id" ], "5" )
512+ })
513+ }
0 commit comments