mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-24 19:44:21 +01:00
We had code for this, but the code had a fatal flaw: it tried to detect a complex operation (an operation that needs projection), and return false (no need for projection), for the others. This is the exact opposite of what we should do: we should identify the *simple* operations, and then return true (needs projection) for the rest. CAST is a special beast, since it is not a function, but rather, a special opcode. Everything else above is the true just the same. But for CAST, we have to do the extra work to capture it in the logical plan and pass it down. Fixes #3372 Fixes #3370 Fixes #3369
1598 lines
48 KiB
Tcl
Executable File
1598 lines
48 KiB
Tcl
Executable File
#!/usr/bin/env tclsh
|
|
|
|
set testdir [file dirname $argv0]
|
|
source $testdir/tester.tcl
|
|
|
|
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|2
|
|
2|2|2
|
|
3|4|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|1
|
|
6|6|1
|
|
7|7|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|1
|
|
6|6|1
|
|
3|7|2
|
|
6|11|2
|
|
3|7|2
|
|
6|11|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|3
|
|
B|60|2
|
|
A|60|2
|
|
B|60|2
|
|
A|60|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|2
|
|
2|600|2
|
|
1|450|2
|
|
2|600|2
|
|
1|150|1
|
|
2|900|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|1
|
|
20|700|2
|
|
10|600|2
|
|
20|700|2
|
|
10|600|2
|
|
20|400|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|2
|
|
200|100|1
|
|
300|60|1
|
|
100|95|3
|
|
200|100|1
|
|
300|60|1
|
|
100|125|3
|
|
200|100|1
|
|
300|60|1
|
|
100|125|3
|
|
300|60|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|3
|
|
2|70|2
|
|
1|85|4
|
|
2|70|2
|
|
1|85|4
|
|
2|75|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|1
|
|
9|3|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|1
|
|
200|75|1
|
|
100|75|2
|
|
200|75|1
|
|
100|75|2
|
|
200|100|1
|
|
100|25|1
|
|
200|100|1
|
|
100|25|1
|
|
200|100|1
|
|
300|150|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|2
|
|
2|400|2
|
|
1|350|3
|
|
2|400|2
|
|
3|300|1
|
|
1|300|2
|
|
2|400|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|2
|
|
200|75|1
|
|
100|150|2
|
|
200|150|1
|
|
100|75|2
|
|
200|75|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|1
|
|
200|100|1
|
|
100|135|2
|
|
100|50|1
|
|
200|100|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
|
|
Bob|250}
|
|
|
|
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|60
|
|
Alice|Widget|9|90
|
|
Bob|Widget|2|20}
|
|
|
|
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
|
|
Alice|Gold|3|6
|
|
Charlie|Bronze|1|1}
|
|
|
|
# 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|3150
|
|
South|18|1500}
|
|
|
|
# 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|16450
|
|
Q2|105|21750}
|
|
|
|
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}
|