#!/usr/bin/env tclsh

set testdir [file dirname $argv0]
source $testdir/tester.tcl

# Test that INSERT with reused primary keys maintains correct aggregate counts
# When a row is deleted and a new row is inserted with the same primary key
# but different group value, all groups should maintain correct counts
do_execsql_test_on_specific_db {:memory:} matview-insert-reused-key-maintains-all-groups {
    CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT);
    INSERT INTO t VALUES (1, 'A'), (2, 'B');

    CREATE MATERIALIZED VIEW v AS
        SELECT val, COUNT(*) as cnt
        FROM t
        GROUP BY val;

    -- Initial state: A=1, B=1
    SELECT * FROM v ORDER BY val;

    -- Delete id=1 (which has 'A')
    DELETE FROM t WHERE id = 1;
    SELECT * FROM v ORDER BY val;

    -- Insert id=1 with different value 'C'
    -- This should NOT affect group 'B'
    INSERT INTO t VALUES (1, 'C');
    SELECT * FROM v ORDER BY val;
} {A|1
B|1
B|1
B|1
C|1}

do_execsql_test_on_specific_db {:memory:} matview-basic-filter-population {
    CREATE TABLE products(id INTEGER, name TEXT, price INTEGER, category TEXT);
    INSERT INTO products VALUES
        (1, 'Laptop', 1200, 'Electronics'),
        (2, 'Mouse', 25, 'Electronics'),
        (3, 'Desk', 350, 'Furniture'),
        (4, 'Chair', 150, 'Furniture'),
        (5, 'Monitor', 400, 'Electronics'),
        (6, 'Keyboard', 75, 'Electronics');

    CREATE MATERIALIZED VIEW expensive_items AS
        SELECT * FROM products WHERE price > 200;

    SELECT id, name, price FROM expensive_items ORDER BY id;
} {1|Laptop|1200
3|Desk|350
5|Monitor|400}

do_execsql_test_on_specific_db {:memory:} matview-aggregation-population {
    CREATE TABLE sales(product_id INTEGER, quantity INTEGER, day INTEGER);
    INSERT INTO sales VALUES
        (1, 2, 1),
        (2, 5, 1),
        (1, 1, 2),
        (3, 1, 2),
        (2, 3, 3),
        (1, 1, 3);

    CREATE MATERIALIZED VIEW daily_totals AS
        SELECT day, SUM(quantity) as total, COUNT(*) as transactions
        FROM sales
        GROUP BY day;

    SELECT * FROM daily_totals ORDER BY day;
} {1|7.0|2
2|2.0|2
3|4.0|2}

do_execsql_test_on_specific_db {:memory:} matview-filter-with-groupby {
    CREATE TABLE t(a INTEGER, b INTEGER);
    INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);

    CREATE MATERIALIZED VIEW v AS 
        SELECT b as yourb, SUM(a) as mysum, COUNT(a) as mycount 
        FROM t 
        WHERE b > 2 
        GROUP BY b;

    SELECT * FROM v ORDER BY yourb;
} {3|3.0|1
6|6.0|1
7|7.0|1}

do_execsql_test_on_specific_db {:memory:} matview-insert-maintenance {
    CREATE TABLE t(a INTEGER, b INTEGER);
    CREATE MATERIALIZED VIEW v AS 
        SELECT b, SUM(a) as total, COUNT(*) as cnt 
        FROM t 
        WHERE b > 2 
        GROUP BY b;

    INSERT INTO t VALUES (3,3), (6,6);
    SELECT * FROM v ORDER BY b;

    INSERT INTO t VALUES (4,3), (5,6);
    SELECT * FROM v ORDER BY b;

    INSERT INTO t VALUES (1,1), (2,2);
    SELECT * FROM v ORDER BY b;
} {3|3.0|1
6|6.0|1
3|7.0|2
6|11.0|2
3|7.0|2
6|11.0|2}

do_execsql_test_on_specific_db {:memory:} matview-delete-maintenance {
    CREATE TABLE items(id INTEGER, category TEXT, amount INTEGER);
    INSERT INTO items VALUES 
        (1, 'A', 10),
        (2, 'B', 20),
        (3, 'A', 30),
        (4, 'B', 40),
        (5, 'A', 50);

    CREATE MATERIALIZED VIEW category_sums AS
        SELECT category, SUM(amount) as total, COUNT(*) as cnt
        FROM items
        GROUP BY category;

    SELECT * FROM category_sums ORDER BY category;

    DELETE FROM items WHERE id = 3;
    SELECT * FROM category_sums ORDER BY category;

    DELETE FROM items WHERE category = 'B';
    SELECT * FROM category_sums ORDER BY category;
} {A|90.0|3
B|60.0|2
A|60.0|2
B|60.0|2
A|60.0|2}

do_execsql_test_on_specific_db {:memory:} matview-update-maintenance {
    CREATE TABLE records(id INTEGER, value INTEGER, status INTEGER);
    INSERT INTO records VALUES
        (1, 100, 1),
        (2, 200, 2),
        (3, 300, 1),
        (4, 400, 2);

    CREATE MATERIALIZED VIEW status_totals AS
        SELECT status, SUM(value) as total, COUNT(*) as cnt
        FROM records
        GROUP BY status;

    SELECT * FROM status_totals ORDER BY status;

    UPDATE records SET value = 150 WHERE id = 1;
    SELECT * FROM status_totals ORDER BY status;

    UPDATE records SET status = 2 WHERE id = 3;
    SELECT * FROM status_totals ORDER BY status;
} {1|400.0|2
2|600.0|2
1|450.0|2
2|600.0|2
1|150.0|1
2|900.0|3}

do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-basic {
    CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
    INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);

    CREATE MATERIALIZED VIEW v AS 
        SELECT * FROM t WHERE b > 2;

    SELECT * FROM v ORDER BY a;
} {3|3
6|6
7|7}

do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-update-rowid {
    CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
    INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);

    CREATE MATERIALIZED VIEW v AS 
        SELECT * FROM t WHERE b > 2;

    SELECT * FROM v ORDER BY a;

    UPDATE t SET a = 1 WHERE b = 3;
    SELECT * FROM v ORDER BY a;

    UPDATE t SET a = 10 WHERE a = 6;
    SELECT * FROM v ORDER BY a;
} {3|3
6|6
7|7
1|3
6|6
7|7
1|3
7|7
10|6}

do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-update-value {
    CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER);
    INSERT INTO t(a,b) VALUES (2,2), (3,3), (6,6), (7,7);

    CREATE MATERIALIZED VIEW v AS 
        SELECT * FROM t WHERE b > 2;

    SELECT * FROM v ORDER BY a;

    UPDATE t SET b = 1 WHERE a = 6;
    SELECT * FROM v ORDER BY a;

    UPDATE t SET b = 5 WHERE a = 2;
    SELECT * FROM v ORDER BY a;
} {3|3
6|6
7|7
3|3
7|7
2|5
3|3
7|7}

do_execsql_test_on_specific_db {:memory:} matview-integer-primary-key-with-aggregation {
    CREATE TABLE t(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER);
    INSERT INTO t VALUES 
        (1, 10, 100),
        (2, 10, 200),
        (3, 20, 300),
        (4, 20, 400),
        (5, 10, 500);

    CREATE MATERIALIZED VIEW v AS
        SELECT b, SUM(c) as total, COUNT(*) as cnt
        FROM t
        WHERE a > 2
        GROUP BY b;

    SELECT * FROM v ORDER BY b;

    UPDATE t SET a = 6 WHERE a = 1;
    SELECT * FROM v ORDER BY b;

    DELETE FROM t WHERE a = 3;
    SELECT * FROM v ORDER BY b;
} {10|500.0|1
20|700.0|2
10|600.0|2
20|700.0|2
10|600.0|2
20|400.0|1}

do_execsql_test_on_specific_db {:memory:} matview-complex-filter-aggregation {
    CREATE TABLE transactions(
        id INTEGER,
        account INTEGER,
        amount INTEGER,
        type INTEGER
    );

    INSERT INTO transactions VALUES
        (1, 100, 50, 1),
        (2, 100, 30, 2),
        (3, 200, 100, 1),
        (4, 100, 20, 1),
        (5, 200, 40, 2),
        (6, 300, 60, 1);

    CREATE MATERIALIZED VIEW account_deposits AS
        SELECT account, SUM(amount) as total_deposits, COUNT(*) as deposit_count
        FROM transactions
        WHERE type = 1
        GROUP BY account;

    SELECT * FROM account_deposits ORDER BY account;

    INSERT INTO transactions VALUES (7, 100, 25, 1);
    SELECT * FROM account_deposits ORDER BY account;

    UPDATE transactions SET amount = 80 WHERE id = 1;
    SELECT * FROM account_deposits ORDER BY account;

    DELETE FROM transactions WHERE id = 3;
    SELECT * FROM account_deposits ORDER BY account;
} {100|70.0|2
200|100.0|1
300|60.0|1
100|95.0|3
200|100.0|1
300|60.0|1
100|125.0|3
200|100.0|1
300|60.0|1
100|125.0|3
300|60.0|1}

do_execsql_test_on_specific_db {:memory:} matview-sum-count-only {
    CREATE TABLE data(id INTEGER, value INTEGER, category INTEGER);
    INSERT INTO data VALUES
        (1, 10, 1),
        (2, 20, 1),
        (3, 30, 2),
        (4, 40, 2),
        (5, 50, 1);

    CREATE MATERIALIZED VIEW category_stats AS
        SELECT category,
               SUM(value) as sum_val,
               COUNT(*) as cnt
        FROM data
        GROUP BY category;

    SELECT * FROM category_stats ORDER BY category;

    INSERT INTO data VALUES (6, 5, 1);
    SELECT * FROM category_stats ORDER BY category;

    UPDATE data SET value = 35 WHERE id = 3;
    SELECT * FROM category_stats ORDER BY category;
} {1|80.0|3
2|70.0|2
1|85.0|4
2|70.0|2
1|85.0|4
2|75.0|2}

do_execsql_test_on_specific_db {:memory:} matview-empty-table-population {
    CREATE TABLE t(a INTEGER, b INTEGER);
    CREATE MATERIALIZED VIEW v AS
        SELECT b, SUM(a) as total, COUNT(*) as cnt
        FROM t
        WHERE b > 5
        GROUP BY b;

    SELECT COUNT(*) FROM v;

    INSERT INTO t VALUES (1, 3), (2, 7), (3, 9);
    SELECT * FROM v ORDER BY b;
} {0
7|2.0|1
9|3.0|1}

do_execsql_test_on_specific_db {:memory:} matview-all-rows-filtered {
    CREATE TABLE t(a INTEGER, b INTEGER);
    INSERT INTO t VALUES (1, 1), (2, 2), (3, 3);

    CREATE MATERIALIZED VIEW v AS
        SELECT * FROM t WHERE b > 10;

    SELECT COUNT(*) FROM v;

    INSERT INTO t VALUES (11, 11);
    SELECT * FROM v;

    UPDATE t SET b = 1 WHERE a = 11;
    SELECT COUNT(*) FROM v;
} {0
11|11
0}

