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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
//! TPC-H Subquery Validation Tests
//!
//! Tests to validate correctness of TPC-H queries with subqueries:
//! - Q11: Scalar subquery in HAVING clause
//! - Q13: Subquery in FROM with LEFT OUTER JOIN
//! - Q20: Nested IN subqueries with correlation
//! - Q21: Correlated EXISTS subqueries
//!
//! These tests execute on SF 0.01 dataset and validate:
//! 1. Queries execute without errors
//! 2. Results are non-empty (where expected)
//! 3. Basic result count validation
//!
//! Related to issue #2426
use vibesql_executor::SelectExecutor;
use vibesql_parser::Parser;
use vibesql_storage::Database;
// Import TPC-H infrastructure
#[path = "../benches/tpch/mod.rs"]
mod tpch;
use tpch::{queries::*, schema::load_vibesql};
/// Helper to execute a TPC-H query and return row count
fn execute_tpch_query(db: &Database, sql: &str) -> Result<usize, String> {
execute_tpch_query_rows(db, sql).map(|rows| rows.len())
}
/// Helper to execute a TPC-H query and return the result rows
fn execute_tpch_query_rows(db: &Database, sql: &str) -> Result<Vec<vibesql_storage::Row>, String> {
let stmt = Parser::parse_sql(sql).map_err(|e| format!("Parse error: {:?}", e))?;
if let vibesql_ast::Statement::Select(select) = stmt {
let executor = SelectExecutor::new(db);
executor.execute(&select).map_err(|e| format!("Execution error: {:?}", e))
} else {
Err("Not a SELECT statement".to_string())
}
}
/// Extract a string value from a row column
fn string_value(value: &vibesql_types::SqlValue) -> String {
match value {
vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s) => {
s.as_str().to_string()
}
other => panic!("Expected string value, got {:?}", other),
}
}
#[test]
fn test_q2_correlated_scalar_subquery() {
// Q2: Minimum Cost Supplier
// Tests correlated scalar subquery in WHERE clause
// Pattern: Correlated on p_partkey - subquery references outer query column
// Performance: Previously timed out (>30s), should now complete with caching
// This test validates issue #2452 is fixed
let db = load_vibesql(0.01);
let result = execute_tpch_query(&db, TPCH_Q2);
match result {
Ok(count) => {
println!("Q2 executed successfully: {} rows", count);
// Q2 has LIMIT 100, so should return at most 100 rows
assert!(count <= 100, "Q2 has LIMIT 100");
// Should return at least some suppliers (exact count varies by data)
println!("Q2 completed without timeout (row count: {})", count);
}
Err(e) => {
panic!("Q2 failed to execute: {}", e);
}
}
}
#[test]
fn test_q11_scalar_subquery_having() {
// Q11: Important Stock Identification
// Tests scalar subquery in HAVING clause
// Pattern: Uncorrelated scalar subquery for threshold computation
// Performance: Fast (~3s on SF 0.01) with scalar subquery caching (#2431)
let db = load_vibesql(0.01);
let result = execute_tpch_query(&db, TPCH_Q11);
match result {
Ok(count) => {
println!("Q11 executed successfully: {} rows", count);
// Q11 should return some parts that meet the threshold
// Exact count depends on data, but should be non-zero
assert!(count > 0, "Q11 should return at least one row");
}
Err(e) => {
panic!("Q11 failed to execute: {}", e);
}
}
}
#[test]
fn test_q13_complex_join_subquery() {
// Q13: Customer Distribution
// Tests subquery in FROM with LEFT OUTER JOIN and aggregate
// Pattern: No decorrelation needed, uses hash join for LEFT OUTER JOIN
// Performance: Fast (~2s on SF 0.01) thanks to hash join optimization (#2465)
let db = load_vibesql(0.01);
let result = execute_tpch_query(&db, TPCH_Q13);
match result {
Ok(count) => {
println!("Q13 executed successfully: {} rows", count);
// Q13 returns distribution of customer order counts
// Should have multiple groups
assert!(count > 0, "Q13 should return at least one row");
}
Err(e) => {
panic!("Q13 failed to execute: {}", e);
}
}
}
#[test]
fn test_q20_nested_in_subqueries() {
// Q20: Potential Part Promotion
// Tests nested IN subqueries with correlated scalar subquery
// Pattern: IN with nested IN, plus correlated scalar subquery
// Performance: Fast (~3s on SF 0.01) with semi-join optimization (#2405, #2475)
//
// Issue #5274: the scalar-comparison decorrelation rewrote
// `ps_availqty > (SELECT 0.5*SUM(l_quantity) ...)` as
// `ps_availqty > COALESCE(__scalar_agg, 0)`, turning empty-group NULL
// (row must be excluded) into 0 (row always passes), so Q20 returned 4
// suppliers instead of 1. Expected values below were computed by running
// the identical deterministic SF 0.01 dataset (ChaCha8 seed 42, via
// vibesql_bench_common::tpch::schema::load_sqlite) through SQLite.
let db = load_vibesql(0.01);
let rows = execute_tpch_query_rows(&db, TPCH_Q20).expect("Q20 should execute");
println!("Q20 executed successfully: {} rows", rows.len());
// SQLite ground truth on this exact dataset: exactly 1 supplier
assert_eq!(
rows.len(),
1,
"Q20 must return exactly 1 row (SQLite-validated). Extra rows indicate \
empty-group NULL thresholds incorrectly passing the > comparison (issue #5274)"
);
assert_eq!(string_value(&rows[0].values[0]), "Supplier#000000029", "Q20 s_name mismatch");
assert_eq!(
string_value(&rows[0].values[1]),
"cnGWR5ROtq0v0HwBFsXzIA0BEnfgkFqjLXWVBB",
"Q20 s_address mismatch"
);
}
#[test]
fn test_q20_scalar_comparison_null_semantics() {
// Issue #5274: directly exercise the decorrelated correlated-SUM threshold
// comparison from Q20 in both directions. Counts are SQLite ground truth
// on the identical deterministic SF 0.01 dataset.
let db = load_vibesql(0.01);
// `>` direction: rows whose 1994 lineitem window is empty produce a NULL
// threshold and must be excluded. With the buggy COALESCE(.., 0) rewrite
// this returned every partsupp row with ps_availqty > 0 instead.
let gt_count = execute_tpch_query(
&db,
r#"
SELECT ps_partkey FROM partsupp
WHERE ps_availqty > (
SELECT 0.5 * SUM(l_quantity)
FROM lineitem
WHERE l_partkey = ps_partkey
AND l_suppkey = ps_suppkey
AND l_shipdate >= '1994-01-01'
AND l_shipdate < '1995-01-01'
)
"#,
)
.expect("> comparison should execute");
assert_eq!(gt_count, 1966, "ps_availqty > (corr SUM) must match SQLite (1966 rows)");
// `<` direction: NULL thresholds are excluded either way, so this count
// was already correct before the fix and must stay correct.
let lt_count = execute_tpch_query(
&db,
r#"
SELECT ps_partkey FROM partsupp
WHERE ps_availqty < (
SELECT 0.5 * SUM(l_quantity)
FROM lineitem
WHERE l_partkey = ps_partkey
AND l_suppkey = ps_suppkey
AND l_shipdate >= '1994-01-01'
AND l_shipdate < '1995-01-01'
)
"#,
)
.expect("< comparison should execute");
assert_eq!(lt_count, 13, "ps_availqty < (corr SUM) must match SQLite (13 rows)");
}
#[test]
fn test_q21_correlated_exists() {
// Q21: Suppliers Who Kept Orders Waiting
// Tests EXISTS with correlated predicate and inequality
// Pattern: EXISTS with complex correlation
// Performance: Moderate (~36s on SF 0.01) with EXISTS to join conversion (#2481, #2473)
let db = load_vibesql(0.01);
let result = execute_tpch_query(&db, TPCH_Q21);
match result {
Ok(count) => {
println!("Q21 executed successfully: {} rows", count);
// Q21 is limited to 100 rows by the query itself
assert!(count <= 100, "Q21 has LIMIT 100");
// May return 0 rows on small dataset if no suppliers meet criteria
println!("Q21 completed without errors (row count: {})", count);
}
Err(e) => {
panic!("Q21 failed to execute: {}", e);
}
}
}
#[test]
fn test_q11_q13_batch() {
// Run Q11 and Q13 together to validate both work
// Performance: Both queries now complete quickly
// Q11: ~3s with scalar subquery caching (#2431)
// Q13: ~3s with hash join optimization (#2472)
let db = load_vibesql(0.01);
// Test Q11
let q11_result = execute_tpch_query(&db, TPCH_Q11);
assert!(q11_result.is_ok(), "Q11 should execute: {:?}", q11_result);
println!("Q11: {} rows", q11_result.unwrap());
// Test Q13
let q13_result = execute_tpch_query(&db, TPCH_Q13);
assert!(q13_result.is_ok(), "Q13 should execute: {:?}", q13_result);
println!("Q13: {} rows", q13_result.unwrap());
}
#[test]
fn test_q18_in_subquery_with_having() {
// Q18: Large Volume Customer
// Tests IN subquery with GROUP BY and HAVING clause
// Pattern: o_orderkey IN (SELECT l_orderkey FROM lineitem GROUP BY l_orderkey HAVING
// SUM(l_quantity) > 300) Issue #2898: Query was crashing/returning 0 rows
let db = load_vibesql(0.01);
// First, test the inner subquery alone
let subquery = r#"
SELECT l_orderkey, SUM(l_quantity) as total_qty
FROM lineitem
GROUP BY l_orderkey
HAVING SUM(l_quantity) > 300
"#;
println!("\n=== Q18 Inner Subquery Test ===");
let subquery_result = execute_tpch_query(&db, subquery);
println!("Subquery result: {:?}", subquery_result);
// At SF 0.01, there might not be orders with > 300 quantity
// But the query should execute without errors
assert!(subquery_result.is_ok(), "Q18 subquery should execute: {:?}", subquery_result);
let subquery_count = subquery_result.unwrap();
println!("Q18 subquery returned {} rows (may be 0 at small scale factor)", subquery_count);
// Check the max quantity per order to understand the data
let max_qty_query = r#"
SELECT l_orderkey, SUM(l_quantity) as total_qty
FROM lineitem
GROUP BY l_orderkey
ORDER BY total_qty DESC
LIMIT 5
"#;
println!("\n=== Top 5 Orders by Quantity ===");
if let Ok(vibesql_ast::Statement::Select(select)) = Parser::parse_sql(max_qty_query) {
let executor = SelectExecutor::new(&db);
if let Ok(rows) = executor.execute(&select) {
for (i, row) in rows.iter().enumerate() {
println!(" Row {}: {:?}", i, row);
}
}
}
// Now test full Q18
println!("\n=== Full Q18 Test ===");
let q18_result = execute_tpch_query(&db, TPCH_Q18);
assert!(q18_result.is_ok(), "Q18 should execute: {:?}", q18_result);
let q18_count = q18_result.unwrap();
println!("Q18 returned {} rows", q18_count);
// Q18 has LIMIT 100, so should return at most 100 rows
assert!(q18_count <= 100, "Q18 has LIMIT 100");
// At SF 0.01, if no orders have quantity > 300, this will return 0 rows
// That's correct behavior - not a bug
if subquery_count == 0 {
assert_eq!(q18_count, 0, "Q18 should return 0 rows if subquery returns 0");
println!(
"Q18 correctly returns 0 rows because no orders have SUM(l_quantity) > 300 at SF 0.01"
);
} else {
assert!(q18_count > 0, "Q18 should return rows if subquery found matching orders");
}
}
#[test]
fn test_q7_volume_shipping() {
// Q7: Volume Shipping
// Tests SUBSTR(l_shipdate, 1, 4) to extract year from DATE column
// Issue #2955: This query was failing because SUBSTRING didn't support Date type
// Pattern: 6-table JOIN with SUBSTR and date filtering
let db = load_vibesql(0.01);
let result = execute_tpch_query(&db, TPCH_Q7);
match result {
Ok(count) => {
println!("Q7 executed successfully: {} rows", count);
// Q7 may return 0 rows on small dataset if no orders match criteria
// The important thing is that it doesn't fail with "SUBSTRING requires string argument"
println!("Q7 completed without errors (row count: {})", count);
}
Err(e) => {
// Specifically check that the error is NOT about SUBSTRING type mismatch
if e.contains("SUBSTRING requires string argument") {
panic!("Q7 failed with SUBSTRING type error - Issue #2955 not fixed: {}", e);
}
panic!("Q7 failed to execute: {}", e);
}
}
}
#[test]
fn test_q9_product_type_profit() {
// Q9: Product Type Profit Measure
// Tests SUBSTR(o_orderdate, 1, 4) to extract year from DATE column
// Issue #2955: This query was failing because SUBSTRING didn't support Date type
// Pattern: 6-table JOIN with LIKE and SUBSTR on date
let db = load_vibesql(0.01);
let result = execute_tpch_query(&db, TPCH_Q9);
match result {
Ok(count) => {
println!("Q9 executed successfully: {} rows", count);
// Q9 may return 0 rows on small dataset if no parts match LIKE '%green%'
// The important thing is that it doesn't fail with "SUBSTRING requires string argument"
println!("Q9 completed without errors (row count: {})", count);
}
Err(e) => {
// Specifically check that the error is NOT about SUBSTRING type mismatch
if e.contains("SUBSTRING requires string argument") {
panic!("Q9 failed with SUBSTRING type error - Issue #2955 not fixed: {}", e);
}
panic!("Q9 failed to execute: {}", e);
}
}
}