1use std::collections::BTreeMap;
13
14use async_trait::async_trait;
15
16use crate::authz::DeltaAction;
17use crate::backend::{
18 CreateTableSpec, CredentialAccess, DeltaBackend, ResolvedTable, SchemaRef, TableRef,
19 UpdateTableSpec, VendedCredential, VendedCredentialKind, etag_of,
20};
21use crate::column::Column;
22use crate::contract;
23use crate::coordinator::CommitInfo;
24use crate::error::{DeltaApiError, DeltaApiResult as Result, DeltaBackendError};
25use crate::models::*;
26
27#[derive(Debug, Clone)]
29pub struct GetConfigQuery {
30 pub catalog: String,
31 pub protocol_versions: String,
33}
34
35#[async_trait]
40pub trait DeltaApiHandler<Cx>: Send + Sync + 'static {
41 async fn get_config(&self, query: GetConfigQuery, context: Cx) -> Result<DeltaCatalogConfig>;
43
44 async fn create_staging_table(
46 &self,
47 path: SchemaRef,
48 request: DeltaCreateStagingTableRequest,
49 context: Cx,
50 ) -> Result<DeltaStagingTableResponse>;
51
52 async fn create_table(
54 &self,
55 path: SchemaRef,
56 request: DeltaCreateTableRequest,
57 context: Cx,
58 ) -> Result<DeltaLoadTableResponse>;
59
60 async fn load_table(&self, path: TableRef, context: Cx) -> Result<DeltaLoadTableResponse>;
62
63 async fn update_table(
65 &self,
66 path: TableRef,
67 request: DeltaUpdateTableRequest,
68 context: Cx,
69 ) -> Result<DeltaLoadTableResponse>;
70
71 async fn delete_table(&self, path: TableRef, context: Cx) -> Result<()>;
73
74 async fn table_exists(&self, path: TableRef, context: Cx) -> Result<()>;
76
77 async fn rename_table(
79 &self,
80 path: TableRef,
81 request: DeltaRenameTableRequest,
82 context: Cx,
83 ) -> Result<()>;
84
85 async fn get_table_credentials(
87 &self,
88 path: TableRef,
89 operation: DeltaCredentialOperation,
90 context: Cx,
91 ) -> Result<DeltaCredentialsResponse>;
92
93 async fn report_metrics(
95 &self,
96 path: TableRef,
97 request: DeltaReportMetricsRequest,
98 context: Cx,
99 ) -> Result<()>;
100
101 async fn get_staging_table_credentials(
103 &self,
104 table_id: String,
105 context: Cx,
106 ) -> Result<DeltaCredentialsResponse>;
107
108 async fn get_temporary_path_credentials(
110 &self,
111 location: String,
112 operation: DeltaCredentialOperation,
113 context: Cx,
114 ) -> Result<DeltaCredentialsResponse>;
115}
116
117#[async_trait]
118impl<B, Cx> DeltaApiHandler<Cx> for B
119where
120 B: DeltaBackend<Cx>,
121 Cx: Send + 'static,
122{
123 async fn get_config(&self, query: GetConfigQuery, context: Cx) -> Result<DeltaCatalogConfig> {
124 if query.catalog.is_empty() {
125 return Err(DeltaApiError::invalid_argument(
126 "catalog query parameter is required",
127 ));
128 }
129 let protocol_version = crate::config::negotiate_version(&query.protocol_versions)?;
132 self.catalog_exists(&query.catalog, &context).await?;
133 Ok(DeltaCatalogConfig {
134 endpoints: crate::config::endpoints_for(self.capabilities()),
135 protocol_version,
136 })
137 }
138
139 async fn create_staging_table(
140 &self,
141 path: SchemaRef,
142 request: DeltaCreateStagingTableRequest,
143 context: Cx,
144 ) -> Result<DeltaStagingTableResponse> {
145 self.authorize(
146 DeltaAction::CreateStaging {
147 at: &path,
148 name: &request.name,
149 },
150 &context,
151 )
152 .await?;
153
154 let staging = self
155 .allocate_staging(&path, &request.name, &context)
156 .await?;
157
158 let creds = self
159 .vend_path_credential(&staging.location, CredentialAccess::ReadWrite, &context)
160 .await?;
161
162 Ok(DeltaStagingTableResponse {
163 table_id: staging.table_id.clone(),
164 table_type: DeltaTableType::Managed,
165 location: staging.location.clone(),
166 storage_credentials: vec![to_storage_credential(
167 &staging.location,
168 &creds,
169 DeltaCredentialOperation::ReadWrite,
170 )],
171 required_protocol: DeltaProtocol {
172 min_reader_version: contract::REQUIRED_MIN_READER_VERSION,
173 min_writer_version: contract::REQUIRED_MIN_WRITER_VERSION,
174 reader_features: Some(feature_vec(contract::REQUIRED_READER_FEATURES)),
175 writer_features: Some(feature_vec(contract::REQUIRED_WRITER_FEATURES)),
176 },
177 suggested_protocol: Some(DeltaSuggestedProtocol {
178 reader_features: Some(feature_vec(contract::SUGGESTED_READER_FEATURES)),
179 writer_features: Some(feature_vec(contract::SUGGESTED_WRITER_FEATURES)),
180 }),
181 required_properties: contract::required_properties(&staging.table_id),
182 suggested_properties: Some(contract::suggested_properties()),
183 })
184 }
185
186 async fn create_table(
187 &self,
188 path: SchemaRef,
189 request: DeltaCreateTableRequest,
190 context: Cx,
191 ) -> Result<DeltaLoadTableResponse> {
192 if request.name.is_empty() {
193 return Err(DeltaApiError::invalid_argument("Table name is required."));
194 }
195 if request.location.is_empty() {
196 return Err(DeltaApiError::invalid_argument(
197 "Table location is required.",
198 ));
199 }
200
201 self.authorize(
202 DeltaAction::CreateTable {
203 at: &path,
204 name: &request.name,
205 table_type: request.table_type,
206 },
207 &context,
208 )
209 .await?;
210
211 if request.table_type == DeltaTableType::Managed {
213 contract::validate(
214 &request.protocol,
215 request.domain_metadata.as_ref(),
216 &request.properties,
217 )?;
218 }
219
220 let columns =
221 contract::delta_columns_to_uc(&request.columns, request.partition_columns.as_deref())?;
222 let stored_properties = contract::build_stored_properties(&request);
223
224 let (table_id, adopt_staging) = if request.table_type == DeltaTableType::Managed {
228 let staging = self
229 .resolve_staging_by_location(&request.location, &context)
230 .await?;
231 self.authorize(
234 DeltaAction::AdoptStaging {
235 reservation: &staging,
236 },
237 &context,
238 )
239 .await?;
240 if staging.stage_committed {
241 return Err(DeltaApiError::invalid_argument(format!(
242 "staging table at '{}' has already been committed",
243 request.location
244 )));
245 }
246 contract::validate_table_id_property(&request.properties, &staging.table_id)?;
247 (Some(staging.table_id.clone()), Some(staging))
248 } else {
249 self.validate_external_location(&request.location, &context)
251 .await?;
252 (None, None)
253 };
254
255 let full_name = format!("{}.{}.{}", path.catalog, path.schema, request.name);
256 let stored = self
257 .create_table_row(
258 CreateTableSpec {
259 at: path,
260 name: request.name,
261 table_type: request.table_type,
262 location: request.location,
263 comment: request.comment,
264 columns,
265 properties: stored_properties,
266 table_id,
267 adopt_staging,
268 },
269 &context,
270 )
271 .await?;
272
273 build_load_table_response(self, &full_name, stored).await
274 }
275
276 async fn load_table(&self, path: TableRef, context: Cx) -> Result<DeltaLoadTableResponse> {
277 self.authorize(DeltaAction::ReadTable { table: &path }, &context)
278 .await?;
279 let table = self.resolve_table(&path, &context).await?;
280 build_load_table_response(self, &path.full_name(), table).await
281 }
282
283 async fn update_table(
284 &self,
285 path: TableRef,
286 request: DeltaUpdateTableRequest,
287 context: Cx,
288 ) -> Result<DeltaLoadTableResponse> {
289 update_table_impl(self, path, request, context).await
290 }
291
292 async fn delete_table(&self, path: TableRef, context: Cx) -> Result<()> {
293 self.authorize(DeltaAction::DeleteTable { table: &path }, &context)
294 .await?;
295 self.delete_table(&path, &context).await?;
296 Ok(())
297 }
298
299 async fn table_exists(&self, path: TableRef, context: Cx) -> Result<()> {
300 self.authorize(DeltaAction::ReadTable { table: &path }, &context)
301 .await?;
302 self.resolve_table(&path, &context).await?;
303 Ok(())
304 }
305
306 async fn rename_table(
307 &self,
308 path: TableRef,
309 request: DeltaRenameTableRequest,
310 context: Cx,
311 ) -> Result<()> {
312 self.authorize(
313 DeltaAction::RenameTable {
314 from: &path,
315 to: &request.new_name,
316 },
317 &context,
318 )
319 .await?;
320 self.rename_table(&path, &request.new_name, &context)
321 .await?;
322 Ok(())
323 }
324
325 async fn get_table_credentials(
326 &self,
327 path: TableRef,
328 operation: DeltaCredentialOperation,
329 context: Cx,
330 ) -> Result<DeltaCredentialsResponse> {
331 let table = self.resolve_table(&path, &context).await?;
332 let table_id = table
333 .table_id
334 .ok_or_else(|| DeltaApiError::invalid_argument("table has no id"))?;
335 let access = to_access(operation);
336 self.authorize(
337 DeltaAction::VendTableCredential {
338 table_id: &table_id,
339 access,
340 },
341 &context,
342 )
343 .await?;
344 let creds = self
345 .vend_table_credential(&table_id, access, &context)
346 .await?;
347 Ok(DeltaCredentialsResponse {
348 storage_credentials: vec![to_storage_credential(&creds.url, &creds, operation)],
349 })
350 }
351
352 async fn report_metrics(
353 &self,
354 path: TableRef,
355 request: DeltaReportMetricsRequest,
356 context: Cx,
357 ) -> Result<()> {
358 let table = self.resolve_table(&path, &context).await?;
359 let table_id = table
360 .table_id
361 .clone()
362 .ok_or_else(|| DeltaApiError::invalid_argument("table has no id"))?;
363 self.authorize(
368 DeltaAction::WriteTable {
369 table: &path,
370 table_id: &table_id,
371 },
372 &context,
373 )
374 .await?;
375 if table.table_id.as_deref() != Some(request.table_id.as_str()) {
376 return Err(DeltaApiError::invalid_argument(
377 "report table-id does not match the table identified by the path",
378 ));
379 }
380 if let Some(cv) = request
381 .report
382 .as_ref()
383 .and_then(|r| r.commit_report.as_ref())
384 .and_then(|c| c.file_size_histogram.as_ref())
385 .and_then(|h| h.commit_version)
386 && cv < 0
387 {
388 return Err(DeltaApiError::invalid_argument(
389 "commit-version must be non-negative",
390 ));
391 }
392 Ok(())
394 }
395
396 async fn get_staging_table_credentials(
397 &self,
398 table_id: String,
399 context: Cx,
400 ) -> Result<DeltaCredentialsResponse> {
401 let staging = self.resolve_staging_by_id(&table_id, &context).await?;
402 self.authorize(
403 DeltaAction::VendPathCredential {
404 location: &staging.location,
405 access: CredentialAccess::ReadWrite,
406 },
407 &context,
408 )
409 .await?;
410 let creds = self
411 .vend_path_credential(&staging.location, CredentialAccess::ReadWrite, &context)
412 .await?;
413 Ok(DeltaCredentialsResponse {
414 storage_credentials: vec![to_storage_credential(
415 &staging.location,
416 &creds,
417 DeltaCredentialOperation::ReadWrite,
418 )],
419 })
420 }
421
422 async fn get_temporary_path_credentials(
423 &self,
424 location: String,
425 operation: DeltaCredentialOperation,
426 context: Cx,
427 ) -> Result<DeltaCredentialsResponse> {
428 let access = to_access(operation);
429 self.authorize(
430 DeltaAction::VendPathCredential {
431 location: &location,
432 access,
433 },
434 &context,
435 )
436 .await?;
437 let creds = self
438 .vend_path_credential(&location, access, &context)
439 .await?;
440 Ok(DeltaCredentialsResponse {
441 storage_credentials: vec![to_storage_credential(&creds.url, &creds, operation)],
442 })
443 }
444}
445
446async fn update_table_impl<B, Cx>(
454 backend: &B,
455 path: TableRef,
456 request: DeltaUpdateTableRequest,
457 context: Cx,
458) -> Result<DeltaLoadTableResponse>
459where
460 B: DeltaBackend<Cx> + ?Sized,
461 Cx: Send + 'static,
462{
463 let mut table = backend.resolve_table(&path, &context).await?;
464 let table_uuid = table
465 .table_id
466 .clone()
467 .ok_or_else(|| DeltaApiError::invalid_argument("table has no id"))?;
468 backend
469 .authorize(
470 DeltaAction::WriteTable {
471 table: &path,
472 table_id: &table_uuid,
473 },
474 &context,
475 )
476 .await?;
477
478 let has_uuid_assert = request
489 .requirements
490 .iter()
491 .any(|r| matches!(r, DeltaTableRequirement::AssertTableUuid { .. }));
492 if !has_uuid_assert {
493 return Err(DeltaApiError::invalid_argument(
494 "assert-table-uuid requirement is required.",
495 ));
496 }
497 let current_etag = etag_of(&table);
498 let mut expected_etag: Option<String> = None;
499 for req in &request.requirements {
500 match req {
501 DeltaTableRequirement::AssertTableUuid { uuid } => {
502 if uuid != &table_uuid {
503 return Err(DeltaApiError(DeltaBackendError::UpdateRequirementConflict(
504 format!(
505 "assert-table-uuid failed: expected {uuid} but table has {table_uuid}"
506 ),
507 )));
508 }
509 }
510 DeltaTableRequirement::AssertEtag { etag } => {
511 if etag != ¤t_etag {
512 return Err(DeltaApiError(DeltaBackendError::UpdateRequirementConflict(
513 "assert-etag failed: table has been modified".to_string(),
514 )));
515 }
516 expected_etag = Some(etag.clone());
517 }
518 }
519 }
520
521 let set_prop_keys: Vec<&String> = request
523 .updates
524 .iter()
525 .filter_map(|u| match u {
526 DeltaTableUpdate::SetProperties { updates } => Some(updates.keys()),
527 _ => None,
528 })
529 .flatten()
530 .collect();
531 if let Some(removals) = request.updates.iter().find_map(|u| match u {
532 DeltaTableUpdate::RemoveProperties { removals } => Some(removals),
533 _ => None,
534 }) {
535 for r in removals {
536 if set_prop_keys.contains(&r) {
537 return Err(DeltaApiError::invalid_argument(format!(
538 "set-properties and remove-properties overlap on key: {r}"
539 )));
540 }
541 }
542 }
543
544 let mut properties: BTreeMap<String, String> = std::mem::take(&mut table.properties);
547 let mut columns: Vec<Column> = std::mem::take(&mut table.columns);
548 let mut comment: Option<String> = None;
549 let is_managed = table.table_type == Some(DeltaTableType::Managed);
550 let mut metadata_changed = false;
551
552 if apply_schema_and_partitions(&mut columns, &request.updates)? {
555 metadata_changed = true;
556 }
557
558 if let Some(protocol) = request.updates.iter().find_map(|u| match u {
560 DeltaTableUpdate::SetProtocol { protocol } => Some(protocol),
561 _ => None,
562 }) {
563 properties.retain(|k, _| !k.starts_with("delta.feature."));
564 contract::derive_from_protocol(&mut properties, protocol);
565 if is_managed {
566 contract::validate(protocol, None, &properties)?;
567 }
568 metadata_changed = true;
569 }
570
571 for update in &request.updates {
573 match update {
574 DeltaTableUpdate::SetProperties { updates } => {
575 properties.extend(updates.clone());
576 metadata_changed = true;
577 }
578 DeltaTableUpdate::RemoveProperties { removals } => {
579 for k in removals {
580 properties.remove(k);
581 }
582 metadata_changed = true;
583 }
584 _ => {}
585 }
586 }
587
588 for update in &request.updates {
590 match update {
591 DeltaTableUpdate::SetDomainMetadata { updates } => {
592 contract::derive_from_domain_metadata(&mut properties, updates);
593 metadata_changed = true;
594 }
595 DeltaTableUpdate::RemoveDomainMetadata { domains } => {
596 for d in domains {
597 match d.as_str() {
598 "delta.clustering" => {
599 properties.remove("delta.clusteringColumns");
600 }
601 "delta.rowTracking" => {
602 properties.remove("delta.rowTracking.rowIdHighWaterMark");
603 }
604 other => {
605 return Err(DeltaApiError::invalid_argument(format!(
606 "Unknown domain in remove-domain-metadata: {other}"
607 )));
608 }
609 }
610 }
611 metadata_changed = true;
612 }
613 _ => {}
614 }
615 }
616
617 if let Some(c) = request.updates.iter().find_map(|u| match u {
619 DeltaTableUpdate::SetTableComment { comment } => Some(comment.clone()),
620 _ => None,
621 }) {
622 comment = Some(c);
623 metadata_changed = true;
624 }
625
626 if let Some((v, ts)) = request.updates.iter().find_map(|u| match u {
628 DeltaTableUpdate::UpdateMetadataSnapshotVersion {
629 last_commit_version,
630 last_commit_timestamp_ms,
631 } => Some((*last_commit_version, *last_commit_timestamp_ms)),
632 _ => None,
633 }) {
634 if is_managed {
635 return Err(DeltaApiError::invalid_argument(
636 "update-metadata-snapshot-version is only valid for EXTERNAL tables",
637 ));
638 }
639 properties.insert(
640 contract::PROP_LAST_UPDATE_VERSION.to_string(),
641 v.to_string(),
642 );
643 properties.insert(
644 contract::PROP_LAST_COMMIT_TIMESTAMP.to_string(),
645 ts.to_string(),
646 );
647 metadata_changed = true;
648 }
649
650 let add_commit = request.updates.iter().find_map(|u| match u {
652 DeltaTableUpdate::AddCommit { commit, .. } => Some(commit.clone()),
653 _ => None,
654 });
655 let backfill = request.updates.iter().find_map(|u| match u {
656 DeltaTableUpdate::SetLatestBackfilledVersion {
657 latest_published_version,
658 } => Some(*latest_published_version),
659 _ => None,
660 });
661 if add_commit.is_some() || backfill.is_some() {
662 if !is_managed {
663 return Err(DeltaApiError::invalid_argument(
664 "add-commit / set-latest-backfilled-version require a MANAGED table",
665 ));
666 }
667 let commit_info = add_commit.map(|c| CommitInfo {
668 version: c.version,
669 timestamp: c.timestamp,
670 file_name: c.file_name,
671 file_size: c.file_size,
672 file_modification_timestamp: c.file_modification_timestamp,
673 });
674 backend
675 .commit_coordinator()
676 .commit(&table_uuid, commit_info, backfill)
677 .await
678 .map_err(commit_error)?;
679 }
680
681 let refreshed = if metadata_changed {
685 backend
686 .update_table_row(
687 UpdateTableSpec {
688 table_id: table_uuid,
689 columns,
690 properties,
691 comment,
692 expected_etag,
693 },
694 &context,
695 )
696 .await?
697 } else {
698 table.properties = properties;
701 table.columns = columns;
702 table
703 };
704 build_load_table_response(backend, &path.full_name(), refreshed).await
705}
706
707fn apply_schema_and_partitions(
710 columns: &mut Vec<Column>,
711 updates: &[DeltaTableUpdate],
712) -> Result<bool> {
713 let new_columns = updates.iter().find_map(|u| match u {
714 DeltaTableUpdate::SetColumns { columns } => Some(columns),
715 _ => None,
716 });
717 let new_partitions = updates.iter().find_map(|u| match u {
718 DeltaTableUpdate::SetPartitionColumns { partition_columns } => Some(partition_columns),
719 _ => None,
720 });
721 if new_columns.is_none() && new_partitions.is_none() {
722 return Ok(false);
723 }
724
725 let result: Vec<Column> = match new_columns {
726 Some(struct_type) => {
727 let existing_partitions = partition_names(columns);
729 let partitions = new_partitions.cloned().unwrap_or(existing_partitions);
730 contract::delta_columns_to_uc(struct_type, Some(&partitions))?
731 }
732 None => {
733 let partitions = new_partitions.expect("checked above");
734 let mut cols = columns.clone();
735 for col in &mut cols {
736 col.partition_index = partitions
737 .iter()
738 .position(|p| p.eq_ignore_ascii_case(&col.name))
739 .map(|i| i as i32);
740 }
741 for p in partitions {
742 if !cols.iter().any(|c| c.name.eq_ignore_ascii_case(p)) {
743 return Err(DeltaApiError::invalid_argument(format!(
744 "partition column '{p}' is not present in the table schema"
745 )));
746 }
747 }
748 cols
749 }
750 };
751 *columns = result;
752 Ok(true)
753}
754
755async fn build_load_table_response<B, Cx>(
765 backend: &B,
766 full_name: &str,
767 table: ResolvedTable,
768) -> Result<DeltaLoadTableResponse>
769where
770 B: DeltaBackend<Cx> + ?Sized,
771{
772 let Some(table_type) = table.table_type else {
773 return Err(DeltaApiError::invalid_argument(format!(
774 "table '{full_name}' is not a Delta table and cannot be loaded via the Delta API"
775 )));
776 };
777
778 let (commits, latest_table_version) = if table_type == DeltaTableType::Managed
781 && table.data_source_format == Some(DeltaDataSourceFormat::Delta)
782 && let Some(id) = table.table_id.as_deref()
783 {
784 let (commits, latest) = backend
785 .commit_coordinator()
786 .get_commits(id, 0, None)
787 .await
788 .map_err(commit_error)?;
789 (
790 Some(commits.into_iter().map(to_delta_commit).collect()),
791 Some(latest),
792 )
793 } else {
794 (None, None)
795 };
796
797 Ok(DeltaLoadTableResponse {
798 metadata: build_table_metadata(table, table_type),
799 commits,
800 uniform: None,
801 latest_table_version,
802 })
803}
804
805fn build_table_metadata(table: ResolvedTable, table_type: DeltaTableType) -> DeltaTableMetadata {
806 let etag = etag_of(&table);
807 let partition_columns = partition_names(&table.columns);
808 let columns = contract::uc_columns_to_delta(&table.columns);
809 let last_commit_version = table
810 .properties
811 .get(contract::PROP_LAST_UPDATE_VERSION)
812 .and_then(|v| v.parse().ok());
813 let last_commit_timestamp_ms = table
814 .properties
815 .get(contract::PROP_LAST_COMMIT_TIMESTAMP)
816 .and_then(|v| v.parse().ok());
817
818 DeltaTableMetadata {
819 etag,
820 table_type,
821 table_uuid: table.table_id.unwrap_or_default(),
822 location: table.location,
823 created_time: table.created_at_ms.unwrap_or_default(),
824 updated_time: table
825 .updated_at_ms
826 .or(table.created_at_ms)
827 .unwrap_or_default(),
828 columns,
829 partition_columns: (!partition_columns.is_empty()).then_some(partition_columns),
830 properties: table.properties,
831 last_commit_version,
832 last_commit_timestamp_ms,
833 }
834}
835
836fn feature_vec(features: &[&str]) -> Vec<String> {
841 features.iter().map(|s| s.to_string()).collect()
842}
843
844fn to_access(op: DeltaCredentialOperation) -> CredentialAccess {
845 match op {
846 DeltaCredentialOperation::Read => CredentialAccess::Read,
847 DeltaCredentialOperation::ReadWrite => CredentialAccess::ReadWrite,
848 }
849}
850
851fn partition_names(columns: &[Column]) -> Vec<String> {
853 let mut p: Vec<(&i32, &Column)> = columns
854 .iter()
855 .filter_map(|c| c.partition_index.as_ref().map(|idx| (idx, c)))
856 .collect();
857 p.sort_by_key(|(idx, _)| **idx);
858 p.into_iter().map(|(_, c)| c.name.clone()).collect()
859}
860
861fn to_delta_commit(c: CommitInfo) -> DeltaCommit {
862 DeltaCommit {
863 version: c.version,
864 timestamp: c.timestamp,
865 file_name: c.file_name,
866 file_size: c.file_size,
867 file_modification_timestamp: c.file_modification_timestamp,
868 }
869}
870
871fn commit_error(err: crate::coordinator::CommitError) -> DeltaApiError {
874 use crate::coordinator::CommitError;
875 DeltaApiError(match err {
876 CommitError::VersionConflict(m) => DeltaBackendError::CommitVersionConflict(m),
877 CommitError::InvalidArgument(m) => DeltaBackendError::InvalidArgument(m),
878 CommitError::ResourceExhausted(m) => DeltaBackendError::ResourceExhausted(m),
879 CommitError::Backend(m) => DeltaBackendError::Internal(m),
880 })
881}
882
883fn to_storage_credential(
885 prefix: &str,
886 creds: &VendedCredential,
887 operation: DeltaCredentialOperation,
888) -> DeltaStorageCredential {
889 let mut config = DeltaStorageCredentialConfig::default();
890 match &creds.kind {
891 VendedCredentialKind::S3 {
892 access_key_id,
893 secret_access_key,
894 session_token,
895 } => {
896 config.s3_access_key_id = Some(access_key_id.clone());
897 config.s3_secret_access_key = Some(secret_access_key.clone());
898 config.s3_session_token = session_token.clone();
899 }
900 VendedCredentialKind::AzureSas { sas_token } => {
901 config.azure_sas_token = Some(sas_token.clone());
902 }
903 VendedCredentialKind::GcsOauth { oauth_token } => {
904 config.gcs_oauth_token = Some(oauth_token.clone());
905 }
906 VendedCredentialKind::None => {}
907 }
908 DeltaStorageCredential {
909 prefix: prefix.to_string(),
910 operation,
911 config,
912 expiration_time_ms: Some(creds.expiration_time_ms),
913 }
914}