//! E2E tests for parser edge cases and query pattern coverage. //! //! Phase 3 of PLAN_COVERAGE_2.md — exercises parser code paths //! for HAVING, CASE, complex JOINs, subqueries in FROM, and //! advanced window frames. //! //! Prerequisites: `./tests/build_e2e_image.sh` mod e2e; use e2e::E2eDb; // ── HAVING Clause ────────────────────────────────────────────────────── #[tokio::test] async fn test_aggregate_with_having_full_mode() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE having_src (id INT PRIMARY KEY, region TEXT, amount NUMERIC)") .await; db.execute( "INSERT INTO having_src VALUES \ (1, 'east', 100), (2, 'east', 200), (3, 'west', 50), \ (4, 'west', 75), (5, 'north', 300)", ) .await; db.create_st( "having_st", "SELECT region, SUM(amount) AS total FROM having_src GROUP BY region HAVING SUM(amount) > 100", "1m", "FULL", ) .await; // east=300 (>100), west=125 (>100), north=300 (>100) → 3 rows let count = db.count("public.having_st").await; assert_eq!(count, 3, "All three regions exceed 100"); // Insert data to make north drop below threshold via update-by-delete db.execute("DELETE FROM having_src WHERE id = 3").await; // west now 75 (<= 100? no, 75) db.execute("DELETE FROM having_src WHERE id = 4").await; // west now 0 db.refresh_st("having_st").await; // west is gone (0 total), east=300, north=300 → 2 rows let count = db.count("public.having_st").await; assert_eq!( count, 2, "West should be filtered out by HAVING after delete" ); } #[tokio::test] async fn test_aggregate_with_having_differential_mode() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE having_inc_src (id INT PRIMARY KEY, category TEXT, score INT)") .await; db.execute( "INSERT INTO having_inc_src VALUES \ (1, 'A', 10), (2, 'A', 20), (3, 'B', 5), (4, 'B', 3)", ) .await; db.create_st( "having_inc_st", "SELECT category, SUM(score) AS total_score FROM having_inc_src GROUP BY category HAVING SUM(score) > 5", "1m", "DIFFERENTIAL", ) .await; // A=30 (>5), B=8 (>5) → 2 rows let count = db.count("public.having_inc_st").await; assert_eq!(count, 2); // Add more data and refresh db.execute("INSERT INTO having_inc_src VALUES (5, 'C', 100)") .await; db.refresh_st("having_inc_st").await; let count = db.count("public.having_inc_st").await; assert_eq!(count, 3, "Category C should appear after insert + refresh"); } // ── CASE Expressions ─────────────────────────────────────────────────── #[tokio::test] async fn test_case_expression_in_defining_query() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE case_src (id INT PRIMARY KEY, val INT)") .await; db.execute("INSERT INTO case_src VALUES (1, 10), (2, 50), (3, 100)") .await; db.create_st( "case_st", "SELECT id, CASE WHEN val < 25 THEN 'low' WHEN val < 75 THEN 'mid' ELSE 'high' END AS bucket FROM case_src", "1m", "FULL", ) .await; let count = db.count("public.case_st").await; assert_eq!(count, 3); // Verify values let low_count: i64 = db .query_scalar("SELECT count(*) FROM public.case_st WHERE bucket = 'low'") .await; assert_eq!(low_count, 1); let high_count: i64 = db .query_scalar("SELECT count(*) FROM public.case_st WHERE bucket = 'high'") .await; assert_eq!(high_count, 1); } #[tokio::test] async fn test_case_expression_in_aggregate() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE case_agg_src (id INT PRIMARY KEY, status TEXT, amount NUMERIC)") .await; db.execute( "INSERT INTO case_agg_src VALUES \ (1, 'active', 100), (2, 'inactive', 50), (3, 'active', 200), (4, 'inactive', 75)", ) .await; db.create_st( "case_agg_st", "SELECT status, SUM(CASE WHEN amount > 60 THEN amount ELSE 0 END) AS big_total FROM case_agg_src GROUP BY status", "1m", "FULL", ) .await; let count = db.count("public.case_agg_st").await; assert_eq!(count, 2, "Two status groups"); // active: 100+200=300 (both > 60), inactive: 0+75=75 (only 75 > 60) let active_total: i64 = db .query_scalar("SELECT big_total::bigint FROM public.case_agg_st WHERE status = 'active'") .await; assert_eq!(active_total, 300); } // ── Complex JOINs ────────────────────────────────────────────────────── #[tokio::test] async fn test_multi_condition_join() { let db = E2eDb::new().await.with_extension().await; db.execute( "CREATE TABLE mc_orders (id INT PRIMARY KEY, customer_id INT, region TEXT, amount NUMERIC)", ) .await; db.execute("CREATE TABLE mc_customers (id INT PRIMARY KEY, region TEXT, name TEXT)") .await; db.execute("INSERT INTO mc_customers VALUES (1, 'east', 'Alice'), (2, 'west', 'Bob')") .await; db.execute( "INSERT INTO mc_orders VALUES \ (1, 1, 'east', 100), (2, 2, 'west', 200), (3, 1, 'west', 50)", ) .await; // Join on both customer_id and region (multi-condition ON) db.create_st( "mc_join_st", "SELECT o.id, c.name, o.amount FROM mc_orders o JOIN mc_customers c ON o.customer_id = c.id AND o.region = c.region", "1m", "FULL", ) .await; // Only orders where customer_id AND region match: order 1 (Alice, east) and order 2 (Bob, west) // Order 3 (customer_id=1=Alice, region=west) doesn't match Alice's region (east) let count = db.count("public.mc_join_st").await; assert_eq!(count, 2, "Multi-condition join should match 2 rows"); } #[tokio::test] async fn test_left_join_with_null_handling() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE lj_products (id INT PRIMARY KEY, name TEXT)") .await; db.execute("CREATE TABLE lj_reviews (id INT PRIMARY KEY, product_id INT, rating INT)") .await; db.execute("INSERT INTO lj_products VALUES (1, 'Widget'), (2, 'Gadget'), (3, 'Doohickey')") .await; db.execute("INSERT INTO lj_reviews VALUES (1, 1, 5), (2, 1, 4), (3, 2, 3)") .await; db.create_st( "lj_st", "SELECT p.id, p.name, COALESCE(AVG(r.rating), 0) AS avg_rating FROM lj_products p LEFT JOIN lj_reviews r ON p.id = r.product_id GROUP BY p.id, p.name", "1m", "FULL", ) .await; let count = db.count("public.lj_st").await; assert_eq!(count, 3, "LEFT JOIN should include all products"); // Doohickey has no reviews, COALESCE should give 0 let doohickey_rating: i64 = db .query_scalar("SELECT avg_rating::bigint FROM public.lj_st WHERE name = 'Doohickey'") .await; assert_eq!(doohickey_rating, 0); } // ── Subquery in FROM (Derived Table) ─────────────────────────────────── #[tokio::test] async fn test_subquery_in_from_clause() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE sub_src (id INT PRIMARY KEY, category TEXT, val INT)") .await; db.execute( "INSERT INTO sub_src VALUES \ (1, 'A', 10), (2, 'A', 20), (3, 'B', 30), (4, 'B', 40)", ) .await; db.create_st( "sub_st", "SELECT category, total FROM (SELECT category, SUM(val) AS total FROM sub_src GROUP BY category) sub WHERE total > 25", "1m", "FULL", ) .await; // A=30 (>25), B=70 (>25) → 2 rows let count = db.count("public.sub_st").await; assert_eq!(count, 2); } #[tokio::test] async fn test_subquery_in_from_with_join() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE sub_join_facts (id INT PRIMARY KEY, dim_id INT, measure INT)") .await; db.execute("CREATE TABLE sub_join_dims (id INT PRIMARY KEY, label TEXT)") .await; db.execute("INSERT INTO sub_join_dims VALUES (1, 'Alpha'), (2, 'Beta')") .await; db.execute("INSERT INTO sub_join_facts VALUES (1, 1, 100), (2, 1, 200), (3, 2, 50)") .await; db.create_st( "sub_join_st", "SELECT d.label, f.total FROM sub_join_dims d JOIN (SELECT dim_id, SUM(measure) AS total FROM sub_join_facts GROUP BY dim_id) f ON d.id = f.dim_id", "1m", "FULL", ) .await; let count = db.count("public.sub_join_st").await; assert_eq!(count, 2, "Should have one row per dimension"); let alpha_total: i64 = db .query_scalar("SELECT total::bigint FROM public.sub_join_st WHERE label = 'Alpha'") .await; assert_eq!(alpha_total, 300); } // ── Advanced Window Frames ───────────────────────────────────────────── #[tokio::test] async fn test_window_rows_between_frame() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE win_frame_src (id INT PRIMARY KEY, ts DATE, val INT)") .await; db.execute( "INSERT INTO win_frame_src VALUES \ (1, '2026-01-01', 10), (2, '2026-01-02', 20), \ (3, '2026-01-03', 30), (4, '2026-01-04', 40), \ (5, '2026-01-05', 50)", ) .await; // 3-day moving average using ROWS BETWEEN db.create_st( "win_frame_st", "SELECT id, ts, val, AVG(val) OVER (ORDER BY ts ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg FROM win_frame_src", "1m", "FULL", ) .await; let count = db.count("public.win_frame_st").await; assert_eq!(count, 5); // Middle row (id=3): avg of 20,30,40 = 30 let mid_avg: i64 = db .query_scalar("SELECT moving_avg::bigint FROM public.win_frame_st WHERE id = 3") .await; assert_eq!(mid_avg, 30, "Moving average of 20,30,40 should be 30"); } #[tokio::test] async fn test_window_range_frame() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE win_range_src (id INT PRIMARY KEY, grp TEXT, val INT)") .await; db.execute( "INSERT INTO win_range_src VALUES \ (1, 'A', 10), (2, 'A', 20), (3, 'A', 30), \ (4, 'B', 100), (5, 'B', 200)", ) .await; // Cumulative sum with RANGE db.create_st( "win_range_st", "SELECT id, grp, val, SUM(val) OVER (PARTITION BY grp ORDER BY val RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumsum FROM win_range_src", "1m", "FULL", ) .await; let count = db.count("public.win_range_st").await; assert_eq!(count, 5); // A group: 10, 30 (10+20), 60 (10+20+30) let a_last: i64 = db .query_scalar("SELECT cumsum::bigint FROM public.win_range_st WHERE id = 3") .await; assert_eq!(a_last, 60, "Cumulative sum for A group should be 60"); } #[tokio::test] async fn test_window_nth_value() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE win_nth_src (id INT PRIMARY KEY, dept TEXT, salary INT)") .await; db.execute( "INSERT INTO win_nth_src VALUES \ (1, 'eng', 100), (2, 'eng', 120), (3, 'eng', 90), \ (4, 'sales', 80), (5, 'sales', 110)", ) .await; db.create_st( "win_nth_st", "SELECT id, dept, salary, NTH_VALUE(salary, 2) OVER (PARTITION BY dept ORDER BY salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS second_highest FROM win_nth_src", "1m", "FULL", ) .await; let count = db.count("public.win_nth_st").await; assert_eq!(count, 5); // eng: sorted desc = 120, 100, 90 → 2nd = 100 let eng_second: i64 = db .query_scalar( "SELECT DISTINCT second_highest::bigint FROM public.win_nth_st WHERE dept = 'eng'", ) .await; assert_eq!(eng_second, 100); }