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("Unsupported CommitLog version {version}: supported range is {floor}..={ceiling}")]
60 UnsupportedCommitLogVersion {
61 version: i32,
62 floor: i32,
63 ceiling: i32,
64 },
65
66 #[error("Corrupt CommitLog frame: {0}")]
73 CorruptCommitLogFrame(String),
74
75 #[error("Operation timeout: {0}")]
77 Timeout(String),
78
79 #[error("Invalid path: {0}")]
81 InvalidPath(String),
82
83 #[error("Invalid state: {0}")]
85 InvalidState(String),
86
87 #[error("Query execution error: {0}")]
89 QueryExecution(String),
90
91 #[error(
101 "result set exceeded the {budget_bytes}-byte materialization budget \
102 (estimated {estimated_bytes} bytes across {rows} rows so far); \
103 add a LIMIT clause to bound the result, or use the streaming query \
104 API (e.g. execute_streaming) to consume rows incrementally"
105 )]
106 ResultTooLarge {
107 budget_bytes: usize,
109 estimated_bytes: usize,
111 rows: usize,
113 },
114
115 #[error("invalid CQLITE_READ_PATH value '{value}': expected one of auto, point, full")]
121 InvalidReadPath {
122 value: String,
124 },
125
126 #[error(
140 "forced read path '{forced}' unavailable: {reason}. This query cannot be \
141 served under CQLITE_READ_PATH={forced} without diverging from the 'auto' \
142 result; use 'auto' to let CQLite choose the read path"
143 )]
144 ForcedReadPathUnavailable {
145 forced: &'static str,
147 reason: String,
149 },
150
151 #[error("Type conversion error: {0}")]
153 TypeConversion(String),
154
155 #[error("Configuration error: {0}")]
157 Configuration(String),
158
159 #[error("Storage error: {0}")]
161 Storage(String),
162
163 #[error("Memory error: {0}")]
165 Memory(String),
166
167 #[error("Concurrency error: {0}")]
169 Concurrency(String),
170
171 #[error(
178 "write_dir '{path}' is already locked by another process. \
179 Only one Database instance may hold a write_dir at a time."
180 )]
181 WriteDirLocked {
182 path: String,
184 },
185
186 #[error("Not found: {0}")]
188 NotFound(String),
189
190 #[error("Table error: {0}")]
192 Table(String),
193
194 #[error("Already exists: {0}")]
196 AlreadyExists(String),
197
198 #[error("Invalid operation: {0}")]
200 InvalidOperation(String),
201
202 #[error("Constraint violation: {0}")]
204 ConstraintViolation(String),
205
206 #[error("Transaction error: {0}")]
208 Transaction(String),
209
210 #[error("Index error: {0}")]
212 Index(String),
213
214 #[error("Compaction error: {0}")]
216 Compaction(String),
217
218 #[cfg(target_arch = "wasm32")]
220 #[error("WASM error: {0}")]
221 Wasm(String),
222
223 #[error("Internal error: {0}")]
225 Internal(String),
226
227 #[error("Parse error: {0}")]
229 Parse(String),
230
231 #[error("Invalid input: {0}")]
233 InvalidInput(String),
234
235 #[error("Unsupported query: {0}")]
237 UnsupportedQuery(String),
238
239 #[error("Operation cancelled")]
246 Cancelled,
247}
248
249impl Error {
250 pub fn serialization(msg: impl Into<String>) -> Self {
252 Self::Serialization {
253 message: msg.into(),
254 source: None,
255 }
256 }
257
258 pub fn corruption(msg: impl Into<String>) -> Self {
260 Self::Corruption(msg.into())
261 }
262
263 pub fn schema(msg: impl Into<String>) -> Self {
265 Self::Schema(msg.into())
266 }
267
268 pub fn cql_parse(msg: impl Into<String>) -> Self {
270 Self::CqlParse(msg.into())
271 }
272
273 pub fn invalid_format(msg: impl Into<String>) -> Self {
275 Self::InvalidFormat(msg.into())
276 }
277
278 pub fn unsupported_format(msg: impl Into<String>) -> Self {
280 Self::UnsupportedFormat(msg.into())
281 }
282
283 pub fn invalid_path(msg: impl Into<String>) -> Self {
285 Self::InvalidPath(msg.into())
286 }
287
288 pub fn invalid_state(msg: impl Into<String>) -> Self {
290 Self::InvalidState(msg.into())
291 }
292
293 pub fn query_execution(msg: impl Into<String>) -> Self {
295 Self::QueryExecution(msg.into())
296 }
297
298 pub fn invalid_read_path(value: impl Into<String>) -> Self {
300 Self::InvalidReadPath {
301 value: value.into(),
302 }
303 }
304
305 pub fn forced_read_path_unavailable(forced: &'static str, reason: impl Into<String>) -> Self {
308 Self::ForcedReadPathUnavailable {
309 forced,
310 reason: reason.into(),
311 }
312 }
313
314 pub fn type_conversion(msg: impl Into<String>) -> Self {
316 Self::TypeConversion(msg.into())
317 }
318
319 pub fn configuration(msg: impl Into<String>) -> Self {
321 Self::Configuration(msg.into())
322 }
323
324 pub fn storage(msg: impl Into<String>) -> Self {
326 Self::Storage(msg.into())
327 }
328
329 pub fn memory(msg: impl Into<String>) -> Self {
331 Self::Memory(msg.into())
332 }
333
334 pub fn concurrency(msg: impl Into<String>) -> Self {
336 Self::Concurrency(msg.into())
337 }
338
339 pub fn not_found(msg: impl Into<String>) -> Self {
341 Self::NotFound(msg.into())
342 }
343
344 pub fn already_exists(msg: impl Into<String>) -> Self {
346 Self::AlreadyExists(msg.into())
347 }
348
349 pub fn invalid_operation(msg: impl Into<String>) -> Self {
351 Self::InvalidOperation(msg.into())
352 }
353
354 pub fn constraint_violation(msg: impl Into<String>) -> Self {
356 Self::ConstraintViolation(msg.into())
357 }
358
359 pub fn transaction(msg: impl Into<String>) -> Self {
361 Self::Transaction(msg.into())
362 }
363
364 pub fn index(msg: impl Into<String>) -> Self {
366 Self::Index(msg.into())
367 }
368
369 pub fn compaction(msg: impl Into<String>) -> Self {
371 Self::Compaction(msg.into())
372 }
373
374 #[cfg(target_arch = "wasm32")]
376 pub fn wasm(msg: impl Into<String>) -> Self {
377 Self::Wasm(msg.into())
378 }
379
380 pub fn internal(msg: impl Into<String>) -> Self {
382 Self::Internal(msg.into())
383 }
384
385 pub fn invalid_input(msg: impl Into<String>) -> Self {
387 Self::InvalidInput(msg.into())
388 }
389
390 pub fn parse(msg: impl Into<String>) -> Self {
392 Self::Parse(msg.into())
393 }
394
395 pub fn unsupported_query(msg: impl Into<String>) -> Self {
397 Self::UnsupportedQuery(msg.into())
398 }
399
400 pub fn write_dir_locked(path: impl Into<String>) -> Self {
402 Self::WriteDirLocked { path: path.into() }
403 }
404
405 pub fn table_not_found(msg: impl Into<String>) -> Self {
407 Self::NotFound(format!("Table not found: {}", msg.into()))
408 }
409
410 pub fn ambiguous_table(msg: impl Into<String>) -> Self {
412 Self::Table(format!("Ambiguous table reference: {}", msg.into()))
413 }
414
415 pub fn is_recoverable(&self) -> bool {
417 match self {
418 Error::Io(_) => true,
420 Error::Concurrency(_) => true,
421 Error::Memory(_) => true,
422
423 Error::Corruption(_) => false,
425 Error::Schema(_) => false,
426 Error::CqlParse(_) => false,
427 Error::Configuration(_) => false,
428
429 Error::Storage(_) => true,
431 Error::QueryExecution(_) => false,
432 Error::ResultTooLarge { .. } => false,
435 Error::InvalidReadPath { .. } => false,
437 Error::ForcedReadPathUnavailable { .. } => false,
438 Error::TypeConversion(_) => false,
439 Error::NotFound(_) => false,
440 Error::AlreadyExists(_) => false,
441 Error::InvalidOperation(_) => false,
442 Error::ConstraintViolation(_) => false,
443 Error::Transaction(_) => true,
444 Error::Index(_) => true,
445 Error::Compaction(_) => true,
446
447 Error::Table(_) => false,
449
450 Error::WriteDirLocked { .. } => false,
452
453 #[cfg(target_arch = "wasm32")]
454 Error::Wasm(_) => false,
455
456 Error::Serialization { .. } => false,
457 Error::Internal(_) => false,
458 Error::Parse(_) => false,
459 Error::InvalidInput(_) => false,
460 Error::InvalidFormat(_) => false,
461 Error::UnsupportedFormat(_) => false,
462 Error::UnsupportedVersion { .. } => false,
463 Error::UnsupportedCommitLogVersion { .. } => false,
464 Error::CorruptCommitLogFrame(_) => false,
465 Error::InvalidPath(_) => false,
466 Error::InvalidState(_) => false,
467 Error::Timeout(_) => false,
468 Error::UnsupportedQuery(_) => false,
469 Error::Cancelled => false,
472 }
473 }
474
475 pub fn category(&self) -> ErrorCategory {
477 match self {
478 Error::Io(_) => ErrorCategory::System,
479 Error::Serialization { .. } => ErrorCategory::Data,
480 Error::Corruption(_) => ErrorCategory::Data,
481 Error::Schema(_) => ErrorCategory::Schema,
482 Error::CqlParse(_) => ErrorCategory::Query,
483 Error::QueryExecution(_) => ErrorCategory::Query,
484 Error::ResultTooLarge { .. } => ErrorCategory::Query,
485 Error::InvalidReadPath { .. } => ErrorCategory::Configuration,
486 Error::ForcedReadPathUnavailable { .. } => ErrorCategory::Query,
487 Error::TypeConversion(_) => ErrorCategory::Data,
488 Error::Configuration(_) => ErrorCategory::Configuration,
489 Error::Storage(_) => ErrorCategory::Storage,
490 Error::Memory(_) => ErrorCategory::System,
491 Error::Concurrency(_) => ErrorCategory::Concurrency,
492 Error::NotFound(_) => ErrorCategory::NotFound,
493 Error::AlreadyExists(_) => ErrorCategory::Conflict,
494 Error::InvalidOperation(_) => ErrorCategory::Logic,
495 Error::ConstraintViolation(_) => ErrorCategory::Constraint,
496 Error::Transaction(_) => ErrorCategory::Transaction,
497 Error::Index(_) => ErrorCategory::Storage,
498 Error::Compaction(_) => ErrorCategory::Storage,
499
500 Error::Table(_) => ErrorCategory::Schema,
502
503 Error::WriteDirLocked { .. } => ErrorCategory::Concurrency,
505
506 #[cfg(target_arch = "wasm32")]
507 Error::Wasm(_) => ErrorCategory::Platform,
508
509 Error::Internal(_) => ErrorCategory::Internal,
510 Error::Parse(_) => ErrorCategory::Data,
511 Error::InvalidInput(_) => ErrorCategory::Data,
512 Error::InvalidFormat(_) => ErrorCategory::Data,
513 Error::UnsupportedFormat(_) => ErrorCategory::Data,
514 Error::UnsupportedVersion { .. } => ErrorCategory::Data,
515 Error::UnsupportedCommitLogVersion { .. } => ErrorCategory::Data,
516 Error::CorruptCommitLogFrame(_) => ErrorCategory::Data,
517 Error::InvalidPath(_) => ErrorCategory::System,
518 Error::InvalidState(_) => ErrorCategory::Logic,
519 Error::Timeout(_) => ErrorCategory::System,
520 Error::UnsupportedQuery(_) => ErrorCategory::Query,
521 Error::Cancelled => ErrorCategory::Cancelled,
522 }
523 }
524}
525
526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub enum ErrorCategory {
529 System,
531 Data,
533 Schema,
535 Query,
537 Configuration,
539 Storage,
541 Concurrency,
543 NotFound,
545 Conflict,
547 Logic,
549 Constraint,
551 Transaction,
553 Platform,
555 Internal,
557 Cancelled,
561}
562
563impl fmt::Display for ErrorCategory {
564 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
565 let name = match self {
566 ErrorCategory::System => "System",
567 ErrorCategory::Data => "Data",
568 ErrorCategory::Schema => "Schema",
569 ErrorCategory::Query => "Query",
570 ErrorCategory::Configuration => "Configuration",
571 ErrorCategory::Storage => "Storage",
572 ErrorCategory::Concurrency => "Concurrency",
573 ErrorCategory::NotFound => "NotFound",
574 ErrorCategory::Conflict => "Conflict",
575 ErrorCategory::Logic => "Logic",
576 ErrorCategory::Constraint => "Constraint",
577 ErrorCategory::Transaction => "Transaction",
578 ErrorCategory::Platform => "Platform",
579 ErrorCategory::Internal => "Internal",
580 ErrorCategory::Cancelled => "Cancelled",
581 };
582 write!(f, "{}", name)
583 }
584}
585
586impl From<bincode::Error> for Error {
588 fn from(err: bincode::Error) -> Self {
589 Error::Serialization {
590 message: err.to_string(),
591 source: Some(Box::new(err)),
592 }
593 }
594}
595
596impl From<serde_json::Error> for Error {
598 fn from(err: serde_json::Error) -> Self {
599 Error::Serialization {
600 message: err.to_string(),
601 source: Some(Box::new(err)),
602 }
603 }
604}
605
606impl<I> From<nom::Err<nom::error::Error<I>>> for Error
608where
609 I: std::fmt::Debug,
610{
611 fn from(err: nom::Err<nom::error::Error<I>>) -> Self {
612 Error::CqlParse(format!("Parse error: {:?}", err))
613 }
614}
615
616pub type ParseResult<I, O> = nom::IResult<I, O, Error>;
618
619#[derive(Debug, Clone)]
621pub struct ParseError {
622 pub message: String,
623}
624
625impl std::fmt::Display for ParseError {
626 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
627 write!(f, "{}", self.message)
628 }
629}
630
631impl std::error::Error for ParseError {}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn test_error_from_conversions() {
639 let io_err = std::io::Error::other("test error");
641 let bincode_err = bincode::Error::new(bincode::ErrorKind::Io(io_err));
642 let error = Error::from(bincode_err);
643 assert!(matches!(error, Error::Serialization { .. }));
644
645 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
647 let error = Error::from(json_err);
648 assert!(matches!(error, Error::Serialization { .. }));
649
650 let nom_err = nom::Err::Error(nom::error::Error::new(
652 "test input",
653 nom::error::ErrorKind::Tag,
654 ));
655 let error = Error::from(nom_err);
656 assert!(matches!(error, Error::CqlParse(_)));
657 }
658
659 #[test]
660 fn test_parse_error_display() {
661 let parse_error = ParseError {
663 message: "test parse error".to_string(),
664 };
665 let display_str = format!("{}", parse_error);
666 assert_eq!(display_str, "test parse error");
667 }
668
669 #[test]
670 #[cfg(target_arch = "wasm32")]
671 fn test_wasm_error_creation() {
672 let err = Error::wasm("WASM error");
674 assert!(matches!(err, Error::Wasm(_)));
675 assert!(!err.is_recoverable());
676 assert_eq!(err.category(), ErrorCategory::Platform);
677 }
678
679 #[test]
680 fn test_new_error_types_coverage() {
681 let table_err = Error::Table("table error".to_string());
683 assert!(!table_err.is_recoverable());
684 assert_eq!(table_err.category(), ErrorCategory::Schema);
685 }
686
687 #[test]
688 fn test_error_creation() {
689 let err = Error::storage("test error");
690 assert!(matches!(err, Error::Storage(_)));
691 assert_eq!(err.to_string(), "Storage error: test error");
692 }
693
694 #[test]
695 fn test_error_categories() {
696 assert_eq!(Error::storage("test").category(), ErrorCategory::Storage);
697 assert_eq!(Error::schema("test").category(), ErrorCategory::Schema);
698 assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
699 }
700
701 #[test]
702 fn test_error_recoverability() {
703 assert!(Error::concurrency("test").is_recoverable());
704 assert!(!Error::corruption("test").is_recoverable());
705 assert!(!Error::schema("test").is_recoverable());
706 }
707
708 #[test]
709 fn test_all_error_constructors() {
710 let _ = Error::serialization("test");
712 let _ = Error::corruption("test");
713 let _ = Error::schema("test");
714 let _ = Error::cql_parse("test");
715 let _ = Error::invalid_format("test");
716 let _ = Error::unsupported_format("test");
717 let _ = Error::invalid_path("test");
718 let _ = Error::invalid_state("test");
719 let _ = Error::query_execution("test");
720 let _ = Error::type_conversion("test");
721 let _ = Error::configuration("test");
722 let _ = Error::storage("test");
723 let _ = Error::memory("test");
724 let _ = Error::concurrency("test");
725 let _ = Error::not_found("test");
726 let _ = Error::already_exists("test");
727 let _ = Error::invalid_operation("test");
728 let _ = Error::constraint_violation("test");
729 let _ = Error::transaction("test");
730 let _ = Error::index("test");
731 let _ = Error::compaction("test");
732 let _ = Error::internal("test");
733 let _ = Error::invalid_input("test");
734 let _ = Error::parse("test");
735 let _ = Error::write_dir_locked("/tmp/test-dir");
736 }
737
738 #[test]
739 fn test_all_error_categories() {
740 assert_eq!(Error::serialization("test").category(), ErrorCategory::Data);
742 assert_eq!(Error::corruption("test").category(), ErrorCategory::Data);
743 assert_eq!(Error::cql_parse("test").category(), ErrorCategory::Query);
744 assert_eq!(
745 Error::invalid_format("test").category(),
746 ErrorCategory::Data
747 );
748 assert_eq!(
749 Error::unsupported_format("test").category(),
750 ErrorCategory::Data
751 );
752 assert_eq!(
753 Error::invalid_path("test").category(),
754 ErrorCategory::System
755 );
756 assert_eq!(
757 Error::invalid_state("test").category(),
758 ErrorCategory::Logic
759 );
760 assert_eq!(
761 Error::query_execution("test").category(),
762 ErrorCategory::Query
763 );
764 assert_eq!(
765 Error::type_conversion("test").category(),
766 ErrorCategory::Data
767 );
768 assert_eq!(
769 Error::configuration("test").category(),
770 ErrorCategory::Configuration
771 );
772 assert_eq!(Error::memory("test").category(), ErrorCategory::System);
773 assert_eq!(
774 Error::concurrency("test").category(),
775 ErrorCategory::Concurrency
776 );
777 assert_eq!(Error::not_found("test").category(), ErrorCategory::NotFound);
778 assert_eq!(
779 Error::already_exists("test").category(),
780 ErrorCategory::Conflict
781 );
782 assert_eq!(
783 Error::invalid_operation("test").category(),
784 ErrorCategory::Logic
785 );
786 assert_eq!(
787 Error::constraint_violation("test").category(),
788 ErrorCategory::Constraint
789 );
790 assert_eq!(
791 Error::transaction("test").category(),
792 ErrorCategory::Transaction
793 );
794 assert_eq!(Error::index("test").category(), ErrorCategory::Storage);
795 assert_eq!(Error::compaction("test").category(), ErrorCategory::Storage);
796 assert_eq!(Error::internal("test").category(), ErrorCategory::Internal);
797 assert_eq!(Error::invalid_input("test").category(), ErrorCategory::Data);
798 assert_eq!(Error::parse("test").category(), ErrorCategory::Data);
799 assert_eq!(
800 Error::write_dir_locked("/tmp/test").category(),
801 ErrorCategory::Concurrency
802 );
803 assert_eq!(Error::Cancelled.category(), ErrorCategory::Cancelled);
806 }
807
808 #[test]
809 fn test_all_error_recoverability() {
810 assert!(Error::memory("test").is_recoverable());
812 assert!(Error::storage("test").is_recoverable());
813 assert!(Error::transaction("test").is_recoverable());
814 assert!(Error::index("test").is_recoverable());
815 assert!(Error::compaction("test").is_recoverable());
816
817 assert!(!Error::serialization("test").is_recoverable());
818 assert!(!Error::cql_parse("test").is_recoverable());
819 assert!(!Error::invalid_format("test").is_recoverable());
820 assert!(!Error::unsupported_format("test").is_recoverable());
821 assert!(!Error::invalid_path("test").is_recoverable());
822 assert!(!Error::invalid_state("test").is_recoverable());
823 assert!(!Error::query_execution("test").is_recoverable());
824 assert!(!Error::type_conversion("test").is_recoverable());
825 assert!(!Error::configuration("test").is_recoverable());
826 assert!(!Error::not_found("test").is_recoverable());
827 assert!(!Error::already_exists("test").is_recoverable());
828 assert!(!Error::invalid_operation("test").is_recoverable());
829 assert!(!Error::constraint_violation("test").is_recoverable());
830 assert!(!Error::internal("test").is_recoverable());
831 assert!(!Error::invalid_input("test").is_recoverable());
832 assert!(!Error::parse("test").is_recoverable());
833 assert!(!Error::write_dir_locked("/tmp/test").is_recoverable());
834 }
835
836 #[test]
837 fn test_error_category_display() {
838 assert_eq!(ErrorCategory::System.to_string(), "System");
840 assert_eq!(ErrorCategory::Data.to_string(), "Data");
841 assert_eq!(ErrorCategory::Schema.to_string(), "Schema");
842 assert_eq!(ErrorCategory::Query.to_string(), "Query");
843 assert_eq!(ErrorCategory::Configuration.to_string(), "Configuration");
844 assert_eq!(ErrorCategory::Storage.to_string(), "Storage");
845 assert_eq!(ErrorCategory::Concurrency.to_string(), "Concurrency");
846 assert_eq!(ErrorCategory::NotFound.to_string(), "NotFound");
847 assert_eq!(ErrorCategory::Conflict.to_string(), "Conflict");
848 assert_eq!(ErrorCategory::Logic.to_string(), "Logic");
849 assert_eq!(ErrorCategory::Constraint.to_string(), "Constraint");
850 assert_eq!(ErrorCategory::Transaction.to_string(), "Transaction");
851 assert_eq!(ErrorCategory::Platform.to_string(), "Platform");
852 assert_eq!(ErrorCategory::Internal.to_string(), "Internal");
853 assert_eq!(ErrorCategory::Cancelled.to_string(), "Cancelled");
854 }
855
856 #[test]
857 fn test_error_from_io_error() {
858 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
860 let cqlite_err: Error = io_err.into();
861 assert!(matches!(cqlite_err, Error::Io(_)));
862 assert_eq!(cqlite_err.category(), ErrorCategory::System);
863 assert!(cqlite_err.is_recoverable());
864 }
865
866 #[test]
867 fn test_result_type_alias() {
868 let success: Result<i32> = Ok(42);
870 let failure: Result<i32> = Err(Error::storage("test error"));
871
872 assert!(success.is_ok());
873 if let Ok(value) = success {
874 assert_eq!(value, 42);
875 }
876 assert!(failure.is_err());
877 }
878}