use powdb_query::executor::Engine;
use powdb_query::result::QueryError;
use powdb_storage::error::{StorageError, StorageErrorKind};
fn typed_refusals() -> Vec<(StorageError, StorageErrorKind)> {
vec![
(
StorageError::DdlInTransaction { verb: "drop" },
StorageErrorKind::DdlInTransaction,
),
(
StorageError::TransactionTooLarge {
pages: 65_536,
limit_bytes: 268_435_456,
},
StorageErrorKind::TransactionTooLarge,
),
(
StorageError::UniqueConstraintViolation {
table: "User".into(),
column: "email".into(),
},
StorageErrorKind::UniqueConstraintViolation,
),
(
StorageError::UniqueExpressionIndexViolation {
table: "Doc".into(),
expression: ".data->code".into(),
},
StorageErrorKind::UniqueExpressionIndexViolation,
),
]
}
#[test]
fn from_storage_io_keeps_the_kind_of_a_typed_refusal() {
for (error, expected) in typed_refusals() {
let wrapped = std::io::Error::new(std::io::ErrorKind::InvalidInput, error);
match QueryError::from_storage_io(wrapped) {
QueryError::Storage { kind, .. } => assert_eq!(
kind, expected,
"the refusal reached the query layer under the wrong kind"
),
other => panic!("expected a typed storage error, got {other:?}"),
}
}
}
#[test]
fn typing_a_refusal_does_not_change_one_byte_of_its_message() {
for (error, _) in typed_refusals() {
let wrapped = std::io::Error::new(std::io::ErrorKind::InvalidInput, error);
let legacy = QueryError::StorageError(wrapped.to_string()).to_string();
let typed = QueryError::from_storage_io(wrapped).to_string();
assert_eq!(
typed, legacy,
"the typed variant must render exactly what the untyped one did"
);
}
}
#[test]
fn a_plain_io_failure_falls_back_to_the_untyped_variant() {
let bare = std::io::Error::new(std::io::ErrorKind::NotFound, "no such file");
let rendered = bare.to_string();
match QueryError::from_storage_io(bare) {
QueryError::StorageError(message) => assert_eq!(message, rendered),
other => panic!("expected the untyped fallback, got {other:?}"),
}
}
#[test]
fn a_duplicate_unique_key_reaches_the_caller_typed() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type Acct { unique email: str }")
.unwrap();
engine
.execute_powql(r#"insert Acct { email := "a@example.com" }"#)
.unwrap();
let error = engine
.execute_powql(r#"insert Acct { email := "a@example.com" }"#)
.expect_err("a duplicate value in a unique column must be refused");
match &error {
QueryError::Storage { kind, message } => {
assert_eq!(*kind, StorageErrorKind::UniqueConstraintViolation);
assert_eq!(message, "unique constraint violation on Acct.email");
}
other => panic!("expected a typed storage refusal, got {other:?}"),
}
}
#[test]
fn a_duplicate_expression_index_key_reaches_the_caller_typed() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type Doc { required id: int, data: json }")
.unwrap();
engine
.execute_powql(r#"insert Doc { id := 1, data := "{\"code\":\"a\"}" }"#)
.unwrap();
engine
.execute_powql("alter Doc add unique (.data->code)")
.unwrap();
let error = engine
.execute_powql(r#"insert Doc { id := 2, data := "{\"code\":\"a\"}" }"#)
.expect_err("a duplicate expression-index key must be refused");
match &error {
QueryError::Storage { kind, message } => {
assert_eq!(*kind, StorageErrorKind::UniqueExpressionIndexViolation);
assert!(
message.starts_with("unique expression index violation on Doc ("),
"unexpected message: {message}"
);
}
other => panic!("expected a typed storage refusal, got {other:?}"),
}
}
#[test]
fn ddl_refused_inside_a_transaction_reaches_the_caller_typed() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type Doomed { required id: int }")
.unwrap();
engine.execute_powql("begin").unwrap();
let error = engine
.execute_powql("drop Doomed")
.expect_err("DDL inside an explicit transaction must be refused");
match &error {
QueryError::Storage { kind, message } => {
assert_eq!(*kind, StorageErrorKind::DdlInTransaction);
assert!(
message.contains("DDL is not transactional in PowDB"),
"unexpected message: {message}"
);
}
other => panic!("expected a typed storage refusal, got {other:?}"),
}
}