do_execsql_test_on_specific_db {:memory:} matview-mixed-operations-sequence {
    CREATE TABLE orders(
        order_id INTEGER PRIMARY KEY,
        customer_id INTEGER,
        amount INTEGER
    );

    INSERT INTO orders VALUES (1, 100, 50);
    INSERT INTO orders VALUES (2, 200, 75);

    CREATE MATERIALIZED VIEW customer_totals AS
        SELECT customer_id, SUM(amount) as total, COUNT(*) as order_count
        FROM orders
        GROUP BY customer_id;

    SELECT * FROM customer_totals ORDER BY customer_id;

    INSERT INTO orders VALUES (3, 100, 25);
    SELECT * FROM customer_totals ORDER BY customer_id;

    UPDATE orders SET amount = 100 WHERE order_id = 2;
    SELECT * FROM customer_totals ORDER BY customer_id;

    DELETE FROM orders WHERE order_id = 1;
    SELECT * FROM customer_totals ORDER BY customer_id;

    INSERT INTO orders VALUES (4, 300, 150);
    SELECT * FROM customer_totals ORDER BY customer_id;
} {100|50.0|1
200|75.0|1
100|75.0|2
200|75.0|1
100|75.0|2
200|100.0|1
100|25.0|1
200|100.0|1
100|25.0|1
200|100.0|1
300|150.0|1}

do_execsql_test_on_specific_db {:memory:} matview-projections {
    CREATE TABLE t(a,b);

    CREATE MATERIALIZED VIEW v AS
        SELECT b, a, b + a as c , (b * a) + 10 as d , min(a,b) as e
        FROM t
        where b > 2;

    INSERT INTO t VALUES (1, 1);
    INSERT INTO t VALUES (2, 2);
    INSERT INTO t VALUES (3, 4);
    INSERT INTO t VALUES (4, 3);

    SELECT * from v;
} {4|3|7|22|3
3|4|7|22|3}

do_execsql_test_on_specific_db {:memory:} matview-rollback-insert {
    CREATE TABLE t(a INTEGER, b INTEGER);
    INSERT INTO t VALUES (1, 10), (2, 20), (3, 30);

    CREATE MATERIALIZED VIEW v AS
        SELECT * FROM t WHERE b > 15;

    SELECT * FROM v ORDER BY a;

    BEGIN;
    INSERT INTO t VALUES (4, 40), (5, 50);
    SELECT * FROM v ORDER BY a;
    ROLLBACK;

    SELECT * FROM v ORDER BY a;
} {2|20
3|30
2|20
3|30
4|40
5|50
2|20
3|30}

do_execsql_test_on_specific_db {:memory:} matview-rollback-delete {
    CREATE TABLE t(a INTEGER, b INTEGER);
    INSERT INTO t VALUES (1, 10), (2, 20), (3, 30), (4, 40);

    CREATE MATERIALIZED VIEW v AS
        SELECT * FROM t WHERE b > 15;

    SELECT * FROM v ORDER BY a;

    BEGIN;
    DELETE FROM t WHERE a IN (2, 3);
    SELECT * FROM v ORDER BY a;
    ROLLBACK;

    SELECT * FROM v ORDER BY a;
} {2|20
3|30
4|40
4|40
2|20
3|30
4|40}

do_execsql_test_on_specific_db {:memory:} matview-rollback-update {
    CREATE TABLE t(a INTEGER, b INTEGER);
    INSERT INTO t VALUES (1, 10), (2, 20), (3, 30);

    CREATE MATERIALIZED VIEW v AS
        SELECT * FROM t WHERE b > 15;

    SELECT * FROM v ORDER BY a;

    BEGIN;
    UPDATE t SET b = 5 WHERE a = 2;
    UPDATE t SET b = 35 WHERE a = 1;
    SELECT * FROM v ORDER BY a;
    ROLLBACK;

    SELECT * FROM v ORDER BY a;
} {2|20
3|30
1|35
3|30
2|20
3|30}

do_execsql_test_on_specific_db {:memory:} matview-rollback-aggregation {
    CREATE TABLE sales(product_id INTEGER, amount INTEGER);
    INSERT INTO sales VALUES (1, 100), (1, 200), (2, 150), (2, 250);

    CREATE MATERIALIZED VIEW product_totals AS
        SELECT product_id, SUM(amount) as total, COUNT(*) as cnt
        FROM sales
        GROUP BY product_id;

    SELECT * FROM product_totals ORDER BY product_id;

    BEGIN;
    INSERT INTO sales VALUES (1, 50), (3, 300);
    SELECT * FROM product_totals ORDER BY product_id;
    ROLLBACK;

    SELECT * FROM product_totals ORDER BY product_id;
} {1|300.0|2
2|400.0|2
1|350.0|3
2|400.0|2
3|300.0|1
1|300.0|2
2|400.0|2}

do_execsql_test_on_specific_db {:memory:} matview-rollback-mixed-operations {
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer INTEGER, amount INTEGER);
    INSERT INTO orders VALUES (1, 100, 50), (2, 200, 75), (3, 100, 25);

    CREATE MATERIALIZED VIEW customer_totals AS
        SELECT customer, SUM(amount) as total, COUNT(*) as cnt
        FROM orders
        GROUP BY customer;

    SELECT * FROM customer_totals ORDER BY customer;

    BEGIN;
    INSERT INTO orders VALUES (4, 100, 100);
    UPDATE orders SET amount = 150 WHERE id = 2;
    DELETE FROM orders WHERE id = 3;
    SELECT * FROM customer_totals ORDER BY customer;
    ROLLBACK;

    SELECT * FROM customer_totals ORDER BY customer;
} {100|75.0|2
200|75.0|1
100|150.0|2
200|150.0|1
100|75.0|2
200|75.0|1}

do_execsql_test_on_specific_db {:memory:} matview-rollback-filtered-aggregation {
    CREATE TABLE transactions(id INTEGER, account INTEGER, amount INTEGER, type TEXT);
    INSERT INTO transactions VALUES
        (1, 100, 50, 'deposit'),
        (2, 100, 30, 'withdraw'),
        (3, 200, 100, 'deposit'),
        (4, 200, 40, 'withdraw');

    CREATE MATERIALIZED VIEW deposits AS
        SELECT account, SUM(amount) as total_deposits, COUNT(*) as cnt
        FROM transactions
        WHERE type = 'deposit'
        GROUP BY account;

    SELECT * FROM deposits ORDER BY account;

    BEGIN;
    INSERT INTO transactions VALUES (5, 100, 75, 'deposit');
    UPDATE transactions SET amount = 60 WHERE id = 1;
    DELETE FROM transactions WHERE id = 3;
    SELECT * FROM deposits ORDER BY account;
    ROLLBACK;

    SELECT * FROM deposits ORDER BY account;
} {100|50.0|1
200|100.0|1
100|135.0|2
100|50.0|1
200|100.0|1}

do_execsql_test_on_specific_db {:memory:} matview-rollback-empty-view {
    CREATE TABLE t(a INTEGER, b INTEGER);
    INSERT INTO t VALUES (1, 5), (2, 8);

    CREATE MATERIALIZED VIEW v AS
        SELECT * FROM t WHERE b > 10;

    SELECT COUNT(*) FROM v;

    BEGIN;
    INSERT INTO t VALUES (3, 15), (4, 20);
    SELECT * FROM v ORDER BY a;
    ROLLBACK;

    SELECT COUNT(*) FROM v;
} {0
3|15
4|20
0}

# Join tests for materialized views

do_execsql_test_on_specific_db {:memory:} matview-simple-join {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
    CREATE TABLE orders(order_id INTEGER PRIMARY KEY, user_id INTEGER, product_id INTEGER, quantity INTEGER);

    INSERT INTO users VALUES (1, 'Alice', 25), (2, 'Bob', 30), (3, 'Charlie', 35);
    INSERT INTO orders VALUES (1, 1, 100, 5), (2, 1, 101, 3), (3, 2, 100, 7);

    CREATE MATERIALIZED VIEW user_orders AS
        SELECT u.name, o.quantity
        FROM users u
        JOIN orders o ON u.id = o.user_id;

    SELECT * FROM user_orders ORDER BY name, quantity;
} {Alice|3
Alice|5
Bob|7}

do_execsql_test_on_specific_db {:memory:} matview-join-with-aggregation {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE orders(order_id INTEGER PRIMARY KEY, user_id INTEGER, amount INTEGER);

    INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
    INSERT INTO orders VALUES (1, 1, 100), (2, 1, 150), (3, 2, 200), (4, 2, 50);

    CREATE MATERIALIZED VIEW user_totals AS
        SELECT u.name, SUM(o.amount) as total_amount
        FROM users u
        JOIN orders o ON u.id = o.user_id
        GROUP BY u.name;

    SELECT * FROM user_totals ORDER BY name;
} {Alice|250.0
Bob|250.0}

do_execsql_test_on_specific_db {:memory:} matview-three-way-join {
    CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT, city TEXT);
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, product_id INTEGER, quantity INTEGER);
    CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, price INTEGER);

    INSERT INTO customers VALUES (1, 'Alice', 'NYC'), (2, 'Bob', 'LA');
    INSERT INTO products VALUES (1, 'Widget', 10), (2, 'Gadget', 20);
    INSERT INTO orders VALUES (1, 1, 1, 5), (2, 1, 2, 3), (3, 2, 1, 2);

    CREATE MATERIALIZED VIEW sales_summary AS
        SELECT c.name as customer_name, p.name as product_name, o.quantity
        FROM customers c
        JOIN orders o ON c.id = o.customer_id
        JOIN products p ON o.product_id = p.id;

    SELECT * FROM sales_summary ORDER BY customer_name, product_name;
} {Alice|Gadget|3
Alice|Widget|5
Bob|Widget|2}

do_execsql_test_on_specific_db {:memory:} matview-three-way-join-with-aggregation {
    CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, product_id INTEGER, quantity INTEGER);
    CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, price INTEGER);

    INSERT INTO customers VALUES (1, 'Alice'), (2, 'Bob');
    INSERT INTO products VALUES (1, 'Widget', 10), (2, 'Gadget', 20);
    INSERT INTO orders VALUES (1, 1, 1, 5), (2, 1, 2, 3), (3, 2, 1, 2), (4, 1, 1, 4);

    CREATE MATERIALIZED VIEW sales_totals AS
        SELECT c.name as customer_name, p.name as product_name,
               SUM(o.quantity) as total_quantity,
               SUM(o.quantity * p.price) as total_value
        FROM customers c
        JOIN orders o ON c.id = o.customer_id
        JOIN products p ON o.product_id = p.id
        GROUP BY c.name, p.name;

    SELECT * FROM sales_totals ORDER BY customer_name, product_name;
} {Alice|Gadget|3.0|60.0
Alice|Widget|9.0|90.0
Bob|Widget|2.0|20.0}

do_execsql_test_on_specific_db {:memory:} matview-join-incremental-insert {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE orders(order_id INTEGER PRIMARY KEY, user_id INTEGER, amount INTEGER);

    INSERT INTO users VALUES (1, 'Alice');
    INSERT INTO orders VALUES (1, 1, 100);

    CREATE MATERIALIZED VIEW user_orders AS
        SELECT u.name, o.amount
        FROM users u
        JOIN orders o ON u.id = o.user_id;

    SELECT COUNT(*) FROM user_orders;

    INSERT INTO orders VALUES (2, 1, 150);
    SELECT COUNT(*) FROM user_orders;

    INSERT INTO users VALUES (2, 'Bob');
    INSERT INTO orders VALUES (3, 2, 200);
    SELECT COUNT(*) FROM user_orders;
} {1
2
3}

