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 #[error("Operation cancelled")]
187 Cancelled,
188}
189
190impl Error {
191 pub fn serialization(msg: impl Into<String>) -> Self {
193 Self::Serialization {
194 message: msg.into(),
195 source: None,
196 }
197 }
198
199 pub fn corruption(msg: impl Into<String>) -> Self {
201 Self::Corruption(msg.into())
202 }
203
204 pub fn schema(msg: impl Into<String>) -> Self {
206 Self::Schema(msg.into())
207 }
208
209 pub fn cql_parse(msg: impl Into<String>) -> Self {
211 Self::CqlParse(msg.into())
212 }
213
214 pub fn invalid_format(msg: impl Into<String>) -> Self {
216 Self::InvalidFormat(msg.into())
217 }
218
219 pub fn unsupported_format(msg: impl Into<String>) -> Self {
221 Self::UnsupportedFormat(msg.into())
222 }
223
224 pub fn invalid_path(msg: impl Into<String>) -> Self {
226 Self::InvalidPath(msg.into())
227 }
228
229 pub fn invalid_state(msg: impl Into<String>) -> Self {
231 Self::InvalidState(msg.into())
232 }
233
234 pub fn query_execution(msg: impl Into<String>) -> Self {
236 Self::QueryExecution(msg.into())
237 }
238
239 pub fn type_conversion(msg: impl Into<String>) -> Self {
241 Self::TypeConversion(msg.into())
242 }
243
244 pub fn configuration(msg: impl Into<String>) -> Self {
246 Self::Configuration(msg.into())
247 }
248
249 pub fn storage(msg: impl Into<String>) -> Self {
251 Self::Storage(msg.into())
252 }
253
254 pub fn memory(msg: impl Into<String>) -> Self {
256 Self::Memory(msg.into())
257 }
258
259 pub fn concurrency(msg: impl Into<String>) -> Self {
261 Self::Concurrency(msg.into())
262 }
263
264 pub fn not_found(msg: impl Into<String>) -> Self {
266 Self::NotFound(msg.into())
267 }
268
269 pub fn already_exists(msg: impl Into<String>) -> Self {
271 Self::AlreadyExists(msg.into())
272 }
273
274 pub fn invalid_operation(msg: impl Into<String>) -> Self {
276 Self::InvalidOperation(msg.into())
277 }
278
279 pub fn constraint_violation(msg: impl Into<String>) -> Self {
281 Self::ConstraintViolation(msg.into())
282 }
283
284 pub fn transaction(msg: impl Into<String>) -> Self {
286 Self::Transaction(msg.into())
287 }
288
289 pub fn index(msg: impl Into<String>) -> Self {
291 Self::Index(msg.into())
292 }
293
294 pub fn compaction(msg: impl Into<String>) -> Self {
296 Self::Compaction(msg.into())
297 }
298
299 #[cfg(target_arch = "wasm32")]
301 pub fn wasm(msg: impl Into<String>) -> Self {
302 Self::Wasm(msg.into())
303 }
304
305 pub fn internal(msg: impl Into<String>) -> Self {
307 Self::Internal(msg.into())
308 }
309
310 pub fn invalid_input(msg: impl Into<String>) -> Self {
312 Self::InvalidInput(msg.into())
313 }
314
315 pub fn parse(msg: impl Into<String>) -> Self {
317 Self::Parse(msg.into())
318 }
319
320 pub fn unsupported_query(msg: impl Into<String>) -> Self {
322 Self::UnsupportedQuery(msg.into())
323 }
324
325 pub fn write_dir_locked(path: impl Into<String>) -> Self {
327 Self::WriteDirLocked { path: path.into() }
328 }
329
330 pub fn table_not_found(msg: impl Into<String>) -> Self {
332 Self::NotFound(format!("Table not found: {}", msg.into()))
333 }
334
335 pub fn ambiguous_table(msg: impl Into<String>) -> Self {
337 Self::Table(format!("Ambiguous table reference: {}", msg.into()))
338 }
339
340 pub fn is_recoverable(&self) -> bool {
342 match self {
343 Error::Io(_) => true,
345 Error::Concurrency(_) => true,
346 Error::Memory(_) => true,
347
348 Error::Corruption(_) => false,
350 Error::Schema(_) => false,
351 Error::CqlParse(_) => false,
352 Error::Configuration(_) => false,
353
354 Error::Storage(_) => true,
356 Error::QueryExecution(_) => false,
357 Error::ResultTooLarge { .. } => false,
360 Error::TypeConversion(_) => false,
361 Error::NotFound(_) => false,
362 Error::AlreadyExists(_) => false,
363 Error::InvalidOperation(_) => false,
364 Error::ConstraintViolation(_) => false,
365 Error::Transaction(_) => true,
366 Error::Index(_) => true,
367 Error::Compaction(_) => true,
368
369 Error::Table(_) => false,
371
372 Error::WriteDirLocked { .. } => false,
374
375 #[cfg(target_arch = "wasm32")]
376 Error::Wasm(_) => false,
377
378 Error::Serialization { .. } => false,
379 Error::Internal(_) => false,
380 Error::Parse(_) => false,
381 Error::InvalidInput(_) => false,
382 Error::InvalidFormat(_) => false,
383 Error::UnsupportedFormat(_) => false,
384 Error::UnsupportedVersion { .. } => false,
385 Error::InvalidPath(_) => false,
386 Error::InvalidState(_) => false,
387 Error::Timeout(_) => false,
388 Error::UnsupportedQuery(_) => false,
389 Error::Cancelled => false,
392 }
393 }
394
395 pub fn category(&self) -> ErrorCategory {
397 match self {
398 Error::Io(_) => ErrorCategory::System,
399 Error::Serialization { .. } => ErrorCategory::Data,
400 Error::Corruption(_) => ErrorCategory::Data,
401 Error::Schema(_) => ErrorCategory::Schema,
402 Error::CqlParse(_) => ErrorCategory::Query,
403 Error::QueryExecution(_) => ErrorCategory::Query,
404 Error::ResultTooLarge { .. } => ErrorCategory::Query,
405 Error::TypeConversion(_) => ErrorCategory::Data,
406 Error::Configuration(_) => ErrorCategory::Configuration,
407 Error::Storage(_) => ErrorCategory::Storage,
408 Error::Memory(_) => ErrorCategory::System,
409 Error::Concurrency(_) => ErrorCategory::Concurrency,
410 Error::NotFound(_) => ErrorCategory::NotFound,
411 Error::AlreadyExists(_) => ErrorCategory::Conflict,
412 Error::InvalidOperation(_) => ErrorCategory::Logic,
413 Error::ConstraintViolation(_) => ErrorCategory::Constraint,
414 Error::Transaction(_) => ErrorCategory::Transaction,
415 Error::Index(_) => ErrorCategory::Storage,
416 Error::Compaction(_) => ErrorCategory::Storage,
417
418 Error::Table(_) => ErrorCategory::Schema,
420
421 Error::WriteDirLocked { .. } => ErrorCategory::Concurrency,
423
424 #[cfg(target_arch = "wasm32")]
425 Error::Wasm(_) => ErrorCategory::Platform,
426
427 Error::Internal(_) => ErrorCategory::Internal,
428 Error::Parse(_) => ErrorCategory::Data,
429 Error::InvalidInput(_) => ErrorCategory::Data,
430 Error::InvalidFormat(_) => ErrorCategory::Data,
431 Error::UnsupportedFormat(_) => ErrorCategory::Data,
432 Error::UnsupportedVersion { .. } => ErrorCategory::Data,
433 Error::InvalidPath(_) => ErrorCategory::System,
434 Error::InvalidState(_) => ErrorCategory::Logic,
435 Error::Timeout(_) => ErrorCategory::System,
436 Error::UnsupportedQuery(_) => ErrorCategory::Query,
437 Error::Cancelled => ErrorCategory::Cancelled,
438 }
439 }
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub enum ErrorCategory {
445 System,
447 Data,
449 Schema,
451 Query,
453 Configuration,
455 Storage,
457 Concurrency,
459 NotFound,
461 Conflict,
463 Logic,
465 Constraint,
467 Transaction,
469 Platform,
471 Internal,
473 Cancelled,
477}
478
479impl fmt::Display for ErrorCategory {
480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481 let name = match self {
482 ErrorCategory::System => "System",
483 ErrorCategory::Data => "Data",
484 ErrorCategory::Schema => "Schema",
485 ErrorCategory::Query => "Query",
486 ErrorCategory::Configuration => "Configuration",
487 ErrorCategory::Storage => "Storage",
488 ErrorCategory::Concurrency => "Concurrency",
489 ErrorCategory::NotFound => "NotFound",
490 ErrorCategory::Conflict => "Conflict",
491 ErrorCategory::Logic => "Logic",
492 ErrorCategory::Constraint => "Constraint",
493 ErrorCategory::Transaction => "Transaction",
494 ErrorCategory::Platform => "Platform",
495 ErrorCategory::Internal => "Internal",
496 ErrorCategory::Cancelled => "Cancelled",
497 };
498 write!(f, "{}", name)
499 }
500}
501
502impl From<bincode::Error> for Error {
504 fn from(err: bincode::Error) -> Self {
505 Error::Serialization {
506 message: err.to_string(),
507 source: Some(Box::new(err)),
508 }
509 }
510}
511
512impl From<serde_json::Error> for Error {
514 fn from(err: serde_json::Error) -> Self {
515 Error::Serialization {
516 message: err.to_string(),
517 source: Some(Box::new(err)),
518 }
519 }
520}
521
522impl<I> From<nom::Err<nom::error::Error<I>>> for Error
524where
525 I: std::fmt::Debug,
526{
527 fn from(err: nom::Err<nom::error::Error<I>>) -> Self {
528 Error::CqlParse(format!("Parse error: {:?}", err))
529 }
530}
531
532pub type ParseResult<I, O> = nom::IResult<I, O, Error>;
534
535#[derive(Debug, Clone)]
537pub struct ParseError {
538 pub message: String,
539}
540
541impl std::fmt::Display for ParseError {
542 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
543 write!(f, "{}", self.message)
544 }
545}
546
547impl std::error::Error for ParseError {}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn test_error_from_conversions() {
555 let io_err = std::io::Error::other("test error");
557 let bincode_err = bincode::Error::new(bincode::ErrorKind::Io(io_err));
558 let error = Error::from(bincode_err);
559 assert!(matches!(error, Error::Serialization { .. }));
560
561 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
563 let error = Error::from(json_err);
564 assert!(matches!(error, Error::Serialization { .. }));
565
566 let nom_err = nom::Err::Error(nom::error::Error::new(
568 "test input",
569 nom::error::ErrorKind::Tag,
570 ));
571 let error = Error::from(nom_err);
572 assert!(matches!(error, Error::CqlParse(_)));
573 }
574
575 #[test]
576 fn test_parse_error_display() {
577 let parse_error = ParseError {
579 message: "test parse error".to_string(),
580 };
581 let display_str = format!("{}", parse_error);
582 assert_eq!(display_str, "test parse error");
583 }
584
585 #[test]
586 #[cfg(target_arch = "wasm32")]
587 fn test_wasm_error_creation() {
588 let err = Error::wasm("WASM error");
590 assert!(matches!(err, Error::Wasm(_)));
591 assert!(!err.is_recoverable());
592 assert_eq!(err.category(), ErrorCategory::Platform);
593 }
594
595 #[test]
596 fn test_new_error_types_coverage() {
597 let table_err = Error::Table("table error".to_string());
599 assert!(!table_err.is_recoverable());
600 assert_eq!(table_err.category(), ErrorCategory::Schema);
601 }
602
603 #[test]
604 fn test_error_creation() {
605 let err = Error::storage("test error");
606 assert!(matches!(err, Error::Storage(_)));
607 assert_eq!(err.to_string(), "Storage error: test error");
608 }
609
610 #[test]
611 fn test_error_categories() {
612 assert_eq!(Error::storage("test").category(), ErrorCategory::Storage);
613 assert_eq!(Error::schema("test").category(), ErrorCategory::Schema);
614 assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
615 }
616
617 #[test]
618 fn test_error_recoverability() {
619 assert!(Error::concurrency("test").is_recoverable());
620 assert!(!Error::corruption("test").is_recoverable());
621 assert!(!Error::schema("test").is_recoverable());
622 }
623
624 #[test]
625 fn test_all_error_constructors() {
626 let _ = Error::serialization("test");
628 let _ = Error::corruption("test");
629 let _ = Error::schema("test");
630 let _ = Error::cql_parse("test");
631 let _ = Error::invalid_format("test");
632 let _ = Error::unsupported_format("test");
633 let _ = Error::invalid_path("test");
634 let _ = Error::invalid_state("test");
635 let _ = Error::query_execution("test");
636 let _ = Error::type_conversion("test");
637 let _ = Error::configuration("test");
638 let _ = Error::storage("test");
639 let _ = Error::memory("test");
640 let _ = Error::concurrency("test");
641 let _ = Error::not_found("test");
642 let _ = Error::already_exists("test");
643 let _ = Error::invalid_operation("test");
644 let _ = Error::constraint_violation("test");
645 let _ = Error::transaction("test");
646 let _ = Error::index("test");
647 let _ = Error::compaction("test");
648 let _ = Error::internal("test");
649 let _ = Error::invalid_input("test");
650 let _ = Error::parse("test");
651 let _ = Error::write_dir_locked("/tmp/test-dir");
652 }
653
654 #[test]
655 fn test_all_error_categories() {
656 assert_eq!(Error::serialization("test").category(), ErrorCategory::Data);
658 assert_eq!(Error::corruption("test").category(), ErrorCategory::Data);
659 assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
660 assert_eq!(
661 Error::invalid_format("test").category(),
662 ErrorCategory::Data
663 );
664 assert_eq!(
665 Error::unsupported_format("test").category(),
666 ErrorCategory::Data
667 );
668 assert_eq!(
669 Error::invalid_path("test").category(),
670 ErrorCategory::System
671 );
672 assert_eq!(
673 Error::invalid_state("test").category(),
674 ErrorCategory::Logic
675 );
676 assert_eq!(
677 Error::query_execution("test").category(),
678 ErrorCategory::Query
679 );
680 assert_eq!(
681 Error::type_conversion("test").category(),
682 ErrorCategory::Data
683 );
684 assert_eq!(
685 Error::configuration("test").category(),
686 ErrorCategory::Configuration
687 );
688 assert_eq!(Error::memory("test").category(), ErrorCategory::System);
689 assert_eq!(
690 Error::concurrency("test").category(),
691 ErrorCategory::Concurrency
692 );
693 assert_eq!(Error::not_found("test").category(), ErrorCategory::NotFound);
694 assert_eq!(
695 Error::already_exists("test").category(),
696 ErrorCategory::Conflict
697 );
698 assert_eq!(
699 Error::invalid_operation("test").category(),
700 ErrorCategory::Logic
701 );
702 assert_eq!(
703 Error::constraint_violation("test").category(),
704 ErrorCategory::Constraint
705 );
706 assert_eq!(
707 Error::transaction("test").category(),
708 ErrorCategory::Transaction
709 );
710 assert_eq!(Error::index("test").category(), ErrorCategory::Storage);
711 assert_eq!(Error::compaction("test").category(), ErrorCategory::Storage);
712 assert_eq!(Error::internal("test").category(), ErrorCategory::Internal);
713 assert_eq!(Error::invalid_input("test").category(), ErrorCategory::Data);
714 assert_eq!(Error::parse("test").category(), ErrorCategory::Data);
715 assert_eq!(
716 Error::write_dir_locked("/tmp/test").category(),
717 ErrorCategory::Concurrency
718 );
719 assert_eq!(Error::Cancelled.category(), ErrorCategory::Cancelled);
722 }
723
724 #[test]
725 fn test_all_error_recoverability() {
726 assert!(Error::memory("test").is_recoverable());
728 assert!(Error::storage("test").is_recoverable());
729 assert!(Error::transaction("test").is_recoverable());
730 assert!(Error::index("test").is_recoverable());
731 assert!(Error::compaction("test").is_recoverable());
732
733 assert!(!Error::serialization("test").is_recoverable());
734 assert!(!Error::cql_parse("test").is_recoverable());
735 assert!(!Error::invalid_format("test").is_recoverable());
736 assert!(!Error::unsupported_format("test").is_recoverable());
737 assert!(!Error::invalid_path("test").is_recoverable());
738 assert!(!Error::invalid_state("test").is_recoverable());
739 assert!(!Error::query_execution("test").is_recoverable());
740 assert!(!Error::type_conversion("test").is_recoverable());
741 assert!(!Error::configuration("test").is_recoverable());
742 assert!(!Error::not_found("test").is_recoverable());
743 assert!(!Error::already_exists("test").is_recoverable());
744 assert!(!Error::invalid_operation("test").is_recoverable());
745 assert!(!Error::constraint_violation("test").is_recoverable());
746 assert!(!Error::internal("test").is_recoverable());
747 assert!(!Error::invalid_input("test").is_recoverable());
748 assert!(!Error::parse("test").is_recoverable());
749 assert!(!Error::write_dir_locked("/tmp/test").is_recoverable());
750 }
751
752 #[test]
753 fn test_error_category_display() {
754 assert_eq!(ErrorCategory::System.to_string(), "System");
756 assert_eq!(ErrorCategory::Data.to_string(), "Data");
757 assert_eq!(ErrorCategory::Schema.to_string(), "Schema");
758 assert_eq!(ErrorCategory::Query.to_string(), "Query");
759 assert_eq!(ErrorCategory::Configuration.to_string(), "Configuration");
760 assert_eq!(ErrorCategory::Storage.to_string(), "Storage");
761 assert_eq!(ErrorCategory::Concurrency.to_string(), "Concurrency");
762 assert_eq!(ErrorCategory::NotFound.to_string(), "NotFound");
763 assert_eq!(ErrorCategory::Conflict.to_string(), "Conflict");
764 assert_eq!(ErrorCategory::Logic.to_string(), "Logic");
765 assert_eq!(ErrorCategory::Constraint.to_string(), "Constraint");
766 assert_eq!(ErrorCategory::Transaction.to_string(), "Transaction");
767 assert_eq!(ErrorCategory::Platform.to_string(), "Platform");
768 assert_eq!(ErrorCategory::Internal.to_string(), "Internal");
769 assert_eq!(ErrorCategory::Cancelled.to_string(), "Cancelled");
770 }
771
772 #[test]
773 fn test_error_from_io_error() {
774 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
776 let cqlite_err: Error = io_err.into();
777 assert!(matches!(cqlite_err, Error::Io(_)));
778 assert_eq!(cqlite_err.category(), ErrorCategory::System);
779 assert!(cqlite_err.is_recoverable());
780 }
781
782 #[test]
783 fn test_result_type_alias() {
784 let success: Result<i32> = Ok(42);
786 let failure: Result<i32> = Err(Error::storage("test error"));
787
788 assert!(success.is_ok());
789 if let Ok(value) = success {
790 assert_eq!(value, 42);
791 }
792 assert!(failure.is_err());
793 }
794}