use citadel::{Argon2Profile, DatabaseBuilder};
use citadel_sql::{Connection, ExecutionResult, Value};
fn create_db(dir: &std::path::Path) -> citadel::Database {
DatabaseBuilder::new(dir.join("test.db"))
.passphrase(b"test-passphrase")
.argon2_profile(Argon2Profile::Iot)
.create()
.unwrap()
}
fn open_db(dir: &std::path::Path) -> citadel::Database {
DatabaseBuilder::new(dir.join("test.db"))
.passphrase(b"test-passphrase")
.argon2_profile(Argon2Profile::Iot)
.open()
.unwrap()
}
#[test]
fn insert_1000_rows_verify_all() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
.unwrap();
for i in 0..1000 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {})", i * 7))
.unwrap();
}
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(1000));
for i in 0..1000i64 {
let qr = conn
.query(&format!("SELECT val FROM t WHERE id = {i}"))
.unwrap();
assert_eq!(qr.rows.len(), 1, "row {i} missing");
assert_eq!(
qr.rows[0][0],
Value::Integer(i * 7),
"wrong value for row {i}"
);
}
}
#[test]
fn insert_then_delete_half_verify_remaining() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY)")
.unwrap();
for i in 0..500 {
conn.execute(&format!("INSERT INTO t VALUES ({i})"))
.unwrap();
}
for i in (0..500).step_by(2) {
conn.execute(&format!("DELETE FROM t WHERE id = {i}"))
.unwrap();
}
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(250));
for i in 0..500 {
let qr = conn
.query(&format!("SELECT id FROM t WHERE id = {i}"))
.unwrap();
if i % 2 == 0 {
assert_eq!(qr.rows.len(), 0, "even row {i} should be deleted");
} else {
assert_eq!(qr.rows.len(), 1, "odd row {i} should exist");
}
}
}
#[test]
fn update_all_rows() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
for i in 0..200 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, 0)"))
.unwrap();
}
conn.execute("UPDATE t SET val = id * 3").unwrap();
let qr = conn.query("SELECT id, val FROM t ORDER BY id").unwrap();
assert_eq!(qr.rows.len(), 200);
for (i, row) in qr.rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(i as i64));
assert_eq!(row[1], Value::Integer(i as i64 * 3));
}
}
#[test]
fn many_sequential_inserts() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, batch INTEGER)")
.unwrap();
let mut id = 0;
for batch in 0..50 {
for _ in 0..10 {
conn.execute(&format!("INSERT INTO t VALUES ({id}, {batch})"))
.unwrap();
id += 1;
}
}
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(500));
for batch in 0..50 {
let qr = conn
.query(&format!("SELECT COUNT(*) FROM t WHERE batch = {batch}"))
.unwrap();
assert_eq!(
qr.rows[0][0],
Value::Integer(10),
"batch {batch} should have 10 rows"
);
}
}
#[test]
fn create_drop_cycle_50_times() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
for i in 0..50 {
let name = format!("table_{i}");
conn.execute(&format!(
"CREATE TABLE {name} (id INTEGER NOT NULL PRIMARY KEY, val TEXT)"
))
.unwrap();
conn.execute(&format!("INSERT INTO {name} VALUES (1, 'round_{i}')"))
.unwrap();
let qr = conn
.query(&format!("SELECT val FROM {name} WHERE id = 1"))
.unwrap();
assert_eq!(qr.rows[0][0], Value::Text(format!("round_{i}").into()));
conn.execute(&format!("DROP TABLE {name}")).unwrap();
}
assert!(conn.tables().is_empty());
}
#[test]
fn recreate_same_table_with_different_schema() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'hello')").unwrap();
conn.execute("DROP TABLE t").unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, score REAL, active BOOLEAN)")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 99.5, TRUE)")
.unwrap();
let qr = conn
.query("SELECT score, active FROM t WHERE id = 1")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Real(99.5));
assert_eq!(qr.rows[0][1], Value::Boolean(true));
}
#[test]
fn create_50_tables_all_active() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
for i in 0..50 {
conn.execute(&format!(
"CREATE TABLE t_{i} (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)"
))
.unwrap();
conn.execute(&format!("INSERT INTO t_{i} VALUES ({i}, {})", i * 100))
.unwrap();
}
assert_eq!(conn.tables().len(), 50);
for i in 0..50 {
let qr = conn
.query(&format!("SELECT val FROM t_{i} WHERE id = {i}"))
.unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(i * 100));
}
}
#[test]
fn repeated_open_close_cycles() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 100)").unwrap();
}
for cycle in 2..=21 {
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(
qr.rows[0][0],
Value::Integer(cycle - 1),
"wrong count at cycle {cycle}"
);
conn.execute(&format!("INSERT INTO t VALUES ({cycle}, {})", cycle * 10))
.unwrap();
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(21));
}
}
#[test]
fn write_close_reopen_verify_10_cycles() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, data TEXT)")
.unwrap();
}
for cycle in 0..10 {
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
for j in 0..10 {
let id = cycle * 10 + j;
conn.execute(&format!(
"INSERT INTO t VALUES ({id}, 'cycle_{cycle}_item_{j}')"
))
.unwrap();
}
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let expected_count = (cycle + 1) * 10;
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(
qr.rows[0][0],
Value::Integer(expected_count as i64),
"wrong count after cycle {cycle}"
);
}
}
}
#[test]
fn multiple_connections_same_db() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn1 = Connection::open(&db).unwrap();
conn1
.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val TEXT)")
.unwrap();
conn1.execute("INSERT INTO t VALUES (1, 'hello')").unwrap();
let conn2 = Connection::open(&db).unwrap();
let qr = conn2.query("SELECT val FROM t WHERE id = 1").unwrap();
assert_eq!(qr.rows[0][0], Value::Text("hello".into()));
}
#[test]
fn connection_sees_writes_from_other_connection() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn1 = Connection::open(&db).unwrap();
conn1
.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
conn1.execute("INSERT INTO t VALUES (1, 100)").unwrap();
let conn2 = Connection::open(&db).unwrap();
let qr = conn2.query("SELECT val FROM t WHERE id = 1").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(100));
conn1.execute("INSERT INTO t VALUES (2, 200)").unwrap();
let qr = conn2.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(2));
}
#[test]
fn delete_all_reinsert_cycle() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, gen INTEGER)")
.unwrap();
for gen in 0..10 {
for i in 0..50 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {gen})"))
.unwrap();
}
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(
qr.rows[0][0],
Value::Integer(50),
"wrong count in generation {gen}"
);
conn.execute("DELETE FROM t").unwrap();
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(
qr.rows[0][0],
Value::Integer(0),
"table should be empty after delete in generation {gen}"
);
}
}
#[test]
fn large_text_values() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, data TEXT)")
.unwrap();
for i in 1..=20 {
let size = i * 25;
let text = "x".repeat(size);
conn.execute(&format!("INSERT INTO t VALUES ({i}, '{text}')"))
.unwrap();
}
for i in 1..=20 {
let size = i * 25;
let qr = conn
.query(&format!("SELECT data FROM t WHERE id = {i}"))
.unwrap();
match &qr.rows[0][0] {
Value::Text(s) => assert_eq!(s.len(), size, "wrong length for row {i}"),
other => panic!("expected Text for row {i}, got {other:?}"),
}
}
}
#[test]
fn value_near_max_inline_size() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, data TEXT)")
.unwrap();
let text = "A".repeat(1800);
conn.execute(&format!("INSERT INTO t VALUES (1, '{text}')"))
.unwrap();
let qr = conn.query("SELECT data FROM t WHERE id = 1").unwrap();
match &qr.rows[0][0] {
Value::Text(s) => assert_eq!(s.len(), 1800),
other => panic!("expected Text, got {other:?}"),
}
}
#[test]
fn value_exceeding_max_inline_size_is_rejected() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, data TEXT)")
.unwrap();
let text = "B".repeat(1920);
let result = conn.execute(&format!("INSERT INTO t VALUES (1, '{text}')"));
assert!(
result.is_err(),
"value exceeding max inline size should be rejected"
);
}
#[test]
fn table_with_20_columns() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
let mut cols: Vec<String> = vec!["id INTEGER NOT NULL PRIMARY KEY".into()];
for i in 1..20 {
cols.push(format!("col_{i} INTEGER"));
}
let create = format!("CREATE TABLE wide ({})", cols.join(", "));
conn.execute(&create).unwrap();
let mut vals: Vec<String> = vec!["1".into()];
for i in 1..20 {
vals.push(format!("{}", i * 10));
}
let insert = format!("INSERT INTO wide VALUES ({})", vals.join(", "));
conn.execute(&insert).unwrap();
let qr = conn.query("SELECT * FROM wide WHERE id = 1").unwrap();
assert_eq!(qr.rows[0].len(), 20);
assert_eq!(qr.rows[0][0], Value::Integer(1));
for i in 1..20 {
assert_eq!(qr.rows[0][i], Value::Integer(i as i64 * 10));
}
}
#[test]
fn table_with_many_nullable_columns() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
let mut cols: Vec<String> = vec!["id INTEGER NOT NULL PRIMARY KEY".into()];
for i in 1..=15 {
cols.push(format!("nullable_{i} INTEGER"));
}
let create = format!("CREATE TABLE sparse ({})", cols.join(", "));
conn.execute(&create).unwrap();
conn.execute("INSERT INTO sparse (id) VALUES (1)").unwrap();
let qr = conn.query("SELECT * FROM sparse WHERE id = 1").unwrap();
assert_eq!(qr.rows[0].len(), 16);
assert_eq!(qr.rows[0][0], Value::Integer(1));
for i in 1..=15 {
assert!(qr.rows[0][i].is_null(), "col {i} should be NULL");
}
conn.execute("INSERT INTO sparse (id, nullable_5, nullable_10) VALUES (2, 50, 100)")
.unwrap();
let qr = conn
.query("SELECT nullable_5, nullable_10 FROM sparse WHERE id = 2")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(50));
assert_eq!(qr.rows[0][1], Value::Integer(100));
}
#[test]
fn order_by_500_rows_ascending() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
for i in (0..500).rev() {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {})", 500 - i))
.unwrap();
}
let qr = conn.query("SELECT id FROM t ORDER BY id ASC").unwrap();
assert_eq!(qr.rows.len(), 500);
for (idx, row) in qr.rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(idx as i64));
}
}
#[test]
fn order_by_text_500_rows() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL)")
.unwrap();
for i in 0..500 {
let name = format!("item_{:05}", 499 - i); conn.execute(&format!("INSERT INTO t VALUES ({i}, '{name}')"))
.unwrap();
}
let qr = conn.query("SELECT name FROM t ORDER BY name ASC").unwrap();
assert_eq!(qr.rows.len(), 500);
for i in 0..499 {
let a = &qr.rows[i][0];
let b = &qr.rows[i + 1][0];
assert!(a <= b, "sort order broken at index {i}: {a:?} > {b:?}");
}
}
#[test]
fn group_by_100_groups() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute(
"CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, grp INTEGER NOT NULL, val INTEGER)",
)
.unwrap();
let mut id = 0;
for grp in 0..100 {
for val in 0..5 {
conn.execute(&format!("INSERT INTO t VALUES ({id}, {grp}, {val})"))
.unwrap();
id += 1;
}
}
let qr = conn
.query("SELECT grp, COUNT(*), SUM(val) FROM t GROUP BY grp ORDER BY grp")
.unwrap();
assert_eq!(qr.rows.len(), 100);
for (i, row) in qr.rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(i as i64), "wrong group at index {i}");
assert_eq!(row[1], Value::Integer(5), "wrong count for group {i}");
assert_eq!(row[2], Value::Integer(10), "wrong sum for group {i}"); }
}
#[test]
fn integer_range_minus_500_to_499_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY)")
.unwrap();
for i in -500..500 {
conn.execute(&format!("INSERT INTO t VALUES ({i})"))
.unwrap();
}
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(1000));
let qr = conn.query("SELECT id FROM t ORDER BY id").unwrap();
assert_eq!(qr.rows.len(), 1000);
for (idx, row) in qr.rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(-500 + idx as i64));
}
}
#[test]
fn interleaved_insert_update_delete() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
for round in 0..20 {
let base = round * 10;
for j in 0..10 {
let id = base + j;
conn.execute(&format!("INSERT INTO t VALUES ({id}, 0)"))
.unwrap();
}
for j in 0..5 {
let id = base + j;
conn.execute(&format!("UPDATE t SET val = 1 WHERE id = {id}"))
.unwrap();
}
for j in 7..10 {
let id = base + j;
conn.execute(&format!("DELETE FROM t WHERE id = {id}"))
.unwrap();
}
}
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(140));
let qr = conn.query("SELECT COUNT(*) FROM t WHERE val = 1").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(100));
let qr = conn.query("SELECT COUNT(*) FROM t WHERE val = 0").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(40)); }
#[test]
fn schema_persists_with_multiple_tables_across_reopen() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"CREATE TABLE posts (id INTEGER NOT NULL PRIMARY KEY, user_id INTEGER, body TEXT)",
)
.unwrap();
conn.execute("CREATE TABLE tags (id INTEGER NOT NULL PRIMARY KEY, label TEXT NOT NULL)")
.unwrap();
conn.execute("INSERT INTO users VALUES (1, 'Alice')")
.unwrap();
conn.execute("INSERT INTO posts VALUES (1, 1, 'Hello world')")
.unwrap();
conn.execute("INSERT INTO tags VALUES (1, 'rust')").unwrap();
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let mut tables = conn.tables();
tables.sort();
assert_eq!(tables, vec!["posts", "tags", "users"]);
let qr = conn.query("SELECT name FROM users WHERE id = 1").unwrap();
assert_eq!(qr.rows[0][0], Value::Text("Alice".into()));
let qr = conn.query("SELECT body FROM posts WHERE id = 1").unwrap();
assert_eq!(qr.rows[0][0], Value::Text("Hello world".into()));
let qr = conn.query("SELECT label FROM tags WHERE id = 1").unwrap();
assert_eq!(qr.rows[0][0], Value::Text("rust".into()));
conn.execute("DROP TABLE posts").unwrap();
conn.execute("CREATE TABLE comments (id INTEGER NOT NULL PRIMARY KEY, text TEXT)")
.unwrap();
conn.execute("INSERT INTO comments VALUES (1, 'Nice!')")
.unwrap();
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let mut tables = conn.tables();
tables.sort();
assert_eq!(tables, vec!["comments", "tags", "users"]);
let result = conn.query("SELECT * FROM posts");
assert!(result.is_err());
let qr = conn
.query("SELECT text FROM comments WHERE id = 1")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Text("Nice!".into()));
}
}
#[test]
fn multi_row_insert_100_at_a_time() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
for batch in 0..5 {
let mut values = Vec::new();
for j in 0..100 {
let id = batch * 100 + j;
values.push(format!("({id}, {})", id * 2));
}
let sql = format!("INSERT INTO t VALUES {}", values.join(", "));
match conn.execute(&sql) {
Ok(ExecutionResult::RowsAffected(n)) => assert_eq!(n, 100),
other => panic!("expected RowsAffected(100), got {other:?}"),
}
}
let qr = conn.query("SELECT COUNT(*) FROM t").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(500));
}
#[test]
fn all_types_in_one_table() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute(
"CREATE TABLE mixed (
id INTEGER NOT NULL PRIMARY KEY,
int_col INTEGER,
real_col REAL,
text_col TEXT,
bool_col BOOLEAN
)",
)
.unwrap();
for i in 0..100 {
let real_val = i as f64 * 0.1;
let text_val = format!("item_{i}");
let bool_val = if i % 2 == 0 { "TRUE" } else { "FALSE" };
conn.execute(&format!(
"INSERT INTO mixed VALUES ({i}, {}, {real_val}, '{text_val}', {bool_val})",
i * 100
))
.unwrap();
}
let qr = conn
.query("SELECT COUNT(*) FROM mixed WHERE int_col >= 5000")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(50));
let qr = conn
.query("SELECT COUNT(*) FROM mixed WHERE real_col > 5.0")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(49));
let qr = conn
.query("SELECT COUNT(*) FROM mixed WHERE bool_col = TRUE")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(50));
let qr = conn.query("SELECT SUM(int_col) FROM mixed").unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(495000));
let qr = conn
.query("SELECT MIN(text_col), MAX(text_col) FROM mixed")
.unwrap();
assert_eq!(qr.rows[0][0], Value::Text("item_0".into()));
assert_eq!(qr.rows[0][1], Value::Text("item_99".into()));
}
#[test]
fn update_all_then_reopen() {
let dir = tempfile::tempdir().unwrap();
{
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
for i in 0..100 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, 0)"))
.unwrap();
}
conn.execute("UPDATE t SET val = id * 2").unwrap();
}
{
let db = open_db(dir.path());
let conn = Connection::open(&db).unwrap();
let qr = conn.query("SELECT id, val FROM t ORDER BY id").unwrap();
assert_eq!(qr.rows.len(), 100);
for (i, row) in qr.rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(i as i64));
assert_eq!(row[1], Value::Integer(i as i64 * 2), "wrong val for id {i}");
}
}
}
#[test]
fn rapid_queries_500() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER)")
.unwrap();
for i in 0..50 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {})", i * i))
.unwrap();
}
for _ in 0..10 {
for i in 0..50 {
let qr = conn
.query(&format!("SELECT val FROM t WHERE id = {i}"))
.unwrap();
assert_eq!(qr.rows[0][0], Value::Integer(i * i));
}
}
}
#[test]
fn distinct_many_duplicates() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
.unwrap();
for i in 0..500 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {})", i % 10))
.unwrap();
}
let qr = conn
.query("SELECT DISTINCT val FROM t ORDER BY val")
.unwrap();
assert_eq!(qr.rows.len(), 10);
for (i, row) in qr.rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(i as i64));
}
}
#[test]
fn distinct_multi_column_many_rows() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute(
"CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, a INTEGER NOT NULL, b INTEGER NOT NULL)",
)
.unwrap();
for i in 0..300 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {}, {})", i % 5, i % 7))
.unwrap();
}
let qr = conn
.query("SELECT DISTINCT a, b FROM t ORDER BY a, b")
.unwrap();
assert_eq!(qr.rows.len(), 35);
}
#[test]
fn distinct_with_limit_on_large_dataset() {
let dir = tempfile::tempdir().unwrap();
let db = create_db(dir.path());
let conn = Connection::open(&db).unwrap();
conn.execute("CREATE TABLE t (id INTEGER NOT NULL PRIMARY KEY, val INTEGER NOT NULL)")
.unwrap();
for i in 0..1000 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {})", i % 50))
.unwrap();
}
let qr = conn
.query("SELECT DISTINCT val FROM t ORDER BY val LIMIT 5")
.unwrap();
assert_eq!(qr.rows.len(), 5);
for (i, row) in qr.rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(i as i64));
}
}