do_execsql_test_on_specific_db {:memory:} matview-join-incremental-delete {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE orders(order_id INTEGER PRIMARY KEY, user_id INTEGER, amount INTEGER);

    INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
    INSERT INTO orders VALUES (1, 1, 100), (2, 1, 150), (3, 2, 200);

    CREATE MATERIALIZED VIEW user_orders AS
        SELECT u.name, o.amount
        FROM users u
        JOIN orders o ON u.id = o.user_id;

    SELECT COUNT(*) FROM user_orders;

    DELETE FROM orders WHERE order_id = 2;
    SELECT COUNT(*) FROM user_orders;

    DELETE FROM users WHERE id = 2;
    SELECT COUNT(*) FROM user_orders;
} {3
2
1}

do_execsql_test_on_specific_db {:memory:} matview-join-incremental-update {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE orders(order_id INTEGER PRIMARY KEY, user_id INTEGER, amount INTEGER);

    INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
    INSERT INTO orders VALUES (1, 1, 100), (2, 2, 200);

    CREATE MATERIALIZED VIEW user_orders AS
        SELECT u.name, o.amount
        FROM users u
        JOIN orders o ON u.id = o.user_id;

    SELECT * FROM user_orders ORDER BY name;

    UPDATE orders SET amount = 150 WHERE order_id = 1;
    SELECT * FROM user_orders ORDER BY name;

    UPDATE users SET name = 'Robert' WHERE id = 2;
    SELECT * FROM user_orders ORDER BY name;
} {Alice|100
Bob|200
Alice|150
Bob|200
Alice|150
Robert|200}

do_execsql_test_on_specific_db {:memory:} matview-join-with-filter {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
    CREATE TABLE orders(order_id INTEGER PRIMARY KEY, user_id INTEGER, amount INTEGER);

    INSERT INTO users VALUES (1, 'Alice', 25), (2, 'Bob', 35), (3, 'Charlie', 20);
    INSERT INTO orders VALUES (1, 1, 100), (2, 2, 200), (3, 3, 150);

    CREATE MATERIALIZED VIEW adult_orders AS
        SELECT u.name, o.amount
        FROM users u
        JOIN orders o ON u.id = o.user_id
        WHERE u.age > 21;

    SELECT * FROM adult_orders ORDER BY name;
} {Alice|100
Bob|200}

do_execsql_test_on_specific_db {:memory:} matview-join-rollback {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE orders(order_id INTEGER PRIMARY KEY, user_id INTEGER, amount INTEGER);

    INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
    INSERT INTO orders VALUES (1, 1, 100), (2, 2, 200);

    CREATE MATERIALIZED VIEW user_orders AS
        SELECT u.name, o.amount
        FROM users u
        JOIN orders o ON u.id = o.user_id;

    SELECT COUNT(*) FROM user_orders;

    BEGIN;
    INSERT INTO users VALUES (3, 'Charlie');
    INSERT INTO orders VALUES (3, 3, 300);
    SELECT COUNT(*) FROM user_orders;
    ROLLBACK;

    SELECT COUNT(*) FROM user_orders;
} {2
3
2}

# ===== COMPREHENSIVE JOIN TESTS =====

# Test 1: Join with filter BEFORE the join (on base tables)
do_execsql_test_on_specific_db {:memory:} matview-join-with-pre-filter {
    CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, department TEXT, salary INTEGER);
    CREATE TABLE departments(id INTEGER PRIMARY KEY, dept_name TEXT, budget INTEGER);

    INSERT INTO employees VALUES
        (1, 'Alice', 'Engineering', 80000),
        (2, 'Bob', 'Engineering', 90000),
        (3, 'Charlie', 'Sales', 60000),
        (4, 'David', 'Sales', 65000),
        (5, 'Eve', 'HR', 70000);

    INSERT INTO departments VALUES
        (1, 'Engineering', 500000),
        (2, 'Sales', 300000),
        (3, 'HR', 200000);

    -- View: Join only high-salary employees with their departments
    CREATE MATERIALIZED VIEW high_earners_by_dept AS
        SELECT e.name, e.salary, d.dept_name, d.budget
        FROM employees e
        JOIN departments d ON e.department = d.dept_name
        WHERE e.salary > 70000;

    SELECT * FROM high_earners_by_dept ORDER BY salary DESC;
} {Bob|90000|Engineering|500000
Alice|80000|Engineering|500000}

# Test 2: Join with filter AFTER the join
do_execsql_test_on_specific_db {:memory:} matview-join-with-post-filter {
    CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, category_id INTEGER, price INTEGER);
    CREATE TABLE categories(id INTEGER PRIMARY KEY, name TEXT, min_price INTEGER);

    INSERT INTO products VALUES
        (1, 'Laptop', 1, 1200),
        (2, 'Mouse', 1, 25),
        (3, 'Shirt', 2, 50),
        (4, 'Shoes', 2, 120);

    INSERT INTO categories VALUES
        (1, 'Electronics', 100),
        (2, 'Clothing', 30);

    -- View: Products that meet or exceed their category's minimum price
    CREATE MATERIALIZED VIEW premium_products AS
        SELECT p.name as product, c.name as category, p.price, c.min_price
        FROM products p
        JOIN categories c ON p.category_id = c.id
        WHERE p.price >= c.min_price;

    SELECT * FROM premium_products ORDER BY price DESC;
} {Laptop|Electronics|1200|100
Shoes|Clothing|120|30
Shirt|Clothing|50|30}

# Test 3: Join with aggregation BEFORE the join
do_execsql_test_on_specific_db {:memory:} matview-aggregation-before-join {
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, product_id INTEGER, quantity INTEGER, order_date INTEGER);
    CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT, tier TEXT);

    INSERT INTO orders VALUES
        (1, 1, 101, 2, 1),
        (2, 1, 102, 1, 1),
        (3, 2, 101, 5, 1),
        (4, 1, 101, 3, 2),
        (5, 2, 103, 2, 2),
        (6, 3, 102, 1, 2);

    INSERT INTO customers VALUES
        (1, 'Alice', 'Gold'),
        (2, 'Bob', 'Silver'),
        (3, 'Charlie', 'Bronze');

    -- View: Customer order counts joined with customer details
    -- Note: Simplified to avoid subquery issues with DBSP compiler
    CREATE MATERIALIZED VIEW customer_order_summary AS
        SELECT c.name, c.tier, COUNT(o.id) as order_count, SUM(o.quantity) as total_quantity
        FROM customers c
        JOIN orders o ON c.id = o.customer_id
        GROUP BY c.id, c.name, c.tier;

    SELECT * FROM customer_order_summary ORDER BY total_quantity DESC;
} {Bob|Silver|2|7.0
Alice|Gold|3|6.0
Charlie|Bronze|1|1.0}

# Test 4: Join with aggregation AFTER the join
do_execsql_test_on_specific_db {:memory:} matview-aggregation-after-join {
    CREATE TABLE sales(id INTEGER PRIMARY KEY, product_id INTEGER, store_id INTEGER, units_sold INTEGER, revenue INTEGER);
    CREATE TABLE stores(id INTEGER PRIMARY KEY, name TEXT, region TEXT);

    INSERT INTO sales VALUES
        (1, 1, 1, 10, 1000),
        (2, 1, 2, 15, 1500),
        (3, 2, 1, 5, 250),
        (4, 2, 2, 8, 400),
        (5, 1, 3, 12, 1200),
        (6, 2, 3, 6, 300);

    INSERT INTO stores VALUES
        (1, 'StoreA', 'North'),
        (2, 'StoreB', 'North'),
        (3, 'StoreC', 'South');

    -- View: Regional sales summary (aggregate after joining)
    CREATE MATERIALIZED VIEW regional_sales AS
        SELECT st.region, SUM(s.units_sold) as total_units, SUM(s.revenue) as total_revenue
        FROM sales s
        JOIN stores st ON s.store_id = st.id
        GROUP BY st.region;

    SELECT * FROM regional_sales ORDER BY total_revenue DESC;
} {North|38.0|3150.0
South|18.0|1500.0}

# Test 5: Modifying both tables in same transaction
do_execsql_test_on_specific_db {:memory:} matview-join-both-tables-modified {
    CREATE TABLE authors(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE books(id INTEGER PRIMARY KEY, title TEXT, author_id INTEGER, year INTEGER);

    INSERT INTO authors VALUES (1, 'Orwell'), (2, 'Asimov');
    INSERT INTO books VALUES (1, '1984', 1, 1949), (2, 'Foundation', 2, 1951);

    CREATE MATERIALIZED VIEW author_books AS
        SELECT a.name, b.title, b.year
        FROM authors a
        JOIN books b ON a.id = b.author_id;

    SELECT COUNT(*) FROM author_books;

    BEGIN;
    INSERT INTO authors VALUES (3, 'Herbert');
    INSERT INTO books VALUES (3, 'Dune', 3, 1965);
    SELECT COUNT(*) FROM author_books;
    COMMIT;

    SELECT * FROM author_books ORDER BY year;
} {2
3
Orwell|1984|1949
Asimov|Foundation|1951
Herbert|Dune|1965}

# Test 6: Modifying only one table in transaction
do_execsql_test_on_specific_db {:memory:} matview-join-single-table-modified {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, active INTEGER);
    CREATE TABLE posts(id INTEGER PRIMARY KEY, user_id INTEGER, content TEXT);

    INSERT INTO users VALUES (1, 'Alice', 1), (2, 'Bob', 1), (3, 'Charlie', 0);
    INSERT INTO posts VALUES (1, 1, 'Hello'), (2, 1, 'World'), (3, 2, 'Test');

    CREATE MATERIALIZED VIEW active_user_posts AS
        SELECT u.name, p.content
        FROM users u
        JOIN posts p ON u.id = p.user_id
        WHERE u.active = 1;

    SELECT COUNT(*) FROM active_user_posts;

    -- Add posts for existing user (modify only posts table)
    BEGIN;
    INSERT INTO posts VALUES (4, 1, 'NewPost'), (5, 2, 'Another');
    SELECT COUNT(*) FROM active_user_posts;
    COMMIT;

    SELECT * FROM active_user_posts ORDER BY name, content;
} {3
5
Alice|Hello
Alice|NewPost
Alice|World
Bob|Another
Bob|Test}


do_execsql_test_on_specific_db {:memory:} matview-three-way-incremental {
    CREATE TABLE students(id INTEGER PRIMARY KEY, name TEXT, major TEXT);
    CREATE TABLE courses(id INTEGER PRIMARY KEY, name TEXT, department TEXT, credits INTEGER);
    CREATE TABLE enrollments(student_id INTEGER, course_id INTEGER, grade TEXT, PRIMARY KEY(student_id, course_id));

    INSERT INTO students VALUES (1, 'Alice', 'CS'), (2, 'Bob', 'Math');
    INSERT INTO courses VALUES (1, 'DatabaseSystems', 'CS', 3), (2, 'Calculus', 'Math', 4);
    INSERT INTO enrollments VALUES (1, 1, 'A'), (2, 2, 'B');

    CREATE MATERIALIZED VIEW student_transcripts AS
        SELECT s.name as student, c.name as course, c.credits, e.grade
        FROM students s
        JOIN enrollments e ON s.id = e.student_id
        JOIN courses c ON e.course_id = c.id;

    SELECT COUNT(*) FROM student_transcripts;

    -- Add new student
    INSERT INTO students VALUES (3, 'Charlie', 'CS');
    SELECT COUNT(*) FROM student_transcripts;

    -- Enroll new student
    INSERT INTO enrollments VALUES (3, 1, 'A'), (3, 2, 'A');
    SELECT COUNT(*) FROM student_transcripts;

    -- Add new course
    INSERT INTO courses VALUES (3, 'Algorithms', 'CS', 3);
    SELECT COUNT(*) FROM student_transcripts;

    -- Enroll existing students in new course
    INSERT INTO enrollments VALUES (1, 3, 'B'), (3, 3, 'A');
    SELECT COUNT(*) FROM student_transcripts;

    SELECT * FROM student_transcripts ORDER BY student, course;
} {2
2
4
4
6
Alice|Algorithms|3|B
Alice|DatabaseSystems|3|A
Bob|Calculus|4|B
Charlie|Algorithms|3|A
Charlie|Calculus|4|A
Charlie|DatabaseSystems|3|A}

