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    /// Validating the incoming and registered table schemas failed.
71    #[snafu(context(false), display("Schema validation failed: {source}"))]
72    SchemaValidation {
73        /// Complete schema compatibility failure.
74        #[snafu(source(from(SchemaCompatibilityError, Box::new)), backtrace)]
75        source: Box<SchemaCompatibilityError>,
76    },
77
78    /// A generated segment is incompatible with the table schema.
79    #[snafu(display(
80        "Generated segment {segment_path} is incompatible with the table schema: {source}"
81    ))]
82    GeneratedSegmentSchemaCompatibility {
83        /// Generated table-relative segment path.
84        segment_path: String,
85        /// Complete schema compatibility failure.
86        #[snafu(source(from(SchemaCompatibilityError, Box::new)), backtrace)]
87        source: Box<SchemaCompatibilityError>,
88    },
89
90    /// Table state lacks the canonical schema required by append.
91    #[snafu(display(
92        "Table has no logical_schema at version {version}; cannot append without a canonical schema"
93    ))]
94    MissingCanonicalTableSchema {
95        /// Transaction log version missing a canonical logical schema.
96        version: u64,
97    },
98
99    /// Reading metadata from the generated segment failed.
100    #[snafu(context(false), display("Generated segment metadata error: {source}"))]
101    SegmentMetadata {
102        /// Complete segment metadata failure.
103        #[snafu(source(from(SegmentError, Box::new)), backtrace)]
104        source: Box<SegmentError>,
105    },
106
107    /// Streaming the append input into Parquet failed.
108    #[snafu(display("Parquet write error: {source}"))]
109    ParquetWrite {
110        /// Parquet writer failure.
111        #[snafu(source)]
112        source: ParquetError,
113        /// Backtrace captured at the append writer boundary.
114        backtrace: Backtrace,
115    },
116
117    /// Deriving ordered-index coverage from the generated segment failed.
118    #[snafu(context(false), display("Segment coverage error: {source}"))]
119    GeneratedSegmentCoverage {
120        /// Complete segment coverage derivation failure.
121        #[snafu(source(from(SegmentCoverageError, Box::new)), backtrace)]
122        source: Box<SegmentCoverageError>,
123    },
124
125    /// An ordered-index interval could not be reconstructed for a diagnostic.
126    #[snafu(context(false), display("Index interval mapping failed: {source}"))]
127    IndexIntervalMapping {
128        /// Complete interval mapping failure.
129        #[snafu(source)]
130        source: IndexIntervalMappingError,
131        /// Backtrace captured at the append boundary.
132        backtrace: Backtrace,
133    },
134
135    /// A coverage sidecar could not be prepared or written.
136    #[snafu(context(false), display("Coverage sidecar error: {source}"))]
137    CoverageSidecar {
138        /// Complete coverage sidecar failure.
139        #[snafu(source(from(CoverageSidecarError, Box::new)), backtrace)]
140        source: Box<CoverageSidecarError>,
141    },
142
143    /// The generated segment overlaps coverage already persisted by the table.
144    #[snafu(display(
145        "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})"
146    ))]
147    PersistedIndexIntervalOverlap {
148        /// Relative path of the generated segment.
149        segment_path: String,
150        /// Number of overlapping identity/index interval pairs.
151        overlap_count: u128,
152        /// First overlapping identity, or `None` for a table-wide index.
153        example_identity: Option<EntityIdentity>,
154        /// Internal ID of the example interval.
155        example_index_interval_id: IndexIntervalId,
156        /// Logical ordered-index interval represented by the example ID.
157        example_index_interval: Box<IndexInterval>,
158    },
159
160    /// An entity-aware generated segment produced no entity coverage.
161    #[snafu(display("No entity coverage derived while appending segment {segment_path}"))]
162    EmptySegmentEntityCoverage {
163        /// Relative path of the generated segment.
164        segment_path: String,
165    },
166
167    /// One entity has rows but no usable ordered-index coverage.
168    #[snafu(display(
169        "Entity {identity:?} in segment {segment_path} has no non-null ordered-index values"
170    ))]
171    EntityWithoutIndexCoverage {
172        /// Relative path of the generated segment.
173        segment_path: String,
174        /// Complete identity whose rows all have null ordered-index values.
175        identity: EntityIdentity,
176    },
177
178    /// An existing segment lacks coverage metadata required by append.
179    #[snafu(display(
180        "Cannot append because existing segment {segment_path} is missing coverage_path"
181    ))]
182    ExistingSegmentMissingCoverageMetadata {
183        /// Canonical segment path missing coverage metadata.
184        segment_path: String,
185    },
186
187    /// Reading one existing segment's coverage sidecar during recovery failed.
188    #[snafu(display(
189        "Cannot recover append coverage: failed to read segment {segment_path} coverage at {coverage_path}: {source}"
190    ))]
191    ExistingSegmentCoverageSidecarRead {
192        /// Canonical path of the segment whose sidecar could not be read.
193        segment_path: String,
194        /// Path of the failed coverage sidecar.
195        coverage_path: String,
196        /// Complete coverage sidecar failure.
197        #[snafu(source(from(CoverageSidecarError, Box::new)), backtrace)]
198        source: Box<CoverageSidecarError>,
199    },
200
201    /// Direct storage access for an append artifact failed.
202    #[snafu(context(false), display("Storage error: {source}"))]
203    Storage {
204        /// Complete storage failure.
205        #[snafu(source, backtrace)]
206        source: StorageError,
207    },
208
209    /// Publishing the append transaction failed with a definite outcome.
210    #[snafu(context(false), display("Commit error: {source}"))]
211    Commit {
212        /// Complete transaction-log failure.
213        #[snafu(source, backtrace)]
214        source: CommitError,
215    },
216
217    /// The append transaction may have committed, so its artifacts were preserved.
218    #[snafu(display(
219        "Commit outcome is ambiguous; generated Parquet path {segment_path} was preserved: {source}"
220    ))]
221    CommitAmbiguous {
222        /// Generated table-relative Parquet path that was preserved.
223        segment_path: String,
224        /// Ambiguous transaction-log failure.
225        #[snafu(source(from(CommitError, Box::new)), backtrace)]
226        source: Box<CommitError>,
227    },
228
229    /// Append failed and one or more attempt-owned artifacts could not be removed.
230    #[snafu(display(
231        "{source}; artifact rollback also failed: [{}]",
232        cleanup_errors
233            .iter()
234            .map(ToString::to_string)
235            .collect::<Vec<_>>()
236            .join("; ")
237    ))]
238    Rollback {
239        /// Original append failure that triggered rollback.
240        #[snafu(source, backtrace)]
241        source: Box<AppendError>,
242        /// Typed cleanup failure for every artifact that could not be removed.
243        cleanup_errors: Vec<StorageError>,
244    },
245}