//! E2E tests for EXISTS/IN with OR in WHERE clause (F21: G3.3). //! //! Validates sublink expressions (EXISTS, NOT EXISTS, IN) combined with OR //! under differential refresh. //! //! Prerequisites: `./tests/build_e2e_image.sh` mod e2e; use e2e::E2eDb; // ═══════════════════════════════════════════════════════════════════════ // EXISTS ... OR col = value // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_exists_or_column_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE eo_orders (id SERIAL PRIMARY KEY, customer TEXT, total INT)") .await; db.execute("CREATE TABLE eo_vip (id SERIAL PRIMARY KEY, customer TEXT)") .await; db.execute("INSERT INTO eo_orders (customer, total) VALUES ('a', 100), ('b', 200), ('c', 50)") .await; db.execute("INSERT INTO eo_vip (customer) VALUES ('a')") .await; let q = "SELECT o.customer, o.total FROM eo_orders o \ WHERE EXISTS (SELECT 1 FROM eo_vip v WHERE v.customer = o.customer) \ OR o.total > 150"; db.create_st("eo_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("eo_st", q).await; // Add to VIP — row should now qualify via EXISTS path db.execute("INSERT INTO eo_vip (customer) VALUES ('c')") .await; db.refresh_st("eo_st").await; db.assert_st_matches_query("eo_st", q).await; // Remove from VIP — row 'c' now only qualifies if total > 150 (it doesn't => gone) db.execute("DELETE FROM eo_vip WHERE customer = 'c'").await; db.refresh_st("eo_st").await; db.assert_st_matches_query("eo_st", q).await; // Update total to qualify via OR db.execute("UPDATE eo_orders SET total = 999 WHERE customer = 'c'") .await; db.refresh_st("eo_st").await; db.assert_st_matches_query("eo_st", q).await; } // ═══════════════════════════════════════════════════════════════════════ // IN ... OR col = value // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_in_or_column_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE io_items (id SERIAL PRIMARY KEY, cat TEXT, price INT)") .await; db.execute("CREATE TABLE io_promo (id SERIAL PRIMARY KEY, cat TEXT)") .await; db.execute("INSERT INTO io_items (cat, price) VALUES ('a', 10), ('b', 50), ('c', 100)") .await; db.execute("INSERT INTO io_promo (cat) VALUES ('a')").await; let q = "SELECT i.cat, i.price FROM io_items i \ WHERE i.cat IN (SELECT p.cat FROM io_promo p) \ OR i.price >= 100"; db.create_st("io_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("io_st", q).await; // Add promo for 'b' db.execute("INSERT INTO io_promo (cat) VALUES ('b')").await; db.refresh_st("io_st").await; db.assert_st_matches_query("io_st", q).await; // Remove promo for 'a' db.execute("DELETE FROM io_promo WHERE cat = 'a'").await; db.refresh_st("io_st").await; db.assert_st_matches_query("io_st", q).await; } // ═══════════════════════════════════════════════════════════════════════ // NOT EXISTS ... OR EXISTS // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_not_exists_or_exists_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE ne_main (id SERIAL PRIMARY KEY, code TEXT, val INT)") .await; db.execute("CREATE TABLE ne_block (id SERIAL PRIMARY KEY, code TEXT)") .await; db.execute("CREATE TABLE ne_fast (id SERIAL PRIMARY KEY, code TEXT)") .await; db.execute("INSERT INTO ne_main (code, val) VALUES ('a', 1), ('b', 2), ('c', 3)") .await; db.execute("INSERT INTO ne_block (code) VALUES ('a')").await; db.execute("INSERT INTO ne_fast (code) VALUES ('c')").await; let q = "SELECT m.code, m.val FROM ne_main m \ WHERE NOT EXISTS (SELECT 1 FROM ne_block b WHERE b.code = m.code) \ OR EXISTS (SELECT 1 FROM ne_fast f WHERE f.code = m.code)"; db.create_st("ne_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("ne_st", q).await; // Unblock 'a' db.execute("DELETE FROM ne_block WHERE code = 'a'").await; db.refresh_st("ne_st").await; db.assert_st_matches_query("ne_st", q).await; // Block 'b' and remove from fast db.execute("INSERT INTO ne_block (code) VALUES ('b')").await; db.execute("DELETE FROM ne_fast WHERE code = 'c'").await; db.refresh_st("ne_st").await; db.assert_st_matches_query("ne_st", q).await; // Add fast-track for 'b' → qualifies via EXISTS even though blocked db.execute("INSERT INTO ne_fast (code) VALUES ('b')").await; db.refresh_st("ne_st").await; db.assert_st_matches_query("ne_st", q).await; } // ═══════════════════════════════════════════════════════════════════════ // EXISTS with aggregate in subquery // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_exists_with_having_in_subquery_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE eh_cust (id SERIAL PRIMARY KEY, name TEXT)") .await; db.execute("CREATE TABLE eh_ord (id SERIAL PRIMARY KEY, cust_id INT, amount INT)") .await; db.execute("INSERT INTO eh_cust (id, name) VALUES (1, 'alice'), (2, 'bob'), (3, 'carol')") .await; db.execute("INSERT INTO eh_ord (cust_id, amount) VALUES (1, 100), (1, 200), (2, 50)") .await; let q = "SELECT c.name FROM eh_cust c \ WHERE EXISTS ( \ SELECT 1 FROM eh_ord o WHERE o.cust_id = c.id \ GROUP BY o.cust_id HAVING SUM(o.amount) > 100 \ )"; db.create_st("eh_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("eh_st", q).await; // Bob's total crosses threshold db.execute("INSERT INTO eh_ord (cust_id, amount) VALUES (2, 200)") .await; db.refresh_st("eh_st").await; db.assert_st_matches_query("eh_st", q).await; // Alice's total drops below db.execute("DELETE FROM eh_ord WHERE cust_id = 1 AND amount = 200") .await; db.refresh_st("eh_st").await; db.assert_st_matches_query("eh_st", q).await; }