1use std::fmt;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Error, Debug)]
11pub enum Error {
12 #[error("I/O error: {0}")]
14 Io(#[from] std::io::Error),
15
16 #[error("Serialization error: {message}")]
18 Serialization {
19 message: String,
20 #[source]
21 source: Option<Box<dyn std::error::Error + Send + Sync>>,
22 },
23
24 #[error("Data corruption: {0}")]
26 Corruption(String),
27
28 #[error("Schema error: {0}")]
30 Schema(String),
31
32 #[error("CQL parse error: {0}")]
34 CqlParse(String),
35
36 #[error("Invalid format: {0}")]
38 InvalidFormat(String),
39
40 #[error("Unsupported format: {0}")]
42 UnsupportedFormat(String),
43
44 #[error("Unsupported SSTable version {version:?}: below supported floor {floor:?}")]
50 UnsupportedVersion { version: String, floor: String },
51
52 #[error("Operation timeout: {0}")]
54 Timeout(String),
55
56 #[error("Invalid path: {0}")]
58 InvalidPath(String),
59
60 #[error("Invalid state: {0}")]
62 InvalidState(String),
63
64 #[error("Query execution error: {0}")]
66 QueryExecution(String),
67
68 #[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 budget_bytes: usize,
86 estimated_bytes: usize,
88 rows: usize,
90 },
91
92 #[error("Type conversion error: {0}")]
94 TypeConversion(String),
95
96 #[error("Configuration error: {0}")]
98 Configuration(String),
99
100 #[error("Storage error: {0}")]
102 Storage(String),
103
104 #[error("Memory error: {0}")]
106 Memory(String),
107
108 #[error("Concurrency error: {0}")]
110 Concurrency(String),
111
112 #[error(
119 "write_dir '{path}' is already locked by another process. \
120 Only one Database instance may hold a write_dir at a time."
121 )]
122 WriteDirLocked {
123 path: String,
125 },
126
127 #[error("Not found: {0}")]
129 NotFound(String),
130
131 #[error("Table error: {0}")]
133 Table(String),
134
135 #[error("Already exists: {0}")]
137 AlreadyExists(String),
138
139 #[error("Invalid operation: {0}")]
141 InvalidOperation(String),
142
143 #[error("Constraint violation: {0}")]
145 ConstraintViolation(String),
146
147 #[error("Transaction error: {0}")]
149 Transaction(String),
150
151 #[error("Index error: {0}")]
153 Index(String),
154
155 #[error("Compaction error: {0}")]
157 Compaction(String),
158
159 #[cfg(target_arch = "wasm32")]
161 #[error("WASM error: {0}")]
162 Wasm(String),
163
164 #[error("Internal error: {0}")]
166 Internal(String),
167
168 #[error("Parse error: {0}")]
170 Parse(String),
171
172 #[error("Invalid input: {0}")]
174 InvalidInput(String),
175
176 #[error("Unsupported query: {0}")]
178 UnsupportedQuery(String),
179}
180
181impl Error {
182 pub fn serialization(msg: impl Into<String>) -> Self {
184 Self::Serialization {
185 message: msg.into(),
186 source: None,
187 }
188 }
189
190 pub fn corruption(msg: impl Into<String>) -> Self {
192 Self::Corruption(msg.into())
193 }
194
195 pub fn schema(msg: impl Into<String>) -> Self {
197 Self::Schema(msg.into())
198 }
199
200 pub fn cql_parse(msg: impl Into<String>) -> Self {
202 Self::CqlParse(msg.into())
203 }
204
205 pub fn invalid_format(msg: impl Into<String>) -> Self {
207 Self::InvalidFormat(msg.into())
208 }
209
210 pub fn unsupported_format(msg: impl Into<String>) -> Self {
212 Self::UnsupportedFormat(msg.into())
213 }
214
215 pub fn invalid_path(msg: impl Into<String>) -> Self {
217 Self::InvalidPath(msg.into())
218 }
219
220 pub fn invalid_state(msg: impl Into<String>) -> Self {
222 Self::InvalidState(msg.into())
223 }
224
225 pub fn query_execution(msg: impl Into<String>) -> Self {
227 Self::QueryExecution(msg.into())
228 }
229
230 pub fn type_conversion(msg: impl Into<String>) -> Self {
232 Self::TypeConversion(msg.into())
233 }
234
235 pub fn configuration(msg: impl Into<String>) -> Self {
237 Self::Configuration(msg.into())
238 }
239
240 pub fn storage(msg: impl Into<String>) -> Self {
242 Self::Storage(msg.into())
243 }
244
245 pub fn memory(msg: impl Into<String>) -> Self {
247 Self::Memory(msg.into())
248 }
249
250 pub fn concurrency(msg: impl Into<String>) -> Self {
252 Self::Concurrency(msg.into())
253 }
254
255 pub fn not_found(msg: impl Into<String>) -> Self {
257 Self::NotFound(msg.into())
258 }
259
260 pub fn already_exists(msg: impl Into<String>) -> Self {
262 Self::AlreadyExists(msg.into())
263 }
264
265 pub fn invalid_operation(msg: impl Into<String>) -> Self {
267 Self::InvalidOperation(msg.into())
268 }
269
270 pub fn constraint_violation(msg: impl Into<String>) -> Self {
272 Self::ConstraintViolation(msg.into())
273 }
274
275 pub fn transaction(msg: impl Into<String>) -> Self {
277 Self::Transaction(msg.into())
278 }
279
280 pub fn index(msg: impl Into<String>) -> Self {
282 Self::Index(msg.into())
283 }
284
285 pub fn compaction(msg: impl Into<String>) -> Self {
287 Self::Compaction(msg.into())
288 }
289
290 #[cfg(target_arch = "wasm32")]
292 pub fn wasm(msg: impl Into<String>) -> Self {
293 Self::Wasm(msg.into())
294 }
295
296 pub fn internal(msg: impl Into<String>) -> Self {
298 Self::Internal(msg.into())
299 }
300
301 pub fn invalid_input(msg: impl Into<String>) -> Self {
303 Self::InvalidInput(msg.into())
304 }
305
306 pub fn parse(msg: impl Into<String>) -> Self {
308 Self::Parse(msg.into())
309 }
310
311 pub fn unsupported_query(msg: impl Into<String>) -> Self {
313 Self::UnsupportedQuery(msg.into())
314 }
315
316 pub fn write_dir_locked(path: impl Into<String>) -> Self {
318 Self::WriteDirLocked { path: path.into() }
319 }
320
321 pub fn table_not_found(msg: impl Into<String>) -> Self {
323 Self::NotFound(format!("Table not found: {}", msg.into()))
324 }
325
326 pub fn ambiguous_table(msg: impl Into<String>) -> Self {
328 Self::Table(format!("Ambiguous table reference: {}", msg.into()))
329 }
330
331 pub fn is_recoverable(&self) -> bool {
333 match self {
334 Error::Io(_) => true,
336 Error::Concurrency(_) => true,
337 Error::Memory(_) => true,
338
339 Error::Corruption(_) => false,
341 Error::Schema(_) => false,
342 Error::CqlParse(_) => false,
343 Error::Configuration(_) => false,
344
345 Error::Storage(_) => true,
347 Error::QueryExecution(_) => false,
348 Error::ResultTooLarge { .. } => false,
351 Error::TypeConversion(_) => false,
352 Error::NotFound(_) => false,
353 Error::AlreadyExists(_) => false,
354 Error::InvalidOperation(_) => false,
355 Error::ConstraintViolation(_) => false,
356 Error::Transaction(_) => true,
357 Error::Index(_) => true,
358 Error::Compaction(_) => true,
359
360 Error::Table(_) => false,
362
363 Error::WriteDirLocked { .. } => false,
365
366 #[cfg(target_arch = "wasm32")]
367 Error::Wasm(_) => false,
368
369 Error::Serialization { .. } => false,
370 Error::Internal(_) => false,
371 Error::Parse(_) => false,
372 Error::InvalidInput(_) => false,
373 Error::InvalidFormat(_) => false,
374 Error::UnsupportedFormat(_) => false,
375 Error::UnsupportedVersion { .. } => false,
376 Error::InvalidPath(_) => false,
377 Error::InvalidState(_) => false,
378 Error::Timeout(_) => false,
379 Error::UnsupportedQuery(_) => false,
380 }
381 }
382
383 pub fn category(&self) -> ErrorCategory {
385 match self {
386 Error::Io(_) => ErrorCategory::System,
387 Error::Serialization { .. } => ErrorCategory::Data,
388 Error::Corruption(_) => ErrorCategory::Data,
389 Error::Schema(_) => ErrorCategory::Schema,
390 Error::CqlParse(_) => ErrorCategory::Query,
391 Error::QueryExecution(_) => ErrorCategory::Query,
392 Error::ResultTooLarge { .. } => ErrorCategory::Query,
393 Error::TypeConversion(_) => ErrorCategory::Data,
394 Error::Configuration(_) => ErrorCategory::Configuration,
395 Error::Storage(_) => ErrorCategory::Storage,
396 Error::Memory(_) => ErrorCategory::System,
397 Error::Concurrency(_) => ErrorCategory::Concurrency,
398 Error::NotFound(_) => ErrorCategory::NotFound,
399 Error::AlreadyExists(_) => ErrorCategory::Conflict,
400 Error::InvalidOperation(_) => ErrorCategory::Logic,
401 Error::ConstraintViolation(_) => ErrorCategory::Constraint,
402 Error::Transaction(_) => ErrorCategory::Transaction,
403 Error::Index(_) => ErrorCategory::Storage,
404 Error::Compaction(_) => ErrorCategory::Storage,
405
406 Error::Table(_) => ErrorCategory::Schema,
408
409 Error::WriteDirLocked { .. } => ErrorCategory::Concurrency,
411
412 #[cfg(target_arch = "wasm32")]
413 Error::Wasm(_) => ErrorCategory::Platform,
414
415 Error::Internal(_) => ErrorCategory::Internal,
416 Error::Parse(_) => ErrorCategory::Data,
417 Error::InvalidInput(_) => ErrorCategory::Data,
418 Error::InvalidFormat(_) => ErrorCategory::Data,
419 Error::UnsupportedFormat(_) => ErrorCategory::Data,
420 Error::UnsupportedVersion { .. } => ErrorCategory::Data,
421 Error::InvalidPath(_) => ErrorCategory::System,
422 Error::InvalidState(_) => ErrorCategory::Logic,
423 Error::Timeout(_) => ErrorCategory::System,
424 Error::UnsupportedQuery(_) => ErrorCategory::Query,
425 }
426 }
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
431pub enum ErrorCategory {
432 System,
434 Data,
436 Schema,
438 Query,
440 Configuration,
442 Storage,
444 Concurrency,
446 NotFound,
448 Conflict,
450 Logic,
452 Constraint,
454 Transaction,
456 Platform,
458 Internal,
460}
461
462impl fmt::Display for ErrorCategory {
463 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464 let name = match self {
465 ErrorCategory::System => "System",
466 ErrorCategory::Data => "Data",
467 ErrorCategory::Schema => "Schema",
468 ErrorCategory::Query => "Query",
469 ErrorCategory::Configuration => "Configuration",
470 ErrorCategory::Storage => "Storage",
471 ErrorCategory::Concurrency => "Concurrency",
472 ErrorCategory::NotFound => "NotFound",
473 ErrorCategory::Conflict => "Conflict",
474 ErrorCategory::Logic => "Logic",
475 ErrorCategory::Constraint => "Constraint",
476 ErrorCategory::Transaction => "Transaction",
477 ErrorCategory::Platform => "Platform",
478 ErrorCategory::Internal => "Internal",
479 };
480 write!(f, "{}", name)
481 }
482}
483
484impl From<bincode::Error> for Error {
486 fn from(err: bincode::Error) -> Self {
487 Error::Serialization {
488 message: err.to_string(),
489 source: Some(Box::new(err)),
490 }
491 }
492}
493
494impl From<serde_json::Error> for Error {
496 fn from(err: serde_json::Error) -> Self {
497 Error::Serialization {
498 message: err.to_string(),
499 source: Some(Box::new(err)),
500 }
501 }
502}
503
504impl<I> From<nom::Err<nom::error::Error<I>>> for Error
506where
507 I: std::fmt::Debug,
508{
509 fn from(err: nom::Err<nom::error::Error<I>>) -> Self {
510 Error::CqlParse(format!("Parse error: {:?}", err))
511 }
512}
513
514pub type ParseResult<I, O> = nom::IResult<I, O, Error>;
516
517#[derive(Debug, Clone)]
519pub struct ParseError {
520 pub message: String,
521}
522
523impl std::fmt::Display for ParseError {
524 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
525 write!(f, "{}", self.message)
526 }
527}
528
529impl std::error::Error for ParseError {}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534
535 #[test]
536 fn test_error_from_conversions() {
537 let io_err = std::io::Error::other("test error");
539 let bincode_err = bincode::Error::new(bincode::ErrorKind::Io(io_err));
540 let error = Error::from(bincode_err);
541 assert!(matches!(error, Error::Serialization { .. }));
542
543 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
545 let error = Error::from(json_err);
546 assert!(matches!(error, Error::Serialization { .. }));
547
548 let nom_err = nom::Err::Error(nom::error::Error::new(
550 "test input",
551 nom::error::ErrorKind::Tag,
552 ));
553 let error = Error::from(nom_err);
554 assert!(matches!(error, Error::CqlParse(_)));
555 }
556
557 #[test]
558 fn test_parse_error_display() {
559 let parse_error = ParseError {
561 message: "test parse error".to_string(),
562 };
563 let display_str = format!("{}", parse_error);
564 assert_eq!(display_str, "test parse error");
565 }
566
567 #[test]
568 #[cfg(target_arch = "wasm32")]
569 fn test_wasm_error_creation() {
570 let err = Error::wasm("WASM error");
572 assert!(matches!(err, Error::Wasm(_)));
573 assert!(!err.is_recoverable());
574 assert_eq!(err.category(), ErrorCategory::Platform);
575 }
576
577 #[test]
578 fn test_new_error_types_coverage() {
579 let table_err = Error::Table("table error".to_string());
581 assert!(!table_err.is_recoverable());
582 assert_eq!(table_err.category(), ErrorCategory::Schema);
583 }
584
585 #[test]
586 fn test_error_creation() {
587 let err = Error::storage("test error");
588 assert!(matches!(err, Error::Storage(_)));
589 assert_eq!(err.to_string(), "Storage error: test error");
590 }
591
592 #[test]
593 fn test_error_categories() {
594 assert_eq!(Error::storage("test").category(), ErrorCategory::Storage);
595 assert_eq!(Error::schema("test").category(), ErrorCategory::Schema);
596 assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
597 }
598
599 #[test]
600 fn test_error_recoverability() {
601 assert!(Error::concurrency("test").is_recoverable());
602 assert!(!Error::corruption("test").is_recoverable());
603 assert!(!Error::schema("test").is_recoverable());
604 }
605
606 #[test]
607 fn test_all_error_constructors() {
608 let _ = Error::serialization("test");
610 let _ = Error::corruption("test");
611 let _ = Error::schema("test");
612 let _ = Error::cql_parse("test");
613 let _ = Error::invalid_format("test");
614 let _ = Error::unsupported_format("test");
615 let _ = Error::invalid_path("test");
616 let _ = Error::invalid_state("test");
617 let _ = Error::query_execution("test");
618 let _ = Error::type_conversion("test");
619 let _ = Error::configuration("test");
620 let _ = Error::storage("test");
621 let _ = Error::memory("test");
622 let _ = Error::concurrency("test");
623 let _ = Error::not_found("test");
624 let _ = Error::already_exists("test");
625 let _ = Error::invalid_operation("test");
626 let _ = Error::constraint_violation("test");
627 let _ = Error::transaction("test");
628 let _ = Error::index("test");
629 let _ = Error::compaction("test");
630 let _ = Error::internal("test");
631 let _ = Error::invalid_input("test");
632 let _ = Error::parse("test");
633 let _ = Error::write_dir_locked("/tmp/test-dir");
634 }
635
636 #[test]
637 fn test_all_error_categories() {
638 assert_eq!(Error::serialization("test").category(), ErrorCategory::Data);
640 assert_eq!(Error::corruption("test").category(), ErrorCategory::Data);
641 assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
642 assert_eq!(
643 Error::invalid_format("test").category(),
644 ErrorCategory::Data
645 );
646 assert_eq!(
647 Error::unsupported_format("test").category(),
648 ErrorCategory::Data
649 );
650 assert_eq!(
651 Error::invalid_path("test").category(),
652 ErrorCategory::System
653 );
654 assert_eq!(
655 Error::invalid_state("test").category(),
656 ErrorCategory::Logic
657 );
658 assert_eq!(
659 Error::query_execution("test").category(),
660 ErrorCategory::Query
661 );
662 assert_eq!(
663 Error::type_conversion("test").category(),
664 ErrorCategory::Data
665 );
666 assert_eq!(
667 Error::configuration("test").category(),
668 ErrorCategory::Configuration
669 );
670 assert_eq!(Error::memory("test").category(), ErrorCategory::System);
671 assert_eq!(
672 Error::concurrency("test").category(),
673 ErrorCategory::Concurrency
674 );
675 assert_eq!(Error::not_found("test").category(), ErrorCategory::NotFound);
676 assert_eq!(
677 Error::already_exists("test").category(),
678 ErrorCategory::Conflict
679 );
680 assert_eq!(
681 Error::invalid_operation("test").category(),
682 ErrorCategory::Logic
683 );
684 assert_eq!(
685 Error::constraint_violation("test").category(),
686 ErrorCategory::Constraint
687 );
688 assert_eq!(
689 Error::transaction("test").category(),
690 ErrorCategory::Transaction
691 );
692 assert_eq!(Error::index("test").category(), ErrorCategory::Storage);
693 assert_eq!(Error::compaction("test").category(), ErrorCategory::Storage);
694 assert_eq!(Error::internal("test").category(), ErrorCategory::Internal);
695 assert_eq!(Error::invalid_input("test").category(), ErrorCategory::Data);
696 assert_eq!(Error::parse("test").category(), ErrorCategory::Data);
697 assert_eq!(
698 Error::write_dir_locked("/tmp/test").category(),
699 ErrorCategory::Concurrency
700 );
701 }
702
703 #[test]
704 fn test_all_error_recoverability() {
705 assert!(Error::memory("test").is_recoverable());
707 assert!(Error::storage("test").is_recoverable());
708 assert!(Error::transaction("test").is_recoverable());
709 assert!(Error::index("test").is_recoverable());
710 assert!(Error::compaction("test").is_recoverable());
711
712 assert!(!Error::serialization("test").is_recoverable());
713 assert!(!Error::cql_parse("test").is_recoverable());
714 assert!(!Error::invalid_format("test").is_recoverable());
715 assert!(!Error::unsupported_format("test").is_recoverable());
716 assert!(!Error::invalid_path("test").is_recoverable());
717 assert!(!Error::invalid_state("test").is_recoverable());
718 assert!(!Error::query_execution("test").is_recoverable());
719 assert!(!Error::type_conversion("test").is_recoverable());
720 assert!(!Error::configuration("test").is_recoverable());
721 assert!(!Error::not_found("test").is_recoverable());
722 assert!(!Error::already_exists("test").is_recoverable());
723 assert!(!Error::invalid_operation("test").is_recoverable());
724 assert!(!Error::constraint_violation("test").is_recoverable());
725 assert!(!Error::internal("test").is_recoverable());
726 assert!(!Error::invalid_input("test").is_recoverable());
727 assert!(!Error::parse("test").is_recoverable());
728 assert!(!Error::write_dir_locked("/tmp/test").is_recoverable());
729 }
730
731 #[test]
732 fn test_error_category_display() {
733 assert_eq!(ErrorCategory::System.to_string(), "System");
735 assert_eq!(ErrorCategory::Data.to_string(), "Data");
736 assert_eq!(ErrorCategory::Schema.to_string(), "Schema");
737 assert_eq!(ErrorCategory::Query.to_string(), "Query");
738 assert_eq!(ErrorCategory::Configuration.to_string(), "Configuration");
739 assert_eq!(ErrorCategory::Storage.to_string(), "Storage");
740 assert_eq!(ErrorCategory::Concurrency.to_string(), "Concurrency");
741 assert_eq!(ErrorCategory::NotFound.to_string(), "NotFound");
742 assert_eq!(ErrorCategory::Conflict.to_string(), "Conflict");
743 assert_eq!(ErrorCategory::Logic.to_string(), "Logic");
744 assert_eq!(ErrorCategory::Constraint.to_string(), "Constraint");
745 assert_eq!(ErrorCategory::Transaction.to_string(), "Transaction");
746 assert_eq!(ErrorCategory::Platform.to_string(), "Platform");
747 assert_eq!(ErrorCategory::Internal.to_string(), "Internal");
748 }
749
750 #[test]
751 fn test_error_from_io_error() {
752 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
754 let cqlite_err: Error = io_err.into();
755 assert!(matches!(cqlite_err, Error::Io(_)));
756 assert_eq!(cqlite_err.category(), ErrorCategory::System);
757 assert!(cqlite_err.is_recoverable());
758 }
759
760 #[test]
761 fn test_result_type_alias() {
762 let success: Result<i32> = Ok(42);
764 let failure: Result<i32> = Err(Error::storage("test error"));
765
766 assert!(success.is_ok());
767 if let Ok(value) = success {
768 assert_eq!(value, 42);
769 }
770 assert!(failure.is_err());
771 }
772}