Skip to main content

timeseries_table_format/table/operations/append/
error.rs

1//! Errors owned by an append operation.
2
3use arrow::error::ArrowError;
4use parquet::errors::ParquetError;
5use snafu::{Backtrace, Snafu};
6
7use crate::{
8    coverage::{
9        EntityIdentity, IndexIntervalId,
10        index_interval::{IndexInterval, IndexIntervalMappingError},
11        io::CoverageSidecarError,
12    },
13    formats::parquet::SegmentCoverageError,
14    metadata::{
15        logical_schema::ArrowToLogicalSchemaError, protocol::TableProtocolError,
16        schema_compat::SchemaCompatibilityError,
17    },
18    storage::StorageError,
19    transaction_log::{CommitError, segments::SegmentError},
20};
21
22/// Errors owned by an append operation.
23#[derive(Debug, Snafu)]
24#[snafu(visibility(pub(crate)))]
25#[non_exhaustive]
26pub enum AppendError {
27    /// The table protocol does not permit this client to append.
28    #[snafu(context(false), display("Table protocol error: {source}"))]
29    Protocol {
30        /// Complete table protocol failure.
31        #[snafu(source)]
32        source: TableProtocolError,
33        /// Backtrace captured at the append boundary.
34        backtrace: Backtrace,
35    },
36
37    /// An Arrow input reader or batch normalization failed.
38    #[snafu(display("Arrow input error: {source}"))]
39    ArrowInput {
40        /// Arrow error returned while reading or normalizing a batch.
41        #[snafu(source)]
42        source: ArrowError,
43        /// Backtrace captured at the append input boundary.
44        backtrace: Backtrace,
45    },
46
47    /// An Arrow schema could not be represented by the table logical schema.
48    #[snafu(display("Invalid append input schema: {source}"))]
49    ArrowToLogicalSchema {
50        /// Arrow-to-logical schema conversion failure.
51        #[snafu(source)]
52        source: ArrowToLogicalSchemaError,
53        /// Backtrace captured at the append schema boundary.
54        backtrace: Backtrace,
55    },
56
57    /// An append input contained no rows.
58    #[snafu(display("Cannot append an empty Arrow input"))]
59    EmptyInput,
60
61    /// A configured Parquet row group cannot contain zero rows.
62    #[snafu(display(
63        "Invalid maximum rows per Parquet row group: {max_rows_per_row_group}; expected a positive value"
64    ))]
65    InvalidMaxRowsPerRowGroup {
66        /// Rejected per-append row-group limit.
67        max_rows_per_row_group: usize,
68    },
69
70    /// A configured Parquet row group cannot contain zero estimated bytes.
71    #[snafu(display(
72        "Invalid maximum bytes per Parquet row group: {max_bytes_per_row_group}; expected a positive value"
73    ))]
74    InvalidMaxBytesPerRowGroup {
75        /// Rejected per-append row-group byte limit.
76        max_bytes_per_row_group: usize,
77    },
78
79    /// Validating the incoming and registered table schemas failed.
80    #[snafu(context(false), display("Schema validation failed: {source}"))]
81    SchemaValidation {
82        /// Complete schema compatibility failure.
83        #[snafu(source(from(SchemaCompatibilityError, Box::new)), backtrace)]
84        source: Box<SchemaCompatibilityError>,
85    },
86
87    /// A generated segment is incompatible with the table schema.
88    #[snafu(display(
89        "Generated segment {segment_path} is incompatible with the table schema: {source}"
90    ))]
91    GeneratedSegmentSchemaCompatibility {
92        /// Generated table-relative segment path.
93        segment_path: String,
94        /// Complete schema compatibility failure.
95        #[snafu(source(from(SchemaCompatibilityError, Box::new)), backtrace)]
96        source: Box<SchemaCompatibilityError>,
97    },
98
99    /// Table state lacks the canonical schema required by append.
100    #[snafu(display(
101        "Table has no logical_schema at version {version}; cannot append without a canonical schema"
102    ))]
103    MissingCanonicalTableSchema {
104        /// Transaction log version missing a canonical logical schema.
105        version: u64,
106    },
107
108    /// Reading metadata from the generated segment failed.
109    #[snafu(context(false), display("Generated segment metadata error: {source}"))]
110    SegmentMetadata {
111        /// Complete segment metadata failure.
112        #[snafu(source(from(SegmentError, Box::new)), backtrace)]
113        source: Box<SegmentError>,
114    },
115
116    /// Streaming the append input into Parquet failed.
117    #[snafu(display("Parquet write error: {source}"))]
118    ParquetWrite {
119        /// Parquet writer failure.
120        #[snafu(source)]
121        source: ParquetError,
122        /// Backtrace captured at the append writer boundary.
123        backtrace: Backtrace,
124    },
125
126    /// Deriving ordered-index coverage from the generated segment failed.
127    #[snafu(context(false), display("Segment coverage error: {source}"))]
128    GeneratedSegmentCoverage {
129        /// Complete segment coverage derivation failure.
130        #[snafu(source(from(SegmentCoverageError, Box::new)), backtrace)]
131        source: Box<SegmentCoverageError>,
132    },
133
134    /// An ordered-index interval could not be reconstructed for a diagnostic.
135    #[snafu(context(false), display("Index interval mapping failed: {source}"))]
136    IndexIntervalMapping {
137        /// Complete interval mapping failure.
138        #[snafu(source)]
139        source: IndexIntervalMappingError,
140        /// Backtrace captured at the append boundary.
141        backtrace: Backtrace,
142    },
143
144    /// A coverage sidecar could not be prepared or written.
145    #[snafu(context(false), display("Coverage sidecar error: {source}"))]
146    CoverageSidecar {
147        /// Complete coverage sidecar failure.
148        #[snafu(source(from(CoverageSidecarError, Box::new)), backtrace)]
149        source: Box<CoverageSidecarError>,
150    },
151
152    /// The generated segment overlaps coverage already persisted by the table.
153    #[snafu(display(
154        "Ordered-index interval overlap while appending {segment_path}: {overlap_count} overlapping identity/index interval pairs (example_identity={example_identity:?}, example_index_interval={example_index_interval})"
155    ))]
156    PersistedIndexIntervalOverlap {
157        /// Relative path of the generated segment.
158        segment_path: String,
159        /// Number of overlapping identity/index interval pairs.
160        overlap_count: u128,
161        /// First overlapping identity, or `None` for a table-wide index.
162        example_identity: Option<EntityIdentity>,
163        /// Internal ID of the example interval.
164        example_index_interval_id: IndexIntervalId,
165        /// Logical ordered-index interval represented by the example ID.
166        example_index_interval: Box<IndexInterval>,
167    },
168
169    /// An entity-aware generated segment produced no entity coverage.
170    #[snafu(display("No entity coverage derived while appending segment {segment_path}"))]
171    EmptySegmentEntityCoverage {
172        /// Relative path of the generated segment.
173        segment_path: String,
174    },
175
176    /// One entity has rows but no usable ordered-index coverage.
177    #[snafu(display(
178        "Entity {identity:?} in segment {segment_path} has no non-null ordered-index values"
179    ))]
180    EntityWithoutIndexCoverage {
181        /// Relative path of the generated segment.
182        segment_path: String,
183        /// Complete identity whose rows all have null ordered-index values.
184        identity: EntityIdentity,
185    },
186
187    /// An existing segment lacks coverage metadata required by append.
188    #[snafu(display(
189        "Cannot append because existing segment {segment_path} is missing coverage_path"
190    ))]
191    ExistingSegmentMissingCoverageMetadata {
192        /// Canonical segment path missing coverage metadata.
193        segment_path: String,
194    },
195
196    /// Reading one existing segment's coverage sidecar during recovery failed.
197    #[snafu(display(
198        "Cannot recover append coverage: failed to read segment {segment_path} coverage at {coverage_path}: {source}"
199    ))]
200    ExistingSegmentCoverageSidecarRead {
201        /// Canonical path of the segment whose sidecar could not be read.
202        segment_path: String,
203        /// Path of the failed coverage sidecar.
204        coverage_path: String,
205        /// Complete coverage sidecar failure.
206        #[snafu(source(from(CoverageSidecarError, Box::new)), backtrace)]
207        source: Box<CoverageSidecarError>,
208    },
209
210    /// Direct storage access for an append artifact failed.
211    #[snafu(context(false), display("Storage error: {source}"))]
212    Storage {
213        /// Complete storage failure.
214        #[snafu(source, backtrace)]
215        source: StorageError,
216    },
217
218    /// Publishing the append transaction failed with a definite outcome.
219    #[snafu(context(false), display("Commit error: {source}"))]
220    Commit {
221        /// Complete transaction-log failure.
222        #[snafu(source, backtrace)]
223        source: CommitError,
224    },
225
226    /// The append transaction may have committed, so its artifacts were preserved.
227    #[snafu(display(
228        "Commit outcome is ambiguous; generated Parquet path {segment_path} was preserved: {source}"
229    ))]
230    CommitAmbiguous {
231        /// Generated table-relative Parquet path that was preserved.
232        segment_path: String,
233        /// Ambiguous transaction-log failure.
234        #[snafu(source(from(CommitError, Box::new)), backtrace)]
235        source: Box<CommitError>,
236    },
237
238    /// Append failed and one or more attempt-owned artifacts could not be removed.
239    #[snafu(display(
240        "{source}; artifact rollback also failed: [{}]",
241        cleanup_errors
242            .iter()
243            .map(ToString::to_string)
244            .collect::<Vec<_>>()
245            .join("; ")
246    ))]
247    Rollback {
248        /// Original append failure that triggered rollback.
249        #[snafu(source, backtrace)]
250        source: Box<AppendError>,
251        /// Typed cleanup failure for every artifact that could not be removed.
252        cleanup_errors: Vec<StorageError>,
253    },
254}