Skip to main content

laminar_connectors/lakehouse/delta_io/
mod.rs

1//! Delta Lake I/O integration module.
2//!
3//! This module provides the actual I/O operations for Delta Lake tables via the
4//! `deltalake` crate. All functions are feature-gated behind `delta-lake`.
5//!
6//! # Architecture
7//!
8//! The I/O module is separate from the business logic in [`delta.rs`](super::delta)
9//! to allow:
10//! - Testing business logic without the `deltalake` dependency
11//! - Clean separation of concerns (buffering/epoch management vs. actual writes)
12//! - Easy mocking for unit tests
13//!
14//! Coordinated exactly-once publication uses runtime-owned, stable transaction
15//! namespaces. Ordinary direct writes do not emit writer-local transaction
16//! actions because a process-random identity cannot deduplicate recovery.
17
18mod attempt_error;
19mod catalog;
20mod descriptor;
21pub(in crate::lakehouse) use descriptor::sorted_protocol_features;
22#[cfg(feature = "delta-lake-gcs")]
23mod gcs_factory;
24mod merge;
25mod publication;
26mod read;
27mod storage_preflight;
28mod table;
29
30pub use catalog::{credentials_need_refresh, credentials_refresh_at, CatalogAccess};
31#[cfg(feature = "delta-lake")]
32pub use catalog::{resolve_catalog_options, ResolvedCatalogTable};
33#[cfg(feature = "delta-lake")]
34pub use merge::MergeResult;
35#[cfg(feature = "delta-lake")]
36pub use read::{get_latest_version, get_partition_columns, get_table_schema, map_cdf_to_changelog};
37#[cfg(feature = "delta-lake")]
38pub use table::{
39    get_coordinated_cursor, open_or_create_partitioned_table, open_or_create_table,
40    open_table_metadata,
41};
42
43#[cfg(feature = "delta-lake")]
44pub(crate) use attempt_error::{
45    classify_delta_metadata_error, classify_delta_object_store_metadata_error,
46    delta_error_category, delta_error_has_retryable_transport,
47    is_definite_coordinated_nonpublication, DeltaWriteAttemptError,
48};
49#[cfg(feature = "delta-lake")]
50pub(crate) use merge::merge_changelog;
51#[cfg(all(feature = "delta-lake", test))]
52pub(crate) use read::read_batches_at_version;
53#[cfg(feature = "delta-lake")]
54pub(crate) use read::snapshot_stream;
55#[cfg(feature = "delta-lake")]
56pub(crate) use table::{
57    reject_unenforced_row_obligations, widen_batch_millisecond_timestamps,
58    widen_millisecond_timestamps, write_batches,
59};
60
61#[cfg(all(feature = "delta-lake", test))]
62pub(super) use descriptor::decode_commit_descriptors;
63#[cfg(feature = "delta-lake")]
64pub(super) use descriptor::{
65    coordinated_table_binding, encode_commit_descriptor, encoded_add_array_len,
66    MAX_COORDINATED_ADD_ACTIONS,
67};
68#[cfg(feature = "delta-lake")]
69pub(super) use publication::commit_batch_coordinated;
70#[cfg(all(feature = "delta-lake", test))]
71pub(super) use publication::{
72    commit_adds_coordinated, DelayedCoordinatedCatalogCommit, DELAY_COORDINATED_CATALOG_COMMIT,
73};
74#[cfg(feature = "delta-lake")]
75pub(super) use storage_preflight::{
76    bound_coordinated_storage_options, custom_s3_endpoint_configured,
77    validate_coordinated_storage_preflight, verify_custom_s3_conditional_create,
78};
79
80#[cfg(feature = "delta-lake")]
81use descriptor::{
82    decode_commit_descriptors_until, ensure_publication_deadline, validate_coordinated_descriptors,
83    CoordinatedObject,
84};
85#[cfg(feature = "delta-lake")]
86use storage_preflight::validate_coordinated_log_store;
87#[cfg(feature = "delta-lake")]
88use table::coordinated_transaction_ids;
89
90#[cfg(all(feature = "delta-lake", test))]
91use descriptor::{
92    validate_descriptor_batch_lengths, MAX_COORDINATED_PARTITION_BYTES, MAX_COORDINATED_PATH_BYTES,
93    MAX_COORDINATED_STATS_BYTES,
94};
95#[cfg(all(feature = "delta-lake", test))]
96use publication::validate_coordinated_retention;
97#[cfg(all(feature = "delta-lake", test))]
98use storage_preflight::{
99    custom_s3_endpoint_configured_with_env, is_certified_coordinated_log_store,
100    validate_coordinated_storage_preflight_with_env,
101};
102#[cfg(all(feature = "delta-lake", test))]
103use table::{adapt_delta_location, apply_url_derived_options, path_to_url};
104
105#[cfg(feature = "delta-lake")]
106use std::collections::{BTreeMap, HashMap, HashSet};
107
108#[cfg(feature = "delta-lake")]
109use std::sync::Arc;
110
111#[cfg(feature = "delta-lake")]
112use std::sync::atomic::{AtomicUsize, Ordering};
113
114#[cfg(feature = "delta-lake")]
115use std::time::Duration;
116
117#[cfg(feature = "delta-lake")]
118use arrow_array::RecordBatch;
119
120#[cfg(feature = "delta-lake")]
121use arrow_schema::SchemaRef;
122
123#[cfg(feature = "delta-lake")]
124use deltalake::kernel::transaction::CommitProperties;
125
126#[cfg(feature = "delta-lake")]
127use deltalake::kernel::Transaction;
128
129#[cfg(feature = "delta-lake")]
130use deltalake::operations::write::SchemaMode;
131
132#[cfg(feature = "delta-lake")]
133use deltalake::protocol::SaveMode;
134
135#[cfg(feature = "delta-lake")]
136use deltalake::DeltaTable;
137
138#[cfg(feature = "delta-lake")]
139use tracing::{debug, info};
140
141#[cfg(feature = "delta-lake")]
142use url::Url;
143
144#[cfg(feature = "delta-lake")]
145use crate::error::ConnectorError;
146
147#[cfg(feature = "delta-lake")]
148use crate::storage::StorageProvider;
149
150#[cfg(feature = "delta-lake")]
151use crate::connector::{
152    CoordinatedCommitBatch, CoordinatedCommitCursor, MAX_COORDINATED_COMMIT_BATCH_BYTES,
153};
154
155#[cfg(feature = "delta-lake")]
156use super::commit_descriptor::{DeltaCommitDescriptor, DeltaTableBinding};
157
158#[cfg(feature = "delta-lake")]
159pub(super) fn to_delta_version(version: i64) -> Result<u64, ConnectorError> {
160    u64::try_from(version).map_err(|_| {
161        ConnectorError::ConfigurationError(format!(
162            "Delta table version must be non-negative, got {version}"
163        ))
164    })
165}
166
167#[cfg(feature = "delta-lake")]
168pub(super) fn from_delta_version(version: u64) -> Result<i64, ConnectorError> {
169    i64::try_from(version).map_err(|_| {
170        ConnectorError::ReadError(format!(
171            "Delta table version {version} exceeds LaminarDB's supported range"
172        ))
173    })
174}
175
176#[cfg(feature = "delta-lake")]
177const SET_TRANSACTION_RETENTION: &str = "delta.setTransactionRetentionDuration";
178
179#[cfg(feature = "delta-lake")]
180const COORDINATED_HEAD_CONCURRENCY: usize = 16;
181
182// Coordinated publication uses a separate metadata-client budget. The caller
183// deadline owns preparation; after preparation, one conditional catalog
184// attempt is allowed to finish without cancellation. Bounding both the HTTP
185// and optimistic retry layers makes that terminal fence finite.
186#[cfg(feature = "delta-lake")]
187const COORDINATED_REQUEST_TIMEOUT: &str = "30s";
188#[cfg(feature = "delta-lake")]
189const COORDINATED_CONNECT_TIMEOUT: &str = "10s";
190#[cfg(feature = "delta-lake")]
191const COORDINATED_RETRY_TIMEOUT: &str = "30s";
192#[cfg(feature = "delta-lake")]
193const COORDINATED_HTTP_MAX_RETRIES: &str = "0";
194#[cfg(feature = "delta-lake")]
195const COORDINATED_MAX_BACKOFF: &str = "1s";
196#[cfg(feature = "delta-lake")]
197const COORDINATED_TERMINAL_IO_HORIZON: Duration = Duration::from_secs(24 * 60 * 60);
198#[cfg(feature = "delta-lake")]
199const COORDINATED_CLOCK_SKEW_MARGIN: Duration = Duration::from_secs(5 * 60);
200#[cfg(feature = "delta-lake")]
201const MIN_COORDINATED_DELETED_FILE_RETENTION: Duration = Duration::from_secs(7 * 24 * 60 * 60);
202
203#[cfg(all(test, feature = "delta-lake"))]
204mod tests;