Skip to main content

cqlite_core/
error.rs

1//! Error types for CQLite
2
3use std::fmt;
4use thiserror::Error;
5
6/// Result type alias for CQLite operations
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Main error type for CQLite operations
10#[derive(Error, Debug)]
11pub enum Error {
12    /// I/O related errors
13    #[error("I/O error: {0}")]
14    Io(#[from] std::io::Error),
15
16    /// Serialization/deserialization errors
17    #[error("Serialization error: {message}")]
18    Serialization {
19        message: String,
20        #[source]
21        source: Option<Box<dyn std::error::Error + Send + Sync>>,
22    },
23
24    /// Data corruption errors
25    #[error("Data corruption: {0}")]
26    Corruption(String),
27
28    /// Schema validation errors
29    #[error("Schema error: {0}")]
30    Schema(String),
31
32    /// CQL parsing errors
33    #[error("CQL parse error: {0}")]
34    CqlParse(String),
35
36    /// Invalid format error (for SSTable parsing)
37    #[error("Invalid format: {0}")]
38    InvalidFormat(String),
39
40    /// Unsupported format error
41    #[error("Unsupported format: {0}")]
42    UnsupportedFormat(String),
43
44    /// SSTable version below the supported Cassandra 5.0 floor.
45    ///
46    /// CQLite targets Cassandra 5.0 (`na`+/`nb` BIG, `oa`/`da` BTI). A
47    /// pre-`na` (`ma`–`me`, Cassandra 3.x) BIG version, or a non-`da` BTI
48    /// version, is out of scope and rejected at version-parse time.
49    #[error("Unsupported SSTable version {version:?}: below supported floor {floor:?}")]
50    UnsupportedVersion { version: String, floor: String },
51
52    /// Timeout error
53    #[error("Operation timeout: {0}")]
54    Timeout(String),
55
56    /// Invalid path error
57    #[error("Invalid path: {0}")]
58    InvalidPath(String),
59
60    /// Invalid state error
61    #[error("Invalid state: {0}")]
62    InvalidState(String),
63
64    /// Query execution errors
65    #[error("Query execution error: {0}")]
66    QueryExecution(String),
67
68    /// A materialized result set exceeded the configured byte budget (issue #1582).
69    ///
70    /// Raised by the SELECT executor *while collecting* a materialized result
71    /// set, before it grows large enough to threaten the process's <128MB
72    /// memory target. This is the byte-unit successor to the coarse row-count
73    /// safety valve: a byte ceiling correctly distinguishes 1M harmless skinny
74    /// rows from 100k memory-blowing wide rows. The remedy is in the message —
75    /// bound the result with a `LIMIT` clause, or consume it incrementally via
76    /// the streaming query API instead of materializing the whole set.
77    #[error(
78        "result set exceeded the {budget_bytes}-byte materialization budget \
79         (estimated {estimated_bytes} bytes across {rows} rows so far); \
80         add a LIMIT clause to bound the result, or use the streaming query \
81         API (e.g. execute_streaming) to consume rows incrementally"
82    )]
83    ResultTooLarge {
84        /// Configured materialization byte budget that was exceeded.
85        budget_bytes: usize,
86        /// Estimated logical size (bytes) of the result collected so far.
87        estimated_bytes: usize,
88        /// Number of rows collected when the budget was exceeded.
89        rows: usize,
90    },
91
92    /// An unrecognized `CQLITE_READ_PATH` value was supplied (issue #1918).
93    ///
94    /// Resolving the read-path forcing knob returns this distinct error rather
95    /// than silently falling through to `auto`, so a typo'd knob is loud instead
96    /// of a no-op. Names the invalid value and the allowed set.
97    #[error("invalid CQLITE_READ_PATH value '{value}': expected one of auto, point, full")]
98    InvalidReadPath {
99        /// The unrecognized value supplied to the knob.
100        value: String,
101    },
102
103    /// Forced `point` read path could not run a partition-targeted lookup (issue
104    /// #1918).
105    ///
106    /// Raised whenever a forced read path cannot serve a query without silently
107    /// diverging from the `auto` result. Under `CQLITE_READ_PATH=point` (or the
108    /// equivalent `QueryConfig` field) this fires when the executor would not run
109    /// a genuinely partition-targeted lookup — a classification fallback, an
110    /// unwired targeted surface (e.g. a metadata `IN` fan-out), or a build/path
111    /// that does not actually prune. Under `CQLITE_READ_PATH=full` it fires for a
112    /// schema-less sole-pk point lookup, which only the specialized targeted seek
113    /// can serve correctly (a full scan would return 0 rows instead of the row
114    /// `auto` returns). Either way the query fails closed instead of silently
115    /// returning a wrong result; `reason` names the concrete cause.
116    #[error(
117        "forced read path '{forced}' unavailable: {reason}. This query cannot be \
118         served under CQLITE_READ_PATH={forced} without diverging from the 'auto' \
119         result; use 'auto' to let CQLite choose the read path"
120    )]
121    ForcedReadPathUnavailable {
122        /// The forced mode that could not be satisfied (`"point"` or `"full"`).
123        forced: &'static str,
124        /// The concrete fallback reason label (e.g. `partition_key_not_fully_constrained`).
125        reason: String,
126    },
127
128    /// Type conversion errors
129    #[error("Type conversion error: {0}")]
130    TypeConversion(String),
131
132    /// Configuration errors
133    #[error("Configuration error: {0}")]
134    Configuration(String),
135
136    /// Storage engine errors
137    #[error("Storage error: {0}")]
138    Storage(String),
139
140    /// Memory management errors
141    #[error("Memory error: {0}")]
142    Memory(String),
143
144    /// Lock/concurrency errors
145    #[error("Concurrency error: {0}")]
146    Concurrency(String),
147
148    /// Write directory already locked by another process or Database instance
149    ///
150    /// Returned by `WriteEngine::new` when the advisory lock on `write_dir`
151    /// cannot be acquired because another `WriteEngine` (in this or another
152    /// process) already holds it.  Only one `Database` instance may hold a
153    /// `write_dir` at a time.
154    #[error(
155        "write_dir '{path}' is already locked by another process. \
156         Only one Database instance may hold a write_dir at a time."
157    )]
158    WriteDirLocked {
159        /// The path that could not be locked
160        path: String,
161    },
162
163    /// Resource not found
164    #[error("Not found: {0}")]
165    NotFound(String),
166
167    /// Table errors
168    #[error("Table error: {0}")]
169    Table(String),
170
171    /// Resource already exists
172    #[error("Already exists: {0}")]
173    AlreadyExists(String),
174
175    /// Invalid operation
176    #[error("Invalid operation: {0}")]
177    InvalidOperation(String),
178
179    /// Constraint violation
180    #[error("Constraint violation: {0}")]
181    ConstraintViolation(String),
182
183    /// Transaction errors
184    #[error("Transaction error: {0}")]
185    Transaction(String),
186
187    /// Index errors
188    #[error("Index error: {0}")]
189    Index(String),
190
191    /// Compaction errors
192    #[error("Compaction error: {0}")]
193    Compaction(String),
194
195    /// WASM-specific errors
196    #[cfg(target_arch = "wasm32")]
197    #[error("WASM error: {0}")]
198    Wasm(String),
199
200    /// Generic internal error
201    #[error("Internal error: {0}")]
202    Internal(String),
203
204    /// Parse error
205    #[error("Parse error: {0}")]
206    Parse(String),
207
208    /// Invalid input error
209    #[error("Invalid input: {0}")]
210    InvalidInput(String),
211
212    /// Unsupported query error
213    #[error("Unsupported query: {0}")]
214    UnsupportedQuery(String),
215
216    /// The operation was cooperatively cancelled (issue #2264).
217    ///
218    /// Raised by a long-running scan (e.g. the compaction streaming read) when
219    /// its cancellation token is tripped — a client disconnect propagated from
220    /// the Flight `do_get` path. Distinct from a genuine failure so callers can
221    /// treat it as a clean, expected abort rather than corruption.
222    #[error("Operation cancelled")]
223    Cancelled,
224}
225
226impl Error {
227    /// Create a serialization error
228    pub fn serialization(msg: impl Into<String>) -> Self {
229        Self::Serialization {
230            message: msg.into(),
231            source: None,
232        }
233    }
234
235    /// Create a corruption error
236    pub fn corruption(msg: impl Into<String>) -> Self {
237        Self::Corruption(msg.into())
238    }
239
240    /// Create a schema error
241    pub fn schema(msg: impl Into<String>) -> Self {
242        Self::Schema(msg.into())
243    }
244
245    /// Create a CQL parse error
246    pub fn cql_parse(msg: impl Into<String>) -> Self {
247        Self::CqlParse(msg.into())
248    }
249
250    /// Create an invalid format error
251    pub fn invalid_format(msg: impl Into<String>) -> Self {
252        Self::InvalidFormat(msg.into())
253    }
254
255    /// Create an unsupported format error
256    pub fn unsupported_format(msg: impl Into<String>) -> Self {
257        Self::UnsupportedFormat(msg.into())
258    }
259
260    /// Create an invalid path error
261    pub fn invalid_path(msg: impl Into<String>) -> Self {
262        Self::InvalidPath(msg.into())
263    }
264
265    /// Create an invalid state error
266    pub fn invalid_state(msg: impl Into<String>) -> Self {
267        Self::InvalidState(msg.into())
268    }
269
270    /// Create a query execution error
271    pub fn query_execution(msg: impl Into<String>) -> Self {
272        Self::QueryExecution(msg.into())
273    }
274
275    /// Create an invalid-read-path error (issue #1918).
276    pub fn invalid_read_path(value: impl Into<String>) -> Self {
277        Self::InvalidReadPath {
278            value: value.into(),
279        }
280    }
281
282    /// Create a forced-read-path-unavailable error (issue #1918). `forced` is the
283    /// forced mode (`"point"` or `"full"`); `reason` is the concrete cause label.
284    pub fn forced_read_path_unavailable(forced: &'static str, reason: impl Into<String>) -> Self {
285        Self::ForcedReadPathUnavailable {
286            forced,
287            reason: reason.into(),
288        }
289    }
290
291    /// Create a type conversion error
292    pub fn type_conversion(msg: impl Into<String>) -> Self {
293        Self::TypeConversion(msg.into())
294    }
295
296    /// Create a configuration error
297    pub fn configuration(msg: impl Into<String>) -> Self {
298        Self::Configuration(msg.into())
299    }
300
301    /// Create a storage error
302    pub fn storage(msg: impl Into<String>) -> Self {
303        Self::Storage(msg.into())
304    }
305
306    /// Create a memory error
307    pub fn memory(msg: impl Into<String>) -> Self {
308        Self::Memory(msg.into())
309    }
310
311    /// Create a concurrency error
312    pub fn concurrency(msg: impl Into<String>) -> Self {
313        Self::Concurrency(msg.into())
314    }
315
316    /// Create a not found error
317    pub fn not_found(msg: impl Into<String>) -> Self {
318        Self::NotFound(msg.into())
319    }
320
321    /// Create an already exists error
322    pub fn already_exists(msg: impl Into<String>) -> Self {
323        Self::AlreadyExists(msg.into())
324    }
325
326    /// Create an invalid operation error
327    pub fn invalid_operation(msg: impl Into<String>) -> Self {
328        Self::InvalidOperation(msg.into())
329    }
330
331    /// Create a constraint violation error
332    pub fn constraint_violation(msg: impl Into<String>) -> Self {
333        Self::ConstraintViolation(msg.into())
334    }
335
336    /// Create a transaction error
337    pub fn transaction(msg: impl Into<String>) -> Self {
338        Self::Transaction(msg.into())
339    }
340
341    /// Create an index error
342    pub fn index(msg: impl Into<String>) -> Self {
343        Self::Index(msg.into())
344    }
345
346    /// Create a compaction error
347    pub fn compaction(msg: impl Into<String>) -> Self {
348        Self::Compaction(msg.into())
349    }
350
351    /// Create a WASM error
352    #[cfg(target_arch = "wasm32")]
353    pub fn wasm(msg: impl Into<String>) -> Self {
354        Self::Wasm(msg.into())
355    }
356
357    /// Create an internal error
358    pub fn internal(msg: impl Into<String>) -> Self {
359        Self::Internal(msg.into())
360    }
361
362    /// Create an invalid input error
363    pub fn invalid_input(msg: impl Into<String>) -> Self {
364        Self::InvalidInput(msg.into())
365    }
366
367    /// Create a parse error
368    pub fn parse(msg: impl Into<String>) -> Self {
369        Self::Parse(msg.into())
370    }
371
372    /// Create an unsupported query error
373    pub fn unsupported_query(msg: impl Into<String>) -> Self {
374        Self::UnsupportedQuery(msg.into())
375    }
376
377    /// Create a write-dir locked error
378    pub fn write_dir_locked(path: impl Into<String>) -> Self {
379        Self::WriteDirLocked { path: path.into() }
380    }
381
382    /// Create a table not found error
383    pub fn table_not_found(msg: impl Into<String>) -> Self {
384        Self::NotFound(format!("Table not found: {}", msg.into()))
385    }
386
387    /// Create an ambiguous table error
388    pub fn ambiguous_table(msg: impl Into<String>) -> Self {
389        Self::Table(format!("Ambiguous table reference: {}", msg.into()))
390    }
391
392    /// Check if this error is recoverable
393    pub fn is_recoverable(&self) -> bool {
394        match self {
395            // These errors are typically recoverable with retry
396            Error::Io(_) => true,
397            Error::Concurrency(_) => true,
398            Error::Memory(_) => true,
399
400            // These errors are typically not recoverable
401            Error::Corruption(_) => false,
402            Error::Schema(_) => false,
403            Error::CqlParse(_) => false,
404            Error::Configuration(_) => false,
405
406            // Context-dependent errors
407            Error::Storage(_) => true,
408            Error::QueryExecution(_) => false,
409            // Not recoverable by retry: the same query would re-materialize the
410            // same oversized result. The user must add LIMIT or stream.
411            Error::ResultTooLarge { .. } => false,
412            // A knob misconfiguration re-fails identically until the operator fixes it.
413            Error::InvalidReadPath { .. } => false,
414            Error::ForcedReadPathUnavailable { .. } => false,
415            Error::TypeConversion(_) => false,
416            Error::NotFound(_) => false,
417            Error::AlreadyExists(_) => false,
418            Error::InvalidOperation(_) => false,
419            Error::ConstraintViolation(_) => false,
420            Error::Transaction(_) => true,
421            Error::Index(_) => true,
422            Error::Compaction(_) => true,
423
424            // New error types
425            Error::Table(_) => false,
426
427            // Write-dir lock conflict — not recoverable without releasing the lock
428            Error::WriteDirLocked { .. } => false,
429
430            #[cfg(target_arch = "wasm32")]
431            Error::Wasm(_) => false,
432
433            Error::Serialization { .. } => false,
434            Error::Internal(_) => false,
435            Error::Parse(_) => false,
436            Error::InvalidInput(_) => false,
437            Error::InvalidFormat(_) => false,
438            Error::UnsupportedFormat(_) => false,
439            Error::UnsupportedVersion { .. } => false,
440            Error::InvalidPath(_) => false,
441            Error::InvalidState(_) => false,
442            Error::Timeout(_) => false,
443            Error::UnsupportedQuery(_) => false,
444            // A cancelled operation is deliberate, not a transient fault: re-running
445            // it would just be cancelled again. The caller decides whether to retry.
446            Error::Cancelled => false,
447        }
448    }
449
450    /// Get the error category
451    pub fn category(&self) -> ErrorCategory {
452        match self {
453            Error::Io(_) => ErrorCategory::System,
454            Error::Serialization { .. } => ErrorCategory::Data,
455            Error::Corruption(_) => ErrorCategory::Data,
456            Error::Schema(_) => ErrorCategory::Schema,
457            Error::CqlParse(_) => ErrorCategory::Query,
458            Error::QueryExecution(_) => ErrorCategory::Query,
459            Error::ResultTooLarge { .. } => ErrorCategory::Query,
460            Error::InvalidReadPath { .. } => ErrorCategory::Configuration,
461            Error::ForcedReadPathUnavailable { .. } => ErrorCategory::Query,
462            Error::TypeConversion(_) => ErrorCategory::Data,
463            Error::Configuration(_) => ErrorCategory::Configuration,
464            Error::Storage(_) => ErrorCategory::Storage,
465            Error::Memory(_) => ErrorCategory::System,
466            Error::Concurrency(_) => ErrorCategory::Concurrency,
467            Error::NotFound(_) => ErrorCategory::NotFound,
468            Error::AlreadyExists(_) => ErrorCategory::Conflict,
469            Error::InvalidOperation(_) => ErrorCategory::Logic,
470            Error::ConstraintViolation(_) => ErrorCategory::Constraint,
471            Error::Transaction(_) => ErrorCategory::Transaction,
472            Error::Index(_) => ErrorCategory::Storage,
473            Error::Compaction(_) => ErrorCategory::Storage,
474
475            // New error types
476            Error::Table(_) => ErrorCategory::Schema,
477
478            // Write-dir lock conflict
479            Error::WriteDirLocked { .. } => ErrorCategory::Concurrency,
480
481            #[cfg(target_arch = "wasm32")]
482            Error::Wasm(_) => ErrorCategory::Platform,
483
484            Error::Internal(_) => ErrorCategory::Internal,
485            Error::Parse(_) => ErrorCategory::Data,
486            Error::InvalidInput(_) => ErrorCategory::Data,
487            Error::InvalidFormat(_) => ErrorCategory::Data,
488            Error::UnsupportedFormat(_) => ErrorCategory::Data,
489            Error::UnsupportedVersion { .. } => ErrorCategory::Data,
490            Error::InvalidPath(_) => ErrorCategory::System,
491            Error::InvalidState(_) => ErrorCategory::Logic,
492            Error::Timeout(_) => ErrorCategory::System,
493            Error::UnsupportedQuery(_) => ErrorCategory::Query,
494            Error::Cancelled => ErrorCategory::Cancelled,
495        }
496    }
497}
498
499/// Error categories for grouping related errors
500#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501pub enum ErrorCategory {
502    /// System-level errors (I/O, memory, etc.)
503    System,
504    /// Data-related errors (corruption, serialization)
505    Data,
506    /// Schema-related errors
507    Schema,
508    /// Query-related errors (parsing, execution)
509    Query,
510    /// Configuration errors
511    Configuration,
512    /// Storage engine errors
513    Storage,
514    /// Concurrency-related errors
515    Concurrency,
516    /// Resource not found
517    NotFound,
518    /// Resource conflicts
519    Conflict,
520    /// Logic errors
521    Logic,
522    /// Constraint violations
523    Constraint,
524    /// Transaction errors
525    Transaction,
526    /// Platform-specific errors
527    Platform,
528    /// Internal errors
529    Internal,
530    /// A cooperative cancellation / abort (issue #2264). Distinct from
531    /// `System` (I/O, memory) so a cancelled operation is never mislabeled as
532    /// a transport/IO failure by a consumer that switches on category.
533    Cancelled,
534}
535
536impl fmt::Display for ErrorCategory {
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        let name = match self {
539            ErrorCategory::System => "System",
540            ErrorCategory::Data => "Data",
541            ErrorCategory::Schema => "Schema",
542            ErrorCategory::Query => "Query",
543            ErrorCategory::Configuration => "Configuration",
544            ErrorCategory::Storage => "Storage",
545            ErrorCategory::Concurrency => "Concurrency",
546            ErrorCategory::NotFound => "NotFound",
547            ErrorCategory::Conflict => "Conflict",
548            ErrorCategory::Logic => "Logic",
549            ErrorCategory::Constraint => "Constraint",
550            ErrorCategory::Transaction => "Transaction",
551            ErrorCategory::Platform => "Platform",
552            ErrorCategory::Internal => "Internal",
553            ErrorCategory::Cancelled => "Cancelled",
554        };
555        write!(f, "{}", name)
556    }
557}
558
559/// Convert from bincode errors
560impl From<bincode::Error> for Error {
561    fn from(err: bincode::Error) -> Self {
562        Error::Serialization {
563            message: err.to_string(),
564            source: Some(Box::new(err)),
565        }
566    }
567}
568
569/// Convert from serde_json errors
570impl From<serde_json::Error> for Error {
571    fn from(err: serde_json::Error) -> Self {
572        Error::Serialization {
573            message: err.to_string(),
574            source: Some(Box::new(err)),
575        }
576    }
577}
578
579/// Convert from nom errors
580impl<I> From<nom::Err<nom::error::Error<I>>> for Error
581where
582    I: std::fmt::Debug,
583{
584    fn from(err: nom::Err<nom::error::Error<I>>) -> Self {
585        Error::CqlParse(format!("Parse error: {:?}", err))
586    }
587}
588
589// Helper function to create custom parse error type
590pub type ParseResult<I, O> = nom::IResult<I, O, Error>;
591
592/// Custom error type for parsing operations
593#[derive(Debug, Clone)]
594pub struct ParseError {
595    pub message: String,
596}
597
598impl std::fmt::Display for ParseError {
599    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600        write!(f, "{}", self.message)
601    }
602}
603
604impl std::error::Error for ParseError {}
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609
610    #[test]
611    fn test_error_from_conversions() {
612        // Test bincode error conversion (covers line 399-400)
613        let io_err = std::io::Error::other("test error");
614        let bincode_err = bincode::Error::new(bincode::ErrorKind::Io(io_err));
615        let error = Error::from(bincode_err);
616        assert!(matches!(error, Error::Serialization { .. }));
617
618        // Test serde_json error conversion (covers line 406-407)
619        let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
620        let error = Error::from(json_err);
621        assert!(matches!(error, Error::Serialization { .. }));
622
623        // Test nom error conversion (covers line 416-417)
624        let nom_err = nom::Err::Error(nom::error::Error::new(
625            "test input",
626            nom::error::ErrorKind::Tag,
627        ));
628        let error = Error::from(nom_err);
629        assert!(matches!(error, Error::CqlParse(_)));
630    }
631
632    #[test]
633    fn test_parse_error_display() {
634        // Test ParseError Display implementation (covers line 431-432)
635        let parse_error = ParseError {
636            message: "test parse error".to_string(),
637        };
638        let display_str = format!("{}", parse_error);
639        assert_eq!(display_str, "test parse error");
640    }
641
642    #[test]
643    #[cfg(target_arch = "wasm32")]
644    fn test_wasm_error_creation() {
645        // Test WASM error creation (covers line 234-235)
646        let err = Error::wasm("WASM error");
647        assert!(matches!(err, Error::Wasm(_)));
648        assert!(!err.is_recoverable());
649        assert_eq!(err.category(), ErrorCategory::Platform);
650    }
651
652    #[test]
653    fn test_new_error_types_coverage() {
654        // Test Table error
655        let table_err = Error::Table("table error".to_string());
656        assert!(!table_err.is_recoverable());
657        assert_eq!(table_err.category(), ErrorCategory::Schema);
658    }
659
660    #[test]
661    fn test_error_creation() {
662        let err = Error::storage("test error");
663        assert!(matches!(err, Error::Storage(_)));
664        assert_eq!(err.to_string(), "Storage error: test error");
665    }
666
667    #[test]
668    fn test_error_categories() {
669        assert_eq!(Error::storage("test").category(), ErrorCategory::Storage);
670        assert_eq!(Error::schema("test").category(), ErrorCategory::Schema);
671        assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
672    }
673
674    #[test]
675    fn test_error_recoverability() {
676        assert!(Error::concurrency("test").is_recoverable());
677        assert!(!Error::corruption("test").is_recoverable());
678        assert!(!Error::schema("test").is_recoverable());
679    }
680
681    #[test]
682    fn test_all_error_constructors() {
683        // Test all error constructor methods for coverage
684        let _ = Error::serialization("test");
685        let _ = Error::corruption("test");
686        let _ = Error::schema("test");
687        let _ = Error::cql_parse("test");
688        let _ = Error::invalid_format("test");
689        let _ = Error::unsupported_format("test");
690        let _ = Error::invalid_path("test");
691        let _ = Error::invalid_state("test");
692        let _ = Error::query_execution("test");
693        let _ = Error::type_conversion("test");
694        let _ = Error::configuration("test");
695        let _ = Error::storage("test");
696        let _ = Error::memory("test");
697        let _ = Error::concurrency("test");
698        let _ = Error::not_found("test");
699        let _ = Error::already_exists("test");
700        let _ = Error::invalid_operation("test");
701        let _ = Error::constraint_violation("test");
702        let _ = Error::transaction("test");
703        let _ = Error::index("test");
704        let _ = Error::compaction("test");
705        let _ = Error::internal("test");
706        let _ = Error::invalid_input("test");
707        let _ = Error::parse("test");
708        let _ = Error::write_dir_locked("/tmp/test-dir");
709    }
710
711    #[test]
712    fn test_all_error_categories() {
713        // Test all error categories for coverage
714        assert_eq!(Error::serialization("test").category(), ErrorCategory::Data);
715        assert_eq!(Error::corruption("test").category(), ErrorCategory::Data);
716        assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
717        assert_eq!(
718            Error::invalid_format("test").category(),
719            ErrorCategory::Data
720        );
721        assert_eq!(
722            Error::unsupported_format("test").category(),
723            ErrorCategory::Data
724        );
725        assert_eq!(
726            Error::invalid_path("test").category(),
727            ErrorCategory::System
728        );
729        assert_eq!(
730            Error::invalid_state("test").category(),
731            ErrorCategory::Logic
732        );
733        assert_eq!(
734            Error::query_execution("test").category(),
735            ErrorCategory::Query
736        );
737        assert_eq!(
738            Error::type_conversion("test").category(),
739            ErrorCategory::Data
740        );
741        assert_eq!(
742            Error::configuration("test").category(),
743            ErrorCategory::Configuration
744        );
745        assert_eq!(Error::memory("test").category(), ErrorCategory::System);
746        assert_eq!(
747            Error::concurrency("test").category(),
748            ErrorCategory::Concurrency
749        );
750        assert_eq!(Error::not_found("test").category(), ErrorCategory::NotFound);
751        assert_eq!(
752            Error::already_exists("test").category(),
753            ErrorCategory::Conflict
754        );
755        assert_eq!(
756            Error::invalid_operation("test").category(),
757            ErrorCategory::Logic
758        );
759        assert_eq!(
760            Error::constraint_violation("test").category(),
761            ErrorCategory::Constraint
762        );
763        assert_eq!(
764            Error::transaction("test").category(),
765            ErrorCategory::Transaction
766        );
767        assert_eq!(Error::index("test").category(), ErrorCategory::Storage);
768        assert_eq!(Error::compaction("test").category(), ErrorCategory::Storage);
769        assert_eq!(Error::internal("test").category(), ErrorCategory::Internal);
770        assert_eq!(Error::invalid_input("test").category(), ErrorCategory::Data);
771        assert_eq!(Error::parse("test").category(), ErrorCategory::Data);
772        assert_eq!(
773            Error::write_dir_locked("/tmp/test").category(),
774            ErrorCategory::Concurrency
775        );
776        // Issue #2264: a cooperative cancellation must NEVER classify as
777        // `System` (which downstream bindings map to an I/O error code).
778        assert_eq!(Error::Cancelled.category(), ErrorCategory::Cancelled);
779    }
780
781    #[test]
782    fn test_all_error_recoverability() {
783        // Test recoverability for all error types
784        assert!(Error::memory("test").is_recoverable());
785        assert!(Error::storage("test").is_recoverable());
786        assert!(Error::transaction("test").is_recoverable());
787        assert!(Error::index("test").is_recoverable());
788        assert!(Error::compaction("test").is_recoverable());
789
790        assert!(!Error::serialization("test").is_recoverable());
791        assert!(!Error::cql_parse("test").is_recoverable());
792        assert!(!Error::invalid_format("test").is_recoverable());
793        assert!(!Error::unsupported_format("test").is_recoverable());
794        assert!(!Error::invalid_path("test").is_recoverable());
795        assert!(!Error::invalid_state("test").is_recoverable());
796        assert!(!Error::query_execution("test").is_recoverable());
797        assert!(!Error::type_conversion("test").is_recoverable());
798        assert!(!Error::configuration("test").is_recoverable());
799        assert!(!Error::not_found("test").is_recoverable());
800        assert!(!Error::already_exists("test").is_recoverable());
801        assert!(!Error::invalid_operation("test").is_recoverable());
802        assert!(!Error::constraint_violation("test").is_recoverable());
803        assert!(!Error::internal("test").is_recoverable());
804        assert!(!Error::invalid_input("test").is_recoverable());
805        assert!(!Error::parse("test").is_recoverable());
806        assert!(!Error::write_dir_locked("/tmp/test").is_recoverable());
807    }
808
809    #[test]
810    fn test_error_category_display() {
811        // Test ErrorCategory Display implementation
812        assert_eq!(ErrorCategory::System.to_string(), "System");
813        assert_eq!(ErrorCategory::Data.to_string(), "Data");
814        assert_eq!(ErrorCategory::Schema.to_string(), "Schema");
815        assert_eq!(ErrorCategory::Query.to_string(), "Query");
816        assert_eq!(ErrorCategory::Configuration.to_string(), "Configuration");
817        assert_eq!(ErrorCategory::Storage.to_string(), "Storage");
818        assert_eq!(ErrorCategory::Concurrency.to_string(), "Concurrency");
819        assert_eq!(ErrorCategory::NotFound.to_string(), "NotFound");
820        assert_eq!(ErrorCategory::Conflict.to_string(), "Conflict");
821        assert_eq!(ErrorCategory::Logic.to_string(), "Logic");
822        assert_eq!(ErrorCategory::Constraint.to_string(), "Constraint");
823        assert_eq!(ErrorCategory::Transaction.to_string(), "Transaction");
824        assert_eq!(ErrorCategory::Platform.to_string(), "Platform");
825        assert_eq!(ErrorCategory::Internal.to_string(), "Internal");
826        assert_eq!(ErrorCategory::Cancelled.to_string(), "Cancelled");
827    }
828
829    #[test]
830    fn test_error_from_io_error() {
831        // Test conversion from std::io::Error
832        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
833        let cqlite_err: Error = io_err.into();
834        assert!(matches!(cqlite_err, Error::Io(_)));
835        assert_eq!(cqlite_err.category(), ErrorCategory::System);
836        assert!(cqlite_err.is_recoverable());
837    }
838
839    #[test]
840    fn test_result_type_alias() {
841        // Test the Result type alias
842        let success: Result<i32> = Ok(42);
843        let failure: Result<i32> = Err(Error::storage("test error"));
844
845        assert!(success.is_ok());
846        if let Ok(value) = success {
847            assert_eq!(value, 42);
848        }
849        assert!(failure.is_err());
850    }
851}