do_execsql_test_on_specific_db {:memory:} matview-self-join {
    CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, manager_id INTEGER, salary INTEGER);

    INSERT INTO employees VALUES
        (1, 'CEO', NULL, 150000),
        (2, 'VPSales', 1, 120000),
        (3, 'VPEngineering', 1, 130000),
        (4, 'Engineer1', 3, 90000),
        (5, 'Engineer2', 3, 85000),
        (6, 'SalesRep', 2, 70000);

    CREATE MATERIALIZED VIEW org_chart AS
        SELECT e.name as employee, m.name as manager, e.salary
        FROM employees e
        JOIN employees m ON e.manager_id = m.id;

    SELECT * FROM org_chart ORDER BY salary DESC;
} {VPEngineering|CEO|130000
VPSales|CEO|120000
Engineer1|VPEngineering|90000
Engineer2|VPEngineering|85000
SalesRep|VPSales|70000}

do_execsql_test_on_specific_db {:memory:} matview-join-cascade-update {
    CREATE TABLE categories(id INTEGER PRIMARY KEY, name TEXT, discount_rate INTEGER);
    CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, category_id INTEGER, base_price INTEGER);

    INSERT INTO categories VALUES (1, 'Electronics', 10), (2, 'Books', 5);
    INSERT INTO products VALUES
        (1, 'Laptop', 1, 1000),
        (2, 'Phone', 1, 500),
        (3, 'Novel', 2, 20),
        (4, 'Textbook', 2, 80);

    CREATE MATERIALIZED VIEW discounted_prices AS
        SELECT p.name as product, c.name as category,
               p.base_price, c.discount_rate,
               (p.base_price * (100 - c.discount_rate) / 100) as final_price
        FROM products p
        JOIN categories c ON p.category_id = c.id;

    SELECT * FROM discounted_prices ORDER BY final_price DESC;

    -- Update discount rate for Electronics
    UPDATE categories SET discount_rate = 20 WHERE id = 1;

    SELECT * FROM discounted_prices ORDER BY final_price DESC;
} {Laptop|Electronics|1000|10|900
Phone|Electronics|500|10|450
Textbook|Books|80|5|76
Novel|Books|20|5|19
Laptop|Electronics|1000|20|800
Phone|Electronics|500|20|400
Textbook|Books|80|5|76
Novel|Books|20|5|19}

do_execsql_test_on_specific_db {:memory:} matview-join-delete-cascade {
    CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, active INTEGER);
    CREATE TABLE sessions(id INTEGER PRIMARY KEY, user_id INTEGER, duration INTEGER);

    INSERT INTO users VALUES (1, 'Alice', 1), (2, 'Bob', 1), (3, 'Charlie', 0);
    INSERT INTO sessions VALUES
        (1, 1, 30),
        (2, 1, 45),
        (3, 2, 60),
        (4, 3, 15),
        (5, 2, 90);

    CREATE MATERIALIZED VIEW active_sessions AS
        SELECT u.name, s.duration
        FROM users u
        JOIN sessions s ON u.id = s.user_id
        WHERE u.active = 1;

    SELECT COUNT(*) FROM active_sessions;

    -- Delete Bob's sessions
    DELETE FROM sessions WHERE user_id = 2;

    SELECT COUNT(*) FROM active_sessions;
    SELECT * FROM active_sessions ORDER BY name, duration;
} {4
2
Alice|30
Alice|45}

do_execsql_test_on_specific_db {:memory:} matview-join-complex-where {
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, product_id INTEGER, quantity INTEGER, price INTEGER, order_date INTEGER);
    CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT, tier TEXT, country TEXT);

    INSERT INTO customers VALUES
        (1, 'Alice', 'Gold', 'USA'),
        (2, 'Bob', 'Silver', 'Canada'),
        (3, 'Charlie', 'Gold', 'USA'),
        (4, 'David', 'Bronze', 'UK');

    INSERT INTO orders VALUES
        (1, 1, 1, 5, 100, 20240101),
        (2, 2, 2, 3, 50, 20240102),
        (3, 3, 1, 10, 100, 20240103),
        (4, 4, 3, 2, 75, 20240104),
        (5, 1, 2, 4, 50, 20240105),
        (6, 3, 3, 6, 75, 20240106);

    -- View: Gold tier USA customers with high-value orders
    CREATE MATERIALIZED VIEW premium_usa_orders AS
        SELECT c.name, o.quantity, o.price, (o.quantity * o.price) as total
        FROM customers c
        JOIN orders o ON c.id = o.customer_id
        WHERE c.tier = 'Gold'
          AND c.country = 'USA'
          AND (o.quantity * o.price) >= 400;

    SELECT * FROM premium_usa_orders ORDER by total DESC;
} {Charlie|10|100|1000
Alice|5|100|500
Charlie|6|75|450}

# Test UNION queries in materialized views
do_execsql_test_on_specific_db {:memory:} matview-union-simple {
    CREATE TABLE sales_online(id INTEGER, product TEXT, amount INTEGER);
    CREATE TABLE sales_store(id INTEGER, product TEXT, amount INTEGER);

    INSERT INTO sales_online VALUES
        (1, 'Laptop', 1200),
        (2, 'Mouse', 25),
        (3, 'Monitor', 400);

    INSERT INTO sales_store VALUES
        (1, 'Keyboard', 75),
        (2, 'Chair', 150),
        (3, 'Desk', 350);

    -- Create a view that combines both sources
    CREATE MATERIALIZED VIEW all_sales AS
        SELECT product, amount FROM sales_online
        UNION ALL
        SELECT product, amount FROM sales_store;

    SELECT * FROM all_sales ORDER BY product;
} {Chair|150
Desk|350
Keyboard|75
Laptop|1200
Monitor|400
Mouse|25}

do_execsql_test_on_specific_db {:memory:} matview-union-with-where {
    CREATE TABLE employees(id INTEGER, name TEXT, dept TEXT, salary INTEGER);
    CREATE TABLE contractors(id INTEGER, name TEXT, dept TEXT, rate INTEGER);

    INSERT INTO employees VALUES
        (1, 'Alice', 'Engineering', 90000),
        (2, 'Bob', 'Sales', 60000),
        (3, 'Charlie', 'Engineering', 85000);

    INSERT INTO contractors VALUES
        (1, 'David', 'Engineering', 150),
        (2, 'Eve', 'Marketing', 120),
        (3, 'Frank', 'Engineering', 180);

    -- High-earning staff from both categories
    CREATE MATERIALIZED VIEW high_earners AS
        SELECT name, dept, salary as compensation FROM employees WHERE salary > 80000
        UNION ALL
        SELECT name, dept, rate * 2000 as compensation FROM contractors WHERE rate > 140;

    SELECT * FROM high_earners ORDER BY name;
} {Alice|Engineering|90000
Charlie|Engineering|85000
David|Engineering|300000
Frank|Engineering|360000}

do_execsql_test_on_specific_db {:memory:} matview-union-same-table-different-filters {
    CREATE TABLE orders(id INTEGER, customer_id INTEGER, product TEXT, amount INTEGER, status TEXT);

    INSERT INTO orders VALUES
        (1, 1, 'Laptop', 1200, 'completed'),
        (2, 2, 'Mouse', 25, 'pending'),
        (3, 1, 'Monitor', 400, 'completed'),
        (4, 3, 'Keyboard', 75, 'cancelled'),
        (5, 2, 'Desk', 350, 'completed'),
        (6, 3, 'Chair', 150, 'pending');

    -- View showing priority orders: high-value OR pending status
    CREATE MATERIALIZED VIEW priority_orders AS
        SELECT id, customer_id, product, amount FROM orders WHERE amount > 300
        UNION ALL
        SELECT id, customer_id, product, amount FROM orders WHERE status = 'pending';

    SELECT * FROM priority_orders ORDER BY id;
} {1|1|Laptop|1200
2|2|Mouse|25
3|1|Monitor|400
5|2|Desk|350
6|3|Chair|150}

do_execsql_test_on_specific_db {:memory:} matview-union-with-aggregation {
    CREATE TABLE q1_sales(product TEXT, quantity INTEGER, revenue INTEGER);
    CREATE TABLE q2_sales(product TEXT, quantity INTEGER, revenue INTEGER);

    INSERT INTO q1_sales VALUES
        ('Laptop', 10, 12000),
        ('Mouse', 50, 1250),
        ('Monitor', 8, 3200);

    INSERT INTO q2_sales VALUES
        ('Laptop', 15, 18000),
        ('Mouse', 60, 1500),
        ('Keyboard', 30, 2250);

    -- Combined quarterly summary
    CREATE MATERIALIZED VIEW half_year_summary AS
        SELECT 'Q1' as quarter, SUM(quantity) as total_units, SUM(revenue) as total_revenue
        FROM q1_sales
        UNION ALL
        SELECT 'Q2' as quarter, SUM(quantity) as total_units, SUM(revenue) as total_revenue
        FROM q2_sales;

    SELECT * FROM half_year_summary ORDER BY quarter;
} {Q1|68.0|16450.0
Q2|105.0|21750.0}

do_execsql_test_on_specific_db {:memory:} matview-union-with-join {
    CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT, type TEXT);
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);
    CREATE TABLE quotes(id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);

    INSERT INTO customers VALUES
        (1, 'Alice', 'premium'),
        (2, 'Bob', 'regular'),
        (3, 'Charlie', 'premium');

    INSERT INTO orders VALUES
        (1, 1, 1000),
        (2, 2, 500),
        (3, 3, 1500);

    INSERT INTO quotes VALUES
        (1, 1, 800),
        (2, 2, 300),
        (3, 3, 2000);

    -- All premium customer transactions (orders and quotes)
    CREATE MATERIALIZED VIEW premium_transactions AS
        SELECT c.name, 'order' as type, o.amount
        FROM customers c
        JOIN orders o ON c.id = o.customer_id
        WHERE c.type = 'premium'
        UNION ALL
        SELECT c.name, 'quote' as type, q.amount
        FROM customers c
        JOIN quotes q ON c.id = q.customer_id
        WHERE c.type = 'premium';

    SELECT * FROM premium_transactions ORDER BY name, type, amount;
} {Alice|order|1000
Alice|quote|800
Charlie|order|1500
Charlie|quote|2000}

