pub use fsqlite_core::connection::{
Connection, ConnectionEnv, IoPollStrategy, PreparedStatement, Row, RuntimeConfig,
RuntimeContext, TraceEvent, TraceMask, init_global_runtime,
};
pub use fsqlite_error::FrankenError;
pub use fsqlite_types::SqliteValue;
pub use fsqlite_vfs;
#[cfg(feature = "session")]
pub mod session {
pub use fsqlite_ext_session::{
ApplyOutcome, ChangeOp, Changeset, ChangesetRow, ChangesetValue, ConflictAction,
ConflictType, Session, SimpleTarget, TableChangeset, TableInfo, changeset_varint_len,
extension_name,
};
}
#[cfg(feature = "async-api")]
pub mod async_api;
#[cfg(feature = "async-api")]
pub use async_api::AsyncConnection;
pub mod compat;
pub mod migrate;
#[cfg(test)]
#[allow(
clippy::too_many_lines,
clippy::items_after_statements,
clippy::needless_collect,
clippy::single_match_else,
clippy::branches_sharing_code
)]
mod tests {
use super::{
Connection, ConnectionEnv, IoPollStrategy, RuntimeConfig, RuntimeContext,
init_global_runtime,
};
use fsqlite_ast::{CreateTableBody, Statement};
use fsqlite_error::FrankenError;
use fsqlite_parser::parse_first_statement_with_tail;
use fsqlite_types::value::SqliteValue;
use std::sync::Arc;
fn row_values(row: &super::Row) -> Vec<SqliteValue> {
row.values().to_vec()
}
#[test]
fn test_connection_open_and_path() {
let conn = Connection::open(":memory:").expect("in-memory connection should open");
assert_eq!(conn.path(), ":memory:");
}
#[test]
fn test_public_api_query_expression() {
let conn = Connection::open(":memory:").expect("in-memory connection should open");
let rows = conn
.query("SELECT 1 + 2, 'ab' || 'cd';")
.expect("query should succeed");
assert_eq!(rows.len(), 1);
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(3), SqliteValue::Text("abcd".into()),]
);
}
#[test]
fn test_public_api_query_with_params() {
let conn = Connection::open(":memory:").expect("in-memory connection should open");
let rows = conn
.query_with_params(
"SELECT ?1 + ?2, ?3;",
&[
SqliteValue::Integer(4),
SqliteValue::Integer(5),
SqliteValue::Text("ok".into()),
],
)
.expect("query_with_params should succeed");
assert_eq!(rows.len(), 1);
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(9), SqliteValue::Text("ok".into())]
);
}
#[test]
fn test_public_api_query_row_multiple_rows_error() {
let conn = Connection::open(":memory:").expect("in-memory connection should open");
let error = conn
.query_row("VALUES (10), (20), (30);")
.expect_err("query_row should fail when more than one row is returned");
assert!(matches!(error, FrankenError::QueryReturnedMultipleRows));
}
#[test]
fn test_public_api_query_row_empty_error() {
let conn = Connection::open(":memory:").expect("in-memory connection should open");
let error = conn
.query_row("SELECT 1 WHERE 0;")
.expect_err("query_row should fail for empty result set");
assert!(matches!(error, FrankenError::QueryReturnedNoRows));
}
#[test]
fn test_public_api_execute_returns_row_count() {
let conn = Connection::open(":memory:").expect("in-memory connection should open");
let count = conn
.execute("VALUES (1), (2), (3);")
.expect("execute should succeed");
assert_eq!(count, 3);
}
#[test]
fn open_empty_path_fails() {
let err = Connection::open("").expect_err("empty path should fail");
assert!(matches!(err, FrankenError::CannotOpen { .. }));
}
#[test]
fn runtime_api_is_reexported() {
let runtime = init_global_runtime(RuntimeConfig {
worker_threads: 2,
io_poll_strategy: IoPollStrategy::Blocking,
});
assert_eq!(runtime.config().worker_threads, 2);
assert_eq!(runtime.config().io_poll_strategy, IoPollStrategy::Blocking);
let parent_cx = fsqlite_types::cx::Cx::new().with_trace_context(11, 0, 0);
let explicit_runtime = Arc::new(RuntimeContext::new_with_root_cx(
RuntimeConfig {
worker_threads: 1,
io_poll_strategy: IoPollStrategy::Auto,
},
&parent_cx,
));
let env = ConnectionEnv::new(Arc::clone(&explicit_runtime));
let conn = Connection::open_with_env(":memory:", env).expect("connection should open");
assert_eq!(conn.path(), ":memory:");
}
#[test]
fn row_get_valid_index() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 42, 'hello';").unwrap();
assert_eq!(row.get(0), Some(&SqliteValue::Integer(42)));
assert_eq!(row.get(1), Some(&SqliteValue::Text("hello".into())));
}
#[test]
fn row_get_out_of_bounds() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 1;").unwrap();
assert_eq!(row.get(99), None);
}
#[test]
fn row_values_returns_all_columns() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 1, 2, 3;").unwrap();
assert_eq!(row.values().len(), 3);
}
#[test]
fn prepared_query() {
let conn = Connection::open(":memory:").unwrap();
let stmt = conn.prepare("SELECT 7 * 6;").unwrap();
let rows = stmt.query().unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(42)]);
}
#[test]
fn prepared_query_with_params() {
let conn = Connection::open(":memory:").unwrap();
let stmt = conn.prepare("SELECT ?1 + ?2;").unwrap();
let rows = stmt
.query_with_params(&[SqliteValue::Integer(10), SqliteValue::Integer(20)])
.unwrap();
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(30)]);
}
#[test]
fn prepared_query_row() {
let conn = Connection::open(":memory:").unwrap();
let stmt = conn.prepare("SELECT 99;").unwrap();
let row = stmt.query_row().unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(99)]);
}
#[test]
fn prepared_query_row_with_params() {
let conn = Connection::open(":memory:").unwrap();
let stmt = conn.prepare("SELECT ?1;").unwrap();
let row = stmt
.query_row_with_params(&[SqliteValue::Text("xyz".into())])
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("xyz".into())]);
}
#[test]
fn prepared_execute() {
let conn = Connection::open(":memory:").unwrap();
let stmt = conn.prepare("VALUES (1), (2);").unwrap();
assert_eq!(stmt.execute().unwrap(), 2);
}
#[test]
fn prepared_execute_with_params() {
let conn = Connection::open(":memory:").unwrap();
let stmt = conn.prepare("SELECT ?1;").unwrap();
assert_eq!(
stmt.execute_with_params(&[SqliteValue::Integer(1)])
.unwrap(),
1
);
}
#[test]
fn prepared_explain_not_empty() {
let conn = Connection::open(":memory:").unwrap();
let stmt = conn.prepare("SELECT 1 + 2;").unwrap();
let explain = stmt.explain();
assert!(!explain.is_empty());
}
#[cfg(feature = "session")]
#[test]
fn session_feature_reexports_manual_session_api() {
assert_eq!(super::session::extension_name(), "session");
let mut session = super::session::Session::new();
session.attach_table("users", 2, vec![true, false]);
session.record_insert(
"users",
vec![
super::session::ChangesetValue::Integer(1),
super::session::ChangesetValue::Text("alice".to_owned()),
],
);
let encoded = session.changeset().encode();
let decoded = super::session::Changeset::decode(&encoded)
.expect("re-exported session API should round-trip changesets");
assert_eq!(decoded.encode(), encoded);
}
#[test]
fn prepared_indexed_equality_explain_uses_duplicate_run_probe() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("indexed-equality.db");
let db = db_path.to_string_lossy().to_string();
{
let conn = Connection::open(&db).unwrap();
conn.execute(
"CREATE TABLE t (id INTEGER PRIMARY KEY, b INTEGER NOT NULL, name TEXT NOT NULL);",
)
.unwrap();
conn.execute("CREATE INDEX idx_t_b ON t(b);").unwrap();
conn.execute(
"INSERT INTO t (id, b, name) VALUES \
(1, 42, 'alice'), (2, 42, 'bruce'), (5, 42, 'claire'), (9, 99, 'dora');",
)
.unwrap();
}
let conn = Connection::open(&db).unwrap();
let stmt = conn.prepare("SELECT name FROM t WHERE b = ?1;").unwrap();
let explain = stmt.explain();
assert!(explain.contains("idx_t_b"));
assert!(explain.contains("SeekGE"));
assert!(explain.contains("IdxRowid"));
assert!(explain.contains("SeekRowid"));
assert!(
explain.contains("-9223372036854775808"),
"expected synthetic low-rowid probe in explain output: {explain}"
);
let rows = stmt.query_with_params(&[SqliteValue::Integer(42)]).unwrap();
assert_eq!(
rows.iter()
.map(|row| row.get(0).cloned().unwrap())
.collect::<Vec<_>>(),
vec![
SqliteValue::Text("alice".to_owned().into()),
SqliteValue::Text("bruce".to_owned().into()),
SqliteValue::Text("claire".to_owned().into()),
]
);
}
#[test]
fn query_row_with_params() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row_with_params("SELECT ?1 * 2;", &[SqliteValue::Integer(5)])
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(10)]);
}
#[test]
fn execute_with_params_returns_count() {
let conn = Connection::open(":memory:").unwrap();
let count = conn
.execute_with_params("SELECT ?1;", &[SqliteValue::Integer(1)])
.unwrap();
assert_eq!(count, 1);
}
#[test]
fn create_table_and_insert_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (a INTEGER, b TEXT);")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'one');").unwrap();
conn.execute("INSERT INTO t1 VALUES (2, 'two');").unwrap();
let rows = conn.query("SELECT a, b FROM t1;").unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn create_table_if_not_exists_no_error() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (x INTEGER);").unwrap();
conn.execute("CREATE TABLE IF NOT EXISTS t1 (x INTEGER);")
.unwrap();
}
#[test]
fn create_duplicate_table_errors() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (x INTEGER);").unwrap();
let err = conn
.execute("CREATE TABLE t1 (x INTEGER);")
.expect_err("duplicate table should fail");
assert!(matches!(err, FrankenError::Internal(_)));
}
#[test]
fn public_api_writable_schema_allows_filebacked_sqlite_master_insert() {
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("writable-schema.db");
let conn = Connection::open(db_path.to_string_lossy().into_owned()).unwrap();
conn.execute("CREATE TABLE real_table (id INTEGER);")
.unwrap();
let before = conn.query_row("PRAGMA writable_schema;").unwrap();
assert_eq!(row_values(&before), vec![SqliteValue::Integer(0)]);
conn.execute("PRAGMA writable_schema = ON;").unwrap();
let after = conn.query_row("PRAGMA writable_schema;").unwrap();
assert_eq!(row_values(&after), vec![SqliteValue::Integer(1)]);
let inserted = conn
.execute(
"INSERT INTO sqlite_master(type, name, tbl_name, rootpage, sql) \
VALUES('table', 'fake_tbl', 'fake_tbl', 0, 'CREATE TABLE fake_tbl(x)');",
)
.unwrap();
assert_eq!(inserted, 1);
let deleted = conn
.execute("DELETE FROM sqlite_master WHERE name = 'fake_tbl';")
.unwrap();
assert_eq!(deleted, 1);
}
#[test]
fn execute_insert_returns_affected_count() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
assert_eq!(conn.execute("INSERT INTO t VALUES (1);").unwrap(), 1);
assert_eq!(
conn.execute("INSERT INTO t VALUES (2), (3), (4);").unwrap(),
3,
);
}
#[test]
fn execute_update_returns_affected_count() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
conn.execute("INSERT INTO t VALUES (2);").unwrap();
conn.execute("INSERT INTO t VALUES (3);").unwrap();
assert_eq!(conn.execute("UPDATE t SET v = 0;").unwrap(), 3);
assert_eq!(conn.execute("UPDATE t SET v = 99 WHERE v = 0;").unwrap(), 3);
}
#[test]
fn execute_delete_returns_affected_count() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
conn.execute("INSERT INTO t VALUES (2);").unwrap();
conn.execute("INSERT INTO t VALUES (3);").unwrap();
assert_eq!(conn.execute("DELETE FROM t WHERE v = 2;").unwrap(), 1);
assert_eq!(conn.execute("DELETE FROM t;").unwrap(), 2);
}
#[test]
fn update_modifies_rows() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (10);").unwrap();
conn.execute("INSERT INTO t VALUES (20);").unwrap();
conn.execute("UPDATE t SET v = 99 WHERE v = 10;").unwrap();
let rows = conn.query("SELECT v FROM t;").unwrap();
let vals: Vec<_> = rows.iter().map(row_values).collect();
assert!(vals.contains(&vec![SqliteValue::Integer(99)]));
assert!(vals.contains(&vec![SqliteValue::Integer(20)]));
}
#[test]
fn update_preserves_integer_primary_key_rowid_alias() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER);")
.unwrap();
conn.execute("INSERT INTO accounts VALUES (1, 100);")
.unwrap();
conn.execute("INSERT INTO accounts VALUES (2, 200);")
.unwrap();
conn.execute("UPDATE accounts SET balance = balance + 5 WHERE id = 1;")
.unwrap();
let rows = conn
.query("SELECT id, balance FROM accounts ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 2, "update must not create or lose rows");
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(1), SqliteValue::Integer(105)],
"id=1 row must be updated in place"
);
assert_eq!(
row_values(&rows[1]),
vec![SqliteValue::Integer(2), SqliteValue::Integer(200)],
"id=2 row must remain unchanged"
);
}
#[test]
fn concurrent_same_row_deposit_commits_must_conflict_or_serialize() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("concurrent_same_row_deposit.db");
let db = db_path.to_string_lossy().to_string();
{
let conn = Connection::open(&db).unwrap();
conn.execute("PRAGMA fsqlite.concurrent_mode=ON;").unwrap();
conn.execute(
"CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER NOT NULL);",
)
.unwrap();
conn.execute("INSERT INTO accounts VALUES (1, 0);").unwrap();
}
let conn1 = Connection::open(&db).unwrap();
let conn2 = Connection::open(&db).unwrap();
conn1.execute("PRAGMA fsqlite.concurrent_mode=ON;").unwrap();
conn2.execute("PRAGMA fsqlite.concurrent_mode=ON;").unwrap();
conn1.execute("BEGIN CONCURRENT;").unwrap();
conn2.execute("BEGIN CONCURRENT;").unwrap();
assert_eq!(
conn1
.execute("UPDATE accounts SET balance = balance + 1 WHERE id = 1;")
.unwrap(),
1
);
let update2 = conn2.execute("UPDATE accounts SET balance = balance + 1 WHERE id = 1;");
let commit1 = conn1.execute("COMMIT;");
let commit2 = match update2 {
Ok(changes2) => {
assert_eq!(changes2, 1, "second update should affect one row");
conn2.execute("COMMIT;")
}
Err(err) => {
assert!(
err.is_transient(),
"second concurrent writer should fail transiently on conflict, got: {err}"
);
let rollback = conn2.execute("ROLLBACK;");
assert!(
rollback.is_ok(),
"second writer should remain rollback-able after transient conflict: {rollback:?}"
);
Err(err)
}
};
let verify = Connection::open(&db).unwrap();
let row = verify
.query_row("SELECT balance FROM accounts WHERE id = 1;")
.unwrap();
let balance = row.get(0).cloned().unwrap_or(SqliteValue::Null);
match (commit1, commit2) {
(Ok(_), Ok(_)) => {
assert_eq!(
balance,
SqliteValue::Integer(2),
"if both commits succeed, both deposits must be visible"
);
}
(Ok(_), Err(err)) | (Err(err), Ok(_)) => {
assert!(
err.is_transient(),
"conflicting concurrent writer should fail with transient busy snapshot/busy, got: {err}"
);
assert_eq!(
balance,
SqliteValue::Integer(1),
"if one writer aborts, exactly one deposit should persist"
);
}
(Err(err1), Err(err2)) => {
panic!("at least one concurrent writer must commit: err1={err1}; err2={err2}");
}
}
}
#[test]
fn delete_removes_rows() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
conn.execute("INSERT INTO t VALUES (2);").unwrap();
conn.execute("INSERT INTO t VALUES (3);").unwrap();
conn.execute("DELETE FROM t WHERE v = 2;").unwrap();
let rows = conn.query("SELECT v FROM t;").unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(vals.contains(&SqliteValue::Integer(1)));
assert!(vals.contains(&SqliteValue::Integer(3)));
}
#[test]
fn null_value_roundtrip() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULL;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
}
#[test]
#[allow(clippy::approx_constant)]
fn real_value_roundtrip() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 3.14;").unwrap();
if let SqliteValue::Float(v) = &row_values(&row)[0] {
assert!((*v - 3.14).abs() < f64::EPSILON);
} else {
unreachable!("expected Float value");
}
}
#[test]
fn text_value_roundtrip() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 'hello world';").unwrap();
assert_eq!(
row_values(&row),
vec![SqliteValue::Text("hello world".into())]
);
}
#[test]
fn blob_value_via_params() {
let conn = Connection::open(":memory:").unwrap();
let blob = vec![0xDE, 0xAD, 0xBE, 0xEF];
let row = conn
.query_row_with_params("SELECT ?1;", &[SqliteValue::Blob(blob.clone().into())])
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Blob(blob.into())]);
}
#[test]
fn in_transaction_flag() {
let conn = Connection::open(":memory:").unwrap();
assert!(!conn.in_transaction());
conn.execute("BEGIN;").unwrap();
assert!(conn.in_transaction());
conn.execute("COMMIT;").unwrap();
assert!(!conn.in_transaction());
}
#[test]
fn begin_commit_persists_changes() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("BEGIN;").unwrap();
conn.execute("INSERT INTO t VALUES (42);").unwrap();
conn.execute("COMMIT;").unwrap();
let rows = conn.query("SELECT v FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(42)]);
}
#[test]
fn rollback_reverts_changes() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
let m1 = conn.query("SELECT * FROM sqlite_master;").unwrap();
eprintln!("MAIN BEFORE BEGIN: {:?}", m1);
conn.execute("BEGIN;").unwrap();
conn.execute("INSERT INTO t VALUES (2);").unwrap();
conn.execute("ROLLBACK;").unwrap();
let m2 = conn.query("SELECT * FROM sqlite_master;").unwrap();
eprintln!("MAIN AFTER ROLLBACK: {:?}", m2);
let rows = conn.query("SELECT v FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
}
#[test]
fn nested_begin_errors() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("BEGIN;").unwrap();
let err = conn
.execute("BEGIN;")
.expect_err("nested begin should fail");
assert!(matches!(err, FrankenError::Internal(_)));
}
#[test]
fn commit_without_transaction_errors() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.execute("COMMIT;")
.expect_err("commit without txn should fail");
assert!(matches!(err, FrankenError::Internal(_)));
}
#[test]
fn rollback_without_transaction_errors() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.execute("ROLLBACK;")
.expect_err("rollback without txn should fail");
assert!(matches!(err, FrankenError::Internal(_)));
}
#[test]
fn savepoint_and_rollback_to() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
conn.execute("SAVEPOINT sp1;").unwrap();
conn.execute("INSERT INTO t VALUES (2);").unwrap();
conn.execute("ROLLBACK TO sp1;").unwrap();
let rows = conn.query("SELECT v FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
}
#[test]
fn savepoint_release_commits_changes() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
conn.execute("SAVEPOINT sp1;").unwrap();
conn.execute("INSERT INTO t VALUES (100);").unwrap();
conn.execute("RELEASE sp1;").unwrap();
let rows = conn.query("SELECT v FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(100)]);
}
#[test]
fn release_nonexistent_savepoint_errors() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("BEGIN;").unwrap();
let err = conn
.execute("RELEASE nosuch;")
.expect_err("release nonexistent savepoint should fail");
assert!(matches!(err, FrankenError::Internal(_)));
}
#[test]
fn parse_error_on_invalid_sql() {
let conn = Connection::open(":memory:").unwrap();
assert!(conn.query("NOT VALID SQL;").is_err());
}
#[test]
fn multiple_statements_in_query() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (v INTEGER);").unwrap();
let rows = conn
.query("INSERT INTO t VALUES (1); INSERT INTO t VALUES (2); SELECT v FROM t;")
.unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn arithmetic_expressions() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 10 - 3, 4 * 5, 20 / 4;").unwrap();
assert_eq!(
row_values(&row),
vec![
SqliteValue::Integer(7),
SqliteValue::Integer(20),
SqliteValue::Integer(5),
]
);
}
#[test]
fn string_concatenation() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 'foo' || 'bar';").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("foobar".into())]);
}
fn setup_three_rows(conn: &Connection) {
conn.execute("CREATE TABLE t3 (a INTEGER, b TEXT);")
.unwrap();
conn.execute("INSERT INTO t3 VALUES (1, 'one');").unwrap();
conn.execute("INSERT INTO t3 VALUES (2, 'two');").unwrap();
conn.execute("INSERT INTO t3 VALUES (3, 'three');").unwrap();
}
#[test]
fn where_and_predicate() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let rows = conn
.query("SELECT a FROM t3 WHERE a > 1 AND b = 'two';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
}
#[test]
fn where_or_predicate() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let rows = conn
.query("SELECT a FROM t3 WHERE a = 1 OR a = 3;")
.unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(vals.contains(&SqliteValue::Integer(1)));
assert!(vals.contains(&SqliteValue::Integer(3)));
}
#[test]
fn where_comparison_operators() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let rows = conn.query("SELECT a FROM t3 WHERE a > 2;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(3)]);
let rows = conn.query("SELECT a FROM t3 WHERE a <= 2;").unwrap();
assert_eq!(rows.len(), 2);
let rows = conn.query("SELECT a FROM t3 WHERE a != 2;").unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn where_is_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tn (a INTEGER, b TEXT);")
.unwrap();
conn.execute("INSERT INTO tn VALUES (1, 'x');").unwrap();
conn.execute("INSERT INTO tn VALUES (2, NULL);").unwrap();
let rows = conn.query("SELECT a FROM tn WHERE b IS NULL;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
}
#[test]
fn where_is_not_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tn2 (a INTEGER, b TEXT);")
.unwrap();
conn.execute("INSERT INTO tn2 VALUES (1, 'x');").unwrap();
conn.execute("INSERT INTO tn2 VALUES (2, NULL);").unwrap();
let rows = conn
.query("SELECT a FROM tn2 WHERE b IS NOT NULL;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
}
#[test]
fn coalesce_expression() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT COALESCE(NULL, NULL, 42);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(42)]);
}
#[test]
fn nullif_expression() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULLIF(1, 1);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
let row = conn.query_row("SELECT NULLIF(1, 2);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
}
#[test]
fn case_when_expression() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT CASE WHEN 1 > 0 THEN 'yes' ELSE 'no' END;")
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("yes".into())]);
}
#[test]
fn case_simple_form() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT CASE 2 WHEN 1 THEN 'a' WHEN 2 THEN 'b' ELSE 'c' END;")
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("b".into())]);
}
#[test]
fn builtin_abs() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT ABS(-42);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(42)]);
}
#[test]
fn builtin_length() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT LENGTH('hello');").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(5)]);
}
#[test]
fn builtin_upper_lower() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT UPPER('hello'), LOWER('WORLD');")
.unwrap();
assert_eq!(
row_values(&row),
vec![
SqliteValue::Text("HELLO".into()),
SqliteValue::Text("world".into()),
]
);
}
#[test]
fn builtin_typeof() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT TYPEOF(42);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("integer".into())]);
}
#[test]
fn cast_integer_to_text() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT CAST(42 AS TEXT);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("42".into())]);
}
#[test]
fn cast_text_to_integer() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT CAST('123' AS INTEGER);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(123)]);
}
#[test]
fn blob_literal_hex() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT X'DEADBEEF';").unwrap();
assert_eq!(
row_values(&row),
vec![SqliteValue::Blob(vec![0xDE, 0xAD, 0xBE, 0xEF].into())]
);
}
#[test]
fn unary_minus() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT -42;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(-42)]);
}
#[test]
fn not_operator() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NOT 0;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
}
#[test]
fn values_order_by() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("VALUES (3), (1), (2) ORDER BY 1;").unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(
vals,
vec![
SqliteValue::Integer(1),
SqliteValue::Integer(2),
SqliteValue::Integer(3),
]
);
}
#[test]
fn values_order_by_desc_with_limit() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("VALUES (3), (1), (2) ORDER BY 1 DESC LIMIT 2;")
.unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(vals, vec![SqliteValue::Integer(3), SqliteValue::Integer(2)]);
}
#[test]
fn values_limit_offset() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("VALUES (10), (20), (30), (40) LIMIT 2 OFFSET 1;")
.unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(
vals,
vec![SqliteValue::Integer(20), SqliteValue::Integer(30)]
);
}
#[test]
fn delete_all_rows() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
conn.execute("DELETE FROM t3;").unwrap();
let rows = conn.query("SELECT a FROM t3;").unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn select_expression_column_arithmetic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE te (a INTEGER);").unwrap();
conn.execute("INSERT INTO te VALUES (10);").unwrap();
conn.execute("INSERT INTO te VALUES (20);").unwrap();
let rows = conn.query("SELECT a + 1 FROM te;").unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(vals.contains(&SqliteValue::Integer(11)));
assert!(vals.contains(&SqliteValue::Integer(21)));
}
#[test]
fn select_expression_column_with_literal() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE te2 (a INTEGER, b TEXT);")
.unwrap();
conn.execute("INSERT INTO te2 VALUES (5, 'hello');")
.unwrap();
let rows = conn.query("SELECT a * 2, b FROM te2;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(10), SqliteValue::Text("hello".into())]
);
}
#[test]
fn insert_multi_row_values() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tm (v INTEGER);").unwrap();
conn.execute("INSERT INTO tm VALUES (1), (2), (3);")
.unwrap();
let rows = conn.query("SELECT v FROM tm;").unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(vals.len(), 3);
assert!(vals.contains(&SqliteValue::Integer(1)));
assert!(vals.contains(&SqliteValue::Integer(2)));
assert!(vals.contains(&SqliteValue::Integer(3)));
}
#[test]
fn in_expression_only() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 2 IN (1, 2, 3);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
}
#[test]
fn between_expression_only() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 2 BETWEEN 1 AND 3;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
}
#[test]
fn where_in_operator() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let rows = conn.query("SELECT a FROM t3 WHERE a IN (1, 3);").unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(vals.len(), 2);
assert!(vals.contains(&SqliteValue::Integer(1)));
assert!(vals.contains(&SqliteValue::Integer(3)));
}
#[test]
fn where_between_operator() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let rows = conn
.query("SELECT a FROM t3 WHERE a BETWEEN 1 AND 2;")
.unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(vals.len(), 2);
assert!(vals.contains(&SqliteValue::Integer(1)));
assert!(vals.contains(&SqliteValue::Integer(2)));
}
#[test]
fn where_like_operator() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let rows = conn.query("SELECT b FROM t3 WHERE b LIKE 't%';").unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(vals.len(), 2);
assert!(vals.contains(&SqliteValue::Text("two".into())));
assert!(vals.contains(&SqliteValue::Text("three".into())));
}
#[test]
fn aggregate_count_star() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let row = conn.query_row("SELECT COUNT(*) FROM t3;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(3)]);
}
#[test]
fn aggregate_sum_min_max() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let row = conn
.query_row("SELECT SUM(a), MIN(a), MAX(a) FROM t3;")
.unwrap();
assert_eq!(
row_values(&row),
vec![
SqliteValue::Integer(6),
SqliteValue::Integer(1),
SqliteValue::Integer(3),
]
);
}
#[test]
fn aggregate_avg() {
let conn = Connection::open(":memory:").unwrap();
setup_three_rows(&conn);
let row = conn.query_row("SELECT AVG(a) FROM t3;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Float(2.0)]);
}
#[test]
fn update_all_rows() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tu (v INTEGER);").unwrap();
conn.execute("INSERT INTO tu VALUES (1);").unwrap();
conn.execute("INSERT INTO tu VALUES (2);").unwrap();
conn.execute("UPDATE tu SET v = 0;").unwrap();
let rows = conn.query("SELECT v FROM tu;").unwrap();
assert!(
rows.iter()
.all(|r| row_values(r) == vec![SqliteValue::Integer(0)])
);
}
fn setup_bd2832(conn: &Connection) {
conn.execute("CREATE TABLE tp (a INTEGER, b TEXT, c REAL);")
.unwrap();
conn.execute("INSERT INTO tp VALUES (1, 'alpha', 1.5);")
.unwrap();
conn.execute("INSERT INTO tp VALUES (2, 'beta', 2.5);")
.unwrap();
conn.execute("INSERT INTO tp VALUES (3, 'gamma', 3.5);")
.unwrap();
conn.execute("INSERT INTO tp VALUES (4, NULL, 4.5);")
.unwrap();
conn.execute("INSERT INTO tp VALUES (5, 'delta', 5.5);")
.unwrap();
}
#[test]
fn where_not_predicate() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp WHERE NOT (a > 3);").unwrap();
assert_eq!(rows.len(), 3);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(vals.contains(&SqliteValue::Integer(1)));
assert!(vals.contains(&SqliteValue::Integer(2)));
assert!(vals.contains(&SqliteValue::Integer(3)));
}
#[test]
fn where_greater_equal() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp WHERE a >= 4;").unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn where_less_than() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp WHERE a < 3;").unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn table_order_by_asc() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tord (v INTEGER);").unwrap();
conn.execute("INSERT INTO tord VALUES (3);").unwrap();
conn.execute("INSERT INTO tord VALUES (1);").unwrap();
conn.execute("INSERT INTO tord VALUES (2);").unwrap();
let rows = conn.query("SELECT v FROM tord ORDER BY v;").unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(
vals,
vec![
SqliteValue::Integer(1),
SqliteValue::Integer(2),
SqliteValue::Integer(3),
]
);
}
#[test]
fn table_order_by_desc() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tord2 (v INTEGER);").unwrap();
conn.execute("INSERT INTO tord2 VALUES (3);").unwrap();
conn.execute("INSERT INTO tord2 VALUES (1);").unwrap();
conn.execute("INSERT INTO tord2 VALUES (2);").unwrap();
let rows = conn.query("SELECT v FROM tord2 ORDER BY v DESC;").unwrap();
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(
vals,
vec![
SqliteValue::Integer(3),
SqliteValue::Integer(2),
SqliteValue::Integer(1),
]
);
}
#[test]
fn table_limit() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp LIMIT 3;").unwrap();
assert_eq!(rows.len(), 3);
}
#[test]
fn table_limit_offset() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp LIMIT 2 OFFSET 2;").unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(vals, vec![SqliteValue::Integer(3), SqliteValue::Integer(4)]);
}
#[test]
fn where_with_limit() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp WHERE a > 1 LIMIT 2;").unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(vals, vec![SqliteValue::Integer(2), SqliteValue::Integer(3)]);
}
#[test]
fn case_when_table_backed() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn
.query("SELECT CASE WHEN a > 3 THEN 'big' ELSE 'small' END FROM tp;")
.unwrap();
assert_eq!(rows.len(), 5);
assert_eq!(rows[0].values()[0], SqliteValue::Text("small".into()));
assert_eq!(rows[3].values()[0], SqliteValue::Text("big".into()));
}
#[test]
fn cast_table_backed() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tcast (v INTEGER);").unwrap();
conn.execute("INSERT INTO tcast VALUES (42);").unwrap();
let row = conn
.query_row("SELECT CAST(v AS TEXT) FROM tcast;")
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("42".into())]);
}
#[test]
fn where_column_is_null_correct() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp WHERE b IS NULL;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(4)]);
}
#[test]
fn where_column_is_not_null_correct() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT a FROM tp WHERE b IS NOT NULL;").unwrap();
assert_eq!(rows.len(), 4);
}
#[test]
fn unary_minus_table_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tneg (x INTEGER);").unwrap();
conn.execute("INSERT INTO tneg VALUES (42);").unwrap();
let row = conn.query_row("SELECT -x FROM tneg;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(-42)]);
}
#[test]
fn builtin_typeof_all_types() {
let conn = Connection::open(":memory:").unwrap();
assert_eq!(
row_values(&conn.query_row("SELECT typeof(3.14);").unwrap()),
vec![SqliteValue::Text("real".into())]
);
assert_eq!(
row_values(&conn.query_row("SELECT typeof('abc');").unwrap()),
vec![SqliteValue::Text("text".into())]
);
assert_eq!(
row_values(&conn.query_row("SELECT typeof(NULL);").unwrap()),
vec![SqliteValue::Text("null".into())]
);
assert_eq!(
row_values(&conn.query_row("SELECT typeof(X'FF');").unwrap()),
vec![SqliteValue::Text("blob".into())]
);
}
#[test]
fn builtin_substr() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT substr('hello world', 7, 5);")
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("world".into())]);
}
#[test]
fn builtin_replace() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT replace('hello world', 'world', 'rust');")
.unwrap();
assert_eq!(
row_values(&row),
vec![SqliteValue::Text("hello rust".into())]
);
}
#[test]
fn builtin_trim() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT trim(' hello ');").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("hello".into())]);
}
#[test]
fn builtin_instr() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT instr('hello world', 'world');")
.unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(7)]);
}
#[test]
fn builtin_hex() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT hex(X'CAFE');").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("CAFE".into())]);
}
#[test]
fn is_null_expression() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULL IS NULL;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
let row = conn.query_row("SELECT 42 IS NULL;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(0)]);
}
#[test]
fn soundex_null_returns_question_marks() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT soundex(NULL);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Text("?000".into())]);
}
#[test]
fn like_underscore_wildcard() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn.query("SELECT b FROM tp WHERE b LIKE 'b_ta';").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Text("beta".into())]);
}
#[test]
fn where_not_in() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn
.query("SELECT a FROM tp WHERE a NOT IN (1, 3, 5);")
.unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(vals.contains(&SqliteValue::Integer(2)));
assert!(vals.contains(&SqliteValue::Integer(4)));
}
#[test]
fn where_in_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (a INTEGER);").unwrap();
conn.execute("CREATE TABLE t2 (b INTEGER);").unwrap();
conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (2), (3), (4);")
.unwrap();
let rows = conn
.query("SELECT a FROM t1 WHERE a IN (SELECT b FROM t2) ORDER BY a;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
assert_eq!(row_values(&rows[1]), vec![SqliteValue::Integer(3)]);
}
#[test]
fn where_in_table_name() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (a INTEGER);").unwrap();
conn.execute("CREATE TABLE t2 (b INTEGER);").unwrap();
conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (2), (3), (4);")
.unwrap();
let rows = conn
.query("SELECT a FROM t1 WHERE a IN t2 ORDER BY a;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(2)]);
assert_eq!(row_values(&rows[1]), vec![SqliteValue::Integer(3)]);
}
#[test]
fn where_not_in_table_name() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (a INTEGER);").unwrap();
conn.execute("CREATE TABLE t2 (b INTEGER);").unwrap();
conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (2), (3), (4);")
.unwrap();
let rows = conn
.query("SELECT a FROM t1 WHERE a NOT IN t2 ORDER BY a;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
}
#[test]
fn where_exists_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (a INTEGER);").unwrap();
conn.execute("CREATE TABLE t2 (b INTEGER);").unwrap();
conn.execute("INSERT INTO t1 VALUES (1), (2);").unwrap();
conn.execute("INSERT INTO t2 VALUES (7);").unwrap();
let rows = conn
.query("SELECT a FROM t1 WHERE EXISTS (SELECT b FROM t2) ORDER BY a;")
.unwrap();
assert_eq!(rows.len(), 2);
conn.execute("DELETE FROM t2;").unwrap();
let rows = conn
.query("SELECT a FROM t1 WHERE EXISTS (SELECT b FROM t2);")
.unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn scalar_subquery_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE s (v INTEGER);").unwrap();
conn.execute("INSERT INTO s VALUES (41);").unwrap();
let row = conn.query_row("SELECT (SELECT v FROM s) + 1;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(42)]);
}
#[test]
fn update_where_in_table_name() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (a INTEGER, flag TEXT);")
.unwrap();
conn.execute("CREATE TABLE t2 (b INTEGER);").unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'orig'), (2, 'orig'), (3, 'orig');")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (2), (3);").unwrap();
conn.execute("UPDATE t1 SET flag='hit' WHERE a IN t2;")
.unwrap();
let rows = conn.query("SELECT a, flag FROM t1 ORDER BY a;").unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(1), SqliteValue::Text("orig".into())]
);
assert_eq!(
row_values(&rows[1]),
vec![SqliteValue::Integer(2), SqliteValue::Text("hit".into())]
);
assert_eq!(
row_values(&rows[2]),
vec![SqliteValue::Integer(3), SqliteValue::Text("hit".into())]
);
}
#[test]
fn delete_where_in_table_name() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (a INTEGER);").unwrap();
conn.execute("CREATE TABLE t2 (b INTEGER);").unwrap();
conn.execute("INSERT INTO t1 VALUES (1), (2), (3);")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (2), (3);").unwrap();
conn.execute("DELETE FROM t1 WHERE a IN t2;").unwrap();
let rows = conn.query("SELECT a FROM t1 ORDER BY a;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0]), vec![SqliteValue::Integer(1)]);
}
#[test]
fn where_not_between() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let rows = conn
.query("SELECT a FROM tp WHERE a NOT BETWEEN 2 AND 4;")
.unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(vals.contains(&SqliteValue::Integer(1)));
assert!(vals.contains(&SqliteValue::Integer(5)));
}
#[test]
fn between_null_operand_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULL BETWEEN 1 AND 5;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
}
#[test]
fn between_null_low_bound() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 3 BETWEEN NULL AND 5;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
}
#[test]
fn between_null_high_bound() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 3 BETWEEN 1 AND NULL;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
}
#[test]
fn between_null_bound_out_of_range_returns_false() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 3 BETWEEN 4 AND NULL;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(0)]);
}
#[test]
fn in_null_operand_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULL IN (1, 2, 3);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
}
#[test]
fn in_list_with_null_no_match_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 2 IN (1, NULL, 3);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
}
#[test]
fn in_list_with_null_match_returns_true() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 1 IN (1, NULL, 3);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(1)]);
}
#[test]
fn not_in_null_operand_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULL NOT IN (1, 2);").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Null]);
}
#[test]
fn distinct_table_backed_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE td (id INTEGER, flag INTEGER);")
.unwrap();
conn.execute("INSERT INTO td VALUES (1, 1);").unwrap();
conn.execute("INSERT INTO td VALUES (2, 0);").unwrap();
conn.execute("INSERT INTO td VALUES (3, 1);").unwrap();
conn.execute("INSERT INTO td VALUES (4, 0);").unwrap();
conn.execute("INSERT INTO td VALUES (5, 1);").unwrap();
let rows = conn.query("SELECT DISTINCT flag FROM td;").unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(vals.contains(&SqliteValue::Integer(0)));
assert!(vals.contains(&SqliteValue::Integer(1)));
}
#[test]
fn aggregate_group_by_count() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE tg (k TEXT);").unwrap();
conn.execute("INSERT INTO tg VALUES ('a');").unwrap();
conn.execute("INSERT INTO tg VALUES ('a');").unwrap();
conn.execute("INSERT INTO tg VALUES ('b');").unwrap();
let rows = conn
.query("SELECT k, COUNT(*) FROM tg GROUP BY k ORDER BY k;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Text("a".into()), SqliteValue::Integer(2)]
);
assert_eq!(
row_values(&rows[1]),
vec![SqliteValue::Text("b".into()), SqliteValue::Integer(1)]
);
}
#[test]
fn group_by_alias_star_expansion() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE ga (k TEXT, v INTEGER);")
.unwrap();
conn.execute("INSERT INTO ga VALUES ('a', 10);").unwrap();
conn.execute("INSERT INTO ga VALUES ('a', 10);").unwrap();
conn.execute("INSERT INTO ga VALUES ('b', 20);").unwrap();
let rows = conn
.query("SELECT t.* FROM ga AS t GROUP BY t.k, t.v ORDER BY t.k, t.v;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Text("a".into()), SqliteValue::Integer(10)]
);
assert_eq!(
row_values(&rows[1]),
vec![SqliteValue::Text("b".into()), SqliteValue::Integer(20)]
);
}
#[test]
fn right_join_null_extension() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE l (id INTEGER, name TEXT);")
.unwrap();
conn.execute("CREATE TABLE r (l_id INTEGER, tag TEXT);")
.unwrap();
conn.execute("INSERT INTO l VALUES (1, 'left-a'), (2, 'left-b');")
.unwrap();
conn.execute("INSERT INTO r VALUES (2, 'right-b'), (3, 'right-c');")
.unwrap();
let rows = conn
.query("SELECT l.name, r.tag FROM l RIGHT JOIN r ON l.id = r.l_id;")
.unwrap();
assert_eq!(rows.len(), 2);
let projected: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert!(projected.contains(&vec![
SqliteValue::Text("left-b".into()),
SqliteValue::Text("right-b".into())
]));
assert!(projected.contains(&vec![
SqliteValue::Null,
SqliteValue::Text("right-c".into())
]));
}
#[test]
fn full_outer_join_null_extension() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE l (id INTEGER, name TEXT);")
.unwrap();
conn.execute("CREATE TABLE r (l_id INTEGER, tag TEXT);")
.unwrap();
conn.execute("INSERT INTO l VALUES (1, 'left-a'), (2, 'left-b');")
.unwrap();
conn.execute("INSERT INTO r VALUES (2, 'right-b'), (3, 'right-c');")
.unwrap();
let rows = conn
.query("SELECT l.name, r.tag FROM l FULL OUTER JOIN r ON l.id = r.l_id;")
.unwrap();
assert_eq!(rows.len(), 3);
let projected: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert!(projected.contains(&vec![SqliteValue::Text("left-a".into()), SqliteValue::Null]));
assert!(projected.contains(&vec![
SqliteValue::Text("left-b".into()),
SqliteValue::Text("right-b".into())
]));
assert!(projected.contains(&vec![
SqliteValue::Null,
SqliteValue::Text("right-c".into())
]));
}
#[test]
fn right_join_using_nulls_do_not_match() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE l (id INTEGER, name TEXT);")
.unwrap();
conn.execute("CREATE TABLE r (id INTEGER, tag TEXT);")
.unwrap();
conn.execute("INSERT INTO l VALUES (NULL, 'left-null'), (1, 'left-one');")
.unwrap();
conn.execute("INSERT INTO r VALUES (NULL, 'right-null'), (1, 'right-one');")
.unwrap();
let rows = conn
.query("SELECT l.name, r.tag FROM l RIGHT JOIN r USING (id);")
.unwrap();
assert_eq!(rows.len(), 2);
let projected: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert!(projected.contains(&vec![
SqliteValue::Text("left-one".into()),
SqliteValue::Text("right-one".into())
]));
assert!(projected.contains(&vec![
SqliteValue::Null,
SqliteValue::Text("right-null".into())
]));
}
#[test]
fn aggregate_group_by_sum() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE gs (dept TEXT, salary INTEGER);")
.unwrap();
conn.execute("INSERT INTO gs VALUES ('eng', 100);").unwrap();
conn.execute("INSERT INTO gs VALUES ('eng', 200);").unwrap();
conn.execute("INSERT INTO gs VALUES ('sales', 50);")
.unwrap();
let rows = conn
.query("SELECT dept, SUM(salary) FROM gs GROUP BY dept;")
.unwrap();
assert_eq!(rows.len(), 2);
let vals: Vec<(SqliteValue, SqliteValue)> = rows
.iter()
.map(|r| {
let v = row_values(r);
(v[0].clone(), v[1].clone())
})
.collect();
assert!(vals.contains(&(SqliteValue::Text("eng".into()), SqliteValue::Integer(300))));
assert!(vals.contains(&(SqliteValue::Text("sales".into()), SqliteValue::Integer(50))));
}
#[test]
fn aggregate_group_by_multiple_aggs() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE gm (cat TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO gm VALUES ('a', 10);").unwrap();
conn.execute("INSERT INTO gm VALUES ('a', 20);").unwrap();
conn.execute("INSERT INTO gm VALUES ('a', 30);").unwrap();
conn.execute("INSERT INTO gm VALUES ('b', 5);").unwrap();
let rows = conn
.query("SELECT cat, COUNT(*), MIN(val), MAX(val) FROM gm GROUP BY cat;")
.unwrap();
assert_eq!(rows.len(), 2);
let a_row = rows
.iter()
.find(|r| row_values(r)[0] == SqliteValue::Text("a".into()))
.unwrap();
assert_eq!(
row_values(a_row),
vec![
SqliteValue::Text("a".into()),
SqliteValue::Integer(3),
SqliteValue::Integer(10),
SqliteValue::Integer(30),
]
);
let b_row = rows
.iter()
.find(|r| row_values(r)[0] == SqliteValue::Text("b".into()))
.unwrap();
assert_eq!(
row_values(b_row),
vec![
SqliteValue::Text("b".into()),
SqliteValue::Integer(1),
SqliteValue::Integer(5),
SqliteValue::Integer(5),
]
);
}
#[test]
fn aggregate_count_column_excludes_null() {
let conn = Connection::open(":memory:").unwrap();
setup_bd2832(&conn);
let row = conn.query_row("SELECT count(b) FROM tp;").unwrap();
assert_eq!(row_values(&row), vec![SqliteValue::Integer(4)]);
}
#[test]
fn execute_with_params_insert_returns_count() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE ewp (v INTEGER);").unwrap();
let count = conn
.execute_with_params("INSERT INTO ewp VALUES (?1);", &[SqliteValue::Integer(42)])
.unwrap();
assert_eq!(count, 1, "INSERT via execute_with_params should return 1");
}
#[test]
fn execute_with_params_insert_respects_explicit_column_order() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE message_payloads(
id INTEGER PRIMARY KEY,
attachments TEXT NOT NULL DEFAULT '[]',
recipients_json TEXT NOT NULL DEFAULT '{}'
);",
)
.unwrap();
let recipients = r#"{"to":["BlueLake"],"cc":[],"bcc":[]}"#;
let attachments = r#"[{"name":"artifact.txt","path":"attachments/demo.txt","content_type":"text/plain","size":"128"}]"#;
conn.execute_with_params(
"INSERT INTO message_payloads(recipients_json, attachments) VALUES (?1, ?2);",
&[
SqliteValue::Text(recipients.into()),
SqliteValue::Text(attachments.into()),
],
)
.unwrap();
let row = conn
.query_row("SELECT recipients_json, attachments FROM message_payloads LIMIT 1;")
.unwrap();
assert_eq!(
row_values(&row),
vec![
SqliteValue::Text(recipients.into()),
SqliteValue::Text(attachments.into())
]
);
}
#[test]
fn execute_with_params_insert_duplicate_target_columns_keep_first_assignment() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE dup_targets(a INTEGER, b INTEGER);")
.unwrap();
conn.execute_with_params(
"INSERT INTO dup_targets(a, a, b) VALUES (?1, ?2, ?3);",
&[
SqliteValue::Integer(1),
SqliteValue::Integer(2),
SqliteValue::Integer(3),
],
)
.unwrap();
let row = conn.query_row("SELECT a, b FROM dup_targets;").unwrap();
assert_eq!(
row_values(&row),
vec![SqliteValue::Integer(1), SqliteValue::Integer(3)]
);
}
#[test]
fn execute_select_returns_row_count() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE es (v INTEGER);").unwrap();
conn.execute("INSERT INTO es VALUES (1);").unwrap();
conn.execute("INSERT INTO es VALUES (2);").unwrap();
let count = conn.execute("SELECT * FROM es;").unwrap();
assert_eq!(count, 2, "SELECT via execute() should return row count");
}
#[test]
fn savepoint_release_ends_implicit_transaction() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE sr (v INTEGER);").unwrap();
conn.execute("SAVEPOINT sp1;").unwrap();
assert!(conn.in_transaction());
conn.execute("INSERT INTO sr VALUES (1);").unwrap();
conn.execute("RELEASE sp1;").unwrap();
assert!(
!conn.in_transaction(),
"RELEASE of last implicit savepoint should end transaction"
);
let rows = conn.query("SELECT * FROM sr;").unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn explicit_begin_savepoint_release_keeps_transaction() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE bsr (v INTEGER);").unwrap();
conn.execute("BEGIN;").unwrap();
conn.execute("SAVEPOINT sp1;").unwrap();
conn.execute("INSERT INTO bsr VALUES (1);").unwrap();
conn.execute("RELEASE sp1;").unwrap();
assert!(
conn.in_transaction(),
"RELEASE after explicit BEGIN should not end the transaction"
);
conn.execute("COMMIT;").unwrap();
assert!(!conn.in_transaction());
}
#[test]
fn probe_update_self_ref_expr() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 10);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 20);").unwrap();
conn.execute("UPDATE t SET val = val + 5;").unwrap();
let rows = conn.query("SELECT id, val FROM t ORDER BY id;").unwrap();
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(1), SqliteValue::Integer(15)]
);
assert_eq!(
row_values(&rows[1]),
vec![SqliteValue::Integer(2), SqliteValue::Integer(25)]
);
}
#[test]
fn probe_delete_compound_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'c');").unwrap();
conn.execute("DELETE FROM t WHERE id > 1 AND val = 'b';")
.unwrap();
let rows = conn.query("SELECT id FROM t ORDER BY id;").unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
}
#[test]
fn probe_coalesce_nulls() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT, b TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, NULL, 'fallback');")
.unwrap();
conn.execute("INSERT INTO t VALUES (2, 'present', 'fallback');")
.unwrap();
let rows = conn
.query("SELECT id, COALESCE(a, b) FROM t ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(
row_values(&rows[0])[1],
SqliteValue::Text("fallback".into())
);
assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("present".into()));
}
#[test]
fn probe_case_when_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, NULL);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 5);").unwrap();
conn.execute("INSERT INTO t VALUES (3, 15);").unwrap();
let rows = conn
.query(
"SELECT id, CASE WHEN val IS NULL THEN 'null' WHEN val < 10 THEN 'small' ELSE 'big' END FROM t ORDER BY id;",
)
.unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("null".into()));
assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("small".into()));
assert_eq!(row_values(&rows[2])[1], SqliteValue::Text("big".into()));
}
#[test]
fn probe_union_all() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("CREATE TABLE t2 (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t2 VALUES (2, 'b');").unwrap();
let rows = conn
.query("SELECT val FROM t1 UNION ALL SELECT val FROM t2;")
.unwrap();
assert_eq!(rows.len(), 2);
}
#[test]
fn probe_union_dedup() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'b');").unwrap();
let rows = conn
.query("SELECT val FROM t UNION SELECT val FROM t;")
.unwrap();
assert_eq!(
rows.len(),
2,
"UNION should deduplicate: got {:?}",
rows.iter().map(row_values).collect::<Vec<_>>()
);
}
#[test]
fn probe_except() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'c');").unwrap();
let rows = conn
.query("SELECT val FROM t EXCEPT SELECT val FROM t WHERE id = 2;")
.unwrap();
assert_eq!(
rows.len(),
2,
"EXCEPT should remove 'b': got {:?}",
rows.iter().map(row_values).collect::<Vec<_>>()
);
}
#[test]
fn probe_intersect() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'c');").unwrap();
let rows = conn
.query("SELECT val FROM t INTERSECT SELECT val FROM t WHERE id <= 2;")
.unwrap();
assert_eq!(
rows.len(),
2,
"INTERSECT should keep 'a' and 'b': got {:?}",
rows.iter().map(row_values).collect::<Vec<_>>()
);
}
#[test]
fn probe_insert_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE src (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("CREATE TABLE dst (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO src VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO src VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO dst SELECT * FROM src;").unwrap();
let rows = conn.query("SELECT id, val FROM dst ORDER BY id;").unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("a".into()));
}
#[test]
fn probe_limit_offset() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
for i in 1..=10 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, 'v{i}');"))
.unwrap();
}
let rows = conn
.query("SELECT id FROM t ORDER BY id LIMIT 3 OFFSET 2;")
.unwrap();
assert_eq!(rows.len(), 3, "LIMIT 3 OFFSET 2 should return 3 rows");
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(4));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(5));
}
#[test]
fn probe_group_by_multi_col() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, cat TEXT, sub TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'A', 'x', 10);")
.unwrap();
conn.execute("INSERT INTO t VALUES (2, 'A', 'x', 20);")
.unwrap();
conn.execute("INSERT INTO t VALUES (3, 'A', 'y', 30);")
.unwrap();
conn.execute("INSERT INTO t VALUES (4, 'B', 'x', 40);")
.unwrap();
let rows = conn
.query("SELECT cat, sub, SUM(val) FROM t GROUP BY cat, sub ORDER BY cat, sub;")
.unwrap();
assert_eq!(
rows.len(),
3,
"Should have 3 groups: got {:?}",
rows.iter().map(row_values).collect::<Vec<_>>()
);
assert_eq!(row_values(&rows[0])[2], SqliteValue::Integer(30));
assert_eq!(row_values(&rows[1])[2], SqliteValue::Integer(30));
assert_eq!(row_values(&rows[2])[2], SqliteValue::Integer(40));
}
#[test]
fn probe_having_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, cat TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'A', 10);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'A', 20);").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'B', 30);").unwrap();
let rows = conn
.query("SELECT cat, COUNT(*) as cnt FROM t GROUP BY cat HAVING cnt > 1;")
.unwrap();
assert_eq!(rows.len(), 1, "Only group A has count > 1");
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("A".into()));
}
#[test]
fn having_between_filters_groups() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE hb (grp INTEGER, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO hb VALUES (1, 10), (1, 20), (2, 30), (3, 40), (3, 50), (3, 60);")
.unwrap();
let rows = conn
.query("SELECT grp, COUNT(*) as cnt FROM hb GROUP BY grp HAVING cnt BETWEEN 2 AND 3;")
.unwrap();
assert_eq!(rows.len(), 2);
let grps: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(grps.contains(&SqliteValue::Integer(1)));
assert!(grps.contains(&SqliteValue::Integer(3)));
}
#[test]
fn having_in_filters_groups() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE hi (grp TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO hi VALUES ('A', 1), ('B', 2), ('C', 3);")
.unwrap();
let rows = conn
.query("SELECT grp FROM hi GROUP BY grp HAVING grp IN ('A', 'C');")
.unwrap();
assert_eq!(rows.len(), 2);
let grps: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(grps.contains(&SqliteValue::Text("A".into())));
assert!(grps.contains(&SqliteValue::Text("C".into())));
}
#[test]
fn having_case_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE hc (grp TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO hc VALUES ('X', 1), ('Y', 2), ('X', 3);")
.unwrap();
let rows = conn
.query("SELECT grp, SUM(val) FROM hc GROUP BY grp HAVING CASE grp WHEN 'X' THEN 1 ELSE 0 END = 1;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("X".into()));
}
#[test]
fn like_null_operand_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT NULL LIKE 'abc';").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn like_null_pattern_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT 'abc' LIKE NULL;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn like_null_both_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT NULL LIKE NULL;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn not_like_null_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT 'abc' NOT LIKE NULL;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn like_integer_coercion() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT 123 LIKE '123';").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn like_null_in_join_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE lnj (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("INSERT INTO lnj VALUES (1, 'alice'), (2, NULL), (3, 'bob');")
.unwrap();
let rows = conn
.query("SELECT id FROM lnj WHERE name LIKE '%' ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
}
#[test]
fn having_like_filters_groups() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE hlk (grp TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO hlk VALUES ('apple', 1), ('banana', 2), ('apricot', 3);")
.unwrap();
let rows = conn
.query("SELECT grp, SUM(val) FROM hlk GROUP BY grp HAVING grp LIKE 'ap%';")
.unwrap();
assert_eq!(rows.len(), 2);
let grps: Vec<_> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert!(grps.contains(&SqliteValue::Text("apple".into())));
assert!(grps.contains(&SqliteValue::Text("apricot".into())));
}
#[test]
fn case_null_base_does_not_match_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT CASE NULL WHEN NULL THEN 'match' ELSE 'no match' END;")
.unwrap();
assert_eq!(
row_values(&rows[0])[0],
SqliteValue::Text("no match".into())
);
}
#[test]
fn case_null_base_skips_all_whens() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT CASE NULL WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'none' END;")
.unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("none".into()));
}
#[test]
fn case_null_when_value_skipped() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT CASE 1 WHEN NULL THEN 'bad' WHEN 1 THEN 'ok' ELSE 'miss' END;")
.unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("ok".into()));
}
#[test]
fn case_null_in_join_filter() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE cj (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO cj VALUES (1, NULL), (2, 'x'), (3, 'y');")
.unwrap();
let rows = conn
.query(
"SELECT id, CASE val WHEN 'x' THEN 'found' ELSE 'other' END AS r FROM cj ORDER BY id;",
)
.unwrap();
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("other".into()));
assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("found".into()));
}
#[test]
fn cast_null_as_integer_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT CAST(NULL AS INTEGER);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn cast_null_as_real_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT CAST(NULL AS REAL);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn cast_null_as_text_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT CAST(NULL AS TEXT);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn cast_null_from_table_returns_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE cn (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO cn VALUES (1, NULL), (2, '5');")
.unwrap();
let rows = conn
.query("SELECT id, CAST(val AS INTEGER) FROM cn ORDER BY id;")
.unwrap();
assert_eq!(row_values(&rows[0])[1], SqliteValue::Null);
assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(5));
}
#[test]
fn collate_in_join_does_not_return_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE cl (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("INSERT INTO cl VALUES (1, 'Alice'), (2, 'bob');")
.unwrap();
let rows = conn
.query("SELECT id FROM cl WHERE name COLLATE NOCASE = 'alice' ORDER BY id;")
.unwrap();
assert!(!rows.is_empty());
}
#[test]
fn probe_nested_functions() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, ' hello ');")
.unwrap();
let rows = conn.query("SELECT UPPER(TRIM(val)) FROM t;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("HELLO".into()));
}
#[test]
fn replace_null_arg_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT REPLACE(NULL, 'a', 'b');").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
let rows = conn.query("SELECT REPLACE('hello', NULL, 'b');").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn trim_null_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT TRIM(NULL);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
let rows = conn.query("SELECT LTRIM(NULL);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
let rows = conn.query("SELECT RTRIM(NULL);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn hex_null_returns_empty_string() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT HEX(NULL);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("".into()));
}
#[test]
fn instr_null_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT INSTR(NULL, 'x');").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
let rows = conn.query("SELECT INSTR('hello', NULL);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn substr_null_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT SUBSTR(NULL, 1, 3);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn substr_negative_start() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT SUBSTR('hello', -1);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("o".into()));
let rows = conn.query("SELECT SUBSTR('hello', -3);").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("llo".into()));
}
#[test]
fn limit_negative_returns_all_rows() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE ln (id INTEGER PRIMARY KEY);")
.unwrap();
conn.execute("INSERT INTO ln VALUES (1), (2), (3), (4), (5);")
.unwrap();
let rows = conn
.query("SELECT id FROM ln ORDER BY id LIMIT -1;")
.unwrap();
assert_eq!(rows.len(), 5);
}
#[test]
fn offset_negative_treated_as_zero() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE on_ (id INTEGER PRIMARY KEY);")
.unwrap();
conn.execute("INSERT INTO on_ VALUES (1), (2), (3);")
.unwrap();
let rows = conn
.query("SELECT id FROM on_ ORDER BY id LIMIT 2 OFFSET -5;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn null_comparison_returns_null_in_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE nc (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO nc VALUES (1, NULL), (2, 5), (3, NULL);")
.unwrap();
let rows = conn
.query("SELECT id FROM nc WHERE val = 5 ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
}
#[test]
fn null_and_true_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT NULL AND 1;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn null_or_false_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT NULL OR 0;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn false_and_null_returns_false() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT 0 AND NULL;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
}
#[test]
fn null_ne_in_where_excludes_row() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE nne (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO nne VALUES (1, NULL), (2, 5), (3, 10);")
.unwrap();
let rows = conn
.query("SELECT id FROM nne WHERE val != 5 ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
}
#[test]
fn mixed_type_comparison_uses_type_ordering() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE mt (id INTEGER PRIMARY KEY, val);")
.unwrap();
conn.execute("INSERT INTO mt VALUES (1, 5), (2, 'hello'), (3, 10);")
.unwrap();
let rows = conn
.query("SELECT id FROM mt WHERE val = 'hello';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
}
#[test]
fn integer_less_than_text_in_type_ordering() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE ilt (id INTEGER PRIMARY KEY, val);")
.unwrap();
conn.execute("INSERT INTO ilt VALUES (1, 42), (2, 'abc');")
.unwrap();
let rows = conn.query("SELECT id FROM ilt WHERE val < 'abc';").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn blob_greater_than_text_in_type_ordering() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE bgt (id INTEGER PRIMARY KEY, val);")
.unwrap();
conn.execute("INSERT INTO bgt VALUES (1, 'text'), (2, X'DEADBEEF');")
.unwrap();
let rows = conn
.query("SELECT id FROM bgt WHERE val > 'text';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
}
#[test]
fn large_integer_float_precision_comparison() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE lip (id INTEGER PRIMARY KEY, ival INTEGER, fval REAL);")
.unwrap();
conn.execute("INSERT INTO lip VALUES (1, 9007199254740993, 9007199254740992.0);")
.unwrap();
let rows = conn.query("SELECT id FROM lip WHERE ival > fval;").unwrap();
assert_eq!(rows.len(), 1, "large integer should be greater than float");
}
#[test]
fn not_null_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT NOT NULL;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn not_null_in_where_excludes_row() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE nn (id INTEGER PRIMARY KEY, flag INTEGER);")
.unwrap();
conn.execute("INSERT INTO nn VALUES (1, NULL), (2, 0), (3, 1);")
.unwrap();
let rows = conn
.query("SELECT id FROM nn WHERE NOT flag ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
}
#[test]
fn bitnot_null_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT ~NULL;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
}
#[test]
fn probe_update_where_column_cmp() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INTEGER, b INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 5, 10);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 15, 10);").unwrap();
conn.execute("UPDATE t SET a = a * 2 WHERE a < b;").unwrap();
let rows = conn.query("SELECT id, a FROM t ORDER BY id;").unwrap();
assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(10));
assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(15));
}
#[test]
fn probe_nullif() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'x');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'skip');").unwrap();
let rows = conn
.query("SELECT id, NULLIF(val, 'skip') FROM t ORDER BY id;")
.unwrap();
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("x".into()));
assert_eq!(row_values(&rows[1])[1], SqliteValue::Null);
}
#[test]
fn probe_iif() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 5);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 15);").unwrap();
let rows = conn
.query("SELECT id, IIF(val > 10, 'big', 'small') FROM t ORDER BY id;")
.unwrap();
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("small".into()));
assert_eq!(row_values(&rows[1])[1], SqliteValue::Text("big".into()));
}
#[test]
fn probe_select_distinct() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (4, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES (5, 'c');").unwrap();
let rows = conn
.query("SELECT DISTINCT val FROM t ORDER BY val;")
.unwrap();
assert_eq!(rows.len(), 3, "DISTINCT should return 3 unique values");
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("b".into()));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Text("c".into()));
}
#[test]
fn probe_order_by_desc() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 30);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 10);").unwrap();
conn.execute("INSERT INTO t VALUES (3, 20);").unwrap();
let rows = conn
.query("SELECT id, val FROM t ORDER BY val DESC;")
.unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(2));
}
#[test]
fn probe_insert_or_replace() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'old');").unwrap();
conn.execute("INSERT OR REPLACE INTO t VALUES (1, 'new');")
.unwrap();
let rows = conn.query("SELECT id, val FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("new".into()));
}
#[test]
fn probe_insert_or_ignore() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'first');").unwrap();
conn.execute("INSERT OR IGNORE INTO t VALUES (1, 'second');")
.unwrap();
let rows = conn.query("SELECT id, val FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("first".into()));
}
#[test]
fn probe_between() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
for i in 1..=10 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, {i});"))
.unwrap();
}
let rows = conn
.query("SELECT val FROM t WHERE val BETWEEN 3 AND 7 ORDER BY val;")
.unwrap();
assert_eq!(rows.len(), 5);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
assert_eq!(row_values(&rows[4])[0], SqliteValue::Integer(7));
}
#[test]
fn probe_in_list() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'c');").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE val IN ('a', 'c') ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
}
#[test]
fn probe_like_pattern() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'Alice');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'Bob');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'Charlie');")
.unwrap();
let rows = conn
.query("SELECT name FROM t WHERE name LIKE '%li%' ORDER BY name;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("Alice".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("Charlie".into()));
}
#[test]
fn probe_subquery_in_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("CREATE TABLE t2 (id INTEGER PRIMARY KEY, t1_id INTEGER);")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t1 VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t1 VALUES (3, 'c');").unwrap();
conn.execute("INSERT INTO t2 VALUES (1, 1);").unwrap();
conn.execute("INSERT INTO t2 VALUES (2, 3);").unwrap();
let result =
conn.query("SELECT val FROM t1 WHERE id IN (SELECT t1_id FROM t2) ORDER BY val;");
if let Ok(rows) = result {
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("c".into()));
} else {
}
}
#[test]
fn probe_insert_returning_star() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (1, 'Alice', 30) RETURNING *;")
.unwrap();
assert_eq!(rows.len(), 1, "RETURNING * should produce 1 row");
assert_eq!(
row_values(&rows[0]),
vec![
SqliteValue::Integer(1),
SqliteValue::Text("Alice".into()),
SqliteValue::Integer(30),
]
);
}
#[test]
fn probe_insert_returning_columns() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (1, 'Bob', 25) RETURNING name, age;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Text("Bob".into()), SqliteValue::Integer(25),]
);
}
#[test]
fn probe_insert_returning_rowid() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (42, 'test') RETURNING id;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(42));
}
#[test]
fn probe_insert_returning_multi_row() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'b');").unwrap();
conn.execute("CREATE TABLE t2 (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t2 SELECT * FROM t RETURNING *;")
.unwrap();
assert_eq!(
rows.len(),
2,
"Multi-row INSERT RETURNING should produce 2 rows"
);
}
#[test]
fn probe_update_returning_star() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'Alice', 30);")
.unwrap();
conn.execute("INSERT INTO t VALUES (2, 'Bob', 25);")
.unwrap();
let rows = conn
.query("UPDATE t SET age = age + 1 WHERE id = 1 RETURNING *;")
.unwrap();
assert_eq!(rows.len(), 1, "UPDATE RETURNING should produce 1 row");
assert_eq!(
row_values(&rows[0]),
vec![
SqliteValue::Integer(1),
SqliteValue::Text("Alice".into()),
SqliteValue::Integer(31),
]
);
}
#[test]
fn probe_update_returning_columns() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 10);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 20);").unwrap();
let rows = conn
.query("UPDATE t SET val = val * 2 RETURNING id, val;")
.unwrap();
assert_eq!(rows.len(), 2, "UPDATE RETURNING should produce 2 rows");
}
#[test]
fn probe_delete_returning_star() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'Alice');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'Bob');").unwrap();
let rows = conn
.query("DELETE FROM t WHERE id = 2 RETURNING *;")
.unwrap();
assert_eq!(rows.len(), 1, "DELETE RETURNING should produce 1 row");
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(2), SqliteValue::Text("Bob".into()),]
);
let remaining = conn.query("SELECT COUNT(*) FROM t;").unwrap();
assert_eq!(row_values(&remaining[0])[0], SqliteValue::Integer(1));
}
#[test]
fn probe_delete_returning_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'c');").unwrap();
let rows = conn
.query("DELETE FROM t WHERE id > 1 RETURNING val;")
.unwrap();
assert_eq!(rows.len(), 2, "DELETE RETURNING should produce 2 rows");
}
#[test]
fn probe_insert_default_values() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t DEFAULT VALUES;").unwrap();
let rows = conn.query("SELECT id, name, val FROM t;").unwrap();
assert_eq!(rows.len(), 1, "DEFAULT VALUES should insert 1 row");
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Null);
assert_eq!(row_values(&rows[0])[2], SqliteValue::Null);
}
#[test]
fn insert_default_values_uses_column_defaults() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE td (id INTEGER PRIMARY KEY, status TEXT DEFAULT 'active', count INTEGER DEFAULT 42, ratio REAL DEFAULT 2.5);",
)
.unwrap();
conn.execute("INSERT INTO td DEFAULT VALUES;").unwrap();
let rows = conn
.query("SELECT id, status, count, ratio FROM td;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(
row_values(&rows[0])[1],
SqliteValue::Text("active".to_string().into()),
"status should use DEFAULT 'active'"
);
assert_eq!(
row_values(&rows[0])[2],
SqliteValue::Integer(42),
"count should use DEFAULT 42"
);
assert_eq!(
row_values(&rows[0])[3],
SqliteValue::Float(2.5),
"ratio should use DEFAULT 2.5"
);
}
#[test]
fn insert_default_values_uses_expression_defaults() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE td (id INTEGER PRIMARY KEY, total INTEGER DEFAULT (40 + 2), label TEXT DEFAULT lower('HELLO'));",
)
.unwrap();
conn.execute("INSERT INTO td DEFAULT VALUES;").unwrap();
let rows = conn.query("SELECT id, total, label FROM td;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(42));
assert_eq!(row_values(&rows[0])[2], SqliteValue::Text("hello".into()));
}
#[test]
fn create_table_rejects_non_constant_default_expression() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.execute("CREATE TABLE t(a INTEGER, b INTEGER DEFAULT (a + 1));")
.expect_err("column-reference DEFAULT should be rejected");
let msg = err.to_string();
assert!(
msg.contains("default value of column [b] is not constant"),
"unexpected error: {msg}"
);
}
#[test]
fn create_table_rejects_aggregate_default_expression() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.execute("CREATE TABLE t(a INTEGER, b INTEGER DEFAULT (sum(1)));")
.expect_err("aggregate DEFAULT should be rejected");
let msg = err.to_string();
assert!(
msg.contains("default value of column [b] is not constant"),
"unexpected error: {msg}"
);
}
#[test]
fn create_table_rejects_hidden_aggregate_default_expression() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.execute("CREATE TABLE t(a INTEGER, b INTEGER DEFAULT (1 IN (sum(1))));")
.expect_err("aggregate hidden inside IN DEFAULT should be rejected");
let msg = err.to_string();
assert!(
msg.contains("default value of column [b] is not constant"),
"unexpected error: {msg}"
);
}
#[test]
fn alter_table_add_column_rejects_non_constant_default_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(a INTEGER);").unwrap();
let err = conn
.execute("ALTER TABLE t ADD COLUMN b INTEGER DEFAULT (a + 1);")
.expect_err("column-reference DEFAULT should be rejected");
let msg = err.to_string();
assert!(
msg.contains("default value of column [b] is not constant"),
"unexpected error: {msg}"
);
}
#[test]
fn alter_table_add_column_rejects_hidden_aggregate_default_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(a INTEGER);").unwrap();
let err = conn
.execute("ALTER TABLE t ADD COLUMN b INTEGER DEFAULT (1 IN (sum(1)));")
.expect_err("aggregate hidden inside IN DEFAULT should be rejected");
let msg = err.to_string();
assert!(
msg.contains("default value of column [b] is not constant"),
"unexpected error: {msg}"
);
}
#[test]
fn alter_table_add_column_rejects_aggregate_default_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(a INTEGER);").unwrap();
let err = conn
.execute("ALTER TABLE t ADD COLUMN b INTEGER DEFAULT (sum(1));")
.expect_err("aggregate DEFAULT should be rejected");
let msg = err.to_string();
assert!(
msg.contains("default value of column [b] is not constant"),
"unexpected error: {msg}"
);
}
#[test]
fn insert_explicit_cols_uses_defaults_for_omitted() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE te (id INTEGER PRIMARY KEY, name TEXT, status TEXT DEFAULT 'pending');",
)
.unwrap();
conn.execute("INSERT INTO te (name) VALUES ('alice');")
.unwrap();
let rows = conn.query("SELECT id, name, status FROM te;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
row_values(&rows[0])[1],
SqliteValue::Text("alice".to_string().into())
);
assert_eq!(
row_values(&rows[0])[2],
SqliteValue::Text("pending".to_string().into()),
"omitted column should use DEFAULT 'pending'"
);
}
#[test]
fn probe_insert_default_values_returning() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t DEFAULT VALUES RETURNING id;")
.unwrap();
assert_eq!(
rows.len(),
1,
"DEFAULT VALUES RETURNING should produce 1 row"
);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn ipk_null_auto_generates_rowid() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let r1 = conn
.query("INSERT INTO t VALUES (NULL, 'a') RETURNING id;")
.unwrap();
let r2 = conn
.query("INSERT INTO t VALUES (NULL, 'b') RETURNING id;")
.unwrap();
let id1 = &row_values(&r1[0])[0];
let id2 = &row_values(&r2[0])[0];
assert!(
matches!(id1, SqliteValue::Integer(n) if *n > 0),
"NULL IPK should auto-generate positive id, got {id1:?}"
);
if let (SqliteValue::Integer(a), SqliteValue::Integer(b)) = (id1, id2) {
assert!(
b > a,
"successive NULL IPK inserts should increment: {a} < {b}"
);
}
}
#[test]
fn ipk_zero_is_valid_rowid() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (0, 'zero') RETURNING id;")
.unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
}
#[test]
fn ipk_negative_is_valid_rowid() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (-5, 'neg') RETURNING id;")
.unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(-5));
}
#[test]
fn ipk_multi_row_explicit_values() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (10,'a'),(20,'b'),(30,'c') RETURNING id;")
.unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(10));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(20));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(30));
}
#[test]
fn ipk_mixed_null_and_explicit() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (10, 'explicit');")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (NULL, 'auto') RETURNING id;")
.unwrap();
if let SqliteValue::Integer(id) = &row_values(&rows[0])[0] {
assert!(
*id > 10,
"auto-generated id after max=10 should be > 10, got {id}"
);
} else {
panic!("expected Integer, got {:?}", row_values(&rows[0])[0]);
}
}
#[test]
fn ipk_returning_star_includes_correct_id() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t VALUES (42, 'x') RETURNING *;")
.unwrap();
let vals = row_values(&rows[0]);
assert_eq!(vals[0], SqliteValue::Integer(42));
assert_eq!(vals[1], SqliteValue::Text("x".into()));
}
#[test]
fn ipk_roundtrip_select_after_insert() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (42, 'Alice');").unwrap();
conn.execute("INSERT INTO t VALUES (100, 'Bob');").unwrap();
let rows = conn.query("SELECT * FROM t ORDER BY id;").unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(42));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("Alice".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(100));
}
#[test]
fn ipk_column_list_reorder() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t(name, id) VALUES ('Alice', 42) RETURNING *;")
.unwrap();
let vals = row_values(&rows[0]);
assert_eq!(
vals[0],
SqliteValue::Integer(42),
"id should be 42 (from column-list position 1)"
);
assert_eq!(
vals[1],
SqliteValue::Text("Alice".into()),
"name should be Alice (from column-list position 0)"
);
let sel = conn.query("SELECT id, name FROM t;").unwrap();
let sv = row_values(&sel[0]);
assert_eq!(sv[0], SqliteValue::Integer(42));
assert_eq!(sv[1], SqliteValue::Text("Alice".into()));
}
#[test]
fn insert_select_without_from_reorders_targets_and_fills_defaults() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE dst (id INTEGER PRIMARY KEY, label TEXT DEFAULT 'seed', qty INTEGER);",
)
.unwrap();
let rows = conn
.query("INSERT INTO dst(qty) SELECT 11 RETURNING label, qty;")
.unwrap();
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Text("seed".into()), SqliteValue::Integer(11)]
);
let rows = conn
.query("INSERT INTO dst(qty, label) SELECT 22, 'fresh' RETURNING label, qty;")
.unwrap();
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Text("fresh".into()), SqliteValue::Integer(22)]
);
}
#[test]
fn insert_select_without_from_explicit_rowid_is_preserved() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (payload TEXT);").unwrap();
let rows = conn
.query("INSERT INTO t(rowid, payload) SELECT 7, 'x' RETURNING rowid, payload;")
.unwrap();
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(7), SqliteValue::Text("x".into())]
);
}
#[test]
fn insert_values_rowid_family_uses_last_target_assignment() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, payload TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t(rowid, id, payload) VALUES (7, 8, 'x') RETURNING rowid, id;")
.unwrap();
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(8), SqliteValue::Integer(8)]
);
let rows = conn
.query("INSERT INTO t(id, rowid, payload) VALUES (9, 10, 'y') RETURNING rowid, id;")
.unwrap();
assert_eq!(
row_values(&rows[0]),
vec![SqliteValue::Integer(10), SqliteValue::Integer(10)]
);
}
#[test]
fn ipk_insert_select_column_list_reorder() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t(name, id) SELECT 'Alice', 42 RETURNING id, name;")
.unwrap();
let vals = row_values(&rows[0]);
assert_eq!(vals[0], SqliteValue::Integer(42));
assert_eq!(vals[1], SqliteValue::Text("Alice".into()));
let sel = conn.query("SELECT id, name FROM t;").unwrap();
let stored = row_values(&sel[0]);
assert_eq!(stored[0], SqliteValue::Integer(42));
assert_eq!(stored[1], SqliteValue::Text("Alice".into()));
}
#[test]
fn ipk_insert_select_hidden_rowid_alias_honors_explicit_rowid() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t(rowid, name) SELECT 7, 'Bob' RETURNING id, rowid, name;")
.unwrap();
let vals = row_values(&rows[0]);
assert_eq!(vals[0], SqliteValue::Integer(7));
assert_eq!(vals[1], SqliteValue::Integer(7));
assert_eq!(vals[2], SqliteValue::Text("Bob".into()));
let sel = conn.query("SELECT id, rowid, name FROM t;").unwrap();
let stored = row_values(&sel[0]);
assert_eq!(stored[0], SqliteValue::Integer(7));
assert_eq!(stored[1], SqliteValue::Integer(7));
assert_eq!(stored[2], SqliteValue::Text("Bob".into()));
}
#[test]
fn upsert_do_update_resolves_hidden_rowid_aliases() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (u TEXT UNIQUE, v INTEGER);")
.unwrap();
conn.execute("INSERT INTO t(rowid, u, v) VALUES (1, 'dup', 10);")
.unwrap();
conn.execute(
"INSERT INTO t(rowid, u, v) VALUES (7, 'dup', 99)
ON CONFLICT(u) DO UPDATE SET v = excluded.rowid + rowid;",
)
.unwrap();
let rows = conn
.query("SELECT rowid, u, v FROM t ORDER BY rowid;")
.unwrap();
let vals = row_values(&rows[0]);
assert_eq!(vals[0], SqliteValue::Integer(1));
assert_eq!(vals[1], SqliteValue::Text("dup".into()));
assert_eq!(vals[2], SqliteValue::Integer(8));
}
#[test]
fn alter_table_preserves_primary_key_sql_in_sqlite_master() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(id TEXT PRIMARY KEY, body TEXT);")
.unwrap();
conn.execute("ALTER TABLE t RENAME COLUMN body TO payload;")
.unwrap();
let rows = conn
.query("SELECT sql FROM sqlite_master WHERE name = 't';")
.unwrap();
let row = row_values(&rows[0]);
let sql = match &row[0] {
SqliteValue::Text(sql) => sql,
other => panic!("expected SQL text, got {other:?}"),
};
assert!(sql.contains("PRIMARY KEY"), "{sql}");
assert!(!sql.contains("UNIQUE"), "{sql}");
}
#[test]
fn alter_table_preserves_typeless_without_rowid_sql_in_sqlite_master() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE wr(id TEXT, body, PRIMARY KEY(id, body)) WITHOUT ROWID;")
.unwrap();
conn.execute("ALTER TABLE wr ADD COLUMN note TEXT;")
.unwrap();
let rows = conn
.query("SELECT sql FROM sqlite_master WHERE name = 'wr';")
.unwrap();
let row = row_values(&rows[0]);
let sql = match &row[0] {
SqliteValue::Text(sql) => sql,
other => panic!("expected SQL text, got {other:?}"),
};
assert!(sql.contains("PRIMARY KEY"), "{sql}");
assert!(sql.contains("WITHOUT ROWID"), "{sql}");
assert!(sql.contains("body"), "{sql}");
assert!(!sql.contains("body BLOB"), "{sql}");
}
#[test]
fn alter_table_drop_primary_key_column_is_rejected() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(id TEXT PRIMARY KEY, body TEXT);")
.unwrap();
let err = conn
.execute("ALTER TABLE t DROP COLUMN id;")
.expect_err("dropping a primary key column must fail");
let msg = err.to_string();
assert!(msg.contains("cannot drop PRIMARY KEY column id"), "{msg}");
}
#[test]
fn ipk_column_list_omit_ipk() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
let rows = conn
.query("INSERT INTO t(name) VALUES ('Bob') RETURNING id, name;")
.unwrap();
let vals = row_values(&rows[0]);
assert!(
matches!(vals[0], SqliteValue::Integer(n) if n > 0),
"omitted IPK should auto-generate, got {:?}",
vals[0]
);
assert_eq!(vals[1], SqliteValue::Text("Bob".into()));
}
#[test]
fn ipk_delete_reinsert_same_id() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'original');")
.unwrap();
conn.execute("DELETE FROM t WHERE id = 1;").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'reinserted');")
.unwrap();
let rows = conn.query("SELECT val FROM t WHERE id = 1;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
row_values(&rows[0])[0],
SqliteValue::Text("reinserted".into())
);
}
#[test]
fn index_insert_single_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'bob');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 'charlie');")
.unwrap();
let rows = conn.query("SELECT id FROM t WHERE name = 'bob';").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn index_insert_multi_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (a INT, b INT, c TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_ab ON t(a, b);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 10, 'x');").unwrap();
conn.execute("INSERT INTO t VALUES (1, 20, 'y');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 10, 'z');").unwrap();
let rows = conn
.query("SELECT c FROM t WHERE a = 1 AND b = 20;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("y".into()));
let rows = conn.query("SELECT c FROM t WHERE a = 1;").unwrap();
assert_eq!(rows.len(), 2); }
#[test]
fn index_delete_removes_entry() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'bob');").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
conn.execute("DELETE FROM t WHERE id = 1;").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE name = 'bob';").unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn index_update_indexed_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice');").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
conn.execute("UPDATE t SET name = 'alicia' WHERE id = 1;")
.unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn
.query("SELECT id FROM t WHERE name = 'alicia';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn index_update_non_indexed_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT, age INT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice', 30);")
.unwrap();
conn.execute("UPDATE t SET age = 31 WHERE id = 1;").unwrap();
let rows = conn
.query("SELECT age FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(31));
}
#[test]
fn index_multiple_indexes_on_table() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (a INT, b INT, c INT, d INT);")
.unwrap();
conn.execute("CREATE INDEX idx_a ON t(a);").unwrap();
conn.execute("CREATE INDEX idx_b ON t(b);").unwrap();
conn.execute("CREATE INDEX idx_ab ON t(a, b);").unwrap();
conn.execute("CREATE INDEX idx_cd ON t(c, d);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 2, 3, 4);").unwrap();
conn.execute("INSERT INTO t VALUES (5, 6, 7, 8);").unwrap();
let rows = conn.query("SELECT b FROM t WHERE a = 1;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
let rows = conn.query("SELECT a FROM t WHERE b = 6;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(5));
let rows = conn
.query("SELECT c FROM t WHERE a = 1 AND b = 2;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
let rows = conn
.query("SELECT a FROM t WHERE c = 7 AND d = 8;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(5));
}
#[test]
fn index_delete_removes_from_all_indexes() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INT, b INT);")
.unwrap();
conn.execute("CREATE INDEX idx_a ON t(a);").unwrap();
conn.execute("CREATE INDEX idx_b ON t(b);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 10, 100);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 20, 200);").unwrap();
conn.execute("DELETE FROM t WHERE id = 1;").unwrap();
let rows = conn.query("SELECT id FROM t WHERE a = 10;").unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE b = 100;").unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE a = 20;").unwrap();
assert_eq!(rows.len(), 1);
let rows = conn.query("SELECT id FROM t WHERE b = 200;").unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn index_update_maintains_all_indexes() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INT, b INT);")
.unwrap();
conn.execute("CREATE INDEX idx_a ON t(a);").unwrap();
conn.execute("CREATE INDEX idx_b ON t(b);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 10, 100);").unwrap();
conn.execute("UPDATE t SET a = 11, b = 101 WHERE id = 1;")
.unwrap();
let rows = conn.query("SELECT id FROM t WHERE a = 10;").unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE b = 100;").unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE a = 11;").unwrap();
assert_eq!(rows.len(), 1);
let rows = conn.query("SELECT id FROM t WHERE b = 101;").unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn index_with_null_values() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, NULL);").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'alice');").unwrap();
conn.execute("INSERT INTO t VALUES (3, NULL);").unwrap();
let rows = conn.query("SELECT id FROM t WHERE name IS NULL;").unwrap();
assert_eq!(rows.len(), 2);
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
}
#[test]
fn index_update_null_to_non_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, NULL);").unwrap();
conn.execute("INSERT INTO t VALUES (2, NULL);").unwrap();
let rows = conn.query("SELECT id FROM t WHERE name IS NULL;").unwrap();
assert_eq!(rows.len(), 2);
conn.execute("UPDATE t SET name = 'bob' WHERE id = 1;")
.unwrap();
let rows = conn.query("SELECT id FROM t WHERE name IS NULL;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
let rows = conn.query("SELECT id FROM t WHERE name = 'bob';").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn index_update_non_null_to_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice');").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
conn.execute("UPDATE t SET name = NULL WHERE id = 1;")
.unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE name IS NULL;").unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn index_bulk_insert() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value INT);")
.unwrap();
conn.execute("CREATE INDEX idx_value ON t(value);").unwrap();
for i in 0..100 {
conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i * 2))
.unwrap();
}
for i in [0, 25, 50, 75, 99] {
let rows = conn
.query(&format!("SELECT id FROM t WHERE value = {};", i * 2))
.unwrap();
assert_eq!(rows.len(), 1, "Should find row with value={}", i * 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(i));
}
}
#[test]
fn index_bulk_delete() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value INT);")
.unwrap();
conn.execute("CREATE INDEX idx_value ON t(value);").unwrap();
for i in 0..50 {
conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i))
.unwrap();
}
for i in (0..50).step_by(2) {
conn.execute(&format!("DELETE FROM t WHERE id = {};", i))
.unwrap();
}
for i in (0..50).step_by(2) {
let rows = conn
.query(&format!("SELECT id FROM t WHERE value = {};", i))
.unwrap();
assert_eq!(
rows.len(),
0,
"Deleted row with value={} should not exist",
i
);
}
for i in (1..50).step_by(2) {
let rows = conn
.query(&format!("SELECT id FROM t WHERE value = {};", i))
.unwrap();
assert_eq!(rows.len(), 1, "Row with value={} should exist", i);
}
}
#[test]
fn index_bulk_update() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value INT);")
.unwrap();
conn.execute("CREATE INDEX idx_value ON t(value);").unwrap();
for i in 0..50 {
conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i))
.unwrap();
}
conn.execute("UPDATE t SET value = value + 1000;").unwrap();
for i in 0..50 {
let rows = conn
.query(&format!("SELECT id FROM t WHERE value = {};", i))
.unwrap();
assert_eq!(rows.len(), 0);
}
for i in 0..50 {
let rows = conn
.query(&format!("SELECT id FROM t WHERE value = {};", i + 1000))
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(i));
}
}
#[test]
fn index_rollback_insert() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice');").unwrap();
conn.execute("BEGIN;").unwrap();
conn.execute("INSERT INTO t VALUES (2, 'bob');").unwrap();
let rows = conn.query("SELECT id FROM t WHERE name = 'bob';").unwrap();
assert_eq!(rows.len(), 1);
conn.execute("ROLLBACK;").unwrap();
let rows = conn.query("SELECT id FROM t WHERE name = 'bob';").unwrap();
assert_eq!(rows.len(), 0);
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn index_rollback_delete() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice');").unwrap();
conn.execute("BEGIN;").unwrap();
conn.execute("DELETE FROM t WHERE id = 1;").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 0);
conn.execute("ROLLBACK;").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn index_rollback_update() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'alice');").unwrap();
conn.execute("BEGIN;").unwrap();
conn.execute("UPDATE t SET name = 'bob' WHERE id = 1;")
.unwrap();
let rows = conn.query("SELECT id FROM t WHERE name = 'bob';").unwrap();
assert_eq!(rows.len(), 1);
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 0);
conn.execute("ROLLBACK;").unwrap();
let rows = conn
.query("SELECT id FROM t WHERE name = 'alice';")
.unwrap();
assert_eq!(rows.len(), 1);
let rows = conn.query("SELECT id FROM t WHERE name = 'bob';").unwrap();
assert_eq!(rows.len(), 0);
}
#[test]
fn index_no_reindex_needed() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t(name);").unwrap();
for i in 0..100 {
conn.execute(&format!("INSERT INTO t VALUES ({}, 'name{}');", i, i))
.unwrap();
}
for i in 0..50 {
conn.execute(&format!("DELETE FROM t WHERE id = {};", i))
.unwrap();
}
for i in 50..100 {
conn.execute(&format!(
"UPDATE t SET name = 'updated{}' WHERE id = {};",
i, i
))
.unwrap();
}
for i in 50..100 {
let rows = conn
.query(&format!("SELECT id FROM t WHERE name = 'updated{}';", i))
.unwrap();
assert_eq!(
rows.len(),
1,
"Row with updated name for id={} should be findable",
i
);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(i));
}
for i in 0..50 {
let rows = conn
.query(&format!("SELECT id FROM t WHERE name = 'name{}';", i))
.unwrap();
assert_eq!(
rows.len(),
0,
"Deleted row with name{} should not be findable",
i
);
}
let rows = conn.query("SELECT COUNT(*) FROM t;").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(50));
}
#[test]
fn index_on_ipk_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_id ON t(id);").unwrap();
conn.execute("INSERT INTO t VALUES (100, 'alice');")
.unwrap();
conn.execute("INSERT INTO t VALUES (200, 'bob');").unwrap();
let rows = conn.query("SELECT name FROM t WHERE id = 100;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("alice".into()));
}
#[test]
fn index_mixed_operations_sequence() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a INT, b TEXT);")
.unwrap();
conn.execute("CREATE INDEX idx_a ON t(a);").unwrap();
conn.execute("CREATE INDEX idx_b ON t(b);").unwrap();
conn.execute("INSERT INTO t VALUES (1, 10, 'x');").unwrap();
conn.execute("INSERT INTO t VALUES (2, 20, 'y');").unwrap();
conn.execute("INSERT INTO t VALUES (3, 30, 'z');").unwrap();
conn.execute("UPDATE t SET a = 15 WHERE id = 1;").unwrap();
conn.execute("DELETE FROM t WHERE id = 2;").unwrap();
conn.execute("INSERT INTO t VALUES (4, 40, 'w');").unwrap();
let rows = conn.query("SELECT id FROM t WHERE a = 10;").unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE a = 15;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
let rows = conn.query("SELECT id FROM t WHERE b = 'y';").unwrap();
assert_eq!(rows.len(), 0);
let rows = conn.query("SELECT id FROM t WHERE a = 40;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(4));
}
#[test]
fn concurrent_writers_stress_conservation() {
use rand::prelude::*;
use std::sync::{Arc, Barrier};
use std::thread;
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("stress.db");
let db_path_str = db_path.to_str().unwrap();
const NUM_ACCOUNTS: i64 = 100;
const INITIAL_BALANCE: i64 = 1000;
const EXPECTED_TOTAL: i64 = NUM_ACCOUNTS * INITIAL_BALANCE;
const NUM_WRITERS: usize = 8;
const OPS_PER_WRITER: u64 = 20;
{
let conn = Connection::open(db_path_str).expect("open db for setup");
conn.execute("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER);")
.expect("create table");
for i in 0..NUM_ACCOUNTS {
conn.execute(&format!(
"INSERT INTO accounts VALUES ({}, {});",
i, INITIAL_BALANCE
))
.expect("insert account");
}
let rows = conn
.query("SELECT SUM(balance) FROM accounts;")
.expect("sum query");
let total = match &row_values(&rows[0])[0] {
SqliteValue::Integer(n) => *n,
other => panic!("unexpected sum type: {:?}", other),
};
assert_eq!(total, EXPECTED_TOTAL, "initial balance mismatch");
}
let barrier = Arc::new(Barrier::new(NUM_WRITERS));
let handles: Vec<_> = (0..NUM_WRITERS)
.map(|thread_id| {
let path = db_path_str.to_owned();
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
let conn = Connection::open(&path).expect("open db in thread");
let mut rng = rand::rngs::StdRng::seed_from_u64(thread_id as u64);
let mut commits = 0_u64;
let mut retries = 0_u64;
let mut errors = 0_u64;
barrier.wait();
while commits < OPS_PER_WRITER {
let from_id = rng.gen_range(0..NUM_ACCOUNTS);
let to_id = rng.gen_range(0..NUM_ACCOUNTS);
if from_id == to_id {
continue; }
let amount = rng.gen_range(1..=10_i64);
if conn.execute("BEGIN;").is_err() {
retries += 1;
continue;
}
let from_balance = match conn.query(&format!(
"SELECT balance FROM accounts WHERE id = {};",
from_id
)) {
Ok(rows) if !rows.is_empty() => match &row_values(&rows[0])[0] {
SqliteValue::Integer(n) => *n,
_ => {
let _ = conn.execute("ROLLBACK;");
errors += 1;
continue;
}
},
_ => {
let _ = conn.execute("ROLLBACK;");
errors += 1;
continue;
}
};
if from_balance < amount {
let _ = conn.execute("ROLLBACK;");
continue;
}
let debit_result = conn.execute(&format!(
"UPDATE accounts SET balance = balance - {} WHERE id = {};",
amount, from_id
));
let credit_result = conn.execute(&format!(
"UPDATE accounts SET balance = balance + {} WHERE id = {};",
amount, to_id
));
if debit_result.is_err() || credit_result.is_err() {
let _ = conn.execute("ROLLBACK;");
retries += 1;
continue;
}
match conn.execute("COMMIT;") {
Ok(_) => commits += 1,
Err(e) => {
let _ = conn.execute("ROLLBACK;");
if format!("{:?}", e).contains("Busy")
|| format!("{:?}", e).contains("busy")
{
retries += 1;
} else {
retries += 1;
}
}
}
}
(commits, retries, errors)
})
})
.collect();
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let mut total_commits = 0_u64;
let mut total_retries = 0_u64;
let mut total_errors = 0_u64;
for (i, (commits, retries, errors)) in results.iter().enumerate() {
eprintln!(
"Thread {}: {} commits, {} retries, {} errors",
i, commits, retries, errors
);
total_commits += commits;
total_retries += retries;
total_errors += errors;
}
eprintln!(
"Total: {} commits, {} retries, {} errors",
total_commits, total_retries, total_errors
);
let conn = Connection::open(db_path_str).expect("reopen db for verification");
let rows = conn
.query("SELECT SUM(balance) FROM accounts;")
.expect("final sum query");
let final_total = match &row_values(&rows[0])[0] {
SqliteValue::Integer(n) => *n,
other => panic!("unexpected final sum type: {:?}", other),
};
assert_eq!(
final_total, EXPECTED_TOTAL,
"Conservation invariant violated! Expected {}, got {}",
EXPECTED_TOTAL, final_total
);
let rows = conn
.query("SELECT COUNT(*) FROM accounts WHERE balance < 0;")
.expect("negative balance check");
let negative_count = match &row_values(&rows[0])[0] {
SqliteValue::Integer(n) => *n,
other => panic!("unexpected count type: {:?}", other),
};
assert_eq!(
negative_count, 0,
"Found {} accounts with negative balance",
negative_count
);
for (i, (commits, _, _)) in results.iter().enumerate() {
assert_eq!(
*commits, OPS_PER_WRITER,
"Thread {} completed {} commits instead of {}",
i, commits, OPS_PER_WRITER
);
}
}
#[test]
fn concurrent_readers_consistency() {
use std::sync::{Arc, Barrier};
use std::thread;
let dir = tempfile::tempdir().expect("create temp dir");
let db_path = dir.path().join("readers.db");
let db_path_str = db_path.to_str().unwrap();
{
let conn = Connection::open(db_path_str).expect("open db for setup");
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val INTEGER);")
.expect("create table");
for i in 0..100 {
conn.execute(&format!("INSERT INTO t VALUES ({}, {});", i, i * 10))
.expect("insert row");
}
}
const NUM_READERS: usize = 4;
const READS_PER_THREAD: usize = 50;
let barrier = Arc::new(Barrier::new(NUM_READERS));
let handles: Vec<_> = (0..NUM_READERS)
.map(|thread_id| {
let path = db_path_str.to_owned();
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
let conn = Connection::open(&path).expect("open db in reader thread");
barrier.wait();
let mut consistent = true;
for _ in 0..READS_PER_THREAD {
conn.execute("BEGIN;").expect("begin");
let rows = conn.query("SELECT SUM(val) FROM t;").expect("sum query");
let sum = if let SqliteValue::Integer(n) = &row_values(&rows[0])[0] {
*n
} else {
consistent = false;
break;
};
let expected = 10 * (99 * 100 / 2);
if sum != expected {
eprintln!(
"Thread {} saw inconsistent sum: {} (expected {})",
thread_id, sum, expected
);
consistent = false;
}
conn.execute("COMMIT;").expect("commit");
}
consistent
})
})
.collect();
for (i, handle) in handles.into_iter().enumerate() {
let consistent = handle.join().expect("reader thread panicked");
assert!(consistent, "Reader thread {} saw inconsistent data", i);
}
}
#[test]
fn conformance_017_type_affinity_edge_numeric_coercion() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE q1(a NUMERIC, b TEXT, c INTEGER)")
.unwrap();
conn.execute("INSERT INTO q1 VALUES('3.0e+5', 123, '0042')")
.unwrap();
let rows = conn
.query("SELECT typeof(a), a, typeof(b), b, typeof(c), c FROM q1")
.unwrap();
assert_eq!(rows.len(), 1);
let vals = row_values(&rows[0]);
assert_eq!(vals[0], SqliteValue::Text("integer".into()));
assert_eq!(vals[1], SqliteValue::Integer(300_000));
assert_eq!(vals[2], SqliteValue::Text("text".into()));
assert_eq!(vals[3], SqliteValue::Text("123".into()));
assert_eq!(vals[4], SqliteValue::Text("integer".into()));
assert_eq!(vals[5], SqliteValue::Integer(42));
}
#[test]
fn conformance_018_collation_nocase_ascii_only() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT 'a' = 'A' COLLATE NOCASE").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn conformance_018_collation_nocase_unicode_sensitive() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT 'æ' = 'Æ' COLLATE NOCASE").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
}
#[test]
fn conformance_019_null_unique_allows_multiple_nulls() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE q3(a INTEGER UNIQUE, note TEXT)")
.unwrap();
conn.execute("INSERT INTO q3(a, note) VALUES(NULL, 'first-null')")
.unwrap();
conn.execute("INSERT INTO q3(a, note) VALUES(NULL, 'second-null')")
.unwrap();
conn.execute("INSERT INTO q3(a, note) VALUES(7, 'first-seven')")
.unwrap();
let rows = conn.query("SELECT COUNT(*) FROM q3").unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
}
#[test]
fn conformance_019_null_unique_rejects_duplicate_non_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE q3b(a INTEGER UNIQUE, note TEXT)")
.unwrap();
conn.execute("INSERT INTO q3b(a, note) VALUES(7, 'first-seven')")
.unwrap();
let result = conn.execute("INSERT INTO q3b(a, note) VALUES(7, 'dup')");
assert!(
result.is_err(),
"Duplicate non-NULL unique value should fail"
);
}
#[test]
fn conformance_020_integer_overflow_promotes_to_real() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT typeof(9223372036854775807 + 1)")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("real".into()));
}
#[test]
fn conformance_020_integer_underflow_promotes_to_real() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT typeof(-9223372036854775808 - 1)")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("real".into()));
}
#[test]
fn conformance_021_savepoint_rollback_preserves_outer() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE q5(id INTEGER PRIMARY KEY, note TEXT)")
.unwrap();
conn.execute("BEGIN").unwrap();
conn.execute("INSERT INTO q5 VALUES(1, 'outer')").unwrap();
conn.execute("SAVEPOINT s1").unwrap();
conn.execute("INSERT INTO q5 VALUES(2, 'inner')").unwrap();
conn.execute("ROLLBACK TO s1").unwrap();
conn.execute("RELEASE s1").unwrap();
conn.execute("COMMIT").unwrap();
let rows = conn.query("SELECT id, note FROM q5 ORDER BY id").unwrap();
assert_eq!(rows.len(), 1, "ROLLBACK TO s1 should undo inner insert");
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("outer".into()));
}
#[test]
fn conformance_021_nested_begin_errors() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("BEGIN").unwrap();
let result = conn.execute("BEGIN");
assert!(result.is_err(), "Nested BEGIN should produce an error");
conn.execute("ROLLBACK").unwrap();
}
#[test]
fn parity_replace_into_inserts_new_row() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("REPLACE INTO t VALUES (1, 'first');").unwrap();
let rows = conn.query("SELECT id, val FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[1], SqliteValue::Text("first".into()));
}
#[test]
fn parity_replace_into_overwrites_existing() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'old');").unwrap();
conn.execute("REPLACE INTO t VALUES (1, 'new');").unwrap();
let rows = conn.query("SELECT val FROM t WHERE id = 1;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("new".into()));
}
#[test]
fn parity_insert_or_ignore_skips_conflict() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (1, 'first');").unwrap();
conn.execute("INSERT OR IGNORE INTO t VALUES (1, 'dup');")
.unwrap();
let rows = conn.query("SELECT val FROM t WHERE id = 1;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("first".into()));
}
#[test]
fn parity_multi_column_order_by() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (a INTEGER, b INTEGER, c TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES (2, 1, 'x');").unwrap();
conn.execute("INSERT INTO t VALUES (1, 2, 'y');").unwrap();
conn.execute("INSERT INTO t VALUES (1, 1, 'z');").unwrap();
let rows = conn.query("SELECT c FROM t ORDER BY a, b;").unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("z".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("y".into()));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Text("x".into()));
}
#[test]
fn parity_limit_with_offset() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
for i in 1..=5 {
conn.execute(&format!("INSERT INTO t VALUES ({i}, 'r{i}');"))
.unwrap();
}
let rows = conn
.query("SELECT val FROM t ORDER BY id LIMIT 2 OFFSET 2;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("r3".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("r4".into()));
}
#[test]
fn parity_subquery_in_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1 (id INTEGER, val TEXT);")
.unwrap();
conn.execute("CREATE TABLE t2 (ref_id INTEGER);").unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'a');").unwrap();
conn.execute("INSERT INTO t1 VALUES (2, 'b');").unwrap();
conn.execute("INSERT INTO t1 VALUES (3, 'c');").unwrap();
conn.execute("INSERT INTO t2 VALUES (1);").unwrap();
conn.execute("INSERT INTO t2 VALUES (3);").unwrap();
let rows = conn
.query("SELECT val FROM t1 WHERE id IN (SELECT ref_id FROM t2) ORDER BY id;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("c".into()));
}
#[test]
fn parity_cast_integer_to_text() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT CAST(42 AS TEXT);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("42".into()));
}
#[test]
fn parity_cast_text_to_integer() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT CAST('123' AS INTEGER);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(123));
}
#[test]
fn parity_exists_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (id INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
let row = conn
.query_row("SELECT EXISTS (SELECT 1 FROM t WHERE id = 1);")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
let row = conn
.query_row("SELECT EXISTS (SELECT 1 FROM t WHERE id = 999);")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
}
#[test]
fn parity_count_distinct() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (val INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
conn.execute("INSERT INTO t VALUES (2);").unwrap();
conn.execute("INSERT INTO t VALUES (1);").unwrap();
conn.execute("INSERT INTO t VALUES (3);").unwrap();
conn.execute("INSERT INTO t VALUES (2);").unwrap();
let row = conn
.query_row("SELECT COUNT(DISTINCT val) FROM t;")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(3));
}
#[test]
fn parity_group_concat_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t (grp TEXT, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES ('a', 'x');").unwrap();
conn.execute("INSERT INTO t VALUES ('a', 'y');").unwrap();
conn.execute("INSERT INTO t VALUES ('b', 'z');").unwrap();
let rows = conn
.query("SELECT grp, GROUP_CONCAT(val, ',') FROM t GROUP BY grp ORDER BY grp;")
.unwrap();
assert_eq!(rows.len(), 2);
let a_val = &row_values(&rows[0])[1];
match a_val {
SqliteValue::Text(s) => {
assert!(&**s == "x,y" || &**s == "y,x", "group_concat for 'a' = {s}");
}
other => panic!("expected Text, got {other:?}"),
}
}
#[test]
fn parity_count_distinct_with_nulls() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE d2(x INTEGER);").unwrap();
conn.execute("INSERT INTO d2 VALUES(1);").unwrap();
conn.execute("INSERT INTO d2 VALUES(NULL);").unwrap();
conn.execute("INSERT INTO d2 VALUES(2);").unwrap();
conn.execute("INSERT INTO d2 VALUES(NULL);").unwrap();
let row = conn.query_row("SELECT COUNT(DISTINCT x) FROM d2;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
}
#[test]
fn parity_sum_distinct() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE d3(x INTEGER);").unwrap();
conn.execute("INSERT INTO d3 VALUES(10);").unwrap();
conn.execute("INSERT INTO d3 VALUES(20);").unwrap();
conn.execute("INSERT INTO d3 VALUES(10);").unwrap();
conn.execute("INSERT INTO d3 VALUES(30);").unwrap();
conn.execute("INSERT INTO d3 VALUES(20);").unwrap();
let row = conn.query_row("SELECT SUM(DISTINCT x) FROM d3;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(60));
}
#[test]
fn parity_count_vs_count_distinct() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE d4(x INTEGER);").unwrap();
conn.execute("INSERT INTO d4 VALUES(1);").unwrap();
conn.execute("INSERT INTO d4 VALUES(1);").unwrap();
conn.execute("INSERT INTO d4 VALUES(2);").unwrap();
conn.execute("INSERT INTO d4 VALUES(2);").unwrap();
conn.execute("INSERT INTO d4 VALUES(2);").unwrap();
let r1 = conn.query_row("SELECT COUNT(x) FROM d4;").unwrap();
assert_eq!(row_values(&r1)[0], SqliteValue::Integer(5));
let r2 = conn.query_row("SELECT COUNT(DISTINCT x) FROM d4;").unwrap();
assert_eq!(row_values(&r2)[0], SqliteValue::Integer(2));
}
#[test]
fn parity_count_distinct_group_by() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE d5(grp TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO d5 VALUES('a', 1);").unwrap();
conn.execute("INSERT INTO d5 VALUES('a', 2);").unwrap();
conn.execute("INSERT INTO d5 VALUES('a', 1);").unwrap();
conn.execute("INSERT INTO d5 VALUES('b', 10);").unwrap();
conn.execute("INSERT INTO d5 VALUES('b', 10);").unwrap();
conn.execute("INSERT INTO d5 VALUES('b', 20);").unwrap();
let rows = conn
.query("SELECT grp, COUNT(DISTINCT val) FROM d5 GROUP BY grp ORDER BY grp;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(2));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("b".into()));
assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(2));
}
#[test]
fn parity_count_distinct_all_same() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE d6(x INTEGER);").unwrap();
conn.execute("INSERT INTO d6 VALUES(42);").unwrap();
conn.execute("INSERT INTO d6 VALUES(42);").unwrap();
conn.execute("INSERT INTO d6 VALUES(42);").unwrap();
let row = conn.query_row("SELECT COUNT(DISTINCT x) FROM d6;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
}
#[test]
fn parity_count_distinct_empty_table() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE d7(x INTEGER);").unwrap();
let row = conn.query_row("SELECT COUNT(DISTINCT x) FROM d7;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
}
#[test]
fn parity_scalar_subquery_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
conn.execute("INSERT INTO t VALUES(20);").unwrap();
conn.execute("INSERT INTO t VALUES(30);").unwrap();
let row = conn.query_row("SELECT (SELECT COUNT(*) FROM t);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(3));
}
#[test]
fn parity_scalar_subquery_max() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(5);").unwrap();
conn.execute("INSERT INTO t VALUES(15);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
let row = conn.query_row("SELECT (SELECT MAX(x) FROM t);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(15));
}
#[test]
fn parity_scalar_subquery_no_from() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT (SELECT 42);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(42));
}
#[test]
fn parity_scalar_subquery_first_row() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(100);").unwrap();
conn.execute("INSERT INTO t VALUES(200);").unwrap();
let row = conn.query_row("SELECT (SELECT x FROM t);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(100));
}
#[test]
fn parity_scalar_subquery_empty_table_is_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
let row = conn.query_row("SELECT (SELECT x FROM t);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Null);
}
#[test]
fn parity_exists_true() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
let row = conn.query_row("SELECT EXISTS (SELECT 1 FROM t);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
}
#[test]
fn parity_exists_false_empty() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
let row = conn.query_row("SELECT EXISTS (SELECT 1 FROM t);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
}
#[test]
fn parity_not_exists_true_empty() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
let row = conn
.query_row("SELECT NOT EXISTS (SELECT 1 FROM t);")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
}
#[test]
fn parity_exists_no_from() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT EXISTS (SELECT 1);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
}
#[test]
fn parity_exists_with_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
conn.execute("INSERT INTO t VALUES(20);").unwrap();
let row = conn
.query_row("SELECT EXISTS (SELECT 1 FROM t WHERE x = 10);")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(1));
let row = conn
.query_row("SELECT EXISTS (SELECT 1 FROM t WHERE x = 99);")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
}
#[test]
fn parity_count_filter() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f1(x INTEGER);").unwrap();
conn.execute("INSERT INTO f1 VALUES(1);").unwrap();
conn.execute("INSERT INTO f1 VALUES(2);").unwrap();
conn.execute("INSERT INTO f1 VALUES(3);").unwrap();
conn.execute("INSERT INTO f1 VALUES(4);").unwrap();
conn.execute("INSERT INTO f1 VALUES(5);").unwrap();
let row = conn
.query_row("SELECT COUNT(*) FILTER (WHERE x > 3) FROM f1;")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
}
#[test]
fn parity_sum_filter() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f2(x INTEGER);").unwrap();
conn.execute("INSERT INTO f2 VALUES(10);").unwrap();
conn.execute("INSERT INTO f2 VALUES(20);").unwrap();
conn.execute("INSERT INTO f2 VALUES(30);").unwrap();
let row = conn
.query_row("SELECT SUM(x) FILTER (WHERE x >= 20) FROM f2;")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(50));
}
#[test]
fn parity_count_filter_none_match() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f3(x INTEGER);").unwrap();
conn.execute("INSERT INTO f3 VALUES(1);").unwrap();
conn.execute("INSERT INTO f3 VALUES(2);").unwrap();
let row = conn
.query_row("SELECT COUNT(*) FILTER (WHERE x > 100) FROM f3;")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
}
#[test]
fn parity_filter_no_group_by_same_table() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f4b(city TEXT, age INTEGER);")
.unwrap();
conn.execute("INSERT INTO f4b VALUES('A', 10);").unwrap();
conn.execute("INSERT INTO f4b VALUES('A', 30);").unwrap();
conn.execute("INSERT INTO f4b VALUES('B', 20);").unwrap();
conn.execute("INSERT INTO f4b VALUES('B', 40);").unwrap();
let row = conn
.query_row("SELECT COUNT(*) FILTER (WHERE age > 25) FROM f4b;")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
}
#[test]
fn parity_filter_group_by_always_false() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f4z(city TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO f4z VALUES('A', 1);").unwrap();
conn.execute("INSERT INTO f4z VALUES('A', 2);").unwrap();
conn.execute("INSERT INTO f4z VALUES('B', 3);").unwrap();
let rows = conn
.query("SELECT city, COUNT(*) FILTER (WHERE 0) FROM f4z GROUP BY city;")
.unwrap();
let mut results: Vec<(String, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let city = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
let cnt = match vals[1] {
SqliteValue::Integer(n) => n,
_ => panic!("expected integer, got {:?}", vals[1]),
};
(city, cnt)
})
.collect();
results.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(results, vec![("A".into(), 0), ("B".into(), 0)]);
}
#[test]
fn parity_filter_with_group_by() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f4(city TEXT, age INTEGER);")
.unwrap();
conn.execute("INSERT INTO f4 VALUES('A', 10);").unwrap();
conn.execute("INSERT INTO f4 VALUES('A', 30);").unwrap();
conn.execute("INSERT INTO f4 VALUES('B', 20);").unwrap();
conn.execute("INSERT INTO f4 VALUES('B', 40);").unwrap();
let rows = conn
.query("SELECT city, COUNT(*) FILTER (WHERE age > 25) FROM f4 GROUP BY city;")
.unwrap();
let mut results: Vec<(String, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let city = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
let cnt = match vals[1] {
SqliteValue::Integer(n) => n,
_ => panic!("expected integer"),
};
(city, cnt)
})
.collect();
results.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(results, vec![("A".into(), 1), ("B".into(), 1)]);
}
#[test]
fn parity_filter_multiple_aggregates() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f5(x INTEGER);").unwrap();
conn.execute("INSERT INTO f5 VALUES(1);").unwrap();
conn.execute("INSERT INTO f5 VALUES(2);").unwrap();
conn.execute("INSERT INTO f5 VALUES(3);").unwrap();
conn.execute("INSERT INTO f5 VALUES(4);").unwrap();
let row = conn
.query_row(
"SELECT COUNT(*) FILTER (WHERE x <= 2), COUNT(*) FILTER (WHERE x >= 3) FROM f5;",
)
.unwrap();
let vals = row_values(&row);
assert_eq!(vals[0], SqliteValue::Integer(2)); assert_eq!(vals[1], SqliteValue::Integer(2)); }
#[test]
fn parity_count_filter_vs_no_filter() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE f6(x INTEGER);").unwrap();
conn.execute("INSERT INTO f6 VALUES(1);").unwrap();
conn.execute("INSERT INTO f6 VALUES(2);").unwrap();
conn.execute("INSERT INTO f6 VALUES(3);").unwrap();
let row = conn
.query_row("SELECT COUNT(*), COUNT(*) FILTER (WHERE x > 1) FROM f6;")
.unwrap();
let vals = row_values(&row);
assert_eq!(vals[0], SqliteValue::Integer(3)); assert_eq!(vals[1], SqliteValue::Integer(2)); }
#[test]
fn parity_case_simple() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT CASE 2 WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END;")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("two".into()));
}
#[test]
fn parity_case_searched() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(15);").unwrap();
let row = conn
.query_row(
"SELECT CASE WHEN x < 10 THEN 'low' WHEN x < 20 THEN 'mid' ELSE 'high' END FROM t;",
)
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("mid".into()));
}
#[test]
fn parity_case_no_else_returns_null() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT CASE 5 WHEN 1 THEN 'one' WHEN 2 THEN 'two' END;")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Null);
}
#[test]
fn parity_coalesce_basic() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT COALESCE(NULL, NULL, 42, 10);")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(42));
}
#[test]
fn parity_coalesce_all_null() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT COALESCE(NULL, NULL);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Null);
}
#[test]
fn parity_nullif_equal() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULLIF(5, 5);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Null);
}
#[test]
fn parity_nullif_not_equal() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT NULLIF(5, 3);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(5));
}
#[test]
fn parity_iif_true() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT IIF(1=1, 'yes', 'no');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("yes".into()));
}
#[test]
fn parity_iif_false() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT IIF(1=0, 'yes', 'no');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("no".into()));
}
#[test]
fn parity_between_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(5);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
conn.execute("INSERT INTO t VALUES(15);").unwrap();
let rows = conn
.query("SELECT x FROM t WHERE x BETWEEN 5 AND 10 ORDER BY x;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(5));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(10));
}
#[test]
fn parity_not_between() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(5);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
let rows = conn
.query("SELECT x FROM t WHERE x NOT BETWEEN 3 AND 7 ORDER BY x;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(10));
}
#[test]
fn parity_like_percent() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(name TEXT);").unwrap();
conn.execute("INSERT INTO t VALUES('apple');").unwrap();
conn.execute("INSERT INTO t VALUES('banana');").unwrap();
conn.execute("INSERT INTO t VALUES('apricot');").unwrap();
let rows = conn
.query("SELECT name FROM t WHERE name LIKE 'ap%' ORDER BY name;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("apple".into()));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("apricot".into()));
}
#[test]
fn parity_like_underscore() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(code TEXT);").unwrap();
conn.execute("INSERT INTO t VALUES('a1');").unwrap();
conn.execute("INSERT INTO t VALUES('b2');").unwrap();
conn.execute("INSERT INTO t VALUES('abc');").unwrap();
let rows = conn
.query("SELECT code FROM t WHERE code LIKE '_2';")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("b2".into()));
}
#[test]
fn parity_not_like() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(name TEXT);").unwrap();
conn.execute("INSERT INTO t VALUES('cat');").unwrap();
conn.execute("INSERT INTO t VALUES('dog');").unwrap();
conn.execute("INSERT INTO t VALUES('car');").unwrap();
let rows = conn
.query("SELECT name FROM t WHERE name NOT LIKE 'ca%' ORDER BY name;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("dog".into()));
}
#[test]
fn parity_inner_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT);")
.unwrap();
conn.execute("CREATE TABLE orders(id INTEGER PRIMARY KEY, user_id INTEGER, item TEXT);")
.unwrap();
conn.execute("INSERT INTO users VALUES(1, 'Alice');")
.unwrap();
conn.execute("INSERT INTO users VALUES(2, 'Bob');").unwrap();
conn.execute("INSERT INTO orders VALUES(1, 1, 'Book');")
.unwrap();
conn.execute("INSERT INTO orders VALUES(2, 1, 'Pen');")
.unwrap();
conn.execute("INSERT INTO orders VALUES(3, 2, 'Notebook');")
.unwrap();
let rows = conn
.query(
"SELECT users.name, orders.item FROM users JOIN orders ON users.id = orders.user_id;",
)
.unwrap();
assert_eq!(rows.len(), 3);
let mut pairs: Vec<(String, String)> = rows
.iter()
.map(|r| {
let v = row_values(r);
let name = match &v[0] {
SqliteValue::Text(s) => s.to_string(),
other => panic!("expected Text, got {other:?}"),
};
let item = match &v[1] {
SqliteValue::Text(s) => s.to_string(),
other => panic!("expected Text, got {other:?}"),
};
(name, item)
})
.collect();
pairs.sort();
assert_eq!(
pairs,
vec![
("Alice".into(), "Book".into()),
("Alice".into(), "Pen".into()),
("Bob".into(), "Notebook".into()),
]
);
}
#[test]
fn parity_left_join_with_nulls() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE a(id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("CREATE TABLE b(id INTEGER PRIMARY KEY, a_id INTEGER, info TEXT);")
.unwrap();
conn.execute("INSERT INTO a VALUES(1, 'x');").unwrap();
conn.execute("INSERT INTO a VALUES(2, 'y');").unwrap();
conn.execute("INSERT INTO b VALUES(1, 1, 'linked');")
.unwrap();
let rows = conn
.query("SELECT a.val, b.info FROM a LEFT JOIN b ON a.id = b.a_id;")
.unwrap();
assert_eq!(rows.len(), 2);
let mut results: Vec<(String, Option<String>)> = rows
.iter()
.map(|r| {
let v = row_values(r);
let val = match &v[0] {
SqliteValue::Text(s) => s.to_string(),
other => panic!("expected Text, got {other:?}"),
};
let info = match &v[1] {
SqliteValue::Text(s) => Some(s.to_string()),
SqliteValue::Null => None,
other => panic!("expected Text or Null, got {other:?}"),
};
(val, info)
})
.collect();
results.sort();
assert_eq!(
results,
vec![("x".into(), Some("linked".into())), ("y".into(), None),]
);
}
#[test]
fn parity_cross_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE a(x INTEGER);").unwrap();
conn.execute("CREATE TABLE b(y INTEGER);").unwrap();
conn.execute("INSERT INTO a VALUES(1);").unwrap();
conn.execute("INSERT INTO a VALUES(2);").unwrap();
conn.execute("INSERT INTO b VALUES(10);").unwrap();
conn.execute("INSERT INTO b VALUES(20);").unwrap();
let rows = conn.query("SELECT x, y FROM a, b ORDER BY x, y;").unwrap();
assert_eq!(rows.len(), 4);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(10));
assert_eq!(row_values(&rows[3])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&rows[3])[1], SqliteValue::Integer(20));
}
#[test]
fn parity_union_removes_duplicates() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE a(x INTEGER);").unwrap();
conn.execute("CREATE TABLE b(x INTEGER);").unwrap();
conn.execute("INSERT INTO a VALUES(1);").unwrap();
conn.execute("INSERT INTO a VALUES(2);").unwrap();
conn.execute("INSERT INTO b VALUES(2);").unwrap();
conn.execute("INSERT INTO b VALUES(3);").unwrap();
let rows = conn
.query("SELECT x FROM a UNION SELECT x FROM b ORDER BY x;")
.unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(3));
}
#[test]
fn parity_union_all_keeps_duplicates() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE a(x INTEGER);").unwrap();
conn.execute("CREATE TABLE b(x INTEGER);").unwrap();
conn.execute("INSERT INTO a VALUES(1);").unwrap();
conn.execute("INSERT INTO a VALUES(2);").unwrap();
conn.execute("INSERT INTO b VALUES(2);").unwrap();
conn.execute("INSERT INTO b VALUES(3);").unwrap();
let rows = conn
.query("SELECT x FROM a UNION ALL SELECT x FROM b ORDER BY x;")
.unwrap();
assert_eq!(rows.len(), 4);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&rows[3])[0], SqliteValue::Integer(3));
}
#[test]
fn parity_update_multiple_columns() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(a INTEGER, b TEXT, c REAL);")
.unwrap();
conn.execute("INSERT INTO t VALUES(1, 'old', 1.0);")
.unwrap();
conn.execute("UPDATE t SET b = 'new', c = 2.5 WHERE a = 1;")
.unwrap();
let row = conn.query_row("SELECT a, b, c FROM t;").unwrap();
let vals = row_values(&row);
assert_eq!(vals[0], SqliteValue::Integer(1));
assert_eq!(vals[1], SqliteValue::Text("new".into()));
assert_eq!(vals[2], SqliteValue::Float(2.5));
}
#[test]
fn parity_delete_with_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES(1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES(2, 'b');").unwrap();
conn.execute("INSERT INTO t VALUES(3, 'c');").unwrap();
conn.execute("DELETE FROM t WHERE id > 1;").unwrap();
let rows = conn.query("SELECT val FROM t;").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("a".into()));
}
#[test]
fn parity_having_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE sales(product TEXT, amount INTEGER);")
.unwrap();
conn.execute("INSERT INTO sales VALUES('A', 10);").unwrap();
conn.execute("INSERT INTO sales VALUES('A', 20);").unwrap();
conn.execute("INSERT INTO sales VALUES('B', 5);").unwrap();
conn.execute("INSERT INTO sales VALUES('C', 30);").unwrap();
conn.execute("INSERT INTO sales VALUES('C', 40);").unwrap();
let rows = conn
.query(
"SELECT product, SUM(amount) FROM sales GROUP BY product HAVING SUM(amount) > 15 ORDER BY product;",
)
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("A".into()));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(30));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Text("C".into()));
assert_eq!(row_values(&rows[1])[1], SqliteValue::Integer(70));
}
#[test]
fn parity_in_values_list() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
conn.execute("INSERT INTO t VALUES(4);").unwrap();
let rows = conn
.query("SELECT x FROM t WHERE x IN (1, 3) ORDER BY x;")
.unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(3));
}
#[test]
fn parity_not_in_values_list() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
let rows = conn
.query("SELECT x FROM t WHERE x NOT IN (1, 3) ORDER BY x;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
}
#[test]
fn parity_unary_minus() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT -42;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(-42));
}
#[test]
fn parity_string_concat_operator() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT 'hello' || ' ' || 'world';").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("hello world".into()));
}
#[test]
fn parity_typeof_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT typeof(42);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("integer".into()));
let row = conn.query_row("SELECT typeof(3.14);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("real".into()));
let row = conn.query_row("SELECT typeof('hi');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("text".into()));
let row = conn.query_row("SELECT typeof(NULL);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("null".into()));
}
#[test]
fn parity_abs_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT ABS(-10);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(10));
let row = conn.query_row("SELECT ABS(10);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(10));
}
#[test]
fn parity_upper_lower_functions() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT UPPER('hello');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("HELLO".into()));
let row = conn.query_row("SELECT LOWER('WORLD');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("world".into()));
}
#[test]
fn parity_length_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT LENGTH('hello');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(5));
let row = conn.query_row("SELECT LENGTH('');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(0));
}
#[test]
fn parity_min_max_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(4);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(5);").unwrap();
let row = conn.query_row("SELECT MIN(x), MAX(x) FROM t;").unwrap();
let vals = row_values(&row);
assert_eq!(vals[0], SqliteValue::Integer(1));
assert_eq!(vals[1], SqliteValue::Integer(5));
}
#[test]
fn parity_avg_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
conn.execute("INSERT INTO t VALUES(20);").unwrap();
conn.execute("INSERT INTO t VALUES(30);").unwrap();
let row = conn.query_row("SELECT AVG(x) FROM t;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Float(20.0));
}
#[test]
fn parity_update_where_in_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT, price INTEGER);")
.unwrap();
conn.execute("INSERT INTO items VALUES(1, 'apple', 10);")
.unwrap();
conn.execute("INSERT INTO items VALUES(2, 'banana', 20);")
.unwrap();
conn.execute("INSERT INTO items VALUES(3, 'cherry', 30);")
.unwrap();
conn.execute("CREATE TABLE expensive(id INTEGER);").unwrap();
conn.execute("INSERT INTO expensive VALUES(2);").unwrap();
conn.execute("INSERT INTO expensive VALUES(3);").unwrap();
conn.execute("UPDATE items SET price = price * 2 WHERE id IN (SELECT id FROM expensive);")
.unwrap();
let rows = conn
.query("SELECT id, price FROM items ORDER BY id;")
.unwrap();
let results: Vec<(i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(vals[0].to_integer(), vals[1].to_integer())
})
.collect();
assert_eq!(results, vec![(1, 10), (2, 40), (3, 60)]);
}
#[test]
fn parity_delete_where_in_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE data(id INTEGER, val TEXT);")
.unwrap();
conn.execute("INSERT INTO data VALUES(1, 'a');").unwrap();
conn.execute("INSERT INTO data VALUES(2, 'b');").unwrap();
conn.execute("INSERT INTO data VALUES(3, 'c');").unwrap();
conn.execute("CREATE TABLE to_remove(id INTEGER);").unwrap();
conn.execute("INSERT INTO to_remove VALUES(1);").unwrap();
conn.execute("INSERT INTO to_remove VALUES(3);").unwrap();
conn.execute("DELETE FROM data WHERE id IN (SELECT id FROM to_remove);")
.unwrap();
let rows = conn.query("SELECT id, val FROM data ORDER BY id;").unwrap();
let results: Vec<(i64, String)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(
vals[0].to_integer(),
match &vals[1] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
},
)
})
.collect();
assert_eq!(results, vec![(2, "b".into())]);
}
#[test]
fn parity_datetime_date_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT date('2023-06-15 14:30:00');")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("2023-06-15".into()));
}
#[test]
fn parity_datetime_time_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT time('2023-06-15 14:30:45');")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("14:30:45".into()));
}
#[test]
fn parity_datetime_strftime() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT strftime('%Y', '2023-06-15');")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("2023".into()));
}
#[test]
fn parity_total_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
let row = conn.query_row("SELECT TOTAL(x) FROM t;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Float(0.0));
}
#[test]
fn parity_total_aggregate_with_values() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
conn.execute("INSERT INTO t VALUES(20);").unwrap();
let row = conn.query_row("SELECT TOTAL(x) FROM t;").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Float(30.0));
}
#[test]
fn parity_glob_operator() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE files(name TEXT);").unwrap();
conn.execute("INSERT INTO files VALUES('readme.txt');")
.unwrap();
conn.execute("INSERT INTO files VALUES('main.rs');")
.unwrap();
conn.execute("INSERT INTO files VALUES('test.txt');")
.unwrap();
let rows = conn
.query("SELECT name FROM files WHERE name GLOB '*.txt' ORDER BY name;")
.unwrap();
let results: Vec<String> = rows
.iter()
.map(|r| match &row_values(r)[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
})
.collect();
assert_eq!(results, vec!["readme.txt", "test.txt"]);
}
#[test]
fn parity_replace_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT replace('hello world', 'world', 'rust');")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("hello rust".into()));
}
#[test]
fn parity_zeroblob_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT typeof(zeroblob(4)), length(zeroblob(4));")
.unwrap();
let vals = row_values(&row);
assert_eq!(vals[0], SqliteValue::Text("blob".into()));
assert_eq!(vals[1], SqliteValue::Integer(4));
}
#[test]
fn parity_unicode_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT unicode('A');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(65));
}
#[test]
fn parity_char_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT char(65, 66, 67);").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("ABC".into()));
}
#[test]
fn parity_instr_multi_occurrence() {
let conn = Connection::open(":memory:").unwrap();
let row = conn.query_row("SELECT instr('abcabc', 'bc');").unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Integer(2));
}
#[test]
fn parity_printf_function() {
let conn = Connection::open(":memory:").unwrap();
let row = conn
.query_row("SELECT printf('%d + %d = %d', 1, 2, 3);")
.unwrap();
assert_eq!(row_values(&row)[0], SqliteValue::Text("1 + 2 = 3".into()));
}
#[test]
fn parity_row_number_window() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE w(name TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO w VALUES('a', 10);").unwrap();
conn.execute("INSERT INTO w VALUES('b', 20);").unwrap();
conn.execute("INSERT INTO w VALUES('c', 30);").unwrap();
let rows = conn
.query("SELECT name, ROW_NUMBER() OVER (ORDER BY val) FROM w;")
.unwrap();
let results: Vec<(String, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let name = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
(name, vals[1].to_integer())
})
.collect();
assert_eq!(
results,
vec![("a".into(), 1), ("b".into(), 2), ("c".into(), 3),]
);
}
#[test]
fn window_row_number_partition_by() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE wp(dept TEXT, name TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO wp VALUES('eng','a',10);")
.unwrap();
conn.execute("INSERT INTO wp VALUES('eng','b',20);")
.unwrap();
conn.execute("INSERT INTO wp VALUES('sales','c',5);")
.unwrap();
conn.execute("INSERT INTO wp VALUES('sales','d',15);")
.unwrap();
let rows = conn
.query("SELECT dept, name, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY val) FROM wp;")
.unwrap();
let results: Vec<(String, String, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let dept = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
let name = match &vals[1] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
(dept, name, vals[2].to_integer())
})
.collect();
assert_eq!(
results,
vec![
("eng".into(), "a".into(), 1),
("eng".into(), "b".into(), 2),
("sales".into(), "c".into(), 1),
("sales".into(), "d".into(), 2),
]
);
}
#[test]
fn window_rank_and_dense_rank() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE wr(name TEXT, score INTEGER);")
.unwrap();
conn.execute("INSERT INTO wr VALUES('a', 100);").unwrap();
conn.execute("INSERT INTO wr VALUES('b', 100);").unwrap();
conn.execute("INSERT INTO wr VALUES('c', 90);").unwrap();
conn.execute("INSERT INTO wr VALUES('d', 80);").unwrap();
let rows = conn
.query(
"SELECT name, RANK() OVER (ORDER BY score DESC), \
DENSE_RANK() OVER (ORDER BY score DESC) FROM wr;",
)
.unwrap();
let results: Vec<(String, i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let name = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
(name, vals[1].to_integer(), vals[2].to_integer())
})
.collect();
assert_eq!(
results,
vec![
("a".into(), 1, 1),
("b".into(), 1, 1),
("c".into(), 3, 2),
("d".into(), 4, 3),
]
);
}
#[test]
fn window_row_number_desc_order() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE wd(x INTEGER);").unwrap();
conn.execute("INSERT INTO wd VALUES(1);").unwrap();
conn.execute("INSERT INTO wd VALUES(2);").unwrap();
conn.execute("INSERT INTO wd VALUES(3);").unwrap();
let rows = conn
.query("SELECT x, ROW_NUMBER() OVER (ORDER BY x DESC) FROM wd;")
.unwrap();
let results: Vec<(i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(vals[0].to_integer(), vals[1].to_integer())
})
.collect();
assert_eq!(results, vec![(3, 1), (2, 2), (1, 3)]);
}
#[test]
fn window_multiple_window_functions() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE wm(name TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO wm VALUES('a', 10);").unwrap();
conn.execute("INSERT INTO wm VALUES('b', 20);").unwrap();
conn.execute("INSERT INTO wm VALUES('c', 30);").unwrap();
let rows = conn
.query(
"SELECT name, ROW_NUMBER() OVER (ORDER BY val), \
DENSE_RANK() OVER (ORDER BY val) FROM wm;",
)
.unwrap();
let results: Vec<(String, i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let name = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
(name, vals[1].to_integer(), vals[2].to_integer())
})
.collect();
assert_eq!(
results,
vec![("a".into(), 1, 1), ("b".into(), 2, 2), ("c".into(), 3, 3),]
);
}
#[test]
fn window_lag_negative_offset_reads_following_row() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t1 VALUES(1,10),(2,20),(3,30);")
.unwrap();
let rows = conn
.query("SELECT id, lag(val,-1,'D') OVER (ORDER BY id) FROM t1 ORDER BY id;")
.unwrap();
let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert_eq!(
results,
vec![
vec![SqliteValue::Integer(1), SqliteValue::Integer(20)],
vec![SqliteValue::Integer(2), SqliteValue::Integer(30)],
vec![SqliteValue::Integer(3), SqliteValue::Text("D".into())],
]
);
}
#[test]
fn window_lag_lead_fractional_offset_uses_default() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT, off REAL);")
.unwrap();
conn.execute("INSERT INTO t1 VALUES(1,'a',1.5),(2,'b',1.5),(3,'c',1.5);")
.unwrap();
let rows = conn
.query(
"SELECT id, \
lag(val,off,'D') OVER (ORDER BY id), \
lead(val,off,'D') OVER (ORDER BY id) \
FROM t1 ORDER BY id;",
)
.unwrap();
let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert_eq!(
results,
vec![
vec![
SqliteValue::Integer(1),
SqliteValue::Text("D".into()),
SqliteValue::Text("D".into()),
],
vec![
SqliteValue::Integer(2),
SqliteValue::Text("D".into()),
SqliteValue::Text("D".into()),
],
vec![
SqliteValue::Integer(3),
SqliteValue::Text("D".into()),
SqliteValue::Text("D".into()),
],
]
);
}
#[test]
fn window_grouped_ntile_advances_two_pass_position() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(id INTEGER, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES(1,10),(2,20),(3,30);")
.unwrap();
let rows = conn
.query("SELECT id, ntile(2) OVER (ORDER BY id) FROM t GROUP BY id ORDER BY id;")
.unwrap();
let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert_eq!(
results,
vec![
vec![SqliteValue::Integer(1), SqliteValue::Integer(1)],
vec![SqliteValue::Integer(2), SqliteValue::Integer(1)],
vec![SqliteValue::Integer(3), SqliteValue::Integer(2)],
]
);
}
#[test]
fn window_nth_value_uses_current_row_n_for_full_frame() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x TEXT, n INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES('a',1),('b',2),('c',3);")
.unwrap();
let rows = conn
.query(
"SELECT x, n, nth_value(x,n) OVER (\
ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
) FROM t ORDER BY x;",
)
.unwrap();
let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert_eq!(
results,
vec![
vec![
SqliteValue::Text("a".into()),
SqliteValue::Integer(1),
SqliteValue::Text("a".into()),
],
vec![
SqliteValue::Text("b".into()),
SqliteValue::Integer(2),
SqliteValue::Text("b".into()),
],
vec![
SqliteValue::Text("c".into()),
SqliteValue::Integer(3),
SqliteValue::Text("c".into()),
],
]
);
}
#[test]
fn window_nth_value_uses_current_row_n_for_sliding_frame() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x TEXT, n INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES('a',1),('b',2),('c',1);")
.unwrap();
let rows = conn
.query(
"SELECT x, n, nth_value(x,n) OVER (\
ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW\
) FROM t ORDER BY x;",
)
.unwrap();
let results: Vec<Vec<SqliteValue>> = rows.iter().map(row_values).collect();
assert_eq!(
results,
vec![
vec![
SqliteValue::Text("a".into()),
SqliteValue::Integer(1),
SqliteValue::Text("a".into()),
],
vec![
SqliteValue::Text("b".into()),
SqliteValue::Integer(2),
SqliteValue::Text("b".into()),
],
vec![
SqliteValue::Text("c".into()),
SqliteValue::Integer(1),
SqliteValue::Text("b".into()),
],
]
);
}
#[test]
fn window_nth_value_rejects_fractional_n() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.query(
"WITH t(x,n) AS (VALUES ('a',1.5),('b',1.5)) \
SELECT nth_value(x,n) OVER (\
ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
) FROM t;",
)
.expect_err("fractional nth_value offset should fail");
assert!(
matches!(&err, FrankenError::FunctionError(message) if message == "second argument to nth_value must be a positive integer"),
"unexpected error: {err:?}"
);
}
#[test]
fn window_nth_value_rejects_text_numeric_prefix_n() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.query(
"WITH t(x,n) AS (VALUES ('a','2x'),('b','2x')) \
SELECT nth_value(x,n) OVER (\
ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
) FROM t;",
)
.expect_err("numeric-prefix nth_value offset should fail");
assert!(
matches!(&err, FrankenError::FunctionError(message) if message == "second argument to nth_value must be a positive integer"),
"unexpected error: {err:?}"
);
}
#[test]
fn window_nth_value_rejects_blob_integer_n() {
let conn = Connection::open(":memory:").unwrap();
let err = conn
.query(
"WITH t(x,n) AS (VALUES ('a',x'32'),('b',x'32')) \
SELECT nth_value(x,n) OVER (\
ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING\
) FROM t;",
)
.expect_err("blob nth_value offset should fail");
assert!(
matches!(&err, FrankenError::FunctionError(message) if message == "second argument to nth_value must be a positive integer"),
"unexpected error: {err:?}"
);
}
#[test]
fn parity_cte_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
let rows = conn
.query("WITH doubled AS (SELECT x * 2 AS d FROM t) SELECT d FROM doubled ORDER BY d;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![2, 4, 6]);
}
#[test]
fn parity_recursive_cte() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query(
"WITH RECURSIVE cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x<5) \
SELECT x FROM cnt;",
)
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![1, 2, 3, 4, 5]);
}
#[test]
fn parity_having_count_and_sum() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE sales(region TEXT, amount INTEGER);")
.unwrap();
conn.execute("INSERT INTO sales VALUES('East', 100);")
.unwrap();
conn.execute("INSERT INTO sales VALUES('East', 200);")
.unwrap();
conn.execute("INSERT INTO sales VALUES('West', 50);")
.unwrap();
conn.execute("INSERT INTO sales VALUES('West', 60);")
.unwrap();
conn.execute("INSERT INTO sales VALUES('West', 70);")
.unwrap();
let rows = conn
.query("SELECT region, SUM(amount) FROM sales GROUP BY region HAVING COUNT(*) > 2;")
.unwrap();
let results: Vec<(String, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(
match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
},
vals[1].to_integer(),
)
})
.collect();
assert_eq!(results, vec![("West".into(), 180)]);
}
#[test]
fn parity_update_with_exists_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE products(pid INTEGER, name TEXT, active INTEGER);")
.unwrap();
conn.execute("INSERT INTO products VALUES(1, 'Widget', 1);")
.unwrap();
conn.execute("INSERT INTO products VALUES(2, 'Gadget', 1);")
.unwrap();
conn.execute("INSERT INTO products VALUES(3, 'Doohickey', 1);")
.unwrap();
conn.execute("CREATE TABLE discontinued(product_id INTEGER);")
.unwrap();
conn.execute("INSERT INTO discontinued VALUES(1);").unwrap();
conn.execute("INSERT INTO discontinued VALUES(3);").unwrap();
conn.execute(
"UPDATE products SET active = 0 WHERE EXISTS \
(SELECT 1 FROM discontinued WHERE discontinued.product_id = products.pid);",
)
.unwrap();
let rows = conn
.query("SELECT pid, active FROM products ORDER BY pid;")
.unwrap();
let results: Vec<(i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(vals[0].to_integer(), vals[1].to_integer())
})
.collect();
assert_eq!(results, vec![(1, 0), (2, 1), (3, 0)]);
}
#[test]
fn probe_correlated_exists_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE a(x INTEGER);").unwrap();
conn.execute("INSERT INTO a VALUES(1);").unwrap();
conn.execute("INSERT INTO a VALUES(2);").unwrap();
conn.execute("INSERT INTO a VALUES(3);").unwrap();
conn.execute("CREATE TABLE b(y INTEGER);").unwrap();
conn.execute("INSERT INTO b VALUES(1);").unwrap();
conn.execute("INSERT INTO b VALUES(3);").unwrap();
let rows = conn
.query("SELECT x FROM a WHERE EXISTS (SELECT 1 FROM b WHERE b.y = a.x) ORDER BY x;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![1, 3]);
}
#[test]
fn probe_order_by_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER, y TEXT);").unwrap();
conn.execute("INSERT INTO t VALUES(3, 'c');").unwrap();
conn.execute("INSERT INTO t VALUES(1, 'a');").unwrap();
conn.execute("INSERT INTO t VALUES(2, 'b');").unwrap();
let rows = conn.query("SELECT y FROM t ORDER BY x * -1;").unwrap();
let results: Vec<String> = rows
.iter()
.map(|r| match &row_values(r)[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
})
.collect();
assert_eq!(results, vec!["c", "b", "a"]);
}
#[test]
fn probe_group_by_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER, v INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES(1, 10);").unwrap();
conn.execute("INSERT INTO t VALUES(2, 20);").unwrap();
conn.execute("INSERT INTO t VALUES(3, 30);").unwrap();
conn.execute("INSERT INTO t VALUES(4, 40);").unwrap();
let rows = conn
.query("SELECT x % 2 AS grp, SUM(v) FROM t GROUP BY x % 2 ORDER BY grp;")
.unwrap();
let results: Vec<(i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(vals[0].to_integer(), vals[1].to_integer())
})
.collect();
assert_eq!(results, vec![(0, 60), (1, 40)]);
}
#[test]
fn probe_having_with_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(cat TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES('a', 10);").unwrap();
conn.execute("INSERT INTO t VALUES('a', 20);").unwrap();
conn.execute("INSERT INTO t VALUES('b', 5);").unwrap();
let rows = conn
.query(
"SELECT cat, SUM(val) AS s FROM t GROUP BY cat HAVING SUM(val) > 10 ORDER BY cat;",
)
.unwrap();
let results: Vec<(String, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let cat = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
(cat, vals[1].to_integer())
})
.collect();
assert_eq!(results, vec![("a".to_string(), 30)]);
}
#[test]
fn probe_scalar_subquery_in_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
let rows = conn
.query("SELECT x, (SELECT MAX(x) FROM t) AS mx FROM t ORDER BY x;")
.unwrap();
let results: Vec<(i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(vals[0].to_integer(), vals[1].to_integer())
})
.collect();
assert_eq!(results, vec![(1, 3), (2, 3), (3, 3)]);
}
#[test]
fn probe_nested_case_when() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
let rows = conn
.query(
"SELECT CASE WHEN x < 2 THEN 'low' \
WHEN x < 3 THEN 'mid' ELSE 'high' END AS label FROM t ORDER BY x;",
)
.unwrap();
let results: Vec<String> = rows
.iter()
.map(|r| match &row_values(r)[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
})
.collect();
assert_eq!(results, vec!["low", "mid", "high"]);
}
#[test]
fn probe_coalesce_multi_arg() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT COALESCE(NULL, NULL, NULL, 42);")
.unwrap();
assert_eq!(row_values(&rows[0])[0].to_integer(), 42);
}
#[test]
fn probe_nullif_function() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT NULLIF(5, 5), NULLIF(5, 3);").unwrap();
let vals = row_values(&rows[0]);
assert_eq!(vals[0], SqliteValue::Null);
assert_eq!(vals[1].to_integer(), 5);
}
#[test]
fn probe_iif_function() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT IIF(1 > 0, 'yes', 'no'), IIF(1 < 0, 'yes', 'no');")
.unwrap();
let vals = row_values(&rows[0]);
let a = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
let b = match &vals[1] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
assert_eq!(a, "yes");
assert_eq!(b, "no");
}
#[test]
fn probe_like_escape() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(s TEXT);").unwrap();
conn.execute("INSERT INTO t VALUES('100% done');").unwrap();
conn.execute("INSERT INTO t VALUES('50 percent');").unwrap();
let rows = conn
.query("SELECT s FROM t WHERE s LIKE '%!%%' ESCAPE '!';")
.unwrap();
assert_eq!(rows.len(), 1);
let val = match &row_values(&rows[0])[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
assert_eq!(val, "100% done");
}
#[test]
fn probe_between_with_expressions() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(5);").unwrap();
conn.execute("INSERT INTO t VALUES(10);").unwrap();
let rows = conn
.query("SELECT x FROM t WHERE x BETWEEN 2 + 1 AND 4 * 2 ORDER BY x;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![5]);
}
#[test]
fn probe_distinct_with_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
conn.execute("INSERT INTO t VALUES(4);").unwrap();
let rows = conn
.query("SELECT DISTINCT x % 2 AS mod2 FROM t ORDER BY mod2;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![0, 1]);
}
#[test]
fn probe_insert_or_ignore_keeps_existing() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES(1, 'first');").unwrap();
conn.execute("INSERT OR IGNORE INTO t VALUES(1, 'second');")
.unwrap();
let rows = conn.query("SELECT v FROM t WHERE id = 1;").unwrap();
let val = match &row_values(&rows[0])[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
assert_eq!(val, "first");
}
#[test]
fn probe_insert_or_replace_overwrites() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT);")
.unwrap();
conn.execute("INSERT INTO t VALUES(1, 'first');").unwrap();
conn.execute("INSERT OR REPLACE INTO t VALUES(1, 'second');")
.unwrap();
let rows = conn.query("SELECT v FROM t WHERE id = 1;").unwrap();
let val = match &row_values(&rows[0])[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
assert_eq!(val, "second");
}
#[test]
fn probe_delete_with_correlated_exists() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE items(id INTEGER, active INTEGER);")
.unwrap();
conn.execute("INSERT INTO items VALUES(1, 1);").unwrap();
conn.execute("INSERT INTO items VALUES(2, 1);").unwrap();
conn.execute("INSERT INTO items VALUES(3, 1);").unwrap();
conn.execute("CREATE TABLE retired(item_id INTEGER);")
.unwrap();
conn.execute("INSERT INTO retired VALUES(2);").unwrap();
conn.execute(
"DELETE FROM items WHERE EXISTS \
(SELECT 1 FROM retired WHERE retired.item_id = items.id);",
)
.unwrap();
let rows = conn.query("SELECT id FROM items ORDER BY id;").unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![1, 3]);
}
#[test]
fn probe_aggregate_in_order_by() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(cat TEXT, val INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES('b', 10);").unwrap();
conn.execute("INSERT INTO t VALUES('a', 30);").unwrap();
conn.execute("INSERT INTO t VALUES('c', 20);").unwrap();
let rows = conn
.query("SELECT cat, SUM(val) AS s FROM t GROUP BY cat ORDER BY SUM(val) DESC;")
.unwrap();
let results: Vec<(String, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
let cat = match &vals[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
(cat, vals[1].to_integer())
})
.collect();
assert_eq!(
results,
vec![
("a".to_string(), 30),
("c".to_string(), 20),
("b".to_string(), 10)
]
);
}
#[test]
fn probe_subquery_in_from() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
let rows = conn
.query("SELECT sub.doubled FROM (SELECT x * 2 AS doubled FROM t) AS sub ORDER BY sub.doubled;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![2, 4, 6]);
}
#[test]
fn probe_multi_column_order_by_mixed() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(a INTEGER, b INTEGER);")
.unwrap();
conn.execute("INSERT INTO t VALUES(1, 3);").unwrap();
conn.execute("INSERT INTO t VALUES(1, 1);").unwrap();
conn.execute("INSERT INTO t VALUES(2, 2);").unwrap();
conn.execute("INSERT INTO t VALUES(2, 4);").unwrap();
let rows = conn
.query("SELECT a, b FROM t ORDER BY a ASC, b DESC;")
.unwrap();
let results: Vec<(i64, i64)> = rows
.iter()
.map(|r| {
let vals = row_values(r);
(vals[0].to_integer(), vals[1].to_integer())
})
.collect();
assert_eq!(results, vec![(1, 3), (1, 1), (2, 4), (2, 2)]);
}
#[test]
fn probe_null_handling_order_by() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(3);").unwrap();
conn.execute("INSERT INTO t VALUES(NULL);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
let rows = conn.query("SELECT x FROM t ORDER BY x ASC;").unwrap();
let results: Vec<SqliteValue> = rows.iter().map(|r| row_values(r)[0].clone()).collect();
assert_eq!(results[0], SqliteValue::Null);
assert_eq!(results[1].to_integer(), 1);
assert_eq!(results[2].to_integer(), 3);
}
#[test]
fn probe_count_distinct() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(2);").unwrap();
conn.execute("INSERT INTO t VALUES(1);").unwrap();
conn.execute("INSERT INTO t VALUES(NULL);").unwrap();
let rows = conn.query("SELECT COUNT(DISTINCT x) FROM t;").unwrap();
assert_eq!(row_values(&rows[0])[0].to_integer(), 2);
}
#[test]
fn probe_cast_in_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x TEXT);").unwrap();
conn.execute("INSERT INTO t VALUES('123');").unwrap();
conn.execute("INSERT INTO t VALUES('456');").unwrap();
conn.execute("INSERT INTO t VALUES('abc');").unwrap();
let rows = conn
.query("SELECT x FROM t WHERE CAST(x AS INTEGER) > 200;")
.unwrap();
assert_eq!(rows.len(), 1);
let val = match &row_values(&rows[0])[0] {
SqliteValue::Text(s) => s.to_string(),
_ => panic!("expected text"),
};
assert_eq!(val, "456");
}
#[test]
fn probe_union_all_three_way() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT 1 AS v UNION ALL SELECT 2 UNION ALL SELECT 1;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![1, 2, 1]);
}
#[test]
fn probe_union_dedup_three_way() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT 1 AS v UNION SELECT 2 UNION SELECT 1 ORDER BY v;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert_eq!(results, vec![1, 2]);
}
#[test]
fn probe_except_compound() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 EXCEPT SELECT 2;")
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert!(results.contains(&1));
assert!(results.contains(&3));
assert!(!results.contains(&2));
}
#[test]
fn probe_intersect_compound() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn
.query(
"SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 \
INTERSECT SELECT 2 UNION ALL SELECT 3;",
)
.unwrap();
let results: Vec<i64> = rows.iter().map(|r| row_values(r)[0].to_integer()).collect();
assert!(results.contains(&2) || results.contains(&3));
}
#[test]
fn conformance_024_substr() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT substr('hello world', 7)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "world");
let r = conn.query("SELECT substr('hello world', 1, 5)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "hello");
let r = conn.query("SELECT substr('hello', -3)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "llo");
}
#[test]
fn conformance_024_replace() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT replace('hello world', 'world', 'rust')")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "hello rust");
let r = conn.query("SELECT replace('aaa', 'a', 'bb')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "bbbbbb");
}
#[test]
fn conformance_024_trim() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT trim(' hello ')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "hello");
let r = conn.query("SELECT ltrim(' hello ')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "hello ");
let r = conn.query("SELECT rtrim(' hello ')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), " hello");
}
#[test]
fn conformance_024_instr() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT instr('hello world', 'world')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "7");
let r = conn.query("SELECT instr('hello', 'xyz')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
let r = conn.query("SELECT instr('hello', '')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
}
#[test]
fn conformance_024_hex_zeroblob() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT hex(zeroblob(4))").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "00000000");
let r = conn.query("SELECT typeof(zeroblob(4))").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "blob");
}
#[test]
fn conformance_024_char_unicode() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT char(65, 66, 67)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "ABC");
let r = conn.query("SELECT unicode('A')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "65");
}
#[test]
fn conformance_025_between() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 5 BETWEEN 1 AND 10").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT 15 BETWEEN 1 AND 10").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
let r = conn.query("SELECT 5 NOT BETWEEN 1 AND 10").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
}
#[test]
fn conformance_025_in_operator() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 3 IN (1, 2, 3, 4)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT 5 IN (1, 2, 3, 4)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
let r = conn.query("SELECT 3 NOT IN (1, 2, 3, 4)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
}
#[test]
fn conformance_025_like_glob() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 'hello' LIKE 'hel%'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT 'hello' LIKE 'HEL%'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT 'hello' GLOB 'hel*'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT 'hello' GLOB 'HEL*'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
}
#[test]
fn conformance_025_coalesce_nullif_iif() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT coalesce(NULL, NULL, 'found')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "found");
let r = conn.query("SELECT nullif(5, 5)").unwrap();
assert!(row_values(&r[0])[0].is_null());
let r = conn.query("SELECT nullif(5, 3)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "5");
let r = conn.query("SELECT iif(1, 'yes', 'no')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "yes");
let r = conn.query("SELECT iif(0, 'yes', 'no')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "no");
}
#[test]
fn conformance_025_bitwise() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 6 & 3").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "2");
let r = conn.query("SELECT 6 | 3").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "7");
let r = conn.query("SELECT ~0").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "-1");
let r = conn.query("SELECT 1 << 4").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "16");
let r = conn.query("SELECT 16 >> 2").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "4");
}
#[test]
fn conformance_025_cast() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT CAST('123' AS INTEGER)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "123");
let r = conn.query("SELECT typeof(CAST(123 AS TEXT))").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "text");
let r = conn.query("SELECT CAST(3.14 AS INTEGER)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3");
}
#[test]
fn conformance_025_unary_operators() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT -(-5)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "5");
let r = conn.query("SELECT +42").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "42");
let r = conn.query("SELECT NOT 0").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT NOT 1").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
}
#[test]
fn conformance_025_is_null() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT NULL IS NULL").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT 5 IS NOT NULL").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn.query("SELECT NULL IS NOT NULL").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
}
#[test]
fn conformance_026_scalar_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 10), (2, 20), (3, 30)")
.unwrap();
let r = conn.query("SELECT (SELECT MAX(val) FROM t1)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "30");
let r = conn.query("SELECT (SELECT COUNT(*) FROM t1)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3");
}
#[test]
fn conformance_026_exists_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE items(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO items VALUES (1, 'apple'), (2, 'banana')")
.unwrap();
let r = conn
.query("SELECT EXISTS(SELECT 1 FROM items WHERE name = 'apple')")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn
.query("SELECT EXISTS(SELECT 1 FROM items WHERE name = 'cherry')")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
}
#[test]
fn conformance_026_cte_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, dept TEXT, salary INTEGER)",
)
.unwrap();
conn.execute(
"INSERT INTO employees VALUES \
(1, 'Alice', 'eng', 100000), \
(2, 'Bob', 'eng', 95000), \
(3, 'Charlie', 'sales', 80000)",
)
.unwrap();
let r = conn
.query(
"WITH eng AS (SELECT * FROM employees WHERE dept = 'eng') \
SELECT name FROM eng ORDER BY name",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(r.len(), 2);
}
#[test]
fn conformance_026_cte_explicit_columns() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE nums(n INTEGER)").unwrap();
conn.execute("INSERT INTO nums VALUES (10), (20), (30)")
.unwrap();
let r = conn
.query(
"WITH doubled(val) AS (SELECT n * 2 FROM nums) \
SELECT val FROM doubled ORDER BY val",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "20");
assert_eq!(row_values(&r[1])[0].to_text(), "40");
assert_eq!(row_values(&r[2])[0].to_text(), "60");
}
#[test]
fn conformance_026_recursive_cte() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query(
"WITH RECURSIVE cnt(x) AS (\
SELECT 1 UNION ALL SELECT x + 1 FROM cnt WHERE x < 5\
) SELECT x FROM cnt",
)
.unwrap();
let vals: Vec<String> = r.iter().map(|row| row_values(row)[0].to_text()).collect();
assert_eq!(vals, ["1", "2", "3", "4", "5"]);
}
#[test]
fn conformance_026_derived_table() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE scores(student TEXT, score INTEGER)")
.unwrap();
conn.execute(
"INSERT INTO scores VALUES \
('Alice', 90), ('Alice', 85), ('Bob', 70), ('Bob', 80)",
)
.unwrap();
let r = conn
.query(
"SELECT student, avg_score FROM \
(SELECT student, AVG(score) as avg_score \
FROM scores GROUP BY student) ORDER BY student",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
}
#[test]
fn conformance_027_row_number() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE sales(id INTEGER PRIMARY KEY, region TEXT, amount REAL)")
.unwrap();
conn.execute(
"INSERT INTO sales VALUES \
(1, 'North', 100.0), (2, 'South', 200.0), \
(3, 'North', 150.0), (4, 'South', 175.0)",
)
.unwrap();
let r = conn
.query(
"SELECT region, amount, \
ROW_NUMBER() OVER (ORDER BY amount DESC) as rn \
FROM sales",
)
.unwrap();
assert_eq!(row_values(&r[0])[2].to_text(), "1");
}
#[test]
fn conformance_027_rank_dense_rank() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE scores(id INTEGER PRIMARY KEY, name TEXT, score INTEGER)")
.unwrap();
conn.execute(
"INSERT INTO scores VALUES \
(1, 'A', 100), (2, 'B', 100), (3, 'C', 90), (4, 'D', 80)",
)
.unwrap();
let r = conn
.query(
"SELECT name, RANK() OVER (ORDER BY score DESC) as rnk \
FROM scores",
)
.unwrap();
assert_eq!(row_values(&r[0])[1].to_text(), "1");
assert_eq!(row_values(&r[1])[1].to_text(), "1");
assert_eq!(row_values(&r[2])[1].to_text(), "3");
}
#[test]
fn conformance_027_sum_over() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE txns(id INTEGER PRIMARY KEY, amount REAL)")
.unwrap();
conn.execute("INSERT INTO txns VALUES (1, 10.0), (2, 20.0), (3, 30.0)")
.unwrap();
let r = conn
.query(
"SELECT id, SUM(amount) OVER (ORDER BY id) as running \
FROM txns",
)
.unwrap();
assert_eq!(row_values(&r[0])[1].to_text(), "10.0");
assert_eq!(row_values(&r[1])[1].to_text(), "30.0");
assert_eq!(row_values(&r[2])[1].to_text(), "60.0");
}
#[test]
fn conformance_027_lag_lead() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE seq(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("INSERT INTO seq VALUES (1, 'a'), (2, 'b'), (3, 'c')")
.unwrap();
let r = conn
.query(
"SELECT val, LAG(val) OVER (ORDER BY id) as prev \
FROM seq",
)
.unwrap();
assert!(row_values(&r[0])[1].is_null());
assert_eq!(row_values(&r[1])[1].to_text(), "a");
}
#[test]
fn conformance_028_create_select_drop_view() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE products(\
id INTEGER PRIMARY KEY, name TEXT, price REAL, category TEXT)",
)
.unwrap();
conn.execute(
"INSERT INTO products VALUES \
(1, 'Widget', 9.99, 'gadgets'), \
(2, 'Gizmo', 24.99, 'gadgets'), \
(3, 'Doohickey', 4.99, 'tools')",
)
.unwrap();
conn.execute("CREATE VIEW expensive AS SELECT * FROM products WHERE price > 10.0")
.unwrap();
let r = conn
.query("SELECT name FROM expensive ORDER BY name")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Gizmo");
assert_eq!(r.len(), 1);
conn.execute("DROP VIEW expensive").unwrap();
}
#[test]
fn conformance_028_view_with_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE products(\
id INTEGER PRIMARY KEY, name TEXT, price REAL, category TEXT)",
)
.unwrap();
conn.execute(
"INSERT INTO products VALUES \
(1, 'Widget', 9.99, 'gadgets'), \
(2, 'Gizmo', 24.99, 'gadgets'), \
(3, 'Doohickey', 4.99, 'tools'), \
(4, 'Thingamajig', 14.99, 'tools'), \
(5, 'Whatchamacallit', 49.99, 'gadgets')",
)
.unwrap();
conn.execute(
"CREATE VIEW category_stats AS \
SELECT category, COUNT(*) as cnt \
FROM products GROUP BY category",
)
.unwrap();
let r = conn
.query("SELECT category, cnt FROM category_stats ORDER BY category")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "gadgets");
assert_eq!(row_values(&r[0])[1].to_text(), "3");
assert_eq!(row_values(&r[1])[0].to_text(), "tools");
assert_eq!(row_values(&r[1])[1].to_text(), "2");
}
#[test]
fn conformance_028_view_with_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE products(\
id INTEGER PRIMARY KEY, name TEXT, price REAL)",
)
.unwrap();
conn.execute(
"INSERT INTO products VALUES \
(1, 'Widget', 9.99), (2, 'Gizmo', 24.99)",
)
.unwrap();
conn.execute(
"CREATE TABLE orders(\
id INTEGER PRIMARY KEY, product_id INTEGER, qty INTEGER)",
)
.unwrap();
conn.execute("INSERT INTO orders VALUES (1, 1, 10), (2, 2, 5)")
.unwrap();
conn.execute(
"CREATE VIEW order_details AS \
SELECT o.id as order_id, p.name, o.qty, \
p.price * o.qty as total \
FROM orders o JOIN products p ON o.product_id = p.id",
)
.unwrap();
let r = conn
.query(
"SELECT order_id, name, total \
FROM order_details ORDER BY order_id",
)
.unwrap();
assert_eq!(row_values(&r[0])[1].to_text(), "Widget");
assert_eq!(row_values(&r[0])[2].to_text(), "99.9");
assert_eq!(row_values(&r[1])[1].to_text(), "Gizmo");
}
#[test]
fn conformance_028_create_view_if_not_exists() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(a INTEGER)").unwrap();
conn.execute("INSERT INTO t1 VALUES (1), (2), (3)").unwrap();
conn.execute("CREATE VIEW v1 AS SELECT a FROM t1").unwrap();
conn.execute("CREATE VIEW IF NOT EXISTS v1 AS SELECT 999")
.unwrap();
let r = conn.query("SELECT COUNT(*) FROM v1").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3");
}
#[test]
fn conformance_029_group_by_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE orders(\
id INTEGER PRIMARY KEY, customer TEXT, amount REAL)",
)
.unwrap();
conn.execute(
"INSERT INTO orders VALUES \
(1,'Alice',50.0),(2,'Bob',30.0),(3,'Alice',70.0),\
(4,'Bob',20.0),(5,'Charlie',100.0)",
)
.unwrap();
let r = conn
.query(
"SELECT customer, SUM(amount) as total \
FROM orders GROUP BY customer ORDER BY customer",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[0])[1].to_text(), "120.0");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[1])[1].to_text(), "50.0");
}
#[test]
fn conformance_029_having() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE orders(\
id INTEGER PRIMARY KEY, customer TEXT, amount REAL)",
)
.unwrap();
conn.execute(
"INSERT INTO orders VALUES \
(1,'Alice',50.0),(2,'Bob',30.0),(3,'Alice',70.0),\
(4,'Bob',20.0),(5,'Charlie',100.0)",
)
.unwrap();
let r = conn
.query(
"SELECT customer, SUM(amount) as total \
FROM orders GROUP BY customer \
HAVING total > 60 ORDER BY customer",
)
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
}
#[test]
fn conformance_029_group_by_count_min_max_avg() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE scores(\
student TEXT, subject TEXT, score INTEGER)",
)
.unwrap();
conn.execute(
"INSERT INTO scores VALUES \
('Alice','Math',90),('Alice','Sci',85),\
('Bob','Math',70),('Bob','Sci',80)",
)
.unwrap();
let r = conn
.query(
"SELECT student, COUNT(*) as cnt, MIN(score), MAX(score) \
FROM scores GROUP BY student ORDER BY student",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[0])[1].to_text(), "2");
assert_eq!(row_values(&r[0])[2].to_text(), "85");
assert_eq!(row_values(&r[0])[3].to_text(), "90");
}
#[test]
fn conformance_029_group_by_multi_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE log(\
dept TEXT, year INTEGER, revenue REAL)",
)
.unwrap();
conn.execute(
"INSERT INTO log VALUES \
('eng',2024,100.0),('eng',2024,200.0),\
('eng',2025,150.0),('sales',2024,80.0)",
)
.unwrap();
let r = conn
.query(
"SELECT dept, year, SUM(revenue) as total \
FROM log GROUP BY dept, year ORDER BY dept, year",
)
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "eng");
assert_eq!(row_values(&r[0])[1].to_text(), "2024");
assert_eq!(row_values(&r[0])[2].to_text(), "300.0");
}
#[test]
fn conformance_030_case_searched() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, score INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 95), (2, 72), (3, 45)")
.unwrap();
let r = conn
.query(
"SELECT id, \
CASE WHEN score >= 90 THEN 'A' \
WHEN score >= 70 THEN 'B' \
ELSE 'F' END as grade \
FROM t1 ORDER BY id",
)
.unwrap();
assert_eq!(row_values(&r[0])[1].to_text(), "A");
assert_eq!(row_values(&r[1])[1].to_text(), "B");
assert_eq!(row_values(&r[2])[1].to_text(), "F");
}
#[test]
fn conformance_030_case_simple() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query(
"SELECT CASE 2 \
WHEN 1 THEN 'one' \
WHEN 2 THEN 'two' \
WHEN 3 THEN 'three' \
ELSE 'other' END",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "two");
}
#[test]
fn conformance_030_case_null() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query(
"SELECT CASE NULL \
WHEN NULL THEN 'match' \
ELSE 'no match' END",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "no match");
let r = conn
.query(
"SELECT CASE WHEN NULL THEN 'truthy' \
ELSE 'falsy' END",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "falsy");
}
#[test]
fn conformance_030_case_in_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE items(id INTEGER PRIMARY KEY, status TEXT)")
.unwrap();
conn.execute(
"INSERT INTO items VALUES \
(1,'active'),(2,'inactive'),(3,'active'),\
(4,'active'),(5,'inactive')",
)
.unwrap();
let r = conn
.query(
"SELECT SUM(CASE WHEN status = 'active' \
THEN 1 ELSE 0 END) as active_count FROM items",
)
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3");
}
#[test]
fn conformance_031_insert_or_replace() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE kv(key TEXT PRIMARY KEY, value TEXT)")
.unwrap();
conn.execute("INSERT INTO kv VALUES ('a', 'first')")
.unwrap();
conn.execute("INSERT OR REPLACE INTO kv VALUES ('a', 'replaced')")
.unwrap();
let r = conn.query("SELECT value FROM kv WHERE key = 'a'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "replaced");
}
#[test]
fn conformance_031_insert_or_ignore() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE kv(key TEXT PRIMARY KEY, value TEXT)")
.unwrap();
conn.execute("INSERT INTO kv VALUES ('a', 'first')")
.unwrap();
conn.execute("INSERT OR IGNORE INTO kv VALUES ('a', 'ignored')")
.unwrap();
let r = conn.query("SELECT value FROM kv WHERE key = 'a'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "first");
conn.execute("INSERT OR IGNORE INTO kv VALUES ('b', 'new')")
.unwrap();
let r = conn.query("SELECT key FROM kv ORDER BY key").unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "a");
assert_eq!(row_values(&r[1])[0].to_text(), "b");
}
#[test]
fn conformance_031_replace_into() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE t1(\
id INTEGER PRIMARY KEY, name TEXT, \
score INTEGER DEFAULT 0)",
)
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'Alice', 100)")
.unwrap();
conn.execute("REPLACE INTO t1 VALUES (1, 'Alice Updated', 200)")
.unwrap();
let r = conn
.query("SELECT name, score FROM t1 WHERE id = 1")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice Updated");
assert_eq!(row_values(&r[0])[1].to_text(), "200");
}
#[test]
fn conformance_032_rename_table() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE original(\
id INTEGER PRIMARY KEY, name TEXT)",
)
.unwrap();
conn.execute("INSERT INTO original VALUES (1, 'Alice'), (2, 'Bob')")
.unwrap();
conn.execute("ALTER TABLE original RENAME TO renamed")
.unwrap();
let r = conn.query("SELECT name FROM renamed ORDER BY id").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
}
#[test]
fn conformance_032_add_column() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'Alice'), (2, 'Bob')")
.unwrap();
conn.execute("ALTER TABLE t1 ADD COLUMN score INTEGER DEFAULT 0")
.unwrap();
let r = conn
.query("SELECT name, score FROM t1 ORDER BY id")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[0])[1].to_text(), "0");
conn.execute("INSERT INTO t1 VALUES (3, 'Charlie', 95)")
.unwrap();
let r = conn
.query("SELECT name, score FROM t1 WHERE id = 3")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Charlie");
assert_eq!(row_values(&r[0])[1].to_text(), "95");
}
#[test]
fn conformance_032_add_multiple_columns() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'Alice'), (2, 'Bob')")
.unwrap();
conn.execute("ALTER TABLE t1 ADD COLUMN score INTEGER DEFAULT 0")
.unwrap();
conn.execute("ALTER TABLE t1 ADD COLUMN active INTEGER DEFAULT 1")
.unwrap();
let r = conn
.query("SELECT name, active FROM t1 ORDER BY id")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[0])[1].to_text(), "1");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[1])[1].to_text(), "1");
}
#[test]
fn alter_table_preserves_without_rowid_in_schema_sql() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE wr(id INTEGER PRIMARY KEY, body TEXT) WITHOUT ROWID;")
.unwrap();
conn.execute("ALTER TABLE wr ADD COLUMN extra INTEGER DEFAULT 0;")
.unwrap();
let rows = conn
.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'wr';")
.unwrap();
let sql = row_values(&rows[0])[0].to_text();
assert!(
sql.to_ascii_uppercase().contains("WITHOUT ROWID"),
"ALTER TABLE must preserve WITHOUT ROWID in sqlite_master SQL: {sql}"
);
}
#[test]
fn alter_table_preserves_typeless_columns_in_schema_sql() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE typeless(payload);").unwrap();
conn.execute("ALTER TABLE typeless ADD COLUMN note;")
.unwrap();
let rows = conn
.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'typeless';")
.unwrap();
let sql = row_values(&rows[0])[0].to_text();
let Some((Statement::CreateTable(create), _)) =
parse_first_statement_with_tail(&sql).expect("sqlite_master sql should parse")
else {
panic!("expected CREATE TABLE sql, got: {sql}");
};
let CreateTableBody::Columns { columns, .. } = create.body else {
panic!("expected CREATE TABLE column definition body, got: {sql}");
};
assert_eq!(
columns.len(),
2,
"ALTER TABLE should preserve both columns in sqlite_master sql: {sql}"
);
assert_eq!(columns[0].name, "payload", "{sql}");
assert!(
columns[0].type_name.is_none(),
"ALTER TABLE must not synthesize a declared type for existing typeless columns: {sql}"
);
assert_eq!(columns[1].name, "note", "{sql}");
assert!(
columns[1].type_name.is_none(),
"ALTER TABLE must not synthesize a declared type for added typeless columns: {sql}"
);
}
#[test]
fn alter_table_preserves_embedded_quote_identifiers_in_schema_sql() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(r#"CREATE TABLE "te""st"("co""l" TEXT PRIMARY KEY);"#)
.unwrap();
conn.execute(r#"ALTER TABLE "te""st" ADD COLUMN "no""te" TEXT;"#)
.unwrap();
let rows = conn
.query(r#"SELECT sql FROM sqlite_master WHERE type='table' AND name='te"st';"#)
.unwrap();
let sql = row_values(&rows[0])[0].to_text();
let Some((Statement::CreateTable(create), _)) =
parse_first_statement_with_tail(&sql).expect("sqlite_master sql should parse")
else {
panic!("expected CREATE TABLE sql, got: {sql}");
};
let CreateTableBody::Columns { columns, .. } = create.body else {
panic!("expected CREATE TABLE column definition body, got: {sql}");
};
assert_eq!(create.name.name, "te\"st", "{sql}");
assert_eq!(columns[0].name, "co\"l", "{sql}");
assert_eq!(columns[1].name, "no\"te", "{sql}");
}
#[test]
fn alter_table_rename_preserves_embedded_quote_identifiers_in_index_sql() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(r#"CREATE TABLE "te""st"("co""l" TEXT);"#)
.unwrap();
conn.execute(r#"CREATE INDEX "ix""q" ON "te""st"("co""l");"#)
.unwrap();
conn.execute(r#"ALTER TABLE "te""st" RENAME TO "ta""rget";"#)
.unwrap();
let rows = conn
.query(r#"SELECT sql FROM sqlite_master WHERE type='index' AND name='ix"q';"#)
.unwrap();
let sql = row_values(&rows[0])[0].to_text();
let Some((Statement::CreateIndex(create), _)) =
parse_first_statement_with_tail(&sql).expect("sqlite_master sql should parse")
else {
panic!("expected CREATE INDEX sql, got: {sql}");
};
assert_eq!(create.name.name, "ix\"q", "{sql}");
assert_eq!(create.table, "ta\"rget", "{sql}");
}
#[test]
fn foreign_key_cascade_supports_embedded_quote_identifiers() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA foreign_keys = ON;").unwrap();
conn.execute(r#"CREATE TABLE "par""ent"("id""x" INTEGER PRIMARY KEY);"#)
.unwrap();
conn.execute(
r#"CREATE TABLE "chi""ld"("fk""x" INTEGER REFERENCES "par""ent"("id""x") ON DELETE CASCADE);"#,
)
.unwrap();
conn.execute(r#"INSERT INTO "par""ent"("id""x") VALUES (1);"#)
.unwrap();
conn.execute(r#"INSERT INTO "chi""ld"("fk""x") VALUES (1);"#)
.unwrap();
conn.execute(r#"DELETE FROM "par""ent" WHERE "id""x" = 1;"#)
.unwrap();
let rows = conn.query(r#"SELECT COUNT(*) FROM "chi""ld";"#).unwrap();
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
}
#[test]
fn conformance_033_inner_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE dept(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER)")
.unwrap();
conn.execute("INSERT INTO dept VALUES (1,'Eng'),(2,'Sales'),(3,'HR')")
.unwrap();
conn.execute(
"INSERT INTO emp VALUES (1,'Alice',1),(2,'Bob',2),(3,'Charlie',1),(4,'Diana',2)",
)
.unwrap();
let r = conn
.query(
"SELECT emp.name, dept.name FROM emp \
INNER JOIN dept ON emp.dept_id = dept.id \
ORDER BY emp.name",
)
.unwrap();
assert_eq!(r.len(), 4);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[0])[1].to_text(), "Eng");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[1])[1].to_text(), "Sales");
assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
assert_eq!(row_values(&r[2])[1].to_text(), "Eng");
assert_eq!(row_values(&r[3])[0].to_text(), "Diana");
assert_eq!(row_values(&r[3])[1].to_text(), "Sales");
}
#[test]
fn conformance_033_left_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE dept(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER)")
.unwrap();
conn.execute("INSERT INTO dept VALUES (1,'Eng'),(2,'Sales'),(3,'HR')")
.unwrap();
conn.execute("INSERT INTO emp VALUES (1,'Alice',1),(2,'Bob',2)")
.unwrap();
let r = conn
.query(
"SELECT dept.name, emp.name FROM dept \
LEFT JOIN emp ON dept.id = emp.dept_id \
ORDER BY dept.name",
)
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "Eng");
assert_eq!(row_values(&r[0])[1].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "HR");
assert!(row_values(&r[1])[1].is_null());
assert_eq!(row_values(&r[2])[0].to_text(), "Sales");
assert_eq!(row_values(&r[2])[1].to_text(), "Bob");
}
#[test]
fn conformance_033_cross_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE colors(c TEXT)").unwrap();
conn.execute("CREATE TABLE sizes(s TEXT)").unwrap();
conn.execute("INSERT INTO colors VALUES ('red'),('blue')")
.unwrap();
conn.execute("INSERT INTO sizes VALUES ('S'),('M'),('L')")
.unwrap();
let r = conn
.query("SELECT c, s FROM colors CROSS JOIN sizes ORDER BY c, s")
.unwrap();
assert_eq!(r.len(), 6);
assert_eq!(row_values(&r[0])[0].to_text(), "blue");
assert_eq!(row_values(&r[0])[1].to_text(), "L");
assert_eq!(row_values(&r[5])[0].to_text(), "red");
assert_eq!(row_values(&r[5])[1].to_text(), "S");
}
#[test]
fn conformance_033_self_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, mgr_id INTEGER)")
.unwrap();
conn.execute(
"INSERT INTO emp VALUES (1,'Boss',NULL),(2,'Alice',1),(3,'Bob',1),(4,'Charlie',2)",
)
.unwrap();
let r = conn
.query(
"SELECT e.name, m.name FROM emp e \
INNER JOIN emp m ON e.mgr_id = m.id \
ORDER BY e.name",
)
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[0])[1].to_text(), "Boss");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[1])[1].to_text(), "Boss");
assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
assert_eq!(row_values(&r[2])[1].to_text(), "Alice");
}
#[test]
fn regression_join_literal_text_numeric_comparison_uses_storage_class_order() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE a(x INTEGER)").unwrap();
conn.execute("CREATE TABLE b(y INTEGER)").unwrap();
conn.execute("INSERT INTO a VALUES (1)").unwrap();
conn.execute("INSERT INTO b VALUES (2)").unwrap();
let rows = conn
.query("SELECT a.x FROM a JOIN b ON '123' = 123;")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn
.query("SELECT a.x FROM a JOIN b ON '123' < 124;")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn
.query("SELECT a.x FROM (SELECT 1 AS x) a JOIN (SELECT 2 AS y) b ON '123' = 123;")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn
.query("SELECT a.x FROM (SELECT 1 AS x) a JOIN (SELECT 2 AS y) b ON '123' < 124;")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn
.query("SELECT a.x FROM a JOIN b ON a.rowid = '1';")
.unwrap();
assert_eq!(rows.len(), 1);
let rows = conn
.query("SELECT a.x FROM a JOIN b ON a.rowid > '0';")
.unwrap();
assert_eq!(rows.len(), 1);
conn.execute("CREATE TABLE txt(v TEXT)").unwrap();
conn.execute("CREATE TABLE num(v NUMERIC)").unwrap();
conn.execute("INSERT INTO txt VALUES ('9')").unwrap();
conn.execute("INSERT INTO num VALUES (CAST('9' AS TEXT))")
.unwrap();
let rows = conn
.query("SELECT v FROM (SELECT v FROM txt) a JOIN (SELECT 10 AS n) b ON v < n;")
.unwrap();
assert_eq!(rows.len(), 0);
let rows = conn
.query("SELECT v FROM (SELECT v FROM num) a JOIN (SELECT 10 AS n) b ON v < n;")
.unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn regression_join_ambiguous_column_surfaces_typed_error() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE a(x INTEGER)").unwrap();
conn.execute("CREATE TABLE b(x INTEGER)").unwrap();
let err = conn
.query("SELECT a.x FROM a JOIN b ON x = 1;")
.expect_err("unqualified duplicate JOIN column should fail");
assert!(
matches!(err, FrankenError::AmbiguousColumn { ref name } if name == "x"),
"unexpected error: {err:?}"
);
}
#[test]
fn regression_table_alias_hides_base_table_qualifier() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(x INTEGER)").unwrap();
let err = conn
.query("SELECT t.x FROM t AS a;")
.expect_err("base table qualifier should not resolve after aliasing");
let message = err.to_string();
assert!(
message.contains("no such table: t") || message.contains("no such column"),
"unexpected error: {err:?}"
);
}
fn assert_wrong_function_arity(conn: &Connection, sql: &str, name: &str) {
let err = conn
.query(sql)
.expect_err("known function with wrong arity should fail");
let expected = format!("wrong number of arguments to function {name}()");
assert!(
matches!(&err, FrankenError::FunctionError(message) if message == &expected),
"unexpected error for {sql}: {err:?}"
);
}
#[test]
fn regression_aggregate_wrong_arity_surfaces_function_error() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(v INTEGER, s TEXT)").unwrap();
conn.execute("INSERT INTO t VALUES (1, 'a'), (2, 'b')")
.unwrap();
assert_wrong_function_arity(&conn, "SELECT sum() FROM t;", "sum");
assert_wrong_function_arity(&conn, "SELECT group_concat() FROM t;", "group_concat");
assert_wrong_function_arity(
&conn,
"SELECT group_concat(s, '-', '!') FROM t;",
"group_concat",
);
}
#[test]
fn regression_window_wrong_arity_surfaces_function_error() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t(v INTEGER)").unwrap();
conn.execute("INSERT INTO t VALUES (1), (2)").unwrap();
assert_wrong_function_arity(&conn, "SELECT rank(1) OVER (ORDER BY v) FROM t;", "rank");
assert_wrong_function_arity(&conn, "SELECT lag() OVER (ORDER BY v) FROM t;", "lag");
assert_wrong_function_arity(
&conn,
"SELECT lag(v, 1, 0, 0) OVER (ORDER BY v) FROM t;",
"lag",
);
assert_wrong_function_arity(&conn, "SELECT count(1, 2) OVER () FROM t;", "count");
}
#[test]
fn conformance_033_join_with_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE dept(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER, salary INTEGER)",
)
.unwrap();
conn.execute("INSERT INTO dept VALUES (1,'Eng'),(2,'Sales')")
.unwrap();
conn.execute("INSERT INTO emp VALUES (1,'Alice',1,100),(2,'Bob',2,80),(3,'Charlie',1,120)")
.unwrap();
let r = conn
.query(
"SELECT emp.name, dept.name FROM emp \
JOIN dept ON emp.dept_id = dept.id \
WHERE emp.salary > 90 ORDER BY emp.name",
)
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
}
#[test]
fn conformance_034_update_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT, score INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'Alice',80),(2,'Bob',90),(3,'Charlie',70)")
.unwrap();
conn.execute("UPDATE t1 SET score = 95 WHERE id = 2")
.unwrap();
let r = conn.query("SELECT score FROM t1 WHERE id = 2").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "95");
}
#[test]
fn conformance_034_update_multiple_columns() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT, score INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'Alice',80)")
.unwrap();
conn.execute("UPDATE t1 SET name = 'Alicia', score = 99 WHERE id = 1")
.unwrap();
let r = conn
.query("SELECT name, score FROM t1 WHERE id = 1")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alicia");
assert_eq!(row_values(&r[0])[1].to_text(), "99");
}
#[test]
fn conformance_034_update_all_rows() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, active INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,1),(2,1),(3,0)")
.unwrap();
conn.execute("UPDATE t1 SET active = 0").unwrap();
let r = conn.query("SELECT active FROM t1 ORDER BY id").unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "0");
assert_eq!(row_values(&r[1])[0].to_text(), "0");
assert_eq!(row_values(&r[2])[0].to_text(), "0");
}
#[test]
fn conformance_034_update_with_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,10),(2,20),(3,30)")
.unwrap();
conn.execute("UPDATE t1 SET val = val * 2 WHERE id <= 2")
.unwrap();
let r = conn.query("SELECT val FROM t1 ORDER BY id").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "20");
assert_eq!(row_values(&r[1])[0].to_text(), "40");
assert_eq!(row_values(&r[2])[0].to_text(), "30");
}
#[test]
fn conformance_035_delete_with_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
.unwrap();
conn.execute("DELETE FROM t1 WHERE id = 2").unwrap();
let r = conn.query("SELECT name FROM t1 ORDER BY id").unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
}
#[test]
fn conformance_035_delete_all() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob')")
.unwrap();
conn.execute("DELETE FROM t1").unwrap();
let r = conn.query("SELECT COUNT(*) FROM t1").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
}
#[test]
fn conformance_035_delete_with_in() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie'),(4,'Diana')")
.unwrap();
conn.execute("DELETE FROM t1 WHERE id IN (2, 4)").unwrap();
let r = conn.query("SELECT name FROM t1 ORDER BY id").unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
}
#[test]
fn conformance_036_union_all() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(name TEXT)").unwrap();
conn.execute("CREATE TABLE t2(name TEXT)").unwrap();
conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob')")
.unwrap();
conn.execute("INSERT INTO t2 VALUES ('Bob'),('Charlie')")
.unwrap();
let r = conn
.query("SELECT name FROM t1 UNION ALL SELECT name FROM t2 ORDER BY name")
.unwrap();
assert_eq!(r.len(), 4);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[2])[0].to_text(), "Bob");
assert_eq!(row_values(&r[3])[0].to_text(), "Charlie");
}
#[test]
fn conformance_036_union_distinct() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(name TEXT)").unwrap();
conn.execute("CREATE TABLE t2(name TEXT)").unwrap();
conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob')")
.unwrap();
conn.execute("INSERT INTO t2 VALUES ('Bob'),('Charlie')")
.unwrap();
let r = conn
.query("SELECT name FROM t1 UNION SELECT name FROM t2 ORDER BY name")
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
}
#[test]
fn conformance_036_intersect() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(name TEXT)").unwrap();
conn.execute("CREATE TABLE t2(name TEXT)").unwrap();
conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob'),('Charlie')")
.unwrap();
conn.execute("INSERT INTO t2 VALUES ('Bob'),('Charlie'),('Diana')")
.unwrap();
let r = conn
.query("SELECT name FROM t1 INTERSECT SELECT name FROM t2 ORDER BY name")
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Bob");
assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
}
#[test]
fn conformance_036_except() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(name TEXT)").unwrap();
conn.execute("CREATE TABLE t2(name TEXT)").unwrap();
conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob'),('Charlie')")
.unwrap();
conn.execute("INSERT INTO t2 VALUES ('Bob'),('Diana')")
.unwrap();
let r = conn
.query("SELECT name FROM t1 EXCEPT SELECT name FROM t2 ORDER BY name")
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
}
#[test]
fn conformance_037_order_by_desc() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
.unwrap();
let r = conn.query("SELECT name FROM t1 ORDER BY id DESC").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Charlie");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[2])[0].to_text(), "Alice");
}
#[test]
fn conformance_037_order_by_multiple() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, dept TEXT, name TEXT)")
.unwrap();
conn.execute(
"INSERT INTO t1 VALUES (1,'A','Charlie'),(2,'B','Alice'),\
(3,'A','Alice'),(4,'B','Bob')",
)
.unwrap();
let r = conn
.query("SELECT dept, name FROM t1 ORDER BY dept ASC, name ASC")
.unwrap();
assert_eq!(row_values(&r[0])[1].to_text(), "Alice");
assert_eq!(row_values(&r[0])[0].to_text(), "A");
assert_eq!(row_values(&r[1])[1].to_text(), "Charlie");
assert_eq!(row_values(&r[2])[1].to_text(), "Alice");
assert_eq!(row_values(&r[2])[0].to_text(), "B");
assert_eq!(row_values(&r[3])[1].to_text(), "Bob");
}
#[test]
fn conformance_037_limit() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie'),(4,'Diana'),(5,'Eve')",
)
.unwrap();
let r = conn
.query("SELECT name FROM t1 ORDER BY id LIMIT 3")
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
}
#[test]
fn conformance_037_limit_offset() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie'),(4,'Diana'),(5,'Eve')",
)
.unwrap();
let r = conn
.query("SELECT name FROM t1 ORDER BY id LIMIT 2 OFFSET 2")
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Charlie");
assert_eq!(row_values(&r[1])[0].to_text(), "Diana");
}
#[test]
fn conformance_037_distinct() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(name TEXT)").unwrap();
conn.execute("INSERT INTO t1 VALUES ('Alice'),('Bob'),('Alice'),('Charlie'),('Bob')")
.unwrap();
let r = conn
.query("SELECT DISTINCT name FROM t1 ORDER BY name")
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[2])[0].to_text(), "Charlie");
}
#[test]
fn conformance_037_order_by_nulls() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'B'),(2,NULL),(3,'A'),(4,NULL),(5,'C')")
.unwrap();
let r = conn.query("SELECT val FROM t1 ORDER BY val ASC").unwrap();
assert_eq!(r.len(), 5);
assert!(row_values(&r[0])[0].is_null());
assert!(row_values(&r[1])[0].is_null());
assert_eq!(row_values(&r[2])[0].to_text(), "A");
assert_eq!(row_values(&r[3])[0].to_text(), "B");
assert_eq!(row_values(&r[4])[0].to_text(), "C");
}
#[test]
fn conformance_038_insert_select_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE src(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE dst(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO src VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
.unwrap();
conn.execute("INSERT INTO dst SELECT * FROM src WHERE id <= 2")
.unwrap();
let r = conn.query("SELECT name FROM dst ORDER BY id").unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
}
#[test]
fn conformance_038_insert_select_with_transform() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE src(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE dst(name TEXT)").unwrap();
conn.execute("INSERT INTO src VALUES (1,'Alice'),(2,'Bob')")
.unwrap();
conn.execute("INSERT INTO dst SELECT upper(name) FROM src")
.unwrap();
let r = conn.query("SELECT name FROM dst ORDER BY name").unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "ALICE");
assert_eq!(row_values(&r[1])[0].to_text(), "BOB");
}
#[test]
fn conformance_039_null_comparison() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'A'),(2,NULL),(3,'B')")
.unwrap();
let r = conn
.query("SELECT COUNT(*) FROM t1 WHERE val = NULL")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0");
let r = conn
.query("SELECT COUNT(*) FROM t1 WHERE val IS NULL")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
let r = conn
.query("SELECT COUNT(*) FROM t1 WHERE val IS NOT NULL")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "2");
}
#[test]
fn conformance_039_coalesce() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, a TEXT, b TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,NULL,'fallback'),(2,'value',NULL)")
.unwrap();
let r = conn
.query("SELECT COALESCE(a, b) FROM t1 ORDER BY id")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "fallback");
assert_eq!(row_values(&r[1])[0].to_text(), "value");
}
#[test]
fn conformance_039_ifnull() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT IFNULL(NULL, 'default'), IFNULL('value', 'default')")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "default");
assert_eq!(row_values(&r[0])[1].to_text(), "value");
}
#[test]
fn conformance_039_nullif() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT NULLIF(1, 1), NULLIF(1, 2)").unwrap();
assert!(row_values(&r[0])[0].is_null());
assert_eq!(row_values(&r[0])[1].to_text(), "1");
}
#[test]
fn conformance_039_null_in_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(val INTEGER)").unwrap();
conn.execute("INSERT INTO t1 VALUES (1),(NULL),(3),(NULL),(5)")
.unwrap();
let r = conn
.query("SELECT COUNT(*), COUNT(val), SUM(val), AVG(val) FROM t1")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "5");
assert_eq!(row_values(&r[0])[1].to_text(), "3");
assert_eq!(row_values(&r[0])[2].to_text(), "9");
assert_eq!(row_values(&r[0])[3].to_text(), "3.0");
}
#[test]
fn conformance_040_create_index_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
.unwrap();
conn.execute("CREATE INDEX idx_name ON t1(name)").unwrap();
let r = conn
.query("SELECT type, name, tbl_name FROM sqlite_master WHERE type = 'index' AND name = 'idx_name'")
.unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0].to_text(), "index");
assert_eq!(row_values(&r[0])[1].to_text(), "idx_name");
assert_eq!(row_values(&r[0])[2].to_text(), "t1");
}
#[test]
fn conformance_040_create_index_if_not_exists() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("CREATE INDEX idx1 ON t1(val)").unwrap();
conn.execute("CREATE INDEX IF NOT EXISTS idx1 ON t1(val)")
.unwrap();
let r = conn
.query("SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx1'")
.unwrap();
assert_eq!(r.len(), 1);
}
#[test]
fn conformance_040_drop_index() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("CREATE INDEX idx1 ON t1(val)").unwrap();
conn.execute("DROP INDEX idx1").unwrap();
let r = conn
.query("SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx1'")
.unwrap();
assert_eq!(r.len(), 0);
}
#[test]
fn conformance_040_unique_index() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, code TEXT)")
.unwrap();
conn.execute("CREATE UNIQUE INDEX idx_code ON t1(code)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'abc')").unwrap();
let result = conn.execute("INSERT INTO t1 VALUES (2, 'abc')");
assert!(result.is_err());
}
#[test]
fn conformance_041_trigger_after_insert() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("CREATE TABLE log(msg TEXT)").unwrap();
conn.execute(
"CREATE TRIGGER t1_after_insert AFTER INSERT ON t1 \
BEGIN INSERT INTO log VALUES ('inserted ' || NEW.val); END",
)
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'hello')").unwrap();
let r = conn.query("SELECT msg FROM log").unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0].to_text(), "inserted hello");
}
#[test]
fn conformance_041_trigger_before_insert() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("CREATE TABLE audit(action TEXT)").unwrap();
conn.execute(
"CREATE TRIGGER t1_before BEFORE INSERT ON t1 \
BEGIN INSERT INTO audit VALUES ('before_insert'); END",
)
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 42)").unwrap();
let r = conn.query("SELECT action FROM audit").unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0].to_text(), "before_insert");
}
#[test]
fn conformance_041_trigger_drop() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY)")
.unwrap();
conn.execute("CREATE TABLE log(x TEXT)").unwrap();
conn.execute(
"CREATE TRIGGER trg1 AFTER INSERT ON t1 \
BEGIN INSERT INTO log VALUES ('fired'); END",
)
.unwrap();
conn.execute("DROP TRIGGER trg1").unwrap();
conn.execute("INSERT INTO t1 VALUES (1)").unwrap();
let r = conn.query("SELECT x FROM log").unwrap();
assert_eq!(r.len(), 0);
}
#[test]
fn conformance_042_autoincrement_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('a')").unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('b')").unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('c')").unwrap();
let r = conn.query("SELECT id, val FROM t1 ORDER BY id").unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "1");
assert_eq!(row_values(&r[1])[0].to_text(), "2");
assert_eq!(row_values(&r[2])[0].to_text(), "3");
}
#[test]
fn conformance_042_autoincrement_after_delete() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('a')").unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('b')").unwrap();
conn.execute("DELETE FROM t1 WHERE id = 2").unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('c')").unwrap();
let r = conn.query("SELECT id FROM t1 ORDER BY id").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
assert_eq!(row_values(&r[1])[0].to_text(), "3");
}
#[test]
fn conformance_042_sqlite_sequence_table() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('x')").unwrap();
conn.execute("INSERT INTO t1(val) VALUES ('y')").unwrap();
let r = conn
.query("SELECT seq FROM sqlite_sequence WHERE name = 't1'")
.unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0].to_text(), "2");
}
#[test]
fn conformance_043_default_literal() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE t1(\
id INTEGER PRIMARY KEY, \
status TEXT DEFAULT 'active', \
count INTEGER DEFAULT 0)",
)
.unwrap();
conn.execute("INSERT INTO t1(id) VALUES (1)").unwrap();
let r = conn
.query("SELECT status, count FROM t1 WHERE id = 1")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "active");
assert_eq!(row_values(&r[0])[1].to_text(), "0");
}
#[test]
fn conformance_043_default_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1(id) VALUES (1)").unwrap();
let r = conn.query("SELECT val FROM t1 WHERE id = 1").unwrap();
assert!(row_values(&r[0])[0].is_null());
}
#[test]
fn conformance_043_default_override() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT DEFAULT 'default_val')")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 'custom')").unwrap();
let r = conn.query("SELECT val FROM t1 WHERE id = 1").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "custom");
}
#[test]
fn conformance_044_order_by_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 10), (2, -5), (3, 3)")
.unwrap();
let r = conn
.query("SELECT id, ABS(val) as a FROM t1 ORDER BY ABS(val)")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3"); assert_eq!(row_values(&r[1])[0].to_text(), "2"); assert_eq!(row_values(&r[2])[0].to_text(), "1"); }
#[test]
fn conformance_044_order_by_column_index() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(a TEXT, b INTEGER)").unwrap();
conn.execute("INSERT INTO t1 VALUES ('x', 3), ('y', 1), ('z', 2)")
.unwrap();
let r = conn.query("SELECT a, b FROM t1 ORDER BY 2").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "y"); assert_eq!(row_values(&r[1])[0].to_text(), "z"); assert_eq!(row_values(&r[2])[0].to_text(), "x"); }
#[test]
fn conformance_044_order_by_alias() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, first TEXT, last TEXT)")
.unwrap();
conn.execute(
"INSERT INTO t1 VALUES (1, 'Bob', 'Smith'), \
(2, 'Alice', 'Jones'), (3, 'Charlie', 'Adams')",
)
.unwrap();
let r = conn
.query("SELECT first || ' ' || last AS full_name FROM t1 ORDER BY full_name")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "Alice Jones");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob Smith");
assert_eq!(row_values(&r[2])[0].to_text(), "Charlie Adams");
}
#[test]
fn conformance_048_abs() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT abs(-42), abs(3.14), abs(0)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "42");
assert_eq!(row_values(&r[0])[1].to_text(), "3.14");
assert_eq!(row_values(&r[0])[2].to_text(), "0");
}
#[test]
fn conformance_048_abs_null() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT abs(NULL)").unwrap();
assert!(row_values(&r[0])[0].is_null());
}
#[test]
fn conformance_048_round() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT round(3.14159), round(3.14159, 2), round(3.5)")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3.0");
assert_eq!(row_values(&r[0])[1].to_text(), "3.14");
assert_eq!(row_values(&r[0])[2].to_text(), "4.0");
}
#[test]
fn conformance_048_sign() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT sign(-5), sign(0), sign(42), sign(NULL)")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "-1");
assert_eq!(row_values(&r[0])[1].to_text(), "0");
assert_eq!(row_values(&r[0])[2].to_text(), "1");
assert!(row_values(&r[0])[3].is_null());
}
#[test]
fn conformance_048_trig_basic() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT sin(0), cos(0), tan(0)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
assert_eq!(row_values(&r[0])[1].to_text(), "1.0");
assert_eq!(row_values(&r[0])[2].to_text(), "0.0");
}
#[test]
fn conformance_048_acos_asin_atan() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT acos(1), asin(0), atan(0)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
assert_eq!(row_values(&r[0])[1].to_text(), "0.0");
assert_eq!(row_values(&r[0])[2].to_text(), "0.0");
}
#[test]
fn conformance_048_acos_domain_error() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT acos(2.0)").unwrap();
assert!(row_values(&r[0])[0].is_null());
}
#[test]
fn conformance_048_sqrt() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT sqrt(144), sqrt(2)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "12.0");
let v = row_values(&r[0])[1].to_text();
assert!(v.starts_with("1.41421356"), "got {v}");
}
#[test]
fn conformance_048_sqrt_negative() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT sqrt(-1)").unwrap();
assert!(row_values(&r[0])[0].is_null());
}
#[test]
fn conformance_048_pow() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT pow(2, 10), power(3, 2)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1024.0");
assert_eq!(row_values(&r[0])[1].to_text(), "9.0");
}
#[test]
fn conformance_048_exp_ln() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT exp(0), ln(1)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1.0");
assert_eq!(row_values(&r[0])[1].to_text(), "0.0");
}
#[test]
fn conformance_048_log_variants() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT log(100), log10(1000), log2(8)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "2.0");
assert_eq!(row_values(&r[0])[1].to_text(), "3.0");
assert_eq!(row_values(&r[0])[2].to_text(), "3.0");
}
#[test]
fn conformance_048_log_two_arg() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT log(2, 8)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3.0");
}
#[test]
fn conformance_048_ln_negative_null() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT ln(-1), ln(0)").unwrap();
assert!(row_values(&r[0])[0].is_null());
assert!(row_values(&r[0])[1].is_null());
}
#[test]
fn conformance_048_pi() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT pi()").unwrap();
let v = row_values(&r[0])[0].to_text();
assert!(v.starts_with("3.14159265"), "got {v}");
}
#[test]
fn conformance_048_ceil_floor_trunc() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT ceil(1.2), floor(1.7), trunc(2.9)")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "2.0");
assert_eq!(row_values(&r[0])[1].to_text(), "1.0");
assert_eq!(row_values(&r[0])[2].to_text(), "2.0");
}
#[test]
fn conformance_048_ceil_floor_negative() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT ceil(-1.2), floor(-1.2), trunc(-2.9)")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "-1.0");
assert_eq!(row_values(&r[0])[1].to_text(), "-2.0");
assert_eq!(row_values(&r[0])[2].to_text(), "-2.0");
}
#[test]
fn conformance_048_degrees_radians() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT degrees(pi()), radians(180)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "180.0");
let v = row_values(&r[0])[1].to_text();
assert!(v.starts_with("3.14159265"), "got {v}");
}
#[test]
fn conformance_048_mod_func() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT mod(10, 3), mod(10, 0)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1.0");
assert!(row_values(&r[0])[1].is_null());
}
#[test]
fn conformance_048_atan2() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT atan2(0, 1), atan2(1, 0)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
let v = row_values(&r[0])[1].to_text();
assert!(v.starts_with("1.57079632"), "got {v}");
}
#[test]
fn conformance_048_math_null_propagation() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT sin(NULL), sqrt(NULL), pow(NULL, 2), mod(NULL, 3)")
.unwrap();
assert!(row_values(&r[0])[0].is_null());
assert!(row_values(&r[0])[1].is_null());
assert!(row_values(&r[0])[2].is_null());
assert!(row_values(&r[0])[3].is_null());
}
#[test]
fn conformance_048_math_with_table() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE nums(x REAL)").unwrap();
conn.execute("INSERT INTO nums VALUES (4.0),(9.0),(16.0),(25.0)")
.unwrap();
let r = conn.query("SELECT sqrt(x) FROM nums ORDER BY x").unwrap();
assert_eq!(r.len(), 4);
assert_eq!(row_values(&r[0])[0].to_text(), "2.0");
assert_eq!(row_values(&r[1])[0].to_text(), "3.0");
assert_eq!(row_values(&r[2])[0].to_text(), "4.0");
assert_eq!(row_values(&r[3])[0].to_text(), "5.0");
}
#[test]
fn conformance_048_hyperbolic() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT sinh(0), cosh(0), tanh(0)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "0.0");
assert_eq!(row_values(&r[0])[1].to_text(), "1.0");
assert_eq!(row_values(&r[0])[2].to_text(), "0.0");
}
#[test]
fn conformance_049_length() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT length('hello'), length(''), length(NULL)")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "5");
assert_eq!(row_values(&r[0])[1].to_text(), "0");
assert!(row_values(&r[0])[2].is_null());
}
#[test]
fn conformance_049_upper_lower() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT upper('hello'), lower('WORLD')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "HELLO");
assert_eq!(row_values(&r[0])[1].to_text(), "world");
}
#[test]
fn conformance_049_typeof() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT typeof(42), typeof(3.14), typeof('hi'), typeof(NULL), typeof(X'00')")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "integer");
assert_eq!(row_values(&r[0])[1].to_text(), "real");
assert_eq!(row_values(&r[0])[2].to_text(), "text");
assert_eq!(row_values(&r[0])[3].to_text(), "null");
assert_eq!(row_values(&r[0])[4].to_text(), "blob");
}
#[test]
fn conformance_049_max_min_scalar() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT max(1, 5, 3), min(10, 2, 7)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "5");
assert_eq!(row_values(&r[0])[1].to_text(), "2");
}
#[test]
fn conformance_049_total_changes() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(x INTEGER)").unwrap();
conn.execute("INSERT INTO t1 VALUES (1),(2),(3)").unwrap();
let r = conn.query("SELECT changes()").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3");
}
#[test]
fn conformance_050_string_concat() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 'hello' || ' ' || 'world'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "hello world");
}
#[test]
fn conformance_050_concat_with_numbers() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 'val=' || 42").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "val=42");
}
#[test]
fn conformance_050_concat_null() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 'abc' || NULL").unwrap();
assert!(row_values(&r[0])[0].is_null());
}
#[test]
fn conformance_050_numeric_string_comparison() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT CASE WHEN 10 = '10' THEN 'equal' ELSE 'not_equal' END")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "not_equal");
}
#[test]
fn conformance_050_mixed_arithmetic() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 1 + 2.5, 10 / 3, 10.0 / 3").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3.5");
assert_eq!(row_values(&r[0])[1].to_text(), "3");
let v = row_values(&r[0])[2].to_text();
assert!(v.starts_with("3.333"), "got {v}");
}
#[test]
fn conformance_050_cast_types() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT CAST('123' AS INTEGER), CAST(3.14 AS INTEGER), CAST(42 AS TEXT)")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "123");
assert_eq!(row_values(&r[0])[1].to_text(), "3");
assert_eq!(row_values(&r[0])[2].to_text(), "42");
}
#[test]
fn conformance_051_unary_minus() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT -(-5), -(3.14)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "5");
assert_eq!(row_values(&r[0])[1].to_text(), "-3.14");
}
#[test]
fn conformance_051_modulo_operator() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 17 % 5, -7 % 3").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "2");
assert_eq!(row_values(&r[0])[1].to_text(), "-1");
}
#[test]
fn conformance_051_boolean_expressions() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT 1 AND 1, 1 AND 0, 0 OR 1, 0 OR 0, NOT 0, NOT 1")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
assert_eq!(row_values(&r[0])[1].to_text(), "0");
assert_eq!(row_values(&r[0])[2].to_text(), "1");
assert_eq!(row_values(&r[0])[3].to_text(), "0");
assert_eq!(row_values(&r[0])[4].to_text(), "1");
assert_eq!(row_values(&r[0])[5].to_text(), "0");
}
#[test]
fn conformance_051_comparison_operators() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT 5 > 3, 5 < 3, 5 >= 5, 5 <= 4, 5 != 3, 5 == 5")
.unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "1");
assert_eq!(row_values(&r[0])[1].to_text(), "0");
assert_eq!(row_values(&r[0])[2].to_text(), "1");
assert_eq!(row_values(&r[0])[3].to_text(), "0");
assert_eq!(row_values(&r[0])[4].to_text(), "1");
assert_eq!(row_values(&r[0])[5].to_text(), "1");
}
#[test]
fn conformance_052_multi_table_join_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE departments(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER, salary REAL)")
.unwrap();
conn.execute("INSERT INTO departments VALUES (1,'Engineering'),(2,'Sales'),(3,'HR')")
.unwrap();
conn.execute("INSERT INTO employees VALUES (1,'Alice',1,80000),(2,'Bob',1,90000),(3,'Charlie',2,70000),(4,'Diana',2,75000),(5,'Eve',3,60000)")
.unwrap();
let r = conn
.query("SELECT d.name, COUNT(e.id), SUM(e.salary) FROM departments d JOIN employees e ON d.id = e.dept_id GROUP BY d.name ORDER BY d.name")
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "Engineering");
assert_eq!(row_values(&r[0])[1].to_text(), "2");
assert_eq!(row_values(&r[0])[2].to_text(), "170000.0");
assert_eq!(row_values(&r[1])[0].to_text(), "HR");
assert_eq!(row_values(&r[1])[1].to_text(), "1");
assert_eq!(row_values(&r[1])[2].to_text(), "60000.0");
assert_eq!(row_values(&r[2])[0].to_text(), "Sales");
assert_eq!(row_values(&r[2])[1].to_text(), "2");
assert_eq!(row_values(&r[2])[2].to_text(), "145000.0");
}
#[test]
fn conformance_052_subquery_in_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE products(id INTEGER PRIMARY KEY, name TEXT, price REAL)")
.unwrap();
conn.execute(
"INSERT INTO products VALUES (1,'A',10.0),(2,'B',20.0),(3,'C',30.0),(4,'D',15.0)",
)
.unwrap();
let r = conn
.query("SELECT name FROM products WHERE price > (SELECT AVG(price) FROM products) ORDER BY name")
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "B");
assert_eq!(row_values(&r[1])[0].to_text(), "C");
}
#[test]
fn conformance_052_insert_with_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE src(val INTEGER)").unwrap();
conn.execute("CREATE TABLE dst(val INTEGER)").unwrap();
conn.execute("INSERT INTO src VALUES (1),(2),(3),(4),(5)")
.unwrap();
conn.execute("INSERT INTO dst SELECT val FROM src WHERE val > 3")
.unwrap();
let r = conn.query("SELECT val FROM dst ORDER BY val").unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "4");
assert_eq!(row_values(&r[1])[0].to_text(), "5");
}
#[test]
fn conformance_053_group_concat_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, grp TEXT, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'a','x'),(2,'a','y'),(3,'b','z'),(4,'a','w')")
.unwrap();
let r = conn
.query("SELECT grp, GROUP_CONCAT(val) FROM t1 GROUP BY grp ORDER BY grp")
.unwrap();
assert_eq!(r.len(), 2);
let a_vals = row_values(&r[0])[1].to_text();
assert!(a_vals.contains('x'));
assert!(a_vals.contains('y'));
assert!(a_vals.contains('w'));
assert_eq!(row_values(&r[1])[1].to_text(), "z");
}
#[test]
fn conformance_053_group_concat_custom_separator() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val TEXT)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'a'),(2,'b'),(3,'c')")
.unwrap();
let r = conn
.query("SELECT GROUP_CONCAT(val, ' | ') FROM t1")
.unwrap();
let result = row_values(&r[0])[0].to_text();
assert!(result.contains('a'));
assert!(result.contains('b'));
assert!(result.contains('c'));
assert!(result.contains('|'));
}
#[test]
fn conformance_053_group_concat_null_skip() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(val TEXT)").unwrap();
conn.execute("INSERT INTO t1 VALUES ('a'),(NULL),('c')")
.unwrap();
let r = conn.query("SELECT GROUP_CONCAT(val) FROM t1").unwrap();
let result = row_values(&r[0])[0].to_text();
assert!(result.contains('a'));
assert!(result.contains('c'));
assert!(!result.contains("NULL"));
}
#[test]
fn conformance_054_pragma_table_info() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT NOT NULL, score REAL DEFAULT 0.0)",
)
.unwrap();
let r = conn.query("PRAGMA table_info(t1)").unwrap();
assert!(r.len() >= 3);
let col_names: Vec<String> = r.iter().map(|row| row_values(row)[1].to_text()).collect();
assert!(col_names.contains(&"id".to_owned()));
assert!(col_names.contains(&"name".to_owned()));
assert!(col_names.contains(&"score".to_owned()));
}
#[test]
fn pragma_table_info_preserves_declared_type_arguments() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE metrics(amount DECIMAL(10, 2), name VARCHAR(255))")
.unwrap();
let rows = conn.query("PRAGMA table_info(metrics)").unwrap();
let amount = rows
.iter()
.find(|row| row_values(row)[1].to_text() == "amount")
.expect("amount column metadata");
let name = rows
.iter()
.find(|row| row_values(row)[1].to_text() == "name")
.expect("name column metadata");
assert_eq!(row_values(amount)[2].to_text(), "DECIMAL(10, 2)");
assert_eq!(row_values(name)[2].to_text(), "VARCHAR(255)");
}
#[test]
fn conformance_054_pragma_user_version() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA user_version = 42").unwrap();
let r = conn.query("PRAGMA user_version").unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0].to_text(), "42");
}
#[test]
fn conformance_054_pragma_table_list() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE alpha(x INTEGER)").unwrap();
conn.execute("CREATE TABLE beta(y TEXT)").unwrap();
let r = conn
.query("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
.unwrap();
assert!(r.len() >= 2);
let names: Vec<String> = r.iter().map(|row| row_values(row)[0].to_text()).collect();
assert!(names.contains(&"alpha".to_owned()));
assert!(names.contains(&"beta".to_owned()));
}
#[test]
fn conformance_055_nested_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,10),(2,20),(3,30),(4,40),(5,50)")
.unwrap();
let r = conn
.query(
"SELECT val FROM t1 WHERE val > \
(SELECT AVG(val) FROM t1 WHERE val < \
(SELECT MAX(val) FROM t1)) ORDER BY val",
)
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "30");
assert_eq!(row_values(&r[1])[0].to_text(), "40");
assert_eq!(row_values(&r[2])[0].to_text(), "50");
}
#[test]
fn conformance_055_in_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE t2(id INTEGER PRIMARY KEY, t1_id INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'Alice'),(2,'Bob'),(3,'Charlie')")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (1,1),(2,3)").unwrap();
let r = conn
.query("SELECT name FROM t1 WHERE id IN (SELECT t1_id FROM t2) ORDER BY name")
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[1])[0].to_text(), "Charlie");
}
#[test]
fn conformance_055_not_in_subquery() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE t2(ref_id INTEGER)").unwrap();
conn.execute("INSERT INTO t1 VALUES (1,'A'),(2,'B'),(3,'C')")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (1),(3)").unwrap();
let r = conn
.query("SELECT name FROM t1 WHERE id NOT IN (SELECT ref_id FROM t2)")
.unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0].to_text(), "B");
}
#[test]
fn conformance_056_three_table_join() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE departments(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, dept_id INTEGER)")
.unwrap();
conn.execute("CREATE TABLE projects(id INTEGER PRIMARY KEY, title TEXT, emp_id INTEGER)")
.unwrap();
conn.execute("INSERT INTO departments VALUES (1,'Eng'),(2,'Sales')")
.unwrap();
conn.execute("INSERT INTO employees VALUES (1,'Alice',1),(2,'Bob',1),(3,'Charlie',2)")
.unwrap();
conn.execute("INSERT INTO projects VALUES (1,'Widget',1),(2,'Gadget',2),(3,'Deal',3)")
.unwrap();
let r = conn
.query(
"SELECT d.name, e.name, p.title \
FROM departments d \
JOIN employees e ON e.dept_id = d.id \
JOIN projects p ON p.emp_id = e.id \
WHERE d.name = 'Eng' \
ORDER BY e.name",
)
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[1].to_text(), "Alice");
assert_eq!(row_values(&r[0])[2].to_text(), "Widget");
assert_eq!(row_values(&r[1])[1].to_text(), "Bob");
assert_eq!(row_values(&r[1])[2].to_text(), "Gadget");
}
#[test]
fn conformance_056_left_join_with_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE teams(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("CREATE TABLE members(id INTEGER PRIMARY KEY, team_id INTEGER, name TEXT)")
.unwrap();
conn.execute("INSERT INTO teams VALUES (1,'Alpha'),(2,'Beta'),(3,'Gamma')")
.unwrap();
conn.execute("INSERT INTO members VALUES (1,1,'A1'),(2,1,'A2'),(3,2,'B1')")
.unwrap();
let r = conn
.query(
"SELECT t.name, SUM(CASE WHEN m.id IS NOT NULL THEN 1 ELSE 0 END) as member_count \
FROM teams t \
LEFT JOIN members m ON m.team_id = t.id \
GROUP BY t.id, t.name \
ORDER BY t.name",
)
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0].to_text(), "Alpha");
assert_eq!(row_values(&r[0])[1].to_text(), "2");
assert_eq!(row_values(&r[1])[0].to_text(), "Beta");
assert_eq!(row_values(&r[1])[1].to_text(), "1");
assert_eq!(row_values(&r[2])[0].to_text(), "Gamma");
assert_eq!(row_values(&r[2])[1].to_text(), "0");
}
#[test]
fn conformance_057_integer_division() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 7/2, -7/2, 1/3").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3");
assert_eq!(row_values(&r[0])[1].to_text(), "-3");
assert_eq!(row_values(&r[0])[2].to_text(), "0");
}
#[test]
fn conformance_057_real_division() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 7.0/2, 1.0/3.0").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "3.5");
let third: f64 = row_values(&r[0])[1].to_text().parse().unwrap();
assert!((third - 1.0 / 3.0).abs() < 1e-10);
}
#[test]
fn conformance_057_string_concatenation_operator() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT 'hello' || ' ' || 'world'").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "hello world");
}
#[test]
fn conformance_057_null_arithmetic() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT NULL + 1, NULL * 5, NULL || 'text'")
.unwrap();
assert!(row_values(&r[0])[0].is_null());
assert!(row_values(&r[0])[1].is_null());
assert!(row_values(&r[0])[2].is_null());
}
#[test]
fn conformance_058_correlated_subquery_in_where() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("CREATE TABLE t2(id INTEGER, t1_id INTEGER, score INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 100), (2, 200), (3, 300)")
.unwrap();
conn.execute("INSERT INTO t2 VALUES (1,1,10),(2,1,20),(3,2,30),(4,3,5)")
.unwrap();
let r = conn
.query(
"SELECT t1.id, t1.val FROM t1 WHERE (SELECT MAX(score) FROM t2 WHERE t2.t1_id = t1.id) > 15 ORDER BY t1.id",
)
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(100));
assert_eq!(row_values(&r[1])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(200));
}
#[test]
fn conformance_058_correlated_subquery_in_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE orders(id INTEGER PRIMARY KEY, customer_id INTEGER, amount REAL)",
)
.unwrap();
conn.execute("CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO customers VALUES (1, 'Alice'), (2, 'Bob')")
.unwrap();
conn.execute("INSERT INTO orders VALUES (1,1,100.0),(2,1,200.0),(3,2,50.0)")
.unwrap();
let r = conn
.query(
"SELECT c.name, (SELECT SUM(amount) FROM orders o WHERE o.customer_id = c.id) AS total FROM customers c ORDER BY c.name",
)
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "Alice");
assert_eq!(row_values(&r[0])[1].to_text(), "300.0");
assert_eq!(row_values(&r[1])[0].to_text(), "Bob");
assert_eq!(row_values(&r[1])[1].to_text(), "50.0");
}
#[test]
fn conformance_058_create_table_as_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE src(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("INSERT INTO src VALUES (1, 100), (2, 200), (3, 300)")
.unwrap();
conn.execute("CREATE TABLE dst AS SELECT id, val * 2 AS doubled FROM src WHERE id <= 2")
.unwrap();
let r = conn.query("SELECT * FROM dst ORDER BY id").unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(200));
assert_eq!(row_values(&r[1])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(400));
}
#[test]
fn conformance_059_group_by_expression() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, val INTEGER)")
.unwrap();
conn.execute("INSERT INTO t1 VALUES (1, 100), (2, 200), (3, 300)")
.unwrap();
let r = conn
.query(
"SELECT val / 100 AS bucket, COUNT(*) FROM t1 GROUP BY val / 100 ORDER BY bucket",
)
.unwrap();
assert_eq!(r.len(), 3);
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(1));
assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(1));
assert_eq!(row_values(&r[1])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(1));
assert_eq!(row_values(&r[2])[0], SqliteValue::Integer(3));
assert_eq!(row_values(&r[2])[1], SqliteValue::Integer(1));
}
#[test]
fn conformance_059_group_by_function() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE names(id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute("INSERT INTO names VALUES (1,'Alice'),(2,'alice'),(3,'Bob'),(4,'BOB')")
.unwrap();
let r = conn
.query("SELECT UPPER(name) AS uname, COUNT(*) FROM names GROUP BY UPPER(name) ORDER BY uname")
.unwrap();
assert_eq!(r.len(), 2);
assert_eq!(row_values(&r[0])[0].to_text(), "ALICE");
assert_eq!(row_values(&r[0])[1], SqliteValue::Integer(2));
assert_eq!(row_values(&r[1])[0].to_text(), "BOB");
assert_eq!(row_values(&r[1])[1], SqliteValue::Integer(2));
}
#[test]
fn conformance_059_having_without_group_by() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(val INTEGER)").unwrap();
conn.execute("INSERT INTO t1 VALUES (1),(2),(3)").unwrap();
let r = conn
.query("SELECT COUNT(*) FROM t1 HAVING COUNT(*) > 2")
.unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(3));
}
#[test]
fn conformance_059_having_without_group_by_no_match() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t1(val INTEGER)").unwrap();
conn.execute("INSERT INTO t1 VALUES (1),(2),(3)").unwrap();
let r = conn
.query("SELECT COUNT(*) FROM t1 HAVING COUNT(*) > 5")
.unwrap();
assert_eq!(r.len(), 0);
}
#[test]
fn conformance_060_lowercase_function_names() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT typeof(42)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "integer");
let r = conn.query("SELECT typeof(3.14)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "real");
let r = conn.query("SELECT typeof('hello')").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "text");
let r = conn.query("SELECT typeof(NULL)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "null");
}
#[test]
fn conformance_060_mixed_case_function_names() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT abs(-7)").unwrap();
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(7));
let r = conn.query("SELECT ABS(-7)").unwrap();
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(7));
let r = conn.query("SELECT Abs(-7)").unwrap();
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(7));
}
#[test]
fn conformance_060_hex_upper_lower() {
let conn = Connection::open(":memory:").unwrap();
let r = conn.query("SELECT hex(255)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "323535");
let r = conn.query("SELECT HEX(255)").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "323535");
}
#[test]
fn conformance_060_order_by_column_index_multi() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t2(x TEXT, y INTEGER, z REAL)")
.unwrap();
conn.execute("INSERT INTO t2 VALUES ('a', 3, 1.0), ('b', 1, 3.0), ('c', 2, 2.0)")
.unwrap();
let r = conn.query("SELECT x, y FROM t2 ORDER BY 1").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "a");
assert_eq!(row_values(&r[1])[0].to_text(), "b");
assert_eq!(row_values(&r[2])[0].to_text(), "c");
}
#[test]
fn conformance_060_order_by_column_index_desc() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE t3(a TEXT, b INTEGER)").unwrap();
conn.execute("INSERT INTO t3 VALUES ('x', 3), ('y', 1), ('z', 2)")
.unwrap();
let r = conn.query("SELECT a, b FROM t3 ORDER BY 2 DESC").unwrap();
assert_eq!(row_values(&r[0])[0].to_text(), "x"); assert_eq!(row_values(&r[1])[0].to_text(), "z"); assert_eq!(row_values(&r[2])[0].to_text(), "y"); }
#[test]
fn conformance_060_having_sum_aggregate() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE sales(amount REAL)").unwrap();
conn.execute("INSERT INTO sales VALUES (100.0),(200.0),(300.0)")
.unwrap();
let r = conn
.query("SELECT SUM(amount) FROM sales HAVING SUM(amount) > 500")
.unwrap();
assert_eq!(r.len(), 1);
assert_eq!(row_values(&r[0])[0], SqliteValue::Float(600.0));
}
#[test]
fn conformance_060_numeric_text_comparison_variants() {
let conn = Connection::open(":memory:").unwrap();
let r = conn
.query("SELECT CASE WHEN 42 = '42' THEN 1 ELSE 0 END")
.unwrap();
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(0));
let r = conn
.query("SELECT CASE WHEN 3.14 = '3.14' THEN 1 ELSE 0 END")
.unwrap();
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(0));
let r = conn
.query("SELECT CASE WHEN 10 = 'ten' THEN 1 ELSE 0 END")
.unwrap();
assert_eq!(row_values(&r[0])[0], SqliteValue::Integer(0));
}
#[test]
fn regression_having_aggregate_not_in_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE emp (dept TEXT, salary INTEGER);")
.unwrap();
conn.execute(
"INSERT INTO emp VALUES ('A', 100), ('A', 200), ('B', 50), ('B', 60), ('B', 70);",
)
.unwrap();
let rows = conn
.query("SELECT dept FROM emp GROUP BY dept HAVING COUNT(*) >= 3;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("B".into()));
}
#[test]
fn regression_having_sum_not_in_select() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE sales (product TEXT, amount INTEGER);")
.unwrap();
conn.execute("INSERT INTO sales VALUES ('X', 10), ('X', 20), ('Y', 100), ('Y', 200);")
.unwrap();
let rows = conn
.query("SELECT product FROM sales GROUP BY product HAVING SUM(amount) > 50;")
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Text("Y".into()));
}
#[test]
fn regression_null_comparison_returns_null_not_zero() {
let conn = Connection::open(":memory:").unwrap();
let rows = conn.query("SELECT (1 = NULL);").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
let rows = conn.query("SELECT (NULL > 5);").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
let rows = conn.query("SELECT (NULL = NULL);").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Null);
let rows = conn.query("SELECT (1 IS NULL);").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(0));
let rows = conn.query("SELECT (NULL IS NULL);").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(1));
}
#[test]
fn generated_column_stored_basic() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) STORED)",
)
.unwrap();
conn.execute("INSERT INTO t (a, b) VALUES (3, 7)").unwrap();
let rows = conn.query("SELECT a, b, c FROM t").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(3));
assert_eq!(row_values(&rows[0])[1], SqliteValue::Integer(7));
assert_eq!(
row_values(&rows[0])[2],
SqliteValue::Integer(10),
"STORED generated column c = a + b should be 10"
);
}
#[test]
fn generated_column_stored_multiplication() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE prices (qty INTEGER, unit_price INTEGER, total INTEGER GENERATED ALWAYS AS (qty * unit_price) STORED)",
)
.unwrap();
conn.execute("INSERT INTO prices (qty, unit_price) VALUES (5, 12)")
.unwrap();
let rows = conn.query("SELECT total FROM prices").unwrap();
assert_eq!(
row_values(&rows[0])[0],
SqliteValue::Integer(60),
"STORED generated column total = qty * unit_price should be 60"
);
}
#[test]
fn generated_column_stored_multi_row() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE t (x INTEGER, doubled INTEGER GENERATED ALWAYS AS (x * 2) STORED)",
)
.unwrap();
conn.execute("INSERT INTO t (x) VALUES (1)").unwrap();
conn.execute("INSERT INTO t (x) VALUES (5)").unwrap();
conn.execute("INSERT INTO t (x) VALUES (100)").unwrap();
let rows = conn.query("SELECT doubled FROM t ORDER BY x").unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(row_values(&rows[0])[0], SqliteValue::Integer(2));
assert_eq!(row_values(&rows[1])[0], SqliteValue::Integer(10));
assert_eq!(row_values(&rows[2])[0], SqliteValue::Integer(200));
}
#[test]
fn generated_column_stored_update_recomputes() {
let conn = Connection::open(":memory:").unwrap();
conn.execute(
"CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) STORED)",
)
.unwrap();
conn.execute("INSERT INTO t (a, b) VALUES (3, 7)").unwrap();
conn.execute("UPDATE t SET a = 10 WHERE b = 7").unwrap();
let rows = conn.query("SELECT c FROM t").unwrap();
assert_eq!(
row_values(&rows[0])[0],
SqliteValue::Integer(17),
"STORED generated column should recompute after UPDATE: 10 + 7 = 17"
);
}
#[test]
fn fk_insert_valid_parent_succeeds() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA foreign_keys = ON").unwrap();
conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
)
.unwrap();
conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
.unwrap();
conn.execute("INSERT INTO child VALUES (1, 1)").unwrap();
let rows = conn.query("SELECT * FROM child").unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn fk_insert_missing_parent_fails() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA foreign_keys = ON").unwrap();
conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
)
.unwrap();
let result = conn.execute("INSERT INTO child VALUES (1, 99)");
assert!(result.is_err(), "INSERT with missing FK parent should fail");
}
#[test]
fn fk_insert_null_fk_value_succeeds() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA foreign_keys = ON").unwrap();
conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY)")
.unwrap();
conn.execute(
"CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
)
.unwrap();
conn.execute("INSERT INTO child VALUES (1, NULL)").unwrap();
let rows = conn.query("SELECT * FROM child").unwrap();
assert_eq!(rows.len(), 1);
}
#[test]
fn fk_off_by_default() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY)")
.unwrap();
conn.execute(
"CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
)
.unwrap();
conn.execute("INSERT INTO child VALUES (1, 99)").unwrap();
}
#[test]
fn fk_delete_parent_with_children_fails() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA foreign_keys = ON").unwrap();
conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
)
.unwrap();
conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
.unwrap();
conn.execute("INSERT INTO child VALUES (1, 1)").unwrap();
let result = conn.execute("DELETE FROM parent WHERE id = 1");
assert!(
result.is_err(),
"DELETE parent with child references should fail with FK ON"
);
}
#[test]
fn fk_delete_cascade() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA foreign_keys = ON").unwrap();
conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE)",
)
.unwrap();
conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
.unwrap();
conn.execute("INSERT INTO child VALUES (1, 1)").unwrap();
conn.execute("INSERT INTO child VALUES (2, 1)").unwrap();
conn.execute("DELETE FROM parent WHERE id = 1").unwrap();
let rows = conn.query("SELECT * FROM child").unwrap();
assert_eq!(
rows.len(),
0,
"ON DELETE CASCADE should delete all child rows"
);
}
#[test]
fn fk_delete_set_null() {
let conn = Connection::open(":memory:").unwrap();
conn.execute("PRAGMA foreign_keys = ON").unwrap();
conn.execute("CREATE TABLE parent (id INTEGER PRIMARY KEY, name TEXT)")
.unwrap();
conn.execute(
"CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id) ON DELETE SET NULL)",
)
.unwrap();
conn.execute("INSERT INTO parent VALUES (1, 'Alice')")
.unwrap();
conn.execute("INSERT INTO child VALUES (1, 1)").unwrap();
conn.execute("DELETE FROM parent WHERE id = 1").unwrap();
let rows = conn.query("SELECT parent_id FROM child").unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
row_values(&rows[0])[0],
SqliteValue::Null,
"ON DELETE SET NULL should set FK column to NULL"
);
}
}