mod common;
use common::TestConnection;
use hyperdb_api::{Catalog, FromRow, ServerVersion};
#[test]
fn test_execute_batch() {
let test = TestConnection::new().expect("Failed to create test connection");
let total = test
.connection
.execute_batch(&[
"CREATE TABLE batch_test (id INT NOT NULL, name TEXT)",
"INSERT INTO batch_test VALUES (1, 'Alice')",
"INSERT INTO batch_test VALUES (2, 'Bob')",
"INSERT INTO batch_test VALUES (3, 'Carol')",
])
.expect("execute_batch");
assert_eq!(total, 3);
let count = test.count_tuples("batch_test").expect("count");
assert_eq!(count, 3);
}
#[test]
fn test_execute_batch_empty() {
let test = TestConnection::new().expect("Failed to create test connection");
let total = test.connection.execute_batch(&[]).expect("empty batch");
assert_eq!(total, 0);
}
#[test]
fn test_execute_batch_skips_blank() {
let test = TestConnection::new().expect("Failed to create test connection");
let total = test
.connection
.execute_batch(&[
"CREATE TABLE blank_test (id INT)",
"",
" ",
"INSERT INTO blank_test VALUES (1)",
])
.expect("batch with blanks");
assert_eq!(total, 1);
}
#[test]
fn test_execute_batch_stops_on_error() {
let test = TestConnection::new().expect("Failed to create test connection");
let result = test.connection.execute_batch(&[
"CREATE TABLE err_test (id INT NOT NULL)",
"INSERT INTO err_test VALUES (1)",
"THIS IS INVALID SQL",
"INSERT INTO err_test VALUES (2)", ]);
assert!(result.is_err(), "Should fail on invalid SQL");
}
#[test]
fn test_catalog_get_row_count() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command("CREATE TABLE count_test (id INT NOT NULL)")
.expect("create");
test.execute_command("INSERT INTO count_test SELECT * FROM GENERATE_SERIES(1, 42)")
.expect("insert");
let catalog = Catalog::new(&test.connection);
let count = catalog.get_row_count("count_test").expect("row count");
assert_eq!(count, 42);
}
#[test]
fn test_catalog_get_column_names() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command(
"CREATE TABLE colname_test (id INT NOT NULL, name TEXT, value DOUBLE PRECISION)",
)
.expect("create");
let catalog = Catalog::new(&test.connection);
let columns = catalog
.get_column_names("colname_test")
.expect("column names");
assert_eq!(columns.len(), 3);
assert_eq!(columns[0], "id");
assert_eq!(columns[1], "name");
assert_eq!(columns[2], "value");
}
#[test]
fn test_catalog_get_database_names() {
let test = TestConnection::new().expect("Failed to create test connection");
let catalog = Catalog::new(&test.connection);
let databases = catalog.get_database_names().expect("database names");
assert!(!databases.is_empty(), "Should have at least one database");
}
#[test]
fn test_server_version_parse() {
let v = ServerVersion::parse("0.0.19038").unwrap();
assert_eq!(v.major(), 0);
assert_eq!(v.minor(), 0);
assert_eq!(v.patch(), 19038);
}
#[test]
fn test_server_version_comparison() {
let v1 = ServerVersion::new(1, 0, 0);
let v2 = ServerVersion::new(1, 0, 1);
let v3 = ServerVersion::new(2, 0, 0);
assert!(v1 < v2);
assert!(v2 < v3);
assert_eq!(v1, ServerVersion::new(1, 0, 0));
}
#[test]
fn test_server_version_from_connection() {
let test = TestConnection::new().expect("Failed to create test connection");
let version = test.connection.server_version();
if let Some(v) = version {
assert!(
v >= ServerVersion::new(0, 0, 1),
"Version should be at least 0.0.1, got: {v}"
);
}
}
#[test]
fn test_copy_database() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command("CREATE TABLE copy_src (id INT NOT NULL)")
.expect("create");
test.execute_command("INSERT INTO copy_src VALUES (1), (2), (3)")
.expect("insert");
let src_path = test.database_path.to_string_lossy().to_string();
let dst_path = src_path.replace(".hyper", "_backup.hyper");
let _ = std::fs::remove_file(&dst_path);
let result = test.connection.copy_database(&src_path, &dst_path);
if let Ok(()) = result {
assert!(
std::path::Path::new(&dst_path).exists(),
"Backup file should exist"
);
let _ = std::fs::remove_file(&dst_path);
}
}
#[test]
fn test_explain() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command("CREATE TABLE explain_test (id INT NOT NULL, value DOUBLE PRECISION)")
.expect("create");
test.execute_command("INSERT INTO explain_test VALUES (1, 1.0), (2, 2.0)")
.expect("insert");
let plan = test
.connection
.explain("SELECT * FROM explain_test WHERE id > 0")
.expect("explain");
assert!(!plan.is_empty(), "EXPLAIN should return a non-empty plan");
}
#[test]
fn test_connection_builder_application_name() {
let test = TestConnection::new().expect("Failed to create test connection");
let endpoint = test.connection.parameter_status("server_version");
assert!(endpoint.is_some() || endpoint.is_none()); }
#[derive(Debug, PartialEq)]
struct TestUser {
id: i32,
name: String,
score: f64,
}
impl FromRow for TestUser {
fn from_row(row: hyperdb_api::RowAccessor<'_>) -> hyperdb_api::Result<Self> {
Ok(TestUser {
id: row.get("id")?,
name: row.get_opt("name")?.unwrap_or_default(),
score: row.get_opt("score")?.unwrap_or(0.0),
})
}
}
#[test]
fn test_fetch_one_as() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command(
"CREATE TABLE from_row_test (id INT NOT NULL, name TEXT, score DOUBLE PRECISION)",
)
.expect("create");
test.execute_command("INSERT INTO from_row_test VALUES (1, 'Alice', 95.5)")
.expect("insert");
let user: TestUser = test
.connection
.fetch_one_as("SELECT id, name, score FROM from_row_test WHERE id = 1")
.expect("fetch_one_as");
assert_eq!(user.id, 1);
assert_eq!(user.name, "Alice");
assert!((user.score - 95.5).abs() < 0.001);
}
#[test]
fn test_fetch_all_as() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command(
"CREATE TABLE from_row_all (id INT NOT NULL, name TEXT, score DOUBLE PRECISION)",
)
.expect("create");
test.execute_command(
"INSERT INTO from_row_all VALUES (1, 'Alice', 95.5), (2, 'Bob', 87.0), (3, 'Carol', 92.3)",
)
.expect("insert");
let users: Vec<TestUser> = test
.connection
.fetch_all_as("SELECT id, name, score FROM from_row_all ORDER BY id")
.expect("fetch_all_as");
assert_eq!(users.len(), 3);
assert_eq!(users[0].id, 1);
assert_eq!(users[0].name, "Alice");
assert_eq!(users[1].id, 2);
assert_eq!(users[1].name, "Bob");
assert_eq!(users[2].id, 3);
assert_eq!(users[2].name, "Carol");
}
#[derive(Debug, PartialEq, FromRow)]
struct TestUserDerived {
id: i32,
name: Option<String>,
score: Option<f64>,
}
#[test]
fn test_derive_from_row_parity_with_handwritten() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command(
"CREATE TABLE derive_parity (id INT NOT NULL, name TEXT, score DOUBLE PRECISION)",
)
.expect("create");
test.execute_command("INSERT INTO derive_parity VALUES (1, 'Alice', 95.5), (2, 'Bob', 87.0)")
.expect("insert");
let handwritten: Vec<TestUser> = test
.connection
.fetch_all_as("SELECT id, name, score FROM derive_parity ORDER BY id")
.expect("fetch_all_as TestUser");
let derived: Vec<TestUserDerived> = test
.connection
.fetch_all_as("SELECT id, name, score FROM derive_parity ORDER BY id")
.expect("fetch_all_as TestUserDerived");
assert_eq!(handwritten.len(), 2);
assert_eq!(derived.len(), 2);
for (hw, drv) in handwritten.iter().zip(derived.iter()) {
assert_eq!(hw.id, drv.id);
assert_eq!(Some(hw.name.clone()), drv.name);
assert_eq!(Some(hw.score), drv.score);
}
}
#[test]
fn test_derive_from_row_null_in_optional_field_returns_none() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command(
"CREATE TABLE derive_null (id INT NOT NULL, name TEXT, score DOUBLE PRECISION)",
)
.expect("create");
test.execute_command("INSERT INTO derive_null VALUES (1, NULL, NULL)")
.expect("insert null row");
let row: TestUserDerived = test
.connection
.fetch_one_as("SELECT id, name, score FROM derive_null WHERE id = 1")
.expect("fetch_one_as");
assert_eq!(row.id, 1);
assert_eq!(row.name, None);
assert_eq!(row.score, None);
}
#[test]
fn test_derive_from_row_with_rename() {
#[derive(Debug, PartialEq, FromRow)]
struct UserWithRename {
id: i32,
#[hyperdb(rename = "score")]
rating: Option<f64>,
}
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command("CREATE TABLE rename_test (id INT NOT NULL, score DOUBLE PRECISION)")
.expect("create");
test.execute_command("INSERT INTO rename_test VALUES (1, 99.9)")
.expect("insert");
let user: UserWithRename = test
.connection
.fetch_one_as("SELECT id, score FROM rename_test")
.expect("fetch_one_as UserWithRename");
assert_eq!(user.id, 1);
assert_eq!(user.rating, Some(99.9));
}
#[test]
fn test_derive_from_row_with_index() {
#[derive(Debug, PartialEq, FromRow)]
struct PositionalRow {
#[hyperdb(index = 0)]
id: i32,
#[hyperdb(index = 1)]
total: Option<i64>,
}
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command("CREATE TABLE positional_test (id INT NOT NULL, value INT)")
.expect("create");
test.execute_command("INSERT INTO positional_test VALUES (1, 10), (1, 20), (2, 30)")
.expect("insert");
let row: PositionalRow = test
.connection
.fetch_one_as("SELECT id, COUNT(*) FROM positional_test WHERE id = 1 GROUP BY id")
.expect("fetch_one_as PositionalRow");
assert_eq!(row.id, 1);
assert_eq!(row.total, Some(2));
}
#[test]
fn test_derive_from_row_missing_column_errors() {
use hyperdb_api::{ColumnErrorKind, Error};
#[derive(Debug, FromRow)]
#[allow(
dead_code,
reason = "fields populated by the generated impl; only error path is asserted"
)]
struct NeedsColumn {
id: i32,
not_in_query: i32,
}
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command("CREATE TABLE missing_col_test (id INT NOT NULL)")
.expect("create");
test.execute_command("INSERT INTO missing_col_test VALUES (1)")
.expect("insert");
let err = test
.connection
.fetch_one_as::<NeedsColumn>("SELECT id FROM missing_col_test")
.expect_err("expected missing-column error");
match err {
Error::Column { name, kind } => {
assert_eq!(name, "not_in_query");
assert!(
matches!(kind, ColumnErrorKind::Missing),
"expected Missing kind, got {kind:?}",
);
}
other => panic!("expected Error::Column {{ kind: Missing }}, got {other:?}"),
}
}
#[test]
fn test_ping() {
let test = TestConnection::new().expect("Failed to create test connection");
test.connection.ping().expect("ping should succeed");
}
#[test]
fn test_ping_after_operations() {
let test = TestConnection::new().expect("Failed to create test connection");
test.execute_command("CREATE TABLE ping_test (id INT)")
.expect("create");
test.execute_command("INSERT INTO ping_test VALUES (1)")
.expect("insert");
test.connection.ping().expect("ping after operations");
}