do_execsql_test_on_specific_db {:memory:} matview-union-distinct {
    CREATE TABLE active_users(id INTEGER, name TEXT, email TEXT);
    CREATE TABLE inactive_users(id INTEGER, name TEXT, email TEXT);

    INSERT INTO active_users VALUES
        (1, 'Alice', 'alice@example.com'),
        (2, 'Bob', 'bob@example.com'),
        (3, 'Charlie', 'charlie@example.com');

    INSERT INTO inactive_users VALUES
        (4, 'David', 'david@example.com'),
        (2, 'Bob', 'bob@example.com'),  -- Bob appears in both
        (5, 'Eve', 'eve@example.com');

    -- All unique users (using UNION to deduplicate)
    CREATE MATERIALIZED VIEW all_users AS
        SELECT id, name, email FROM active_users
        UNION
        SELECT id, name, email FROM inactive_users;

    SELECT * FROM all_users ORDER BY id;
} {1|Alice|alice@example.com
2|Bob|bob@example.com
3|Charlie|charlie@example.com
4|David|david@example.com
5|Eve|eve@example.com}

do_execsql_test_on_specific_db {:memory:} matview-union-complex-multiple-branches {
    CREATE TABLE products(id INTEGER, name TEXT, category TEXT, price INTEGER);

    INSERT INTO products VALUES
        (1, 'Laptop', 'Electronics', 1200),
        (2, 'Mouse', 'Electronics', 25),
        (3, 'Desk', 'Furniture', 350),
        (4, 'Chair', 'Furniture', 150),
        (5, 'Monitor', 'Electronics', 400),
        (6, 'Keyboard', 'Electronics', 75),
        (7, 'Bookshelf', 'Furniture', 200),
        (8, 'Tablet', 'Electronics', 600);

    -- Products of interest: expensive electronics, all furniture, or very cheap items
    CREATE MATERIALIZED VIEW featured_products AS
        SELECT name, category, price, 'PremiumElectronic' as tag
        FROM products
        WHERE category = 'Electronics' AND price > 500
        UNION ALL
        SELECT name, category, price, 'Furniture' as tag
        FROM products
        WHERE category = 'Furniture'
        UNION ALL
        SELECT name, category, price, 'Budget' as tag
        FROM products
        WHERE price < 50;

    SELECT * FROM featured_products ORDER BY tag, name;
} {Mouse|Electronics|25|Budget
Bookshelf|Furniture|200|Furniture
Chair|Furniture|150|Furniture
Desk|Furniture|350|Furniture
Laptop|Electronics|1200|PremiumElectronic
Tablet|Electronics|600|PremiumElectronic}

do_execsql_test_on_specific_db {:memory:} matview-union-maintenance-insert {
    CREATE TABLE t1(id INTEGER, value INTEGER);
    CREATE TABLE t2(id INTEGER, value INTEGER);

    INSERT INTO t1 VALUES (1, 100), (2, 200);
    INSERT INTO t2 VALUES (3, 300), (4, 400);

    CREATE MATERIALIZED VIEW combined AS
        SELECT id, value FROM t1 WHERE value > 150
        UNION ALL
        SELECT id, value FROM t2 WHERE value > 350;

    SELECT * FROM combined ORDER BY id;

    -- Insert into t1
    INSERT INTO t1 VALUES (5, 500);
    SELECT * FROM combined ORDER BY id;

    -- Insert into t2
    INSERT INTO t2 VALUES (6, 600);
    SELECT * FROM combined ORDER BY id;
} {2|200
4|400
2|200
4|400
5|500
2|200
4|400
5|500
6|600}

do_execsql_test_on_specific_db {:memory:} matview-union-maintenance-delete {
    CREATE TABLE source1(id INTEGER PRIMARY KEY, data TEXT);
    CREATE TABLE source2(id INTEGER PRIMARY KEY, data TEXT);

    INSERT INTO source1 VALUES (1, 'A'), (2, 'B'), (3, 'C');
    INSERT INTO source2 VALUES (4, 'D'), (5, 'E'), (6, 'F');

    CREATE MATERIALIZED VIEW merged AS
        SELECT id, data FROM source1
        UNION ALL
        SELECT id, data FROM source2;

    SELECT COUNT(*) FROM merged;

    DELETE FROM source1 WHERE id = 2;
    SELECT COUNT(*) FROM merged;

    DELETE FROM source2 WHERE id > 4;
    SELECT COUNT(*) FROM merged;
} {6
5
3}

do_execsql_test_on_specific_db {:memory:} matview-union-maintenance-update {
    CREATE TABLE high_priority(id INTEGER PRIMARY KEY, task TEXT, priority INTEGER);
    CREATE TABLE normal_priority(id INTEGER PRIMARY KEY, task TEXT, priority INTEGER);

    INSERT INTO high_priority VALUES (1, 'Task A', 10), (2, 'Task B', 9);
    INSERT INTO normal_priority VALUES (3, 'Task C', 5), (4, 'Task D', 6);

    CREATE MATERIALIZED VIEW active_tasks AS
        SELECT id, task, priority FROM high_priority WHERE priority >= 9
        UNION ALL
        SELECT id, task, priority FROM normal_priority WHERE priority >= 5;

    SELECT COUNT(*) FROM active_tasks;

    -- Update drops a high priority task below threshold
    UPDATE high_priority SET priority = 8 WHERE id = 2;
    SELECT COUNT(*) FROM active_tasks;

    -- Update brings a normal task above threshold
    UPDATE normal_priority SET priority = 3 WHERE id = 3;
    SELECT COUNT(*) FROM active_tasks;
} {4
3
2}

# Test UNION ALL with same table and different WHERE conditions
do_execsql_test_on_specific_db {:memory:} matview-union-all-same-table {
    CREATE TABLE test(id INTEGER PRIMARY KEY, value INTEGER);
    INSERT INTO test VALUES (1, 10), (2, 20);

    -- This UNION ALL should return both rows
    CREATE MATERIALIZED VIEW union_view AS
        SELECT id, value FROM test WHERE value < 15
        UNION ALL
        SELECT id, value FROM test WHERE value > 15;

    -- Should return 2 rows: (1,10) and (2,20)
    SELECT * FROM union_view ORDER BY id;
} {1|10
2|20}

# Test UNION ALL preserves all rows in count
do_execsql_test_on_specific_db {:memory:} matview-union-all-row-count {
    CREATE TABLE data(id INTEGER PRIMARY KEY, num INTEGER);
    INSERT INTO data VALUES (1, 5), (2, 15), (3, 25);

    CREATE MATERIALIZED VIEW split_view AS
        SELECT id, num FROM data WHERE num <= 10
        UNION ALL
        SELECT id, num FROM data WHERE num > 10;

    -- Should return count of 3
    SELECT COUNT(*) FROM split_view;
} {3}

# Test UNION ALL with text columns and filtering
do_execsql_test_on_specific_db {:memory:} matview-union-all-text-filter {
    CREATE TABLE items(id INTEGER PRIMARY KEY, category TEXT, price INTEGER);
    INSERT INTO items VALUES
        (1, 'cheap', 10),
        (2, 'expensive', 100),
        (3, 'cheap', 20),
        (4, 'expensive', 200);

    CREATE MATERIALIZED VIEW price_categories AS
        SELECT id, category, price FROM items WHERE category = 'cheap'
        UNION ALL
        SELECT id, category, price FROM items WHERE category = 'expensive';

    -- Should return all 4 items
    SELECT COUNT(*) FROM price_categories;
    SELECT id FROM price_categories ORDER BY id;
} {4
1
2
3
4}

# Test BETWEEN in WHERE clause
do_execsql_test_on_specific_db {:memory:} matview-between-filter {
    CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, price INTEGER);
    INSERT INTO products VALUES
        (1, 'Cheap', 10),
        (2, 'Mid1', 50),
        (3, 'Mid2', 75),
        (4, 'Expensive', 150);

    CREATE MATERIALIZED VIEW mid_range AS
        SELECT id, name, price FROM products WHERE price BETWEEN 40 AND 100;

    SELECT * FROM mid_range ORDER BY id;
} {2|Mid1|50
3|Mid2|75}

# Test IN list in WHERE clause
do_execsql_test_on_specific_db {:memory:} matview-in-filter {
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer TEXT, status TEXT);
    INSERT INTO orders VALUES
        (1, 'Alice', 'shipped'),
        (2, 'Bob', 'pending'),
        (3, 'Charlie', 'delivered'),
        (4, 'David', 'cancelled'),
        (5, 'Eve', 'shipped');

    CREATE MATERIALIZED VIEW active_orders AS
        SELECT id, customer FROM orders WHERE status IN ('pending', 'shipped');

    SELECT * FROM active_orders ORDER BY id;
} {1|Alice
2|Bob
5|Eve}

# Test CAST with TEXT in WHERE clause
do_execsql_test_on_specific_db {:memory:} matview-cast-text {
    CREATE TABLE records(id INTEGER PRIMARY KEY, code TEXT);
    INSERT INTO records VALUES
        (1, 'A100'),
        (2, 'B200'),
        (3, 'A300');

    CREATE MATERIALIZED VIEW filtered AS
        SELECT id FROM records WHERE code < CAST('B' AS TEXT);

    SELECT * FROM filtered ORDER BY id;
} {1
3}

# Test BETWEEN and IN together
do_execsql_test_on_specific_db {:memory:} matview-between-and-in {
    CREATE TABLE inventory(id INTEGER PRIMARY KEY, product TEXT, quantity INTEGER, location TEXT);
    INSERT INTO inventory VALUES
        (1, 'Widget', 50, 'WH1'),
        (2, 'Gadget', 30, 'WH2'),
        (3, 'Tool', 80, 'WH1'),
        (4, 'Part', 15, 'WH3'),
        (5, 'Device', 45, 'WH2');

    CREATE MATERIALIZED VIEW wh1_wh2_medium_stock AS
        SELECT id, product, quantity
        FROM inventory
        WHERE quantity BETWEEN 25 AND 60
          AND location IN ('WH1', 'WH2');

    SELECT * FROM wh1_wh2_medium_stock ORDER BY id;
} {1|Widget|50
2|Gadget|30
5|Device|45}

# Test complex OR conditions with IN
do_execsql_test_on_specific_db {:memory:} matview-complex-or-with-in {
    CREATE TABLE shipments(id INTEGER PRIMARY KEY, size INTEGER, mode TEXT, priority TEXT);
    INSERT INTO shipments VALUES
        (1, 5, 'AIR', 'high'),
        (2, 15, 'TRUCK', 'normal'),
        (3, 8, 'AIR', 'normal'),
        (4, 20, 'SHIP', 'low'),
        (5, 12, 'AIR_REG', 'high');

    CREATE MATERIALIZED VIEW express_shipments AS
        SELECT id, size, mode
        FROM shipments
        WHERE (size BETWEEN 5 AND 10 AND mode IN ('AIR', 'AIR_REG'))
           OR priority = 'high';

    SELECT * FROM express_shipments ORDER BY id;
} {1|5|AIR
3|8|AIR
5|12|AIR_REG}

# Test join with BETWEEN in WHERE
do_execsql_test_on_specific_db {:memory:} matview-join-with-between {
    CREATE TABLE parts(id INTEGER PRIMARY KEY, size INTEGER);
    CREATE TABLE suppliers(id INTEGER PRIMARY KEY, part_id INTEGER, price INTEGER);

    INSERT INTO parts VALUES (1, 5), (2, 10), (3, 20);
    INSERT INTO suppliers VALUES (1, 1, 100), (2, 2, 150), (3, 3, 200);

    CREATE MATERIALIZED VIEW medium_parts AS
        SELECT p.id, p.size, s.price
        FROM parts p
        JOIN suppliers s ON p.id = s.part_id
        WHERE p.size BETWEEN 8 AND 15;

    SELECT * FROM medium_parts ORDER BY id;
} {2|10|150}

