Skip to content

Commit 344275d

Browse files
committed
chore(embedded/sql): add support for LEFT JOIN
Signed-off-by: Stefano Scafiti <[email protected]>
1 parent 4cf4af7 commit 344275d

File tree

4 files changed

+173
-14
lines changed

4 files changed

+173
-14
lines changed

embedded/sql/engine_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5986,6 +5986,123 @@ func TestNestedJoins(t *testing.T) {
59865986
require.NoError(t, err)
59875987
}
59885988

5989+
func TestLeftJoins(t *testing.T) {
5990+
e := setupCommonTest(t)
5991+
5992+
_, _, err := e.Exec(
5993+
context.Background(),
5994+
nil,
5995+
`
5996+
CREATE TABLE customers (
5997+
customer_id INTEGER,
5998+
customer_name VARCHAR(50),
5999+
email VARCHAR(100),
6000+
6001+
PRIMARY KEY customer_id
6002+
);
6003+
6004+
CREATE TABLE products (
6005+
product_id INTEGER,
6006+
product_name VARCHAR(50),
6007+
price FLOAT,
6008+
6009+
PRIMARY KEY product_id
6010+
);
6011+
6012+
CREATE TABLE orders (
6013+
order_id INTEGER,
6014+
customer_id INTEGER,
6015+
order_date TIMESTAMP,
6016+
6017+
PRIMARY KEY order_id
6018+
);
6019+
6020+
CREATE TABLE order_items (
6021+
order_item_id INTEGER,
6022+
order_id INTEGER,
6023+
product_id INTEGER,
6024+
quantity INTEGER,
6025+
6026+
PRIMARY KEY order_item_id
6027+
);
6028+
6029+
INSERT INTO customers (customer_id, customer_name, email)
6030+
VALUES
6031+
(1, 'Alice Johnson', '[email protected]'),
6032+
(2, 'Bob Smith', '[email protected]'),
6033+
(3, 'Charlie Brown', '[email protected]');
6034+
6035+
INSERT INTO products (product_id, product_name, price)
6036+
VALUES
6037+
(1, 'Laptop', 1200.00),
6038+
(2, 'Smartphone', 800.00),
6039+
(3, 'Tablet', 400.00);
6040+
6041+
INSERT INTO orders (order_id, customer_id, order_date)
6042+
VALUES
6043+
(101, 1, '2024-11-01'::TIMESTAMP),
6044+
(102, 2, '2024-11-02'::TIMESTAMP),
6045+
(103, 1, '2024-11-03'::TIMESTAMP);
6046+
6047+
INSERT INTO order_items (order_item_id, order_id, product_id, quantity)
6048+
VALUES
6049+
(1, 101, 1, 2),
6050+
(2, 101, 2, 1),
6051+
(3, 102, 3, 3),
6052+
(4, 103, 2, 2);
6053+
`,
6054+
nil,
6055+
)
6056+
require.NoError(t, err)
6057+
6058+
assertQueryShouldProduceResults(
6059+
t,
6060+
e,
6061+
`SELECT c.customer_id, c.customer_name, c.email, o.order_id, o.order_date
6062+
FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id
6063+
ORDER BY c.customer_id, o.order_date;`,
6064+
`
6065+
SELECT *
6066+
FROM (
6067+
VALUES
6068+
(1, 'Alice Johnson', '[email protected]', 101, '2024-11-01'::TIMESTAMP),
6069+
(1, 'Alice Johnson', '[email protected]', 103, '2024-11-03'::TIMESTAMP),
6070+
(2, 'Bob Smith', '[email protected]', 102, '2024-11-02'::TIMESTAMP),
6071+
(3, 'Charlie Brown', '[email protected]', NULL, NULL)
6072+
)`,
6073+
)
6074+
6075+
assertQueryShouldProduceResults(
6076+
t,
6077+
e,
6078+
`
6079+
SELECT
6080+
c.customer_name,
6081+
c.email,
6082+
o.order_id,
6083+
o.order_date,
6084+
p.product_name,
6085+
oi.quantity,
6086+
p.price,
6087+
(oi.quantity * p.price) AS total_price
6088+
FROM
6089+
products p
6090+
LEFT JOIN order_Items oi ON p.product_id = oi.product_id
6091+
LEFT JOIN orders o ON oi.order_id = o.order_id
6092+
LEFT JOIN customers c ON o.customer_id = c.customer_id
6093+
ORDER BY o.order_date, c.customer_name;`,
6094+
`
6095+
SELECT *
6096+
FROM (
6097+
VALUES
6098+
('Alice Johnson', '[email protected]', 101, '2024-11-01'::TIMESTAMP, 'Laptop', 2, 1200.00, 2400.00),
6099+
('Alice Johnson', '[email protected]', 101, '2024-11-01'::TIMESTAMP, 'Smartphone', 1, 800.00, 800.00),
6100+
('Bob Smith', '[email protected]', 102, '2024-11-02'::TIMESTAMP, 'Tablet', 3, 400.00, 1200.00),
6101+
('Alice Johnson', '[email protected]', 103, '2024-11-03'::TIMESTAMP, 'Smartphone', 2, 800.00, 1600.00)
6102+
)`,
6103+
)
6104+
}
6105+
59896106
func TestReOpening(t *testing.T) {
59906107
st, err := store.Open(t.TempDir(), store.DefaultOptions().WithMultiIndexing(true))
59916108
require.NoError(t, err)
@@ -9434,3 +9551,24 @@ func TestFunctions(t *testing.T) {
94349551
require.Equal(t, "OBJECT", rows[0].ValuesByPosition[0].RawValue().(string))
94359552
})
94369553
}
9554+
9555+
func assertQueryShouldProduceResults(t *testing.T, e *Engine, query, resultQuery string) {
9556+
queryReader, err := e.Query(context.Background(), nil, query, nil)
9557+
require.NoError(t, err)
9558+
defer queryReader.Close()
9559+
9560+
resultReader, err := e.Query(context.Background(), nil, resultQuery, nil)
9561+
require.NoError(t, err)
9562+
defer resultReader.Close()
9563+
9564+
for {
9565+
actualRow, actualErr := queryReader.Read(context.Background())
9566+
expectedRow, expectedErr := resultReader.Read(context.Background())
9567+
require.Equal(t, expectedErr, actualErr)
9568+
9569+
if errors.Is(actualErr, ErrNoMoreRows) {
9570+
break
9571+
}
9572+
require.Equal(t, expectedRow.ValuesByPosition, actualRow.ValuesByPosition)
9573+
}
9574+
}

