mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-25 12:04:21 +01:00
Trying to return integer sometimes to match SQLite led to more problems that I anticipated. The reason being, we can't *really* match SQLite's behavior unless we know the type of *every* element in the sum. This is not impossible, but it is very hard, for very little gain. Fixes #3831
2497 lines
74 KiB
Tcl
Executable File
2497 lines
74 KiB
Tcl
Executable File
#!/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}
|