use ckg_core::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CozoErrorKind {
UnsupportedBuiltin,
RelationMissing,
RelationConflict,
RelationAlreadyExists,
Other,
}
impl CozoErrorKind {
pub fn of(err: &Error) -> Self {
let msg = err.to_string();
if msg.contains("unknown function")
|| msg.contains("Unknown function")
|| msg.contains("CannotFindOp")
|| msg.contains("undefined op")
|| msg.contains("undefined function")
|| msg.contains("operator") && msg.contains("not found")
{
return Self::UnsupportedBuiltin;
}
if msg.contains("EvalRelationConflict") {
return Self::RelationConflict;
}
if msg.contains("does not exist") {
return Self::RelationMissing;
}
if msg.contains("already exists") {
return Self::RelationAlreadyExists;
}
Self::Other
}
pub fn is_flush_recoverable(self) -> bool {
matches!(self, Self::RelationMissing | Self::RelationConflict)
}
}
pub fn is_unsupported_builtin(err: &Error) -> bool {
matches!(CozoErrorKind::of(err), CozoErrorKind::UnsupportedBuiltin)
}
pub fn is_relation_missing(err: &Error) -> bool {
matches!(CozoErrorKind::of(err), CozoErrorKind::RelationMissing)
}
pub fn is_relation_conflict(err: &Error) -> bool {
matches!(CozoErrorKind::of(err), CozoErrorKind::RelationConflict)
}
pub fn is_relation_already_exists(err: &Error) -> bool {
matches!(CozoErrorKind::of(err), CozoErrorKind::RelationAlreadyExists)
}
pub fn is_flush_recoverable(err: &Error) -> bool {
CozoErrorKind::of(err).is_flush_recoverable()
}
#[cfg(test)]
mod tests {
use super::*;
fn s(msg: &str) -> Error {
Error::Storage(msg.into())
}
#[test]
fn matches_unsupported_builtin_messages() {
for m in [
"operator str_includes not found",
"Unknown function: str_includes",
"CannotFindOp(\"is_in\")",
"undefined op: foo",
"undefined function bar",
] {
assert_eq!(
CozoErrorKind::of(&s(m)),
CozoErrorKind::UnsupportedBuiltin,
"should classify as UnsupportedBuiltin: {m}"
);
assert!(is_unsupported_builtin(&s(m)));
}
}
#[test]
fn does_not_match_real_errors() {
for m in [
"I/O error: disk full",
"permission denied",
"expected ; near line 5",
"value of type bool expected, got string",
"RocksDB lock contention",
] {
assert_eq!(
CozoErrorKind::of(&s(m)),
CozoErrorKind::Other,
"should classify as Other: {m}"
);
assert!(!is_unsupported_builtin(&s(m)));
}
}
#[test]
fn classifies_relation_missing() {
assert_eq!(
CozoErrorKind::of(&s("relation CROSS_CALLS does not exist")),
CozoErrorKind::RelationMissing
);
assert!(is_relation_missing(&s("relation CROSS_CALLS does not exist")));
assert!(!is_relation_missing(&s("I/O error: disk full")));
}
#[test]
fn classifies_relation_conflict() {
assert_eq!(
CozoErrorKind::of(&s("EvalRelationConflict(\"X\")")),
CozoErrorKind::RelationConflict
);
assert!(is_relation_conflict(&s("EvalRelationConflict(\"X\")")));
assert!(!is_relation_conflict(&s("RocksDB lock contention")));
}
#[test]
fn classifies_already_exists() {
assert_eq!(
CozoErrorKind::of(&s("relation Symbol already exists")),
CozoErrorKind::RelationAlreadyExists
);
assert!(is_relation_already_exists(&s("relation Symbol already exists")));
assert!(!is_relation_already_exists(&s("not found")));
}
#[test]
fn flush_recoverable_combines_missing_and_conflict() {
assert!(CozoErrorKind::RelationMissing.is_flush_recoverable());
assert!(CozoErrorKind::RelationConflict.is_flush_recoverable());
assert!(!CozoErrorKind::Other.is_flush_recoverable());
assert!(!CozoErrorKind::UnsupportedBuiltin.is_flush_recoverable());
assert!(!CozoErrorKind::RelationAlreadyExists.is_flush_recoverable());
assert!(is_flush_recoverable(&s("relation CROSS_CALLS does not exist")));
assert!(is_flush_recoverable(&s("EvalRelationConflict")));
assert!(!is_flush_recoverable(&s("disk full")));
}
#[test]
fn operator_not_found_classifies_as_builtin_not_relation_missing() {
let e = s("operator str_includes not found");
assert_eq!(CozoErrorKind::of(&e), CozoErrorKind::UnsupportedBuiltin);
}
#[test]
fn every_variant_has_at_least_one_matching_message() {
use CozoErrorKind::*;
let probes = [
("Unknown function: foo", UnsupportedBuiltin),
("relation CROSS_CALLS does not exist", RelationMissing),
("EvalRelationConflict(\"X\")", RelationConflict),
("relation Symbol already exists", RelationAlreadyExists),
("I/O error: disk full", Other),
];
for (msg, want) in probes {
assert_eq!(CozoErrorKind::of(&s(msg)), want, "probe: {msg}");
}
}
}