embedded/sql/joint_row_reader.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ func newJointRowReader(rowReader RowReader, joins []*JoinSpec) (*jointRowReader,
3939
}
4040

4141
for _, jspec := range joins {
42-
if jspec.joinType != InnerJoin {
42+
switch jspec.joinType {
43+
case InnerJoin, LeftJoin:
44+
default:
4345
return nil, ErrUnsupportedJoinType
4446
}
4547
}
@@ -113,7 +115,6 @@ func (jointr *jointRowReader) colsBySelector(ctx context.Context) (map[string]Co
113115
colDescriptors[sel] = des
114116
}
115117
}
116-
117118
return colDescriptors, nil
118119
}
119120

@@ -240,17 +241,35 @@ func (jointr *jointRowReader) Read(ctx context.Context) (row *Row, err error) {
240241

241242
r, err := reader.Read(ctx)
242243
if err == ErrNoMoreRows {
243-
// previous reader will need to read next row
244-
unsolvedFK = true
245-
246-
err = reader.Close()
247-
if err != nil {
248-
return nil, err
244+
if jspec.joinType == InnerJoin {
245+
// previous reader will need to read next row
246+
unsolvedFK = true
247+
248+
err = reader.Close()
249+
if err != nil {
250+
return nil, err
251+
}
252+
253+
break
254+
} else { // LEFT JOIN: fill column values with NULLs
255+
cols, err := reader.Columns(ctx)
256+
if err != nil {
257+
return nil, err
258+
}
259+
260+
r = &Row{
261+
ValuesByPosition: make([]TypedValue, len(cols)),
262+
ValuesBySelector: make(map[string]TypedValue, len(cols)),
263+
}
264+
265+
for i, col := range cols {
266+
nullValue := NewNull(col.Type)
267+
268+
r.ValuesByPosition[i] = nullValue
269+
r.ValuesBySelector[col.Selector()] = nullValue
270+
}
249271
}
250-
251-
break
252-
}
253-
if err != nil {
272+
} else if err != nil {
254273
reader.Close()
255274
return nil, err
256275
}

embedded/sql/joint_row_reader_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,12 @@ func TestJointRowReader(t *testing.T) {
5151
r, err := newRawRowReader(tx, nil, table, period{}, "", &ScanSpecs{Index: table.primaryIndex})
5252
require.NoError(t, err)
5353

54-
_, err = newJointRowReader(r, []*JoinSpec{{joinType: LeftJoin}})
54+
_, err = newJointRowReader(r, []*JoinSpec{{joinType: RightJoin}})
5555
require.ErrorIs(t, err, ErrUnsupportedJoinType)
5656

57+
_, err = newJointRowReader(r, []*JoinSpec{{joinType: LeftJoin}})
58+
require.NoError(t, err)
59+
5760
_, err = newJointRowReader(r, []*JoinSpec{{joinType: InnerJoin, ds: &SelectStmt{}}})
5861
require.NoError(t, err)
5962

embedded/sql/stmt.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3409,7 +3409,6 @@ func (stmt *SelectStmt) Resolve(ctx context.Context, tx *SQLTx, params map[strin
34093409
rowReader = newLimitRowReader(rowReader, limit)
34103410
}
34113411
}
3412-
34133412
return rowReader, nil
34143413
}
34153414

0 commit comments

Comments
 (0)