use citadel::{Argon2Profile, DatabaseBuilder};
use citadel_sql::{Connection, ExecutionResult, SqlError, Value};
fn create_db(dir: &std::path::Path) -> citadel::Database {
let db_path = dir.join("test.db");
DatabaseBuilder::new(db_path)
.passphrase(b"test-passphrase")
.argon2_profile(Argon2Profile::Iot)
.create()
.unwrap()
}
fn open_db(dir: &std::path::Path) -> citadel::Database {
let db_path = dir.join("test.db");
DatabaseBuilder::new(db_path)
.passphrase(b"test-passphrase")
.argon2_profile(Argon2Profile::Iot)
.open()
.unwrap()
}
fn assert_rows_affected(result: ExecutionResult, expected: u64) {
match result {
ExecutionResult::RowsAffected(n) => assert_eq!(n, expected),
other => panic!("expected RowsAffected({expected}), got {other:?}"),
}
}
fn assert_ok(result: ExecutionResult) {
match result {
ExecutionResult::Ok => {}
other => panic!("expected Ok, got {other:?}"),
}
}
fn setup_users(conn: &Connection) {
assert_ok(conn.execute(
"CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY, name TEXT, email TEXT, age INTEGER)"
).unwrap());
assert_rows_affected(
conn.execute(
"INSERT INTO users (id, name, email, age) VALUES (1, 'Alice', 'alice@test.com', 30)",
)
.unwrap(),
1,
);
assert_rows_affected(
conn.execute(
"INSERT INTO users (id, name, email, age) VALUES (2, 'Bob', 'bob@test.com', 25)",
)
.unwrap(),
1,
);
assert_rows_affected(conn.execute(
"INSERT INTO users (id, name, email, age) VALUES (3, 'Charlie', 'charlie@test.com', 35)"
).unwrap(), 1);
}
#[test]
fn create_simple_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW all_users AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM all_users").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn create_view_with_column_aliases() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW user_info (user_id, user_name) AS SELECT id, name FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM user_info").unwrap();
assert_eq!(qr.columns[0], "user_id");
assert_eq!(qr.columns[1], "user_name");
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn create_view_if_not_exists_new() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW IF NOT EXISTS v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn create_view_if_not_exists_existing() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(
conn.execute("CREATE VIEW IF NOT EXISTS v AS SELECT id FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.columns.len(), 4);
}
#[test]
fn create_view_or_replace_new() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE OR REPLACE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn create_view_or_replace_existing() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(
conn.execute("CREATE OR REPLACE VIEW v AS SELECT id, name FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.columns.len(), 2);
}
#[test]
fn create_view_case_insensitive() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW MyView AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM myview").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn create_view_with_where() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW adults AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
let qr = conn.query("SELECT * FROM adults").unwrap();
assert_eq!(qr.rows.len(), 2);
}
#[test]
fn create_view_with_join() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute(
"CREATE TABLE orders (id INTEGER NOT NULL PRIMARY KEY, user_id INTEGER, total REAL)",
)
.unwrap(),
);
assert_rows_affected(
conn.execute("INSERT INTO orders (id, user_id, total) VALUES (1, 1, 99.99)")
.unwrap(),
1,
);
assert_ok(conn.execute(
"CREATE VIEW user_orders AS SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.user_id"
).unwrap());
let qr = conn.query("SELECT * FROM user_orders").unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][0], Value::Text("Alice".into()));
}
#[test]
fn create_view_with_aggregates() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute(
"CREATE VIEW age_stats AS SELECT COUNT(*) AS cnt, AVG(age) AS avg_age FROM users",
)
.unwrap(),
);
let qr = conn.query("SELECT * FROM age_stats").unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][0], Value::Integer(3));
assert_eq!(qr.rows[0][1], Value::Real(30.0));
}
#[test]
fn create_view_with_distinct() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_rows_affected(
conn.execute(
"INSERT INTO users (id, name, email, age) VALUES (4, 'Dave', 'dave@test.com', 30)",
)
.unwrap(),
1,
);
assert_ok(
conn.execute("CREATE VIEW distinct_ages AS SELECT DISTINCT age FROM users")
.unwrap(),
);
let qr = conn
.query("SELECT * FROM distinct_ages ORDER BY age")
.unwrap();
assert_eq!(qr.rows.len(), 3); }
#[test]
fn create_view_with_expressions() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(conn.execute(
"CREATE VIEW user_labels AS SELECT id, name || ' (age ' || age || ')' AS label FROM users"
).unwrap());
let qr = conn
.query("SELECT label FROM user_labels WHERE id = 1")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Text("Alice (age 30)".into()));
}
#[test]
fn create_view_with_union() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(conn.execute(
"CREATE VIEW young_or_old AS SELECT * FROM users WHERE age < 28 UNION ALL SELECT * FROM users WHERE age > 32"
).unwrap());
let qr = conn.query("SELECT * FROM young_or_old").unwrap();
assert_eq!(qr.rows.len(), 2); }
#[test]
fn create_view_with_cte_in_body() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(conn.execute(
"CREATE VIEW top_users AS WITH ranked AS (SELECT * FROM users WHERE age >= 30) SELECT * FROM ranked"
).unwrap());
let qr = conn.query("SELECT * FROM top_users").unwrap();
assert_eq!(qr.rows.len(), 2);
}
#[test]
fn create_view_on_nonexistent_table_lazy() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM ghost")
.unwrap(),
);
let err = conn.query("SELECT * FROM v").unwrap_err();
assert!(matches!(err, SqlError::TableNotFound(_)));
}
#[test]
fn create_view_same_name_as_table() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
let err = conn
.execute("CREATE VIEW users AS SELECT * FROM users")
.unwrap_err();
assert!(matches!(err, SqlError::TableAlreadyExists(_)));
}
#[test]
fn create_table_same_name_as_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let err = conn
.execute("CREATE TABLE v (id INTEGER NOT NULL PRIMARY KEY)")
.unwrap_err();
assert!(matches!(err, SqlError::ViewAlreadyExists(_)));
}
#[test]
fn create_duplicate_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let err = conn
.execute("CREATE VIEW v AS SELECT id FROM users")
.unwrap_err();
assert!(matches!(err, SqlError::ViewAlreadyExists(_)));
}
#[test]
fn drop_view_basic() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP VIEW v").unwrap());
let err = conn.query("SELECT * FROM v").unwrap_err();
assert!(matches!(err, SqlError::TableNotFound(_)));
}
#[test]
fn drop_nonexistent_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
let err = conn.execute("DROP VIEW ghost").unwrap_err();
assert!(matches!(err, SqlError::ViewNotFound(_)));
}
#[test]
fn drop_view_if_exists_existing() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP VIEW IF EXISTS v").unwrap());
}
#[test]
fn drop_view_if_exists_nonexistent() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
assert_ok(conn.execute("DROP VIEW IF EXISTS ghost").unwrap());
}
#[test]
fn drop_and_recreate_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP VIEW v").unwrap());
assert_ok(
conn.execute("CREATE VIEW v AS SELECT id FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.columns.len(), 1);
}
#[test]
fn drop_view_case_insensitive() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW MyView AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP VIEW myview").unwrap());
}
#[test]
fn drop_view_table_intact() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP VIEW v").unwrap());
let qr = conn.query("SELECT COUNT(*) FROM users").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(3));
}
#[test]
fn select_star_from_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 3);
assert_eq!(qr.columns.len(), 4);
}
#[test]
fn select_columns_from_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT id, name FROM users")
.unwrap(),
);
let qr = conn.query("SELECT name FROM v WHERE id = 1").unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][0], Value::Text("Alice".into()));
}
#[test]
fn select_with_where_on_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v WHERE age > 28").unwrap();
assert_eq!(qr.rows.len(), 2);
}
#[test]
fn select_with_order_by_on_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT name FROM v ORDER BY age DESC").unwrap();
assert_eq!(qr.rows[0][0], Value::Text("Charlie".into()));
assert_eq!(qr.rows[2][0], Value::Text("Bob".into()));
}
#[test]
fn select_with_limit_offset_on_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v ORDER BY id LIMIT 2").unwrap();
assert_eq!(qr.rows.len(), 2);
let qr = conn
.query("SELECT * FROM v ORDER BY id LIMIT 1 OFFSET 1")
.unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][0], Value::Integer(2));
}
#[test]
fn select_with_group_by_on_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_rows_affected(
conn.execute(
"INSERT INTO users (id, name, email, age) VALUES (4, 'Dave', 'dave@test.com', 30)",
)
.unwrap(),
1,
);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn
.query("SELECT age, COUNT(*) FROM v GROUP BY age ORDER BY age")
.unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn select_aggregate_on_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT COUNT(*), SUM(age) FROM v").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(3));
assert_eq!(qr.rows[0][1], Value::Integer(90));
}
#[test]
fn select_distinct_on_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_rows_affected(
conn.execute(
"INSERT INTO users (id, name, email, age) VALUES (4, 'Dave', 'dave@test.com', 30)",
)
.unwrap(),
1,
);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn
.query("SELECT DISTINCT age FROM v ORDER BY age")
.unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn count_star_on_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
let qr = conn.query("SELECT COUNT(*) FROM v").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(2));
}
#[test]
fn view_inner_join_table() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute(
"CREATE TABLE orders (id INTEGER NOT NULL PRIMARY KEY, user_id INTEGER, total REAL)",
)
.unwrap(),
);
assert_rows_affected(
conn.execute("INSERT INTO orders (id, user_id, total) VALUES (1, 1, 50.0)")
.unwrap(),
1,
);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn
.query("SELECT v.name, orders.total FROM v INNER JOIN orders ON v.id = orders.user_id")
.unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][0], Value::Text("Alice".into()));
}
#[test]
fn table_left_join_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute(
"CREATE TABLE scores (id INTEGER NOT NULL PRIMARY KEY, user_id INTEGER, score INTEGER)",
)
.unwrap(),
);
assert_rows_affected(
conn.execute("INSERT INTO scores (id, user_id, score) VALUES (1, 1, 100)")
.unwrap(),
1,
);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM scores")
.unwrap(),
);
let qr = conn.query(
"SELECT users.name, v.score FROM users LEFT JOIN v ON users.id = v.user_id ORDER BY users.id"
).unwrap();
assert_eq!(qr.rows.len(), 3);
assert_eq!(qr.rows[0][1], Value::Integer(100));
assert!(qr.rows[1][1].is_null());
}
#[test]
fn view_join_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute(
"CREATE TABLE orders (id INTEGER NOT NULL PRIMARY KEY, user_id INTEGER, total REAL)",
)
.unwrap(),
);
assert_rows_affected(
conn.execute("INSERT INTO orders (id, user_id, total) VALUES (1, 1, 50.0)")
.unwrap(),
1,
);
assert_ok(
conn.execute("CREATE VIEW vu AS SELECT * FROM users")
.unwrap(),
);
assert_ok(
conn.execute("CREATE VIEW vo AS SELECT * FROM orders")
.unwrap(),
);
let qr = conn
.query("SELECT vu.name, vo.total FROM vu INNER JOIN vo ON vu.id = vo.user_id")
.unwrap();
assert_eq!(qr.rows.len(), 1);
}
#[test]
fn view_in_in_subquery() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW adults AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
let qr = conn
.query("SELECT name FROM users WHERE id IN (SELECT id FROM adults)")
.unwrap();
assert_eq!(qr.rows.len(), 2);
}
#[test]
fn view_in_exists_subquery() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW adults AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
let qr = conn
.query("SELECT name FROM users WHERE EXISTS (SELECT 1 FROM adults)")
.unwrap();
assert_eq!(qr.rows.len(), 3);
assert_ok(
conn.execute("CREATE VIEW centenarians AS SELECT * FROM users WHERE age >= 100")
.unwrap(),
);
let qr = conn
.query("SELECT name FROM users WHERE NOT EXISTS (SELECT 1 FROM centenarians)")
.unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn view_in_scalar_subquery() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute(
"CREATE VIEW adult_count AS SELECT COUNT(*) AS cnt FROM users WHERE age >= 30",
)
.unwrap(),
);
let qr = conn
.query("SELECT (SELECT cnt FROM adult_count) AS n")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(2));
}
#[test]
fn view_in_insert_select() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(conn.execute(
"CREATE TABLE archive (id INTEGER NOT NULL PRIMARY KEY, name TEXT, email TEXT, age INTEGER)"
).unwrap());
assert_ok(
conn.execute("CREATE VIEW adults AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
assert_rows_affected(
conn.execute("INSERT INTO archive SELECT * FROM adults")
.unwrap(),
2,
);
let qr = conn.query("SELECT COUNT(*) FROM archive").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(2));
}
#[test]
fn nested_view_two_levels() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v1 AS SELECT * FROM users WHERE age >= 25")
.unwrap(),
);
assert_ok(
conn.execute("CREATE VIEW v2 AS SELECT * FROM v1 WHERE age <= 35")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v2").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn nested_view_three_levels() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v1 AS SELECT * FROM users")
.unwrap(),
);
assert_ok(
conn.execute("CREATE VIEW v2 AS SELECT * FROM v1 WHERE age >= 25")
.unwrap(),
);
assert_ok(
conn.execute("CREATE VIEW v3 AS SELECT name FROM v2 WHERE age <= 30")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v3").unwrap();
assert_eq!(qr.rows.len(), 2); }
#[test]
fn circular_view_detection() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW va AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("CREATE VIEW vb AS SELECT * FROM va").unwrap());
assert_ok(
conn.execute("CREATE OR REPLACE VIEW va AS SELECT * FROM vb")
.unwrap(),
);
let err = conn.query("SELECT * FROM va").unwrap_err();
assert!(matches!(err, SqlError::CircularViewReference(_)));
}
#[test]
fn self_referencing_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
assert_ok(conn.execute("CREATE VIEW v AS SELECT 1").unwrap());
assert_ok(
conn.execute("CREATE OR REPLACE VIEW v AS SELECT * FROM v")
.unwrap(),
);
let err = conn.query("SELECT * FROM v").unwrap_err();
assert!(matches!(err, SqlError::CircularViewReference(_)));
}
#[test]
fn insert_into_view_error() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let err = conn
.execute("INSERT INTO v (id, name, email, age) VALUES (4, 'Dave', 'd@t.com', 40)")
.unwrap_err();
assert!(matches!(err, SqlError::CannotModifyView(_)));
}
#[test]
fn update_view_error() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let err = conn
.execute("UPDATE v SET age = 99 WHERE id = 1")
.unwrap_err();
assert!(matches!(err, SqlError::CannotModifyView(_)));
}
#[test]
fn delete_from_view_error() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let err = conn.execute("DELETE FROM v WHERE id = 1").unwrap_err();
assert!(matches!(err, SqlError::CannotModifyView(_)));
}
#[test]
fn view_persists_across_reopen() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 2);
}
}
#[test]
fn view_drop_persists_across_reopen() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
assert_ok(conn.execute("DROP VIEW v").unwrap());
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let err = conn.query("SELECT * FROM v").unwrap_err();
assert!(matches!(err, SqlError::TableNotFound(_)));
}
}
#[test]
fn view_reflects_new_data_after_reopen() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
assert_rows_affected(
conn.execute(
"INSERT INTO users (id, name, email, age) VALUES (4, 'Dave', 'd@t.com', 40)",
)
.unwrap(),
1,
);
let qr = conn.query("SELECT COUNT(*) FROM v").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(4));
}
}
#[test]
fn multiple_views_persist() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v1 AS SELECT * FROM users WHERE age < 30")
.unwrap(),
);
assert_ok(
conn.execute("CREATE VIEW v2 AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let qr1 = conn.query("SELECT COUNT(*) FROM v1").unwrap();
assert_eq!(qr1.rows[0][0], Value::Integer(1));
let qr2 = conn.query("SELECT COUNT(*) FROM v2").unwrap();
assert_eq!(qr2.rows[0][0], Value::Integer(2));
}
}
#[test]
fn create_view_in_commit() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
conn.execute("BEGIN").unwrap();
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
conn.execute("COMMIT").unwrap();
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn create_view_in_rollback() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
conn.execute("BEGIN").unwrap();
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
conn.execute("ROLLBACK").unwrap();
let err = conn.query("SELECT * FROM v").unwrap_err();
assert!(matches!(err, SqlError::TableNotFound(_)));
}
#[test]
fn drop_view_in_rollback() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
conn.execute("BEGIN").unwrap();
assert_ok(conn.execute("DROP VIEW v").unwrap());
conn.execute("ROLLBACK").unwrap();
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn select_view_inside_transaction() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
conn.execute("BEGIN").unwrap();
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 3);
conn.execute("COMMIT").unwrap();
}
#[test]
fn create_table_and_view_in_txn() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("BEGIN").unwrap();
assert_ok(
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap(),
);
assert_rows_affected(
conn.execute("INSERT INTO t (id, val) VALUES (1, 42)")
.unwrap(),
1,
);
assert_ok(conn.execute("CREATE VIEW v AS SELECT * FROM t").unwrap());
conn.execute("COMMIT").unwrap();
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][1], Value::Integer(42));
}
#[test]
fn drop_table_view_breaks() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP TABLE users").unwrap());
let err = conn.query("SELECT * FROM v").unwrap_err();
assert!(matches!(err, SqlError::TableNotFound(_)));
}
#[test]
fn drop_table_recreate_view_works() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP TABLE users").unwrap());
setup_users(&conn);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 3);
}
#[test]
fn drop_table_recreate_different_schema() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
assert_ok(conn.execute("DROP TABLE users").unwrap());
assert_ok(
conn.execute("CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY, username TEXT)")
.unwrap(),
);
assert_rows_affected(
conn.execute("INSERT INTO users (id, username) VALUES (1, 'alice')")
.unwrap(),
1,
);
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.columns.len(), 2);
assert_eq!(qr.rows.len(), 1);
}
#[test]
fn cte_body_references_view() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users WHERE age >= 30")
.unwrap(),
);
let qr = conn
.query("WITH filtered AS (SELECT * FROM v WHERE name = 'Alice') SELECT * FROM filtered")
.unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][1], Value::Text("Alice".into()));
}
#[test]
fn cte_same_name_as_view_takes_precedence() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn
.query("WITH v AS (SELECT 42 AS val) SELECT * FROM v")
.unwrap();
assert_eq!(qr.rows.len(), 1);
assert_eq!(qr.rows[0][0], Value::Integer(42));
}
#[test]
fn view_body_contains_cte() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(conn.execute(
"CREATE VIEW v AS WITH adults AS (SELECT * FROM users WHERE age >= 30) SELECT * FROM adults"
).unwrap());
let qr = conn.query("SELECT * FROM v").unwrap();
assert_eq!(qr.rows.len(), 2);
}
#[test]
fn simple_view_fuses_with_outer_where() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users WHERE age >= 25")
.unwrap(),
);
let qr = conn.query("SELECT * FROM v WHERE age <= 30").unwrap();
assert_eq!(qr.rows.len(), 2); }
#[test]
fn view_new_data_visible() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
setup_users(&conn);
assert_ok(
conn.execute("CREATE VIEW v AS SELECT * FROM users")
.unwrap(),
);
let qr = conn.query("SELECT COUNT(*) FROM v").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(3));
assert_rows_affected(
conn.execute("INSERT INTO users (id, name, email, age) VALUES (4, 'Dave', 'd@t.com', 40)")
.unwrap(),
1,
);
let qr = conn.query("SELECT COUNT(*) FROM v").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(4));
}