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("invalid CQLITE_READ_PATH value '{value}': expected one of auto, point, full")]
98 InvalidReadPath {
99 value: String,
101 },
102
103 #[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 forced: &'static str,
124 reason: String,
126 },
127
128 #[error("Type conversion error: {0}")]
130 TypeConversion(String),
131
132 #[error("Configuration error: {0}")]
134 Configuration(String),
135
136 #[error("Storage error: {0}")]
138 Storage(String),
139
140 #[error("Memory error: {0}")]
142 Memory(String),
143
144 #[error("Concurrency error: {0}")]
146 Concurrency(String),
147
148 #[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 path: String,
161 },
162
163 #[error("Not found: {0}")]
165 NotFound(String),
166
167 #[error("Table error: {0}")]
169 Table(String),
170
171 #[error("Already exists: {0}")]
173 AlreadyExists(String),
174
175 #[error("Invalid operation: {0}")]
177 InvalidOperation(String),
178
179 #[error("Constraint violation: {0}")]
181 ConstraintViolation(String),
182
183 #[error("Transaction error: {0}")]
185 Transaction(String),
186
187 #[error("Index error: {0}")]
189 Index(String),
190
191 #[error("Compaction error: {0}")]
193 Compaction(String),
194
195 #[cfg(target_arch = "wasm32")]
197 #[error("WASM error: {0}")]
198 Wasm(String),
199
200 #[error("Internal error: {0}")]
202 Internal(String),
203
204 #[error("Parse error: {0}")]
206 Parse(String),
207
208 #[error("Invalid input: {0}")]
210 InvalidInput(String),
211
212 #[error("Unsupported query: {0}")]
214 UnsupportedQuery(String),
215
216 #[error("Operation cancelled")]
223 Cancelled,
224}
225
226impl Error {
227 pub fn serialization(msg: impl Into<String>) -> Self {
229 Self::Serialization {
230 message: msg.into(),
231 source: None,
232 }
233 }
234
235 pub fn corruption(msg: impl Into<String>) -> Self {
237 Self::Corruption(msg.into())
238 }
239
240 pub fn schema(msg: impl Into<String>) -> Self {
242 Self::Schema(msg.into())
243 }
244
245 pub fn cql_parse(msg: impl Into<String>) -> Self {
247 Self::CqlParse(msg.into())
248 }
249
250 pub fn invalid_format(msg: impl Into<String>) -> Self {
252 Self::InvalidFormat(msg.into())
253 }
254
255 pub fn unsupported_format(msg: impl Into<String>) -> Self {
257 Self::UnsupportedFormat(msg.into())
258 }
259
260 pub fn invalid_path(msg: impl Into<String>) -> Self {
262 Self::InvalidPath(msg.into())
263 }
264
265 pub fn invalid_state(msg: impl Into<String>) -> Self {
267 Self::InvalidState(msg.into())
268 }
269
270 pub fn query_execution(msg: impl Into<String>) -> Self {
272 Self::QueryExecution(msg.into())
273 }
274
275 pub fn invalid_read_path(value: impl Into<String>) -> Self {
277 Self::InvalidReadPath {
278 value: value.into(),
279 }
280 }
281
282 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 pub fn type_conversion(msg: impl Into<String>) -> Self {
293 Self::TypeConversion(msg.into())
294 }
295
296 pub fn configuration(msg: impl Into<String>) -> Self {
298 Self::Configuration(msg.into())
299 }
300
301 pub fn storage(msg: impl Into<String>) -> Self {
303 Self::Storage(msg.into())
304 }
305
306 pub fn memory(msg: impl Into<String>) -> Self {
308 Self::Memory(msg.into())
309 }
310
311 pub fn concurrency(msg: impl Into<String>) -> Self {
313 Self::Concurrency(msg.into())
314 }
315
316 pub fn not_found(msg: impl Into<String>) -> Self {
318 Self::NotFound(msg.into())
319 }
320
321 pub fn already_exists(msg: impl Into<String>) -> Self {
323 Self::AlreadyExists(msg.into())
324 }
325
326 pub fn invalid_operation(msg: impl Into<String>) -> Self {
328 Self::InvalidOperation(msg.into())
329 }
330
331 pub fn constraint_violation(msg: impl Into<String>) -> Self {
333 Self::ConstraintViolation(msg.into())
334 }
335
336 pub fn transaction(msg: impl Into<String>) -> Self {
338 Self::Transaction(msg.into())
339 }
340
341 pub fn index(msg: impl Into<String>) -> Self {
343 Self::Index(msg.into())
344 }
345
346 pub fn compaction(msg: impl Into<String>) -> Self {
348 Self::Compaction(msg.into())
349 }
350
351 #[cfg(target_arch = "wasm32")]
353 pub fn wasm(msg: impl Into<String>) -> Self {
354 Self::Wasm(msg.into())
355 }
356
357 pub fn internal(msg: impl Into<String>) -> Self {
359 Self::Internal(msg.into())
360 }
361
362 pub fn invalid_input(msg: impl Into<String>) -> Self {
364 Self::InvalidInput(msg.into())
365 }
366
367 pub fn parse(msg: impl Into<String>) -> Self {
369 Self::Parse(msg.into())
370 }
371
372 pub fn unsupported_query(msg: impl Into<String>) -> Self {
374 Self::UnsupportedQuery(msg.into())
375 }
376
377 pub fn write_dir_locked(path: impl Into<String>) -> Self {
379 Self::WriteDirLocked { path: path.into() }
380 }
381
382 pub fn table_not_found(msg: impl Into<String>) -> Self {
384 Self::NotFound(format!("Table not found: {}", msg.into()))
385 }
386
387 pub fn ambiguous_table(msg: impl Into<String>) -> Self {
389 Self::Table(format!("Ambiguous table reference: {}", msg.into()))
390 }
391
392 pub fn is_recoverable(&self) -> bool {
394 match self {
395 Error::Io(_) => true,
397 Error::Concurrency(_) => true,
398 Error::Memory(_) => true,
399
400 Error::Corruption(_) => false,
402 Error::Schema(_) => false,
403 Error::CqlParse(_) => false,
404 Error::Configuration(_) => false,
405
406 Error::Storage(_) => true,
408 Error::QueryExecution(_) => false,
409 Error::ResultTooLarge { .. } => false,
412 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 Error::Table(_) => false,
426
427 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 Error::Cancelled => false,
447 }
448 }
449
450 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 Error::Table(_) => ErrorCategory::Schema,
477
478 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501pub enum ErrorCategory {
502 System,
504 Data,
506 Schema,
508 Query,
510 Configuration,
512 Storage,
514 Concurrency,
516 NotFound,
518 Conflict,
520 Logic,
522 Constraint,
524 Transaction,
526 Platform,
528 Internal,
530 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
559impl 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
569impl 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
579impl<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
589pub type ParseResult<I, O> = nom::IResult<I, O, Error>;
591
592#[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 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 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 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 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 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 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 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 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 assert_eq!(Error::Cancelled.category(), ErrorCategory::Cancelled);
779 }
780
781 #[test]
782 fn test_all_error_recoverability() {
783 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 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 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 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}