# Test join with IN in WHERE
do_execsql_test_on_specific_db {:memory:} matview-join-with-in {
    CREATE TABLE customers(id INTEGER PRIMARY KEY, region TEXT);
    CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount INTEGER);

    INSERT INTO customers VALUES (1, 'USA'), (2, 'Canada'), (3, 'UK'), (4, 'Mexico');
    INSERT INTO orders VALUES (1, 1, 100), (2, 2, 200), (3, 3, 150), (4, 4, 300);

    CREATE MATERIALIZED VIEW north_america_orders AS
        SELECT c.region, o.amount
        FROM customers c
        JOIN orders o ON c.id = o.customer_id
        WHERE c.region IN ('USA', 'Canada', 'Mexico');

    SELECT * FROM north_america_orders ORDER BY region, amount;
} {Canada|200
Mexico|300
USA|100}

# Test incremental maintenance with BETWEEN
do_execsql_test_on_specific_db {:memory:} matview-between-incremental {
    CREATE TABLE items(id INTEGER PRIMARY KEY, value INTEGER);
    INSERT INTO items VALUES (1, 5), (2, 15);

    CREATE MATERIALIZED VIEW mid_values AS
        SELECT id, value FROM items WHERE value BETWEEN 10 AND 20;

    SELECT COUNT(*) FROM mid_values;

    INSERT INTO items VALUES (3, 12), (4, 25);
    SELECT * FROM mid_values ORDER BY id;

    UPDATE items SET value = 30 WHERE id = 2;
    SELECT * FROM mid_values ORDER BY id;
} {1
2|15
3|12
3|12}

# Test incremental maintenance with IN
do_execsql_test_on_specific_db {:memory:} matview-in-incremental {
    CREATE TABLE logs(id INTEGER PRIMARY KEY, level TEXT, message TEXT);
    INSERT INTO logs VALUES (1, 'INFO', 'start'), (2, 'DEBUG', 'test');

    CREATE MATERIALIZED VIEW important_logs AS
        SELECT id, level, message FROM logs WHERE level IN ('ERROR', 'WARN', 'INFO');

    SELECT COUNT(*) FROM important_logs;

    INSERT INTO logs VALUES (3, 'ERROR', 'fail'), (4, 'TRACE', 'detail');
    SELECT * FROM important_logs ORDER BY id;

    DELETE FROM logs WHERE id = 1;
    SELECT * FROM important_logs ORDER BY id;
} {1
1|INFO|start
3|ERROR|fail
3|ERROR|fail}

# Test join with swapped column order in ON clause
do_execsql_test_on_specific_db {:memory:} matview-join-swapped-columns {
    CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT);
    CREATE TABLE departments(emp_id INTEGER PRIMARY KEY, dept_name TEXT);

    INSERT INTO employees VALUES (1, 'Alice'), (2, 'Bob');
    INSERT INTO departments VALUES (1, 'Engineering'), (2, 'Sales');

    CREATE MATERIALIZED VIEW emp_dept AS
        SELECT e.name, d.dept_name
        FROM employees e
        JOIN departments d ON d.emp_id = e.id;

    SELECT * FROM emp_dept ORDER BY name;
} {Alice|Engineering
Bob|Sales}

do_execsql_test_on_specific_db {:memory:} matview-groupby-scalar-function {
    CREATE TABLE orders(id INTEGER, orderdate TEXT, amount INTEGER);
    INSERT INTO orders VALUES (1, '2020-01-15', 100);
    INSERT INTO orders VALUES (2, '2020-06-10', 150);
    INSERT INTO orders VALUES (3, '2021-03-20', 200);

    CREATE MATERIALIZED VIEW yearly_totals AS
        SELECT substr(orderdate, 1, 4), sum(amount)
        FROM orders
        GROUP BY substr(orderdate, 1, 4);

    SELECT * FROM yearly_totals ORDER BY 1;
} {2020|250.0
2021|200.0}

do_execsql_test_on_specific_db {:memory:} matview-groupby-alias {
    CREATE TABLE orders(id INTEGER, orderdate TEXT, amount INTEGER);
    INSERT INTO orders VALUES (1, '2020-01-15', 100);
    INSERT INTO orders VALUES (2, '2020-06-10', 150);
    INSERT INTO orders VALUES (3, '2021-03-20', 200);

    CREATE MATERIALIZED VIEW yearly_totals AS
        SELECT substr(orderdate, 1, 4) as year, sum(amount) as total
        FROM orders
        GROUP BY year;

    SELECT * FROM yearly_totals ORDER BY year;
} {2020|250.0
2021|200.0}

do_execsql_test_on_specific_db {:memory:} matview-groupby-position {
    CREATE TABLE orders(id INTEGER, orderdate TEXT, amount INTEGER, nation TEXT);
    INSERT INTO orders VALUES (1, '2020-01-15', 100, 'USA');
    INSERT INTO orders VALUES (2, '2020-06-10', 150, 'USA');
    INSERT INTO orders VALUES (3, '2021-03-20', 200, 'UK');

    CREATE MATERIALIZED VIEW national_yearly AS
        SELECT nation, substr(orderdate, 1, 4) as year, sum(amount) as total
        FROM orders
        GROUP BY 1, 2;

    SELECT * FROM national_yearly ORDER BY nation, year;
} {UK|2021|200.0
USA|2020|250.0}

do_execsql_test_on_specific_db {:memory:} matview-groupby-scalar-incremental {
    CREATE TABLE orders(id INTEGER, orderdate TEXT, amount INTEGER);
    INSERT INTO orders VALUES (1, '2020-01-15', 100);

    CREATE MATERIALIZED VIEW yearly_totals AS
        SELECT substr(orderdate, 1, 4) as year, sum(amount) as total
        FROM orders
        GROUP BY year;

    SELECT * FROM yearly_totals;
    INSERT INTO orders VALUES (2, '2020-06-10', 150);
    SELECT * FROM yearly_totals;
    INSERT INTO orders VALUES (3, '2021-03-20', 200);
    SELECT * FROM yearly_totals ORDER BY year;
} {2020|100.0
2020|250.0
2020|250.0
2021|200.0}

do_execsql_test_on_specific_db {:memory:} matview-groupby-join-position {
    CREATE TABLE t(a INTEGER);
    CREATE TABLE u(a INTEGER);
    INSERT INTO t VALUES (1), (2), (3);
    INSERT INTO u VALUES (1), (1), (2);

    CREATE MATERIALIZED VIEW tujoingroup AS
        SELECT t.a, count(u.a) as cnt
        FROM t JOIN u ON t.a = u.a
        GROUP BY 1;

    SELECT * FROM tujoingroup ORDER BY a;
} {1|2
2|1}

do_execsql_test_on_specific_db {:memory:} matview-distinct-basic {
    CREATE TABLE items(id INTEGER, category TEXT, value INTEGER);
    INSERT INTO items VALUES
        (1, 'A', 100),
        (2, 'B', 200),
        (3, 'A', 100),  -- duplicate of row 1
        (4, 'C', 300),
        (5, 'B', 200),  -- duplicate of row 2
        (6, 'A', 100);  -- another duplicate of row 1

    CREATE MATERIALIZED VIEW distinct_items AS
        SELECT DISTINCT category, value FROM items;

    SELECT category, value FROM distinct_items ORDER BY category, value;
} {A|100
B|200
C|300}

do_execsql_test_on_specific_db {:memory:} matview-distinct-single-column {
    CREATE TABLE numbers(n INTEGER);
    INSERT INTO numbers VALUES (1), (2), (1), (3), (2), (1), (4), (3);

    CREATE MATERIALIZED VIEW distinct_numbers AS
        SELECT DISTINCT n FROM numbers;

    SELECT n FROM distinct_numbers ORDER BY n;
} {1
2
3
4}

do_execsql_test_on_specific_db {:memory:} matview-distinct-incremental-insert {
    CREATE TABLE data(x INTEGER, y TEXT);
    CREATE MATERIALIZED VIEW distinct_data AS
        SELECT DISTINCT x, y FROM data;

    -- Initial data
    INSERT INTO data VALUES (1, 'alpha'), (2, 'beta'), (1, 'alpha');
    SELECT x, y FROM distinct_data ORDER BY x, y;
} {1|alpha
2|beta}

do_execsql_test_on_specific_db {:memory:} matview-distinct-incremental-insert-new {
    CREATE TABLE data(x INTEGER, y TEXT);
    CREATE MATERIALIZED VIEW distinct_data AS
        SELECT DISTINCT x, y FROM data;

    -- Initial data
    INSERT INTO data VALUES (1, 'alpha'), (2, 'beta'), (1, 'alpha');

    -- Add new distinct values
    INSERT INTO data VALUES (3, 'gamma'), (4, 'delta');
    SELECT x, y FROM distinct_data ORDER BY x, y;
} {1|alpha
2|beta
3|gamma
4|delta}

do_execsql_test_on_specific_db {:memory:} matview-distinct-incremental-insert-dups {
    CREATE TABLE data(x INTEGER, y TEXT);
    CREATE MATERIALIZED VIEW distinct_data AS
        SELECT DISTINCT x, y FROM data;

    -- Initial data with some new values
    INSERT INTO data VALUES
        (1, 'alpha'), (2, 'beta'), (1, 'alpha'),
        (3, 'gamma'), (4, 'delta');

    -- Add duplicates of existing values
    INSERT INTO data VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma');
    -- Should be same as before the duplicate insert
    SELECT x, y FROM distinct_data ORDER BY x, y;
} {1|alpha
2|beta
3|gamma
4|delta}

do_execsql_test_on_specific_db {:memory:} matview-distinct-incremental-delete {
    CREATE TABLE records(id INTEGER PRIMARY KEY, category TEXT, status INTEGER);
    INSERT INTO records VALUES
        (1, 'X', 1),
        (2, 'Y', 2),
        (3, 'X', 1),  -- duplicate values
        (4, 'Z', 3),
        (5, 'Y', 2);  -- duplicate values

    CREATE MATERIALIZED VIEW distinct_records AS
        SELECT DISTINCT category, status FROM records;

    SELECT category, status FROM distinct_records ORDER BY category, status;
} {X|1
Y|2
Z|3}

do_execsql_test_on_specific_db {:memory:} matview-distinct-incremental-delete-partial {
    CREATE TABLE records(id INTEGER PRIMARY KEY, category TEXT, status INTEGER);
    INSERT INTO records VALUES
        (1, 'X', 1),
        (2, 'Y', 2),
        (3, 'X', 1),  -- duplicate values
        (4, 'Z', 3),
        (5, 'Y', 2);  -- duplicate values

    CREATE MATERIALIZED VIEW distinct_records AS
        SELECT DISTINCT category, status FROM records;

    -- Delete one instance of duplicate
    DELETE FROM records WHERE id = 3;
    -- X|1 should still exist (one instance remains)
    SELECT category, status FROM distinct_records ORDER BY category, status;
} {X|1
Y|2
Z|3}

do_execsql_test_on_specific_db {:memory:} matview-distinct-incremental-delete-all {
    CREATE TABLE records(id INTEGER PRIMARY KEY, category TEXT, status INTEGER);
    INSERT INTO records VALUES
        (1, 'X', 1),
        (2, 'Y', 2),
        (4, 'Z', 3),
        (5, 'Y', 2);  -- duplicate values

    CREATE MATERIALIZED VIEW distinct_records AS
        SELECT DISTINCT category, status FROM records;

    -- Delete all instances of X|1
    DELETE FROM records WHERE category = 'X' AND status = 1;
    -- Now X|1 should be gone
    SELECT category, status FROM distinct_records ORDER BY category, status;
} {Y|2
Z|3}

do_execsql_test_on_specific_db {:memory:} matview-distinct-incremental-reappear {
    CREATE TABLE records(id INTEGER PRIMARY KEY, category TEXT, status INTEGER);
    INSERT INTO records VALUES
        (2, 'Y', 2),
        (4, 'Z', 3),
        (5, 'Y', 2);  -- duplicate values

    CREATE MATERIALIZED VIEW distinct_records AS
        SELECT DISTINCT category, status FROM records;

    -- Re-add a previously deleted value
    INSERT INTO records VALUES (6, 'X', 1);
    -- X|1 should appear
    SELECT category, status FROM distinct_records ORDER BY category, status;
} {X|1
Y|2
Z|3}

do_execsql_test_on_specific_db {:memory:} matview-distinct-null-handling {
    CREATE TABLE nullable(a INTEGER, b TEXT);
    INSERT INTO nullable VALUES
        (1, 'one'),
        (2, NULL),
        (NULL, 'three'),
        (1, 'one'),     -- duplicate
        (2, NULL),      -- duplicate with NULL
        (NULL, 'three'), -- duplicate with NULL
        (NULL, NULL);

    CREATE MATERIALIZED VIEW distinct_nullable AS
        SELECT DISTINCT a, b FROM nullable;

    -- NULLs should be handled as distinct values
    SELECT a, b FROM distinct_nullable ORDER BY a, b;
} {|
|three
1|one
2|}

do_execsql_test_on_specific_db {:memory:} matview-distinct-empty-table {
    CREATE TABLE empty_source(x INTEGER, y TEXT);
    CREATE MATERIALIZED VIEW distinct_empty AS
        SELECT DISTINCT x, y FROM empty_source;

    -- Should be empty
    SELECT COUNT(*) FROM distinct_empty;
} {0}

do_execsql_test_on_specific_db {:memory:} matview-distinct-empty-then-insert {
    CREATE TABLE empty_source(x INTEGER, y TEXT);
    CREATE MATERIALIZED VIEW distinct_empty AS
        SELECT DISTINCT x, y FROM empty_source;

    -- Insert into previously empty table
    INSERT INTO empty_source VALUES (1, 'first'), (1, 'first'), (2, 'second');
    SELECT x, y FROM distinct_empty ORDER BY x, y;
} {1|first
2|second}

do_execsql_test_on_specific_db {:memory:} matview-distinct-multi-column-types {
    CREATE TABLE mixed_types(i INTEGER, t TEXT, r REAL, b BLOB);
    INSERT INTO mixed_types VALUES
        (1, 'text1', 1.5, x'0102'),
        (2, 'text2', 2.5, x'0304'),
        (1, 'text1', 1.5, x'0102'),  -- exact duplicate
        (3, 'text3', 3.5, x'0506'),
        (2, 'text2', 2.5, x'0304');  -- another duplicate

    CREATE MATERIALIZED VIEW distinct_mixed AS
        SELECT DISTINCT i, t FROM mixed_types;

    SELECT i, t FROM distinct_mixed ORDER BY i, t;
} {1|text1
2|text2
3|text3}

do_execsql_test_on_specific_db {:memory:} matview-distinct-update-simulation {
    CREATE TABLE updatable(id INTEGER PRIMARY KEY, val TEXT);
    INSERT INTO updatable VALUES (1, 'old'), (2, 'old'), (3, 'new');

    CREATE MATERIALIZED VIEW distinct_vals AS
        SELECT DISTINCT val FROM updatable;

    SELECT val FROM distinct_vals ORDER BY val;
} {new
old}

do_execsql_test_on_specific_db {:memory:} matview-distinct-update-simulation-change {
    CREATE TABLE updatable(id INTEGER PRIMARY KEY, val TEXT);
    INSERT INTO updatable VALUES (1, 'old'), (2, 'old'), (3, 'new');

    CREATE MATERIALIZED VIEW distinct_vals AS
        SELECT DISTINCT val FROM updatable;

    -- Simulate update by delete + insert
    DELETE FROM updatable WHERE id = 1;
    INSERT INTO updatable VALUES (1, 'new');

    -- Now we have two 'new' and one 'old', but distinct shows each once
    SELECT val FROM distinct_vals ORDER BY val;
} {new
old}

do_execsql_test_on_specific_db {:memory:} matview-distinct-update-simulation-all-same {
    CREATE TABLE updatable(id INTEGER PRIMARY KEY, val TEXT);
    INSERT INTO updatable VALUES (1, 'new'), (2, 'old'), (3, 'new');

    CREATE MATERIALIZED VIEW distinct_vals AS
        SELECT DISTINCT val FROM updatable;

    -- Change the 'old' to 'new'
    DELETE FROM updatable WHERE id = 2;
    INSERT INTO updatable VALUES (2, 'new');

    -- Now all three rows have 'new', old should disappear
    SELECT val FROM distinct_vals ORDER BY val;
} {new}

do_execsql_test_on_specific_db {:memory:} matview-distinct-large-duplicates {
    CREATE TABLE many_dups(x INTEGER);

    -- Insert many duplicates
    INSERT INTO many_dups VALUES (1), (1), (1), (1), (1);
    INSERT INTO many_dups VALUES (2), (2), (2), (2), (2);
    INSERT INTO many_dups VALUES (3), (3), (3), (3), (3);

    CREATE MATERIALIZED VIEW distinct_many AS
        SELECT DISTINCT x FROM many_dups;

    SELECT x FROM distinct_many ORDER BY x;
} {1
2
3}

do_execsql_test_on_specific_db {:memory:} matview-distinct-large-duplicates-remove {
    CREATE TABLE many_dups(x INTEGER);

    -- Insert many duplicates
    INSERT INTO many_dups VALUES (1), (1), (1), (1), (1);
    INSERT INTO many_dups VALUES (2), (2), (2), (2), (2);
    INSERT INTO many_dups VALUES (3), (3), (3), (3), (3);

    CREATE MATERIALIZED VIEW distinct_many AS
        SELECT DISTINCT x FROM many_dups;

    -- Remove some instances of value 2 (rowids 7,8,9,10 keeping rowid 6)
    DELETE FROM many_dups WHERE rowid IN (7, 8, 9, 10);

    -- Should still have all three values
    SELECT x FROM distinct_many ORDER BY x;
} {1
2
3}

do_execsql_test_on_specific_db {:memory:} matview-distinct-large-duplicates-remove-all {
    CREATE TABLE many_dups(x INTEGER);

    -- Insert many duplicates but only one instance of 2
    INSERT INTO many_dups VALUES (1), (1), (1), (1), (1);
    INSERT INTO many_dups VALUES (2);
    INSERT INTO many_dups VALUES (3), (3), (3), (3), (3);

    CREATE MATERIALIZED VIEW distinct_many AS
        SELECT DISTINCT x FROM many_dups;

    -- Remove ALL instances of value 2
    DELETE FROM many_dups WHERE x = 2;

    -- Now 2 should be gone
    SELECT x FROM distinct_many ORDER BY x;
} {1
3}

