@@ -101,6 +101,76 @@ test_result_t test_join_left_basic() {
101101 PASS ();
102102}
103103
104+ // ==================== LEFT JOIN SHARED COLUMN TYPES ====================
105+ test_result_t test_join_left_shared_column_types () {
106+ // Shared I64 column, partial match — exercises select_column I64 branch
107+ TEST_ASSERT_EQ (
108+ "(set t1 (table [id shared] (list [1 2 3] [10 20 30])))"
109+ "(set t2 (table [id shared] (list [1 3] [100 300])))"
110+ "(at (left-join [id] t1 t2) 'shared)" ,
111+ "[100 20 300]" );
112+
113+ // Shared F64 column, partial match
114+ TEST_ASSERT_EQ (
115+ "(set t1 (table [id shared] (list [1 2 3] [1.1 2.2 3.3])))"
116+ "(set t2 (table [id shared] (list [1 3] [10.1 30.3])))"
117+ "(at (left-join [id] t1 t2) 'shared)" ,
118+ "[10.1 2.2 30.3]" );
119+
120+ // Shared Symbol column, partial match
121+ TEST_ASSERT_EQ (
122+ "(set t1 (table [id shared] (list [1 2 3] [aaa bbb ccc])))"
123+ "(set t2 (table [id shared] (list [1 3] [xxx zzz])))"
124+ "(at (left-join [id] t1 t2) 'shared)" ,
125+ "[xxx bbb zzz]" );
126+
127+ // Shared Date column, partial match
128+ TEST_ASSERT_EQ (
129+ "(set t1 (table [id shared] (list [1 2 3] [2024.01.01 2024.01.02 2024.01.03])))"
130+ "(set t2 (table [id shared] (list [1 3] [2025.06.01 2025.06.03])))"
131+ "(at (left-join [id] t1 t2) 'shared)" ,
132+ "[2025.06.01 2024.01.02 2025.06.03]" );
133+
134+ // Shared Time column, partial match
135+ TEST_ASSERT_EQ (
136+ "(set t1 (table [id shared] (list [1 2 3] [10:00:00.000 10:00:01.000 10:00:02.000])))"
137+ "(set t2 (table [id shared] (list [1 3] [20:00:00.000 20:00:02.000])))"
138+ "(at (left-join [id] t1 t2) 'shared)" ,
139+ "[20:00:00.000 10:00:01.000 20:00:02.000]" );
140+
141+ // Shared Timestamp column, partial match
142+ TEST_ASSERT_EQ (
143+ "(set t1 (table [id shared] (list [1 2 3] "
144+ "[2024.01.01D10:00:00.000000000 2024.01.01D10:00:01.000000000 2024.01.01D10:00:02.000000000])))"
145+ "(set t2 (table [id shared] (list [1 3] "
146+ "[2025.06.01D20:00:00.000000000 2025.06.01D20:00:02.000000000])))"
147+ "(at (left-join [id] t1 t2) 'shared)" ,
148+ "[2025.06.01D20:00:00.000000000 2024.01.01D10:00:01.000000000 2025.06.01D20:00:02.000000000]" );
149+
150+ // Shared B8 column, partial match
151+ TEST_ASSERT_EQ (
152+ "(set t1 (table [id shared] (list [1 2 3] [true false true])))"
153+ "(set t2 (table [id shared] (list [1 3] [false true])))"
154+ "(at (left-join [id] t1 t2) 'shared)" ,
155+ "[false false true]" );
156+
157+ // Shared GUID column, partial match
158+ TEST_ASSERT_EQ (
159+ "(set t1 (table [id shared] (list [1 2 3] (guid 3))))"
160+ "(set t2 (table [id shared] (list [1 3] (guid 2))))"
161+ "(count (left-join [id] t1 t2))" ,
162+ "3" );
163+
164+ // Shared I16 column (cast via as), partial match
165+ TEST_ASSERT_EQ (
166+ "(set t1 (table [id shared] (list [1 2 3] (as 'I16 [10 20 30]))))"
167+ "(set t2 (table [id shared] (list [1 3] (as 'I16 [100 300]))))"
168+ "(at (left-join [id] t1 t2) 'shared)" ,
169+ "[100 20 300]" );
170+
171+ PASS ();
172+ }
173+
104174// ==================== JOIN ON SINGLE KEY TYPES ====================
105175test_result_t test_join_single_key_types () {
106176 // Join on I32 key
@@ -574,8 +644,7 @@ test_result_t test_join_parallel() {
574644 "(sum (at (left-join [id] t1 t2) 'val1))" ,
575645 "199990000" );
576646
577- // Verify left join — right values correct for matched rows
578- // val2 = 2*id for ids 0..9999, should be filled from right; rest from left (val1)
647+ // Left join — right-only column sum
579648 TEST_ASSERT_EQ (
580649 "(set t1 (table [id val1] (list (til 20000) (til 20000))))"
581650 "(set t2 (table [id val2] (list (til 10000) (* 2 (til 10000)))))"
@@ -599,5 +668,40 @@ test_result_t test_join_parallel() {
599668 "(sum (at (left-join [id k] t1 t2) 'val1))" ,
600669 "199990000" );
601670
671+ // Left join, right-only column with partial match (multi-key)
672+ // t1 has no val2 column. t2 has val2. Rows (3,a) and (4,a) are unmatched.
673+ // val2 for unmatched rows must be null-filled, not crash.
674+ TEST_ASSERT_EQ (
675+ "(set t1 (table [k1 k2] (list [1 2 3 4] [a a a a])))"
676+ "(set t2 (table [k1 k2 val2] (list [1 2] [a a] [100 200])))"
677+ "(count (left-join [k1 k2] t1 t2))" ,
678+ "4" );
679+
680+ TEST_ASSERT_EQ (
681+ "(set t1 (table [k1 k2] (list [1 2 3 4] [a a a a])))"
682+ "(set t2 (table [k1 k2 val2] (list [1 2] [a a] [100 200])))"
683+ "(at (left-join [k1 k2] t1 t2) 'val2)" ,
684+ "[100 200 0Nl 0Nl]" );
685+
686+ // Left join, right-only column, >16K rows, half unmatched
687+ TEST_ASSERT_EQ (
688+ "(set t1 (table [id k] (list (til 20000) (* 10 (til 20000)))))"
689+ "(set t2 (table [id k val2] (list (til 10000) (* 10 (til 10000)) (* 2 (til 10000)))))"
690+ "(count (left-join [id k] t1 t2))" ,
691+ "20000" );
692+
693+ // Multi-key join with duplicate composite keys in right table
694+ TEST_ASSERT_EQ (
695+ "(set t1 (table [k1 k2 val1] (list [1 1 2 2] [a b a b] [10 20 30 40])))"
696+ "(set t2 (table [k1 k2 val2] (list [1 1 2] [a a b] [100 101 200])))"
697+ "(count (inner-join [k1 k2] t1 t2))" ,
698+ "2" );
699+
700+ TEST_ASSERT_EQ (
701+ "(set t1 (table [k1 k2 val1] (list [1 1 2 2] [a b a b] [10 20 30 40])))"
702+ "(set t2 (table [k1 k2 val2] (list [1 1 2] [a a b] [100 101 200])))"
703+ "(count (left-join [k1 k2] t1 t2))" ,
704+ "4" );
705+
602706 PASS ();
603707}
0 commit comments