//! E2E tests for INTERSECT / EXCEPT differential correctness (F19: G2.4). //! //! Validates set operations (INTERSECT, INTERSECT ALL, EXCEPT, EXCEPT ALL) //! under differential refresh with INSERT, UPDATE, and DELETE mutations. //! //! Prerequisites: `./tests/build_e2e_image.sh` mod e2e; use e2e::E2eDb; // ═══════════════════════════════════════════════════════════════════════ // INTERSECT // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_intersect_basic_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE isect_a (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("CREATE TABLE isect_b (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("INSERT INTO isect_a (val) VALUES (1), (2), (3)") .await; db.execute("INSERT INTO isect_b (val) VALUES (2), (3), (4)") .await; let q = "SELECT val FROM isect_a INTERSECT SELECT val FROM isect_b"; db.create_st("isect_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("isect_st", q).await; // Add value to both → appears in intersection db.execute("INSERT INTO isect_a (val) VALUES (4)").await; db.refresh_st("isect_st").await; db.assert_st_matches_query("isect_st", q).await; // Remove shared value from one side db.execute("DELETE FROM isect_b WHERE val = 2").await; db.refresh_st("isect_st").await; db.assert_st_matches_query("isect_st", q).await; } #[tokio::test] async fn test_intersect_all_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE isect_all_a (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("CREATE TABLE isect_all_b (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("INSERT INTO isect_all_a (val) VALUES (1), (1), (2), (3)") .await; db.execute("INSERT INTO isect_all_b (val) VALUES (1), (2), (2), (3)") .await; let q = "SELECT val FROM isect_all_a INTERSECT ALL SELECT val FROM isect_all_b"; db.create_st("isect_all_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("isect_all_st", q).await; // Add duplicate in A db.execute("INSERT INTO isect_all_a (val) VALUES (2)").await; db.refresh_st("isect_all_st").await; db.assert_st_matches_query("isect_all_st", q).await; // Remove from B db.execute("DELETE FROM isect_all_b WHERE val = 1").await; db.refresh_st("isect_all_st").await; db.assert_st_matches_query("isect_all_st", q).await; } // ═══════════════════════════════════════════════════════════════════════ // EXCEPT // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_except_basic_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE exc_a (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("CREATE TABLE exc_b (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("INSERT INTO exc_a (val) VALUES (1), (2), (3)") .await; db.execute("INSERT INTO exc_b (val) VALUES (2), (4)").await; let q = "SELECT val FROM exc_a EXCEPT SELECT val FROM exc_b"; db.create_st("exc_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("exc_st", q).await; // Add to B a value that exists in A → shrinks result db.execute("INSERT INTO exc_b (val) VALUES (1)").await; db.refresh_st("exc_st").await; db.assert_st_matches_query("exc_st", q).await; // Add to A a new value db.execute("INSERT INTO exc_a (val) VALUES (5)").await; db.refresh_st("exc_st").await; db.assert_st_matches_query("exc_st", q).await; // Remove from B → re-exposes value in A db.execute("DELETE FROM exc_b WHERE val = 2").await; db.refresh_st("exc_st").await; db.assert_st_matches_query("exc_st", q).await; } #[tokio::test] async fn test_except_all_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE exc_all_a (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("CREATE TABLE exc_all_b (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("INSERT INTO exc_all_a (val) VALUES (1), (1), (1), (2)") .await; db.execute("INSERT INTO exc_all_b (val) VALUES (1), (2)") .await; let q = "SELECT val FROM exc_all_a EXCEPT ALL SELECT val FROM exc_all_b"; db.create_st("exc_all_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("exc_all_st", q).await; // Remove duplicate from A db.execute("DELETE FROM exc_all_a WHERE id = (SELECT MIN(id) FROM exc_all_a WHERE val = 1)") .await; db.refresh_st("exc_all_st").await; db.assert_st_matches_query("exc_all_st", q).await; // Add to B → more subtractions db.execute("INSERT INTO exc_all_b (val) VALUES (1)").await; db.refresh_st("exc_all_st").await; db.assert_st_matches_query("exc_all_st", q).await; } // ═══════════════════════════════════════════════════════════════════════ // Multi-way chain (A UNION ALL B EXCEPT C) // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_set_ops_chain_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE so_a (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("CREATE TABLE so_b (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("CREATE TABLE so_c (id SERIAL PRIMARY KEY, val INT)") .await; db.execute("INSERT INTO so_a (val) VALUES (1), (2)").await; db.execute("INSERT INTO so_b (val) VALUES (3), (4)").await; db.execute("INSERT INTO so_c (val) VALUES (2), (3)").await; let q = "(SELECT val FROM so_a UNION ALL SELECT val FROM so_b) \ EXCEPT SELECT val FROM so_c"; db.create_st("so_chain_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("so_chain_st", q).await; db.execute("INSERT INTO so_c (val) VALUES (1)").await; db.refresh_st("so_chain_st").await; db.assert_st_matches_query("so_chain_st", q).await; db.execute("DELETE FROM so_b WHERE val = 4").await; db.refresh_st("so_chain_st").await; db.assert_st_matches_query("so_chain_st", q).await; } // ═══════════════════════════════════════════════════════════════════════ // Multi-column set operations // ═══════════════════════════════════════════════════════════════════════ #[tokio::test] async fn test_intersect_multi_column_differential() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE isect_mc_a (id SERIAL PRIMARY KEY, x INT, y TEXT)") .await; db.execute("CREATE TABLE isect_mc_b (id SERIAL PRIMARY KEY, x INT, y TEXT)") .await; db.execute("INSERT INTO isect_mc_a (x, y) VALUES (1, 'a'), (2, 'b'), (3, 'c')") .await; db.execute("INSERT INTO isect_mc_b (x, y) VALUES (1, 'a'), (2, 'z'), (4, 'd')") .await; let q = "SELECT x, y FROM isect_mc_a INTERSECT SELECT x, y FROM isect_mc_b"; db.create_st("isect_mc_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("isect_mc_st", q).await; // Make (2,'b') match by updating B db.execute("UPDATE isect_mc_b SET y = 'b' WHERE x = 2") .await; db.refresh_st("isect_mc_st").await; db.assert_st_matches_query("isect_mc_st", q).await; // Remove matching row from A db.execute("DELETE FROM isect_mc_a WHERE x = 1").await; db.refresh_st("isect_mc_st").await; db.assert_st_matches_query("isect_mc_st", q).await; } #[tokio::test] async fn test_set_operation_with_nulls() { let db = E2eDb::new().await.with_extension().await; db.execute("CREATE TABLE set_null_a (id INT, val TEXT)") .await; db.execute("CREATE TABLE set_null_b (id INT, val TEXT)") .await; db.execute("INSERT INTO set_null_a VALUES (1, NULL), (NULL, 'A')") .await; db.execute("INSERT INTO set_null_b VALUES (1, NULL), (NULL, 'B')") .await; let q = "SELECT id, val FROM set_null_a UNION ALL SELECT id, val FROM set_null_b"; db.create_st("set_null_st", q, "1m", "DIFFERENTIAL").await; db.assert_st_matches_query("set_null_st", q).await; db.execute("INSERT INTO set_null_a VALUES (NULL, NULL)") .await; db.refresh_st("set_null_st").await; db.assert_st_matches_query("set_null_st", q).await; }