# COUNT(DISTINCT) tests for materialized views

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-basic {
    CREATE TABLE sales(region TEXT, product TEXT, amount INTEGER);
    INSERT INTO sales VALUES
        ('North', 'A', 100),
        ('North', 'A', 100),  -- Duplicate
        ('North', 'B', 200),
        ('South', 'A', 150),
        ('South', 'A', 150);  -- Duplicate

    CREATE MATERIALIZED VIEW sales_summary AS
        SELECT region, COUNT(DISTINCT product) as unique_products
        FROM sales GROUP BY region;

    SELECT * FROM sales_summary ORDER BY region;
} {North|2
South|1}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-nulls {
    -- COUNT(DISTINCT) should ignore NULL values per SQL standard
    CREATE TABLE data(grp INTEGER, val INTEGER);
    INSERT INTO data VALUES
        (1, 10),
        (1, 20),
        (1, NULL),
        (1, NULL),  -- Multiple NULLs
        (2, 30),
        (2, NULL);

    CREATE MATERIALIZED VIEW v AS
        SELECT grp, COUNT(DISTINCT val) as cnt FROM data GROUP BY grp;

    SELECT * FROM v ORDER BY grp;

    -- Add more NULLs (should not affect count)
    INSERT INTO data VALUES (1, NULL), (2, NULL);
    SELECT * FROM v ORDER BY grp;

    -- Add a non-NULL value
    INSERT INTO data VALUES (2, 40);
    SELECT * FROM v ORDER BY grp;
} {1|2
2|1
1|2
2|1
1|2
2|2}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-empty-groups {
    CREATE TABLE items(category TEXT, item TEXT);
    INSERT INTO items VALUES
        ('A', 'x'),
        ('A', 'y'),
        ('B', 'z');

    CREATE MATERIALIZED VIEW category_counts AS
        SELECT category, COUNT(DISTINCT item) as unique_items
        FROM items GROUP BY category;

    SELECT * FROM category_counts ORDER BY category;

    -- Delete all items from category B
    DELETE FROM items WHERE category = 'B';
    SELECT * FROM category_counts ORDER BY category;

    -- Re-add items to B
    INSERT INTO items VALUES ('B', 'w'), ('B', 'w');  -- Same value twice
    SELECT * FROM category_counts ORDER BY category;
} {A|2
B|1
A|2
A|2
B|1}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-updates {
    CREATE TABLE records(id INTEGER PRIMARY KEY, grp TEXT, val INTEGER);
    INSERT INTO records VALUES
        (1, 'X', 100),
        (2, 'X', 200),
        (3, 'Y', 100),
        (4, 'Y', 100);  -- Duplicate

    CREATE MATERIALIZED VIEW grp_summary AS
        SELECT grp, COUNT(DISTINCT val) as distinct_vals
        FROM records GROUP BY grp;

    SELECT * FROM grp_summary ORDER BY grp;

    -- Update that changes group membership
    UPDATE records SET grp = 'Y' WHERE id = 1;
    SELECT * FROM grp_summary ORDER BY grp;

    -- Update that changes value within group
    UPDATE records SET val = 300 WHERE id = 3;
    SELECT * FROM grp_summary ORDER BY grp;
} {X|2
Y|1
X|1
Y|1
X|1
Y|2}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-large-scale {
    CREATE TABLE events(user_id INTEGER, event_type INTEGER);

    -- Insert many events with varying duplication
    INSERT INTO events VALUES
        (1, 1), (1, 1), (1, 1), (1, 2), (1, 3),  -- User 1: 3 distinct
        (2, 1), (2, 2), (2, 2), (2, 2),           -- User 2: 2 distinct
        (3, 4), (3, 4), (3, 4), (3, 4), (3, 4);   -- User 3: 1 distinct

    CREATE MATERIALIZED VIEW user_stats AS
        SELECT user_id, COUNT(DISTINCT event_type) as unique_events
        FROM events GROUP BY user_id;

    SELECT * FROM user_stats ORDER BY user_id;

    -- Mass deletion
    DELETE FROM events WHERE event_type = 2;
    SELECT * FROM user_stats ORDER BY user_id;

    -- Mass insertion with duplicates
    INSERT INTO events VALUES
        (1, 5), (1, 5), (1, 6),
        (2, 5), (2, 6), (2, 7);
    SELECT * FROM user_stats ORDER BY user_id;
} {1|3
2|2
3|1
1|2
2|1
3|1
1|4
2|4
3|1}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-group-by-empty-start {
    CREATE TABLE measurements(device TEXT, reading INTEGER);

    CREATE MATERIALIZED VIEW device_summary AS
        SELECT device, COUNT(DISTINCT reading) as unique_readings
        FROM measurements GROUP BY device;

    -- Start with empty table (no groups yet)
    SELECT COUNT(*) FROM device_summary;

    -- Add first group
    INSERT INTO measurements VALUES ('D1', 100), ('D1', 100);
    SELECT * FROM device_summary;

    -- Add second group with distinct values
    INSERT INTO measurements VALUES ('D2', 200), ('D2', 300);
    SELECT * FROM device_summary ORDER BY device;

    -- Remove all data
    DELETE FROM measurements;
    SELECT COUNT(*) FROM device_summary;
} {0
D1|1
D1|1
D2|2
0}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-single-row-groups {
    CREATE TABLE singles(k TEXT PRIMARY KEY, v INTEGER);
    INSERT INTO singles VALUES
        ('a', 1),
        ('b', 2),
        ('c', 3);

    CREATE MATERIALIZED VIEW v AS
        SELECT k, COUNT(DISTINCT v) as cnt FROM singles GROUP BY k;

    SELECT * FROM v ORDER BY k;

    -- Each group has exactly one row, so COUNT(DISTINCT v) = 1
    UPDATE singles SET v = 999 WHERE k = 'b';
    SELECT * FROM v ORDER BY k;

    DELETE FROM singles WHERE k = 'c';
    SELECT * FROM v ORDER BY k;
} {a|1
b|1
c|1
a|1
b|1
c|1
a|1
b|1}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-transactions {
    CREATE TABLE txn_data(grp TEXT, val INTEGER);
    INSERT INTO txn_data VALUES ('A', 1), ('A', 2);

    CREATE MATERIALIZED VIEW txn_view AS
        SELECT grp, COUNT(DISTINCT val) as cnt FROM txn_data GROUP BY grp;

    SELECT * FROM txn_view;

    -- Transaction that adds duplicates (should not change count)
    BEGIN;
    INSERT INTO txn_data VALUES ('A', 1), ('A', 2);
    SELECT * FROM txn_view;
    COMMIT;

    SELECT * FROM txn_view;

    -- Transaction that adds new distinct value then rolls back
    BEGIN;
    INSERT INTO txn_data VALUES ('A', 3);
    SELECT * FROM txn_view;
    ROLLBACK;

    SELECT * FROM txn_view;
} {A|2
A|2
A|2
A|3
A|2}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-text-values {
    CREATE TABLE strings(category INTEGER, str TEXT);
    INSERT INTO strings VALUES
        (1, 'hello'),
        (1, 'world'),
        (1, 'hello'),  -- Duplicate
        (2, 'foo'),
        (2, 'bar'),
        (2, 'bar');    -- Duplicate

    CREATE MATERIALIZED VIEW str_counts AS
        SELECT category, COUNT(DISTINCT str) as unique_strings
        FROM strings GROUP BY category;

    SELECT * FROM str_counts ORDER BY category;

    -- Case sensitivity test
    INSERT INTO strings VALUES (1, 'HELLO'), (2, 'FOO');
    SELECT * FROM str_counts ORDER BY category;

    -- Empty strings
    INSERT INTO strings VALUES (1, ''), (1, ''), (2, '');
    SELECT * FROM str_counts ORDER BY category;
} {1|2
2|2
1|3
2|3
1|4
2|4}

do_execsql_test_on_specific_db {:memory:} matview-sum-distinct {
    CREATE TABLE sales(region TEXT, amount INTEGER);
    INSERT INTO sales VALUES
        ('North', 100),
        ('North', 200),
        ('North', 100),  -- Duplicate
        ('North', NULL),
        ('South', 300),
        ('South', 300),  -- Duplicate
        ('South', 400);

    CREATE MATERIALIZED VIEW sales_summary AS
        SELECT region, SUM(DISTINCT amount) as total_distinct
        FROM sales GROUP BY region;

    SELECT * FROM sales_summary ORDER BY region;

    -- Add a duplicate value
    INSERT INTO sales VALUES ('North', 200);
    SELECT * FROM sales_summary ORDER BY region;

    -- Add a new distinct value
    INSERT INTO sales VALUES ('South', 500);
    SELECT * FROM sales_summary ORDER BY region;
} {North|300.0
South|700.0
North|300.0
South|700.0
North|300.0
South|1200.0}

do_execsql_test_on_specific_db {:memory:} matview-avg-distinct {
    CREATE TABLE grades(student TEXT, score INTEGER);
    INSERT INTO grades VALUES
        ('Alice', 90),
        ('Alice', 85),
        ('Alice', 90),  -- Duplicate
        ('Alice', NULL),
        ('Bob', 75),
        ('Bob', 80),
        ('Bob', 75);  -- Duplicate

    CREATE MATERIALIZED VIEW avg_grades AS
        SELECT student, AVG(DISTINCT score) as avg_score
        FROM grades GROUP BY student;

    SELECT * FROM avg_grades ORDER BY student;

    -- Add duplicate scores
    INSERT INTO grades VALUES ('Alice', 85), ('Bob', 80);
    SELECT * FROM avg_grades ORDER BY student;

    -- Add new distinct score
    INSERT INTO grades VALUES ('Alice', 95);
    SELECT * FROM avg_grades ORDER BY student;
} {Alice|87.5
Bob|77.5
Alice|87.5
Bob|77.5
Alice|90.0
Bob|77.5}

do_execsql_test_on_specific_db {:memory:} matview-min-distinct {
    CREATE TABLE metrics(category TEXT, value INTEGER);
    INSERT INTO metrics VALUES
        ('A', 10),
        ('A', 20),
        ('A', 10),  -- Duplicate
        ('A', 30),
        ('A', NULL),
        ('B', 5),
        ('B', 15),
        ('B', 5);  -- Duplicate

    CREATE MATERIALIZED VIEW metric_min AS
        SELECT category,
               MIN(DISTINCT value) as min_val
        FROM metrics GROUP BY category;

    SELECT * FROM metric_min ORDER BY category;

    -- Add values that don't change min
    INSERT INTO metrics VALUES ('A', 15), ('B', 10);
    SELECT * FROM metric_min ORDER BY category;

    -- Add values that change min
    INSERT INTO metrics VALUES ('A', 5), ('B', 3);
    SELECT * FROM metric_min ORDER BY category;
} {A|10
B|5
A|10
B|5
A|5
B|3}

do_execsql_test_on_specific_db {:memory:} matview-max-distinct {
    CREATE TABLE metrics2(category TEXT, value INTEGER);
    INSERT INTO metrics2 VALUES
        ('A', 10),
        ('A', 20),
        ('A', 10),  -- Duplicate
        ('A', 30),
        ('A', NULL),
        ('B', 5),
        ('B', 15),
        ('B', 5);  -- Duplicate

    CREATE MATERIALIZED VIEW metric_max AS
        SELECT category,
               MAX(DISTINCT value) as max_val
        FROM metrics2 GROUP BY category;

    SELECT * FROM metric_max ORDER BY category;

    -- Add values that don't change max
    INSERT INTO metrics2 VALUES ('A', 15), ('B', 10);
    SELECT * FROM metric_max ORDER BY category;

    -- Add values that change max
    INSERT INTO metrics2 VALUES ('A', 40), ('B', 20);
    SELECT * FROM metric_max ORDER BY category;
} {A|30
B|15
A|30
B|15
A|40
B|20}

do_execsql_test_on_specific_db {:memory:} matview-multiple-distinct-aggregates-with-groupby {
    CREATE TABLE data(grp TEXT, x INTEGER, y INTEGER, z INTEGER);
    INSERT INTO data VALUES
        ('A', 1, 10, 100),
        ('A', 2, 20, 200),
        ('A', 1, 10, 300),  -- x,y duplicates
        ('A', 3, 30, 100),  -- z duplicate
        ('A', NULL, 40, 400),
        ('B', 4, 50, 500),
        ('B', 5, 50, 600),  -- y duplicate
        ('B', 4, 60, 700),  -- x duplicate
        ('B', 6, NULL, 500), -- z duplicate
        ('B', NULL, 70, NULL);

    CREATE MATERIALIZED VIEW multi_distinct AS
        SELECT grp,
               COUNT(DISTINCT x) as cnt_x,
               SUM(DISTINCT y) as sum_y,
               AVG(DISTINCT z) as avg_z
        FROM data GROUP BY grp;

    SELECT * FROM multi_distinct ORDER BY grp;

    -- Add more data with duplicates
    INSERT INTO data VALUES
        ('A', 1, 20, 200),  -- Existing values
        ('B', 7, 80, 800);  -- New values

    SELECT * FROM multi_distinct ORDER BY grp;
} {A|3|100.0|250.0
B|3|180.0|600.0
A|3|100.0|250.0
B|4|260.0|650.0}

do_execsql_test_on_specific_db {:memory:} matview-multiple-distinct-aggregates-no-groupby {
    CREATE TABLE data2(x INTEGER, y INTEGER, z INTEGER);
    INSERT INTO data2 VALUES
        (1, 10, 100),
        (2, 20, 200),
        (1, 10, 300),  -- x,y duplicates
        (3, 30, 100),  -- z duplicate
        (NULL, 40, 400),
        (4, 50, 500),
        (5, 50, 600),  -- y duplicate
        (4, 60, 700),  -- x duplicate
        (6, NULL, 500), -- z duplicate
        (NULL, 70, NULL);

    CREATE MATERIALIZED VIEW multi_distinct_global AS
        SELECT COUNT(DISTINCT x) as cnt_x,
               SUM(DISTINCT y) as sum_y,
               AVG(DISTINCT z) as avg_z
        FROM data2;

    SELECT * FROM multi_distinct_global;

    -- Add more data
    INSERT INTO data2 VALUES
        (1, 20, 200),  -- Existing values
        (7, 80, 800);  -- New values

    SELECT * FROM multi_distinct_global;
} {6|280.0|400.0
7|360.0|450.0}

do_execsql_test_on_specific_db {:memory:} matview-count-distinct-global-aggregate {
    CREATE TABLE all_data(val INTEGER);
    INSERT INTO all_data VALUES (1), (2), (1), (3), (2);

    CREATE MATERIALIZED VIEW summary AS
        SELECT COUNT(DISTINCT val) as total_distinct FROM all_data;

    SELECT * FROM summary;

    -- Add duplicates
    INSERT INTO all_data VALUES (1), (2), (3);
    SELECT * FROM summary;

    -- Add new distinct values
    INSERT INTO all_data VALUES (4), (5);
    SELECT * FROM summary;

    -- Delete all of one value
    DELETE FROM all_data WHERE val = 3;
    SELECT * FROM summary;
} {3
3
5
4}
