1pub mod memory;
21mod metadata_location;
22mod session;
23pub(crate) mod utils;
24
25use std::collections::HashMap;
26use std::fmt::{Debug, Display};
27use std::future::Future;
28use std::mem::take;
29use std::ops::Deref;
30use std::str::FromStr;
31use std::sync::Arc;
32
33use _serde::{deserialize_snapshot, serialize_snapshot};
34use async_trait::async_trait;
35pub use memory::MemoryCatalog;
36pub use metadata_location::*;
37#[cfg(test)]
38use mockall::automock;
39use serde_derive::{Deserialize, Serialize};
40pub use session::*;
41use typed_builder::TypedBuilder;
42use uuid::Uuid;
43
44use crate::encryption::kms::KmsClientFactory;
45use crate::io::StorageFactory;
46use crate::runtime::Runtime;
47use crate::spec::{
48 EncryptedKey, FormatVersion, PartitionStatisticsFile, Schema, SchemaId, Snapshot,
49 SnapshotReference, SortOrder, StatisticsFile, TableMetadata, TableMetadataBuilder,
50 UnboundPartitionSpec, ViewFormatVersion, ViewRepresentations, ViewVersion,
51};
52use crate::table::Table;
53use crate::{Error, ErrorKind, Result};
54
55#[async_trait]
57#[cfg_attr(test, automock)]
58pub trait Catalog: Debug + Sync + Send {
59 async fn list_namespaces(&self, parent: Option<&NamespaceIdent>)
61 -> Result<Vec<NamespaceIdent>>;
62
63 async fn create_namespace(
65 &self,
66 namespace: &NamespaceIdent,
67 properties: HashMap<String, String>,
68 ) -> Result<Namespace>;
69
70 async fn get_namespace(&self, namespace: &NamespaceIdent) -> Result<Namespace>;
72
73 async fn namespace_exists(&self, namespace: &NamespaceIdent) -> Result<bool>;
75
76 async fn update_namespace(
82 &self,
83 namespace: &NamespaceIdent,
84 properties: HashMap<String, String>,
85 ) -> Result<()>;
86
87 async fn drop_namespace(&self, namespace: &NamespaceIdent) -> Result<()>;
89
90 async fn list_tables(&self, namespace: &NamespaceIdent) -> Result<Vec<TableIdent>>;
92
93 async fn create_table(
95 &self,
96 namespace: &NamespaceIdent,
97 creation: TableCreation,
98 ) -> Result<Table>;
99
100 async fn load_table(&self, table: &TableIdent) -> Result<Table>;
102
103 async fn drop_table(&self, table: &TableIdent) -> Result<()>;
105
106 async fn purge_table(&self, table: &TableIdent) -> Result<()>;
113
114 async fn table_exists(&self, table: &TableIdent) -> Result<bool>;
116
117 async fn rename_table(&self, src: &TableIdent, dest: &TableIdent) -> Result<()>;
119
120 async fn register_table(&self, table: &TableIdent, metadata_location: String) -> Result<Table>;
122
123 async fn update_table(&self, commit: TableCommit) -> Result<Table>;
125}
126
127pub trait CatalogBuilder: Default + Debug + Send + Sync {
129 type C: Catalog;
131
132 fn with_storage_factory(self, storage_factory: Arc<dyn StorageFactory>) -> Self;
157
158 fn with_kms_client_factory(self, kms_client_factory: Arc<dyn KmsClientFactory>) -> Self;
179
180 fn with_runtime(self, runtime: Runtime) -> Self;
186
187 fn load(
189 self,
190 name: impl Into<String>,
191 props: HashMap<String, String>,
192 ) -> impl Future<Output = Result<Self::C>> + Send;
193}
194
195#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
201pub struct NamespaceIdent(Vec<String>);
202
203impl NamespaceIdent {
204 pub fn new(name: String) -> Self {
206 Self(vec![name])
207 }
208
209 pub fn from_vec(names: Vec<String>) -> Result<Self> {
211 if names.is_empty() {
212 return Err(Error::new(
213 ErrorKind::DataInvalid,
214 "Namespace identifier can't be empty!",
215 ));
216 }
217 Ok(Self(names))
218 }
219
220 pub fn from_strs(iter: impl IntoIterator<Item = impl ToString>) -> Result<Self> {
222 Self::from_vec(iter.into_iter().map(|s| s.to_string()).collect())
223 }
224
225 pub fn to_url_string(&self) -> String {
227 self.as_ref().join("\u{001f}")
228 }
229
230 pub fn inner(self) -> Vec<String> {
232 self.0
233 }
234
235 pub fn parent(&self) -> Option<Self> {
238 self.0.split_last().and_then(|(_, parent)| {
239 if parent.is_empty() {
240 None
241 } else {
242 Some(Self(parent.to_vec()))
243 }
244 })
245 }
246}
247
248impl AsRef<Vec<String>> for NamespaceIdent {
249 fn as_ref(&self) -> &Vec<String> {
250 &self.0
251 }
252}
253
254impl Deref for NamespaceIdent {
255 type Target = [String];
256
257 fn deref(&self) -> &Self::Target {
258 &self.0
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Eq)]
264pub struct Namespace {
265 name: NamespaceIdent,
266 properties: HashMap<String, String>,
267}
268
269impl Namespace {
270 pub fn new(name: NamespaceIdent) -> Self {
272 Self::with_properties(name, HashMap::default())
273 }
274
275 pub fn with_properties(name: NamespaceIdent, properties: HashMap<String, String>) -> Self {
277 Self { name, properties }
278 }
279
280 pub fn name(&self) -> &NamespaceIdent {
282 &self.name
283 }
284
285 pub fn properties(&self) -> &HashMap<String, String> {
287 &self.properties
288 }
289}
290
291impl Display for NamespaceIdent {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 write!(f, "{}", self.0.join("."))
294 }
295}
296
297#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
299pub struct TableIdent {
300 pub namespace: NamespaceIdent,
302 pub name: String,
304}
305
306impl TableIdent {
307 pub fn new(namespace: NamespaceIdent, name: String) -> Self {
309 Self { namespace, name }
310 }
311
312 pub fn namespace(&self) -> &NamespaceIdent {
314 &self.namespace
315 }
316
317 pub fn name(&self) -> &str {
319 &self.name
320 }
321
322 pub fn from_strs(iter: impl IntoIterator<Item = impl ToString>) -> Result<Self> {
324 let mut vec: Vec<String> = iter.into_iter().map(|s| s.to_string()).collect();
325 let table_name = vec.pop().ok_or_else(|| {
326 Error::new(ErrorKind::DataInvalid, "Table identifier can't be empty!")
327 })?;
328 let namespace_ident = NamespaceIdent::from_vec(vec)?;
329
330 Ok(Self {
331 namespace: namespace_ident,
332 name: table_name,
333 })
334 }
335}
336
337impl Display for TableIdent {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 write!(f, "{}.{}", self.namespace, self.name)
340 }
341}
342
343#[derive(Debug, TypedBuilder)]
345pub struct TableCreation {
346 pub name: String,
348 #[builder(default, setter(strip_option(fallback = location_opt)))]
350 pub location: Option<String>,
351 pub schema: Schema,
353 #[builder(default, setter(strip_option(fallback = partition_spec_opt), into))]
355 pub partition_spec: Option<UnboundPartitionSpec>,
356 #[builder(default, setter(strip_option(fallback = sort_order_opt)))]
358 pub sort_order: Option<SortOrder>,
359 #[builder(default, setter(transform = |props: impl IntoIterator<Item=(String, String)>| {
361 props.into_iter().collect()
362 }))]
363 pub properties: HashMap<String, String>,
364 #[builder(default = FormatVersion::V2)]
366 pub format_version: FormatVersion,
367}
368
369#[derive(Debug, TypedBuilder)]
375#[builder(build_method(vis = "pub(crate)"))]
376pub struct TableCommit {
377 ident: TableIdent,
379 requirements: Vec<TableRequirement>,
383 updates: Vec<TableUpdate>,
385}
386
387impl TableCommit {
388 pub fn identifier(&self) -> &TableIdent {
390 &self.ident
391 }
392
393 pub fn take_requirements(&mut self) -> Vec<TableRequirement> {
395 take(&mut self.requirements)
396 }
397
398 pub fn take_updates(&mut self) -> Vec<TableUpdate> {
400 take(&mut self.updates)
401 }
402
403 pub fn apply(self, table: Table) -> Result<Table> {
409 for requirement in self.requirements {
411 requirement.check(Some(table.metadata()))?;
412 }
413
414 let current_metadata_location = table.metadata_location_result()?;
416
417 let mut metadata_builder = table
419 .metadata()
420 .clone()
421 .into_builder(Some(current_metadata_location.to_string()));
422 for update in self.updates {
423 metadata_builder = update.apply(metadata_builder)?;
424 }
425
426 let new_metadata = metadata_builder.build()?.metadata;
428
429 let new_metadata_location = MetadataLocation::from_str(current_metadata_location)?
430 .with_next_version()
431 .try_with_new_metadata(&new_metadata)?
432 .to_string();
433
434 Ok(table
435 .with_metadata(Arc::new(new_metadata))
436 .with_metadata_location(new_metadata_location))
437 }
438}
439
440#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
442#[serde(tag = "type")]
443pub enum TableRequirement {
444 #[serde(rename = "assert-create")]
446 NotExist,
447 #[serde(rename = "assert-table-uuid")]
449 UuidMatch {
450 uuid: Uuid,
452 },
453 #[serde(rename = "assert-ref-snapshot-id")]
456 RefSnapshotIdMatch {
457 r#ref: String,
459 #[serde(rename = "snapshot-id")]
462 snapshot_id: Option<i64>,
463 },
464 #[serde(rename = "assert-last-assigned-field-id")]
466 LastAssignedFieldIdMatch {
467 #[serde(rename = "last-assigned-field-id")]
469 last_assigned_field_id: i32,
470 },
471 #[serde(rename = "assert-current-schema-id")]
473 CurrentSchemaIdMatch {
474 #[serde(rename = "current-schema-id")]
476 current_schema_id: SchemaId,
477 },
478 #[serde(rename = "assert-last-assigned-partition-id")]
481 LastAssignedPartitionIdMatch {
482 #[serde(rename = "last-assigned-partition-id")]
484 last_assigned_partition_id: i32,
485 },
486 #[serde(rename = "assert-default-spec-id")]
488 DefaultSpecIdMatch {
489 #[serde(rename = "default-spec-id")]
491 default_spec_id: i32,
492 },
493 #[serde(rename = "assert-default-sort-order-id")]
495 DefaultSortOrderIdMatch {
496 #[serde(rename = "default-sort-order-id")]
498 default_sort_order_id: i64,
499 },
500}
501
502#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
504#[serde(tag = "action", rename_all = "kebab-case")]
505#[allow(clippy::large_enum_variant)]
506pub enum TableUpdate {
507 #[serde(rename_all = "kebab-case")]
509 UpgradeFormatVersion {
510 format_version: FormatVersion,
512 },
513 #[serde(rename_all = "kebab-case")]
515 AssignUuid {
516 uuid: Uuid,
518 },
519 #[serde(rename_all = "kebab-case")]
521 AddSchema {
522 schema: Schema,
524 },
525 #[serde(rename_all = "kebab-case")]
527 SetCurrentSchema {
528 schema_id: i32,
530 },
531 AddSpec {
533 spec: UnboundPartitionSpec,
535 },
536 #[serde(rename_all = "kebab-case")]
538 SetDefaultSpec {
539 spec_id: i32,
541 },
542 #[serde(rename_all = "kebab-case")]
544 AddSortOrder {
545 sort_order: SortOrder,
547 },
548 #[serde(rename_all = "kebab-case")]
550 SetDefaultSortOrder {
551 sort_order_id: i64,
553 },
554 #[serde(rename_all = "kebab-case")]
556 AddSnapshot {
557 #[serde(
559 deserialize_with = "deserialize_snapshot",
560 serialize_with = "serialize_snapshot"
561 )]
562 snapshot: Snapshot,
563 },
564 #[serde(rename_all = "kebab-case")]
566 SetSnapshotRef {
567 ref_name: String,
569 #[serde(flatten)]
571 reference: SnapshotReference,
572 },
573 #[serde(rename_all = "kebab-case")]
575 RemoveSnapshots {
576 snapshot_ids: Vec<i64>,
578 },
579 #[serde(rename_all = "kebab-case")]
581 RemoveSnapshotRef {
582 ref_name: String,
584 },
585 SetLocation {
587 location: String,
589 },
590 SetProperties {
592 updates: HashMap<String, String>,
594 },
595 RemoveProperties {
597 removals: Vec<String>,
599 },
600 #[serde(rename_all = "kebab-case")]
602 RemovePartitionSpecs {
603 spec_ids: Vec<i32>,
605 },
606 #[serde(with = "_serde_set_statistics")]
608 SetStatistics {
609 statistics: StatisticsFile,
611 },
612 #[serde(rename_all = "kebab-case")]
614 RemoveStatistics {
615 snapshot_id: i64,
617 },
618 #[serde(rename_all = "kebab-case")]
620 SetPartitionStatistics {
621 partition_statistics: PartitionStatisticsFile,
623 },
624 #[serde(rename_all = "kebab-case")]
626 RemovePartitionStatistics {
627 snapshot_id: i64,
629 },
630 #[serde(rename_all = "kebab-case")]
632 RemoveSchemas {
633 schema_ids: Vec<i32>,
635 },
636 #[serde(rename_all = "kebab-case")]
638 AddEncryptionKey {
639 encryption_key: EncryptedKey,
641 },
642 #[serde(rename_all = "kebab-case")]
644 RemoveEncryptionKey {
645 key_id: String,
647 },
648}
649
650impl TableUpdate {
651 pub fn apply(self, builder: TableMetadataBuilder) -> Result<TableMetadataBuilder> {
653 match self {
654 TableUpdate::AssignUuid { uuid } => Ok(builder.assign_uuid(uuid)),
655 TableUpdate::AddSchema { schema, .. } => Ok(builder.add_schema(schema)?),
656 TableUpdate::SetCurrentSchema { schema_id } => builder.set_current_schema(schema_id),
657 TableUpdate::AddSpec { spec } => builder.add_partition_spec(spec),
658 TableUpdate::SetDefaultSpec { spec_id } => builder.set_default_partition_spec(spec_id),
659 TableUpdate::AddSortOrder { sort_order } => builder.add_sort_order(sort_order),
660 TableUpdate::SetDefaultSortOrder { sort_order_id } => {
661 builder.set_default_sort_order(sort_order_id)
662 }
663 TableUpdate::AddSnapshot { snapshot } => builder.add_snapshot(snapshot),
664 TableUpdate::SetSnapshotRef {
665 ref_name,
666 reference,
667 } => builder.set_ref(&ref_name, reference),
668 TableUpdate::RemoveSnapshots { snapshot_ids } => {
669 Ok(builder.remove_snapshots(&snapshot_ids))
670 }
671 TableUpdate::RemoveSnapshotRef { ref_name } => Ok(builder.remove_ref(&ref_name)),
672 TableUpdate::SetLocation { location } => Ok(builder.set_location(location)),
673 TableUpdate::SetProperties { updates } => builder.set_properties(updates),
674 TableUpdate::RemoveProperties { removals } => builder.remove_properties(&removals),
675 TableUpdate::UpgradeFormatVersion { format_version } => {
676 builder.upgrade_format_version(format_version)
677 }
678 TableUpdate::RemovePartitionSpecs { spec_ids } => {
679 builder.remove_partition_specs(&spec_ids)
680 }
681 TableUpdate::SetStatistics { statistics } => Ok(builder.set_statistics(statistics)),
682 TableUpdate::RemoveStatistics { snapshot_id } => {
683 Ok(builder.remove_statistics(snapshot_id))
684 }
685 TableUpdate::SetPartitionStatistics {
686 partition_statistics,
687 } => Ok(builder.set_partition_statistics(partition_statistics)),
688 TableUpdate::RemovePartitionStatistics { snapshot_id } => {
689 Ok(builder.remove_partition_statistics(snapshot_id))
690 }
691 TableUpdate::RemoveSchemas { schema_ids } => builder.remove_schemas(&schema_ids),
692 TableUpdate::AddEncryptionKey { encryption_key } => {
693 Ok(builder.add_encryption_key(encryption_key))
694 }
695 TableUpdate::RemoveEncryptionKey { key_id } => {
696 Ok(builder.remove_encryption_key(&key_id))
697 }
698 }
699 }
700}
701
702impl TableRequirement {
703 pub fn check(&self, metadata: Option<&TableMetadata>) -> Result<()> {
708 if let Some(metadata) = metadata {
709 match self {
710 TableRequirement::NotExist => {
711 return Err(Error::new(
712 ErrorKind::CatalogCommitConflicts,
713 format!(
714 "Requirement failed: Table with id {} already exists",
715 metadata.uuid()
716 ),
717 )
718 .with_retryable(true));
719 }
720 TableRequirement::UuidMatch { uuid } => {
721 if &metadata.uuid() != uuid {
722 return Err(Error::new(
723 ErrorKind::CatalogCommitConflicts,
724 "Requirement failed: Table UUID does not match",
725 )
726 .with_context("expected", *uuid)
727 .with_context("found", metadata.uuid())
728 .with_retryable(true));
729 }
730 }
731 TableRequirement::CurrentSchemaIdMatch { current_schema_id } => {
732 if metadata.current_schema_id != *current_schema_id {
734 return Err(Error::new(
735 ErrorKind::CatalogCommitConflicts,
736 "Requirement failed: Current schema id does not match",
737 )
738 .with_context("expected", current_schema_id.to_string())
739 .with_context("found", metadata.current_schema_id.to_string())
740 .with_retryable(true));
741 }
742 }
743 TableRequirement::DefaultSortOrderIdMatch {
744 default_sort_order_id,
745 } => {
746 if metadata.default_sort_order().order_id != *default_sort_order_id {
747 return Err(Error::new(
748 ErrorKind::CatalogCommitConflicts,
749 "Requirement failed: Default sort order id does not match",
750 )
751 .with_context("expected", default_sort_order_id.to_string())
752 .with_context("found", metadata.default_sort_order().order_id.to_string())
753 .with_retryable(true));
754 }
755 }
756 TableRequirement::RefSnapshotIdMatch { r#ref, snapshot_id } => {
757 let snapshot_ref = metadata.snapshot_for_ref(r#ref);
758 if let Some(snapshot_id) = snapshot_id {
759 let snapshot_ref = snapshot_ref.ok_or(
760 Error::new(
761 ErrorKind::CatalogCommitConflicts,
762 format!("Requirement failed: Branch or tag `{ref}` not found"),
763 )
764 .with_retryable(true),
765 )?;
766 if snapshot_ref.snapshot_id() != *snapshot_id {
767 return Err(Error::new(
768 ErrorKind::CatalogCommitConflicts,
769 format!(
770 "Requirement failed: Branch or tag `{ref}`'s snapshot has changed"
771 ),
772 )
773 .with_context("expected", snapshot_id.to_string())
774 .with_context("found", snapshot_ref.snapshot_id().to_string())
775 .with_retryable(true));
776 }
777 } else if snapshot_ref.is_some() {
778 return Err(Error::new(
780 ErrorKind::CatalogCommitConflicts,
781 format!("Requirement failed: Branch or tag `{ref}` already exists"),
782 )
783 .with_retryable(true));
784 }
785 }
786 TableRequirement::DefaultSpecIdMatch { default_spec_id } => {
787 if metadata.default_partition_spec_id() != *default_spec_id {
789 return Err(Error::new(
790 ErrorKind::CatalogCommitConflicts,
791 "Requirement failed: Default partition spec id does not match",
792 )
793 .with_context("expected", default_spec_id.to_string())
794 .with_context("found", metadata.default_partition_spec_id().to_string())
795 .with_retryable(true));
796 }
797 }
798 TableRequirement::LastAssignedPartitionIdMatch {
799 last_assigned_partition_id,
800 } => {
801 if metadata.last_partition_id != *last_assigned_partition_id {
802 return Err(Error::new(
803 ErrorKind::CatalogCommitConflicts,
804 "Requirement failed: Last assigned partition id does not match",
805 )
806 .with_context("expected", last_assigned_partition_id.to_string())
807 .with_context("found", metadata.last_partition_id.to_string())
808 .with_retryable(true));
809 }
810 }
811 TableRequirement::LastAssignedFieldIdMatch {
812 last_assigned_field_id,
813 } => {
814 if &metadata.last_column_id != last_assigned_field_id {
815 return Err(Error::new(
816 ErrorKind::CatalogCommitConflicts,
817 "Requirement failed: Last assigned field id does not match",
818 )
819 .with_context("expected", last_assigned_field_id.to_string())
820 .with_context("found", metadata.last_column_id.to_string())
821 .with_retryable(true));
822 }
823 }
824 };
825 } else {
826 match self {
827 TableRequirement::NotExist => {}
828 _ => {
829 return Err(Error::new(
830 ErrorKind::TableNotFound,
831 "Requirement failed: Table does not exist",
832 ));
833 }
834 }
835 }
836
837 Ok(())
838 }
839}
840
841pub(super) mod _serde {
842 use serde::{Deserialize as _, Deserializer, Serialize as _};
843
844 use super::*;
845 use crate::spec::{SchemaId, Summary};
846
847 pub(super) fn deserialize_snapshot<'de, D>(
848 deserializer: D,
849 ) -> std::result::Result<Snapshot, D::Error>
850 where D: Deserializer<'de> {
851 let buf = CatalogSnapshot::deserialize(deserializer)?;
852 Ok(buf.into())
853 }
854
855 pub(super) fn serialize_snapshot<S>(
856 snapshot: &Snapshot,
857 serializer: S,
858 ) -> std::result::Result<S::Ok, S::Error>
859 where
860 S: serde::Serializer,
861 {
862 let buf: CatalogSnapshot = snapshot.clone().into();
863 buf.serialize(serializer)
864 }
865
866 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
867 #[serde(rename_all = "kebab-case")]
868 struct CatalogSnapshot {
872 snapshot_id: i64,
873 #[serde(skip_serializing_if = "Option::is_none")]
874 parent_snapshot_id: Option<i64>,
875 #[serde(default)]
876 sequence_number: i64,
877 timestamp_ms: i64,
878 manifest_list: String,
879 summary: Summary,
880 #[serde(skip_serializing_if = "Option::is_none")]
881 schema_id: Option<SchemaId>,
882 #[serde(skip_serializing_if = "Option::is_none")]
883 first_row_id: Option<u64>,
884 #[serde(skip_serializing_if = "Option::is_none")]
885 added_rows: Option<u64>,
886 #[serde(skip_serializing_if = "Option::is_none")]
887 key_id: Option<String>,
888 }
889
890 impl From<CatalogSnapshot> for Snapshot {
891 fn from(snapshot: CatalogSnapshot) -> Self {
892 let CatalogSnapshot {
893 snapshot_id,
894 parent_snapshot_id,
895 sequence_number,
896 timestamp_ms,
897 manifest_list,
898 schema_id,
899 summary,
900 first_row_id,
901 added_rows,
902 key_id,
903 } = snapshot;
904 let builder = Snapshot::builder()
905 .with_snapshot_id(snapshot_id)
906 .with_parent_snapshot_id(parent_snapshot_id)
907 .with_sequence_number(sequence_number)
908 .with_timestamp_ms(timestamp_ms)
909 .with_manifest_list(manifest_list)
910 .with_summary(summary)
911 .with_encryption_key_id(key_id);
912 let row_range = first_row_id.zip(added_rows);
913 match (schema_id, row_range) {
914 (None, None) => builder.build(),
915 (Some(schema_id), None) => builder.with_schema_id(schema_id).build(),
916 (None, Some((first_row_id, last_row_id))) => {
917 builder.with_row_range(first_row_id, last_row_id).build()
918 }
919 (Some(schema_id), Some((first_row_id, last_row_id))) => builder
920 .with_schema_id(schema_id)
921 .with_row_range(first_row_id, last_row_id)
922 .build(),
923 }
924 }
925 }
926
927 impl From<Snapshot> for CatalogSnapshot {
928 fn from(snapshot: Snapshot) -> Self {
929 let first_row_id = snapshot.first_row_id();
930 let added_rows = snapshot.added_rows_count();
931 let Snapshot {
932 snapshot_id,
933 parent_snapshot_id,
934 sequence_number,
935 timestamp_ms,
936 manifest_list,
937 summary,
938 schema_id,
939 row_range: _,
940 encryption_key_id: key_id,
941 } = snapshot;
942 CatalogSnapshot {
943 snapshot_id,
944 parent_snapshot_id,
945 sequence_number,
946 timestamp_ms,
947 manifest_list,
948 summary,
949 schema_id,
950 first_row_id,
951 added_rows,
952 key_id,
953 }
954 }
955 }
956}
957
958#[derive(Debug, TypedBuilder)]
960pub struct ViewCreation {
961 pub name: String,
963 pub location: String,
965 pub representations: ViewRepresentations,
967 pub schema: Schema,
969 #[builder(default)]
971 pub properties: HashMap<String, String>,
972 pub default_namespace: NamespaceIdent,
974 #[builder(default)]
976 pub default_catalog: Option<String>,
977 #[builder(default)]
980 pub summary: HashMap<String, String>,
981}
982
983#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
985#[serde(tag = "action", rename_all = "kebab-case")]
986#[allow(clippy::large_enum_variant)]
987pub enum ViewUpdate {
988 #[serde(rename_all = "kebab-case")]
990 AssignUuid {
991 uuid: Uuid,
993 },
994 #[serde(rename_all = "kebab-case")]
996 UpgradeFormatVersion {
997 format_version: ViewFormatVersion,
999 },
1000 #[serde(rename_all = "kebab-case")]
1002 AddSchema {
1003 schema: Schema,
1005 last_column_id: Option<i32>,
1007 },
1008 #[serde(rename_all = "kebab-case")]
1010 SetLocation {
1011 location: String,
1013 },
1014 #[serde(rename_all = "kebab-case")]
1018 SetProperties {
1019 updates: HashMap<String, String>,
1021 },
1022 #[serde(rename_all = "kebab-case")]
1024 RemoveProperties {
1025 removals: Vec<String>,
1027 },
1028 #[serde(rename_all = "kebab-case")]
1030 AddViewVersion {
1031 view_version: ViewVersion,
1033 },
1034 #[serde(rename_all = "kebab-case")]
1036 SetCurrentViewVersion {
1037 view_version_id: i32,
1039 },
1040}
1041
1042mod _serde_set_statistics {
1043 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1046
1047 use super::*;
1048
1049 #[derive(Debug, Serialize, Deserialize)]
1050 #[serde(rename_all = "kebab-case")]
1051 struct SetStatistics {
1052 snapshot_id: Option<i64>,
1053 statistics: StatisticsFile,
1054 }
1055
1056 pub fn serialize<S>(
1057 value: &StatisticsFile,
1058 serializer: S,
1059 ) -> std::result::Result<S::Ok, S::Error>
1060 where
1061 S: Serializer,
1062 {
1063 SetStatistics {
1064 snapshot_id: Some(value.snapshot_id),
1065 statistics: value.clone(),
1066 }
1067 .serialize(serializer)
1068 }
1069
1070 pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result<StatisticsFile, D::Error>
1071 where D: Deserializer<'de> {
1072 let SetStatistics {
1073 snapshot_id,
1074 statistics,
1075 } = SetStatistics::deserialize(deserializer)?;
1076 if let Some(snapshot_id) = snapshot_id
1077 && snapshot_id != statistics.snapshot_id
1078 {
1079 return Err(serde::de::Error::custom(format!(
1080 "Snapshot id to set {snapshot_id} does not match the statistics file snapshot id {}",
1081 statistics.snapshot_id
1082 )));
1083 }
1084
1085 Ok(statistics)
1086 }
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091 use std::collections::HashMap;
1092 use std::fmt::Debug;
1093 use std::fs::File;
1094 use std::io::BufReader;
1095
1096 use base64::Engine as _;
1097 use serde::Serialize;
1098 use serde::de::DeserializeOwned;
1099 use uuid::uuid;
1100
1101 use super::ViewUpdate;
1102 use crate::io::FileIO;
1103 use crate::spec::{
1104 BlobMetadata, EncryptedKey, FormatVersion, MAIN_BRANCH, NestedField, NullOrder, Operation,
1105 PartitionStatisticsFile, PrimitiveType, Schema, Snapshot, SnapshotReference,
1106 SnapshotRetention, SortDirection, SortField, SortOrder, SqlViewRepresentation,
1107 StatisticsFile, Summary, TableMetadata, TableMetadataBuilder, Transform, Type,
1108 UnboundPartitionSpec, ViewFormatVersion, ViewRepresentation, ViewRepresentations,
1109 ViewVersion,
1110 };
1111 use crate::table::Table;
1112 use crate::test_utils::test_runtime;
1113 use crate::{
1114 NamespaceIdent, TableCommit, TableCreation, TableIdent, TableRequirement, TableUpdate,
1115 };
1116
1117 #[test]
1118 fn test_parent_namespace() {
1119 let ns1 = NamespaceIdent::from_strs(vec!["ns1"]).unwrap();
1120 let ns2 = NamespaceIdent::from_strs(vec!["ns1", "ns2"]).unwrap();
1121 let ns3 = NamespaceIdent::from_strs(vec!["ns1", "ns2", "ns3"]).unwrap();
1122
1123 assert_eq!(ns1.parent(), None);
1124 assert_eq!(ns2.parent(), Some(ns1.clone()));
1125 assert_eq!(ns3.parent(), Some(ns2.clone()));
1126 }
1127
1128 #[test]
1129 fn test_create_table_id() {
1130 let table_id = TableIdent {
1131 namespace: NamespaceIdent::from_strs(vec!["ns1"]).unwrap(),
1132 name: "t1".to_string(),
1133 };
1134
1135 assert_eq!(table_id, TableIdent::from_strs(vec!["ns1", "t1"]).unwrap());
1136 }
1137
1138 #[test]
1139 fn test_table_creation_iterator_properties() {
1140 let builder = TableCreation::builder()
1141 .name("table".to_string())
1142 .schema(Schema::builder().build().unwrap());
1143
1144 fn s(k: &str, v: &str) -> (String, String) {
1145 (k.to_string(), v.to_string())
1146 }
1147
1148 let table_creation = builder
1149 .properties([s("key", "value"), s("foo", "bar")])
1150 .build();
1151
1152 assert_eq!(
1153 HashMap::from([s("key", "value"), s("foo", "bar")]),
1154 table_creation.properties
1155 );
1156 }
1157
1158 fn test_serde_json<T: Serialize + DeserializeOwned + PartialEq + Debug>(
1159 json: impl ToString,
1160 expected: T,
1161 ) {
1162 let json_str = json.to_string();
1163 let actual: T = serde_json::from_str(&json_str).expect("Failed to parse from json");
1164 assert_eq!(actual, expected, "Parsed value is not equal to expected");
1165
1166 let restored: T = serde_json::from_str(
1167 &serde_json::to_string(&actual).expect("Failed to serialize to json"),
1168 )
1169 .expect("Failed to parse from serialized json");
1170
1171 assert_eq!(
1172 restored, expected,
1173 "Parsed restored value is not equal to expected"
1174 );
1175 }
1176
1177 fn metadata() -> TableMetadata {
1178 let tbl_creation = TableCreation::builder()
1179 .name("table".to_string())
1180 .location("/path/to/table".to_string())
1181 .schema(Schema::builder().build().unwrap())
1182 .build();
1183
1184 TableMetadataBuilder::from_table_creation(tbl_creation)
1185 .unwrap()
1186 .assign_uuid(uuid::Uuid::nil())
1187 .build()
1188 .unwrap()
1189 .metadata
1190 }
1191
1192 #[test]
1193 fn test_check_requirement_not_exist() {
1194 let metadata = metadata();
1195 let requirement = TableRequirement::NotExist;
1196
1197 assert!(requirement.check(Some(&metadata)).is_err());
1198 assert!(requirement.check(None).is_ok());
1199 }
1200
1201 #[test]
1202 fn test_check_table_uuid() {
1203 let metadata = metadata();
1204
1205 let requirement = TableRequirement::UuidMatch {
1206 uuid: uuid::Uuid::now_v7(),
1207 };
1208 assert!(requirement.check(Some(&metadata)).is_err());
1209
1210 let requirement = TableRequirement::UuidMatch {
1211 uuid: uuid::Uuid::nil(),
1212 };
1213 assert!(requirement.check(Some(&metadata)).is_ok());
1214 }
1215
1216 #[test]
1217 fn test_check_ref_snapshot_id() {
1218 let metadata = metadata();
1219
1220 let requirement = TableRequirement::RefSnapshotIdMatch {
1222 r#ref: "my_branch".to_string(),
1223 snapshot_id: Some(1),
1224 };
1225 assert!(requirement.check(Some(&metadata)).is_err());
1226
1227 let requirement = TableRequirement::RefSnapshotIdMatch {
1229 r#ref: "my_branch".to_string(),
1230 snapshot_id: None,
1231 };
1232 assert!(requirement.check(Some(&metadata)).is_ok());
1233
1234 let snapshot = Snapshot::builder()
1236 .with_snapshot_id(3051729675574597004)
1237 .with_sequence_number(10)
1238 .with_timestamp_ms(9992191116217)
1239 .with_manifest_list("s3://b/wh/.../s1.avro".to_string())
1240 .with_schema_id(0)
1241 .with_summary(Summary {
1242 operation: Operation::Append,
1243 additional_properties: HashMap::new(),
1244 })
1245 .build();
1246
1247 let builder = metadata.into_builder(None);
1248 let builder = TableUpdate::AddSnapshot {
1249 snapshot: snapshot.clone(),
1250 }
1251 .apply(builder)
1252 .unwrap();
1253 let metadata = TableUpdate::SetSnapshotRef {
1254 ref_name: MAIN_BRANCH.to_string(),
1255 reference: SnapshotReference {
1256 snapshot_id: snapshot.snapshot_id(),
1257 retention: SnapshotRetention::Branch {
1258 min_snapshots_to_keep: Some(10),
1259 max_snapshot_age_ms: None,
1260 max_ref_age_ms: None,
1261 },
1262 },
1263 }
1264 .apply(builder)
1265 .unwrap()
1266 .build()
1267 .unwrap()
1268 .metadata;
1269
1270 let requirement = TableRequirement::RefSnapshotIdMatch {
1272 r#ref: "main".to_string(),
1273 snapshot_id: Some(3051729675574597004),
1274 };
1275 assert!(requirement.check(Some(&metadata)).is_ok());
1276
1277 let requirement = TableRequirement::RefSnapshotIdMatch {
1279 r#ref: "main".to_string(),
1280 snapshot_id: Some(1),
1281 };
1282 assert!(requirement.check(Some(&metadata)).is_err());
1283 }
1284
1285 #[test]
1286 fn test_check_last_assigned_field_id() {
1287 let metadata = metadata();
1288
1289 let requirement = TableRequirement::LastAssignedFieldIdMatch {
1290 last_assigned_field_id: 1,
1291 };
1292 assert!(requirement.check(Some(&metadata)).is_err());
1293
1294 let requirement = TableRequirement::LastAssignedFieldIdMatch {
1295 last_assigned_field_id: 0,
1296 };
1297 assert!(requirement.check(Some(&metadata)).is_ok());
1298 }
1299
1300 #[test]
1301 fn test_check_current_schema_id() {
1302 let metadata = metadata();
1303
1304 let requirement = TableRequirement::CurrentSchemaIdMatch {
1305 current_schema_id: 1,
1306 };
1307 assert!(requirement.check(Some(&metadata)).is_err());
1308
1309 let requirement = TableRequirement::CurrentSchemaIdMatch {
1310 current_schema_id: 0,
1311 };
1312 assert!(requirement.check(Some(&metadata)).is_ok());
1313 }
1314
1315 #[test]
1316 fn test_check_last_assigned_partition_id() {
1317 let metadata = metadata();
1318 let requirement = TableRequirement::LastAssignedPartitionIdMatch {
1319 last_assigned_partition_id: 0,
1320 };
1321 assert!(requirement.check(Some(&metadata)).is_err());
1322
1323 let requirement = TableRequirement::LastAssignedPartitionIdMatch {
1324 last_assigned_partition_id: 999,
1325 };
1326 assert!(requirement.check(Some(&metadata)).is_ok());
1327 }
1328
1329 #[test]
1330 fn test_check_default_spec_id() {
1331 let metadata = metadata();
1332
1333 let requirement = TableRequirement::DefaultSpecIdMatch { default_spec_id: 1 };
1334 assert!(requirement.check(Some(&metadata)).is_err());
1335
1336 let requirement = TableRequirement::DefaultSpecIdMatch { default_spec_id: 0 };
1337 assert!(requirement.check(Some(&metadata)).is_ok());
1338 }
1339
1340 #[test]
1341 fn test_check_default_sort_order_id() {
1342 let metadata = metadata();
1343
1344 let requirement = TableRequirement::DefaultSortOrderIdMatch {
1345 default_sort_order_id: 1,
1346 };
1347 assert!(requirement.check(Some(&metadata)).is_err());
1348
1349 let requirement = TableRequirement::DefaultSortOrderIdMatch {
1350 default_sort_order_id: 0,
1351 };
1352 assert!(requirement.check(Some(&metadata)).is_ok());
1353 }
1354
1355 #[test]
1356 fn test_table_uuid() {
1357 test_serde_json(
1358 r#"
1359{
1360 "type": "assert-table-uuid",
1361 "uuid": "2cc52516-5e73-41f2-b139-545d41a4e151"
1362}
1363 "#,
1364 TableRequirement::UuidMatch {
1365 uuid: uuid!("2cc52516-5e73-41f2-b139-545d41a4e151"),
1366 },
1367 );
1368 }
1369
1370 #[test]
1371 fn test_assert_table_not_exists() {
1372 test_serde_json(
1373 r#"
1374{
1375 "type": "assert-create"
1376}
1377 "#,
1378 TableRequirement::NotExist,
1379 );
1380 }
1381
1382 #[test]
1383 fn test_assert_ref_snapshot_id() {
1384 test_serde_json(
1385 r#"
1386{
1387 "type": "assert-ref-snapshot-id",
1388 "ref": "snapshot-name",
1389 "snapshot-id": null
1390}
1391 "#,
1392 TableRequirement::RefSnapshotIdMatch {
1393 r#ref: "snapshot-name".to_string(),
1394 snapshot_id: None,
1395 },
1396 );
1397
1398 test_serde_json(
1399 r#"
1400{
1401 "type": "assert-ref-snapshot-id",
1402 "ref": "snapshot-name",
1403 "snapshot-id": 1
1404}
1405 "#,
1406 TableRequirement::RefSnapshotIdMatch {
1407 r#ref: "snapshot-name".to_string(),
1408 snapshot_id: Some(1),
1409 },
1410 );
1411 }
1412
1413 #[test]
1414 fn test_assert_last_assigned_field_id() {
1415 test_serde_json(
1416 r#"
1417{
1418 "type": "assert-last-assigned-field-id",
1419 "last-assigned-field-id": 12
1420}
1421 "#,
1422 TableRequirement::LastAssignedFieldIdMatch {
1423 last_assigned_field_id: 12,
1424 },
1425 );
1426 }
1427
1428 #[test]
1429 fn test_assert_current_schema_id() {
1430 test_serde_json(
1431 r#"
1432{
1433 "type": "assert-current-schema-id",
1434 "current-schema-id": 4
1435}
1436 "#,
1437 TableRequirement::CurrentSchemaIdMatch {
1438 current_schema_id: 4,
1439 },
1440 );
1441 }
1442
1443 #[test]
1444 fn test_assert_last_assigned_partition_id() {
1445 test_serde_json(
1446 r#"
1447{
1448 "type": "assert-last-assigned-partition-id",
1449 "last-assigned-partition-id": 1004
1450}
1451 "#,
1452 TableRequirement::LastAssignedPartitionIdMatch {
1453 last_assigned_partition_id: 1004,
1454 },
1455 );
1456 }
1457
1458 #[test]
1459 fn test_assert_default_spec_id() {
1460 test_serde_json(
1461 r#"
1462{
1463 "type": "assert-default-spec-id",
1464 "default-spec-id": 5
1465}
1466 "#,
1467 TableRequirement::DefaultSpecIdMatch { default_spec_id: 5 },
1468 );
1469 }
1470
1471 #[test]
1472 fn test_assert_default_sort_order() {
1473 let json = r#"
1474{
1475 "type": "assert-default-sort-order-id",
1476 "default-sort-order-id": 10
1477}
1478 "#;
1479
1480 let update = TableRequirement::DefaultSortOrderIdMatch {
1481 default_sort_order_id: 10,
1482 };
1483
1484 test_serde_json(json, update);
1485 }
1486
1487 #[test]
1488 fn test_parse_assert_invalid() {
1489 assert!(
1490 serde_json::from_str::<TableRequirement>(
1491 r#"
1492{
1493 "default-sort-order-id": 10
1494}
1495"#
1496 )
1497 .is_err(),
1498 "Table requirements should not be parsed without type."
1499 );
1500 }
1501
1502 #[test]
1503 fn test_assign_uuid() {
1504 test_serde_json(
1505 r#"
1506{
1507 "action": "assign-uuid",
1508 "uuid": "2cc52516-5e73-41f2-b139-545d41a4e151"
1509}
1510 "#,
1511 TableUpdate::AssignUuid {
1512 uuid: uuid!("2cc52516-5e73-41f2-b139-545d41a4e151"),
1513 },
1514 );
1515 }
1516
1517 #[test]
1518 fn test_upgrade_format_version() {
1519 test_serde_json(
1520 r#"
1521{
1522 "action": "upgrade-format-version",
1523 "format-version": 2
1524}
1525 "#,
1526 TableUpdate::UpgradeFormatVersion {
1527 format_version: FormatVersion::V2,
1528 },
1529 );
1530 }
1531
1532 #[test]
1533 fn test_add_schema() {
1534 let test_schema = Schema::builder()
1535 .with_schema_id(1)
1536 .with_identifier_field_ids(vec![2])
1537 .with_fields(vec![
1538 NestedField::optional(1, "foo", Type::Primitive(PrimitiveType::String)).into(),
1539 NestedField::required(2, "bar", Type::Primitive(PrimitiveType::Int)).into(),
1540 NestedField::optional(3, "baz", Type::Primitive(PrimitiveType::Boolean)).into(),
1541 ])
1542 .build()
1543 .unwrap();
1544 test_serde_json(
1545 r#"
1546{
1547 "action": "add-schema",
1548 "schema": {
1549 "type": "struct",
1550 "schema-id": 1,
1551 "fields": [
1552 {
1553 "id": 1,
1554 "name": "foo",
1555 "required": false,
1556 "type": "string"
1557 },
1558 {
1559 "id": 2,
1560 "name": "bar",
1561 "required": true,
1562 "type": "int"
1563 },
1564 {
1565 "id": 3,
1566 "name": "baz",
1567 "required": false,
1568 "type": "boolean"
1569 }
1570 ],
1571 "identifier-field-ids": [
1572 2
1573 ]
1574 },
1575 "last-column-id": 3
1576}
1577 "#,
1578 TableUpdate::AddSchema {
1579 schema: test_schema.clone(),
1580 },
1581 );
1582
1583 test_serde_json(
1584 r#"
1585{
1586 "action": "add-schema",
1587 "schema": {
1588 "type": "struct",
1589 "schema-id": 1,
1590 "fields": [
1591 {
1592 "id": 1,
1593 "name": "foo",
1594 "required": false,
1595 "type": "string"
1596 },
1597 {
1598 "id": 2,
1599 "name": "bar",
1600 "required": true,
1601 "type": "int"
1602 },
1603 {
1604 "id": 3,
1605 "name": "baz",
1606 "required": false,
1607 "type": "boolean"
1608 }
1609 ],
1610 "identifier-field-ids": [
1611 2
1612 ]
1613 }
1614}
1615 "#,
1616 TableUpdate::AddSchema {
1617 schema: test_schema.clone(),
1618 },
1619 );
1620 }
1621
1622 #[test]
1623 fn test_set_current_schema() {
1624 test_serde_json(
1625 r#"
1626{
1627 "action": "set-current-schema",
1628 "schema-id": 23
1629}
1630 "#,
1631 TableUpdate::SetCurrentSchema { schema_id: 23 },
1632 );
1633 }
1634
1635 #[test]
1636 fn test_add_spec() {
1637 test_serde_json(
1638 r#"
1639{
1640 "action": "add-spec",
1641 "spec": {
1642 "fields": [
1643 {
1644 "source-id": 4,
1645 "name": "ts_day",
1646 "transform": "day"
1647 },
1648 {
1649 "source-id": 1,
1650 "name": "id_bucket",
1651 "transform": "bucket[16]"
1652 },
1653 {
1654 "source-id": 2,
1655 "name": "id_truncate",
1656 "transform": "truncate[4]"
1657 }
1658 ]
1659 }
1660}
1661 "#,
1662 TableUpdate::AddSpec {
1663 spec: UnboundPartitionSpec::builder()
1664 .add_partition_field(4, "ts_day".to_string(), Transform::Day)
1665 .unwrap()
1666 .add_partition_field(1, "id_bucket".to_string(), Transform::Bucket(16))
1667 .unwrap()
1668 .add_partition_field(2, "id_truncate".to_string(), Transform::Truncate(4))
1669 .unwrap()
1670 .build(),
1671 },
1672 );
1673 }
1674
1675 #[test]
1676 fn test_set_default_spec() {
1677 test_serde_json(
1678 r#"
1679{
1680 "action": "set-default-spec",
1681 "spec-id": 1
1682}
1683 "#,
1684 TableUpdate::SetDefaultSpec { spec_id: 1 },
1685 )
1686 }
1687
1688 #[test]
1689 fn test_add_sort_order() {
1690 let json = r#"
1691{
1692 "action": "add-sort-order",
1693 "sort-order": {
1694 "order-id": 1,
1695 "fields": [
1696 {
1697 "transform": "identity",
1698 "source-id": 2,
1699 "direction": "asc",
1700 "null-order": "nulls-first"
1701 },
1702 {
1703 "transform": "bucket[4]",
1704 "source-id": 3,
1705 "direction": "desc",
1706 "null-order": "nulls-last"
1707 }
1708 ]
1709 }
1710}
1711 "#;
1712
1713 let update = TableUpdate::AddSortOrder {
1714 sort_order: SortOrder::builder()
1715 .with_order_id(1)
1716 .with_sort_field(
1717 SortField::builder()
1718 .source_id(2)
1719 .direction(SortDirection::Ascending)
1720 .null_order(NullOrder::First)
1721 .transform(Transform::Identity)
1722 .build(),
1723 )
1724 .with_sort_field(
1725 SortField::builder()
1726 .source_id(3)
1727 .direction(SortDirection::Descending)
1728 .null_order(NullOrder::Last)
1729 .transform(Transform::Bucket(4))
1730 .build(),
1731 )
1732 .build_unbound()
1733 .unwrap(),
1734 };
1735
1736 test_serde_json(json, update);
1737 }
1738
1739 #[test]
1740 fn test_set_default_order() {
1741 let json = r#"
1742{
1743 "action": "set-default-sort-order",
1744 "sort-order-id": 2
1745}
1746 "#;
1747 let update = TableUpdate::SetDefaultSortOrder { sort_order_id: 2 };
1748
1749 test_serde_json(json, update);
1750 }
1751
1752 #[test]
1753 fn test_add_snapshot() {
1754 let json = r#"
1755{
1756 "action": "add-snapshot",
1757 "snapshot": {
1758 "snapshot-id": 3055729675574597000,
1759 "parent-snapshot-id": 3051729675574597000,
1760 "timestamp-ms": 1555100955770,
1761 "sequence-number": 1,
1762 "summary": {
1763 "operation": "append"
1764 },
1765 "manifest-list": "s3://a/b/2.avro",
1766 "schema-id": 1
1767 }
1768}
1769 "#;
1770
1771 let update = TableUpdate::AddSnapshot {
1772 snapshot: Snapshot::builder()
1773 .with_snapshot_id(3055729675574597000)
1774 .with_parent_snapshot_id(Some(3051729675574597000))
1775 .with_timestamp_ms(1555100955770)
1776 .with_sequence_number(1)
1777 .with_manifest_list("s3://a/b/2.avro")
1778 .with_schema_id(1)
1779 .with_summary(Summary {
1780 operation: Operation::Append,
1781 additional_properties: HashMap::default(),
1782 })
1783 .build(),
1784 };
1785
1786 test_serde_json(json, update);
1787 }
1788
1789 #[test]
1790 fn test_add_snapshot_v1() {
1791 let json = r#"
1792{
1793 "action": "add-snapshot",
1794 "snapshot": {
1795 "snapshot-id": 3055729675574597000,
1796 "parent-snapshot-id": 3051729675574597000,
1797 "timestamp-ms": 1555100955770,
1798 "summary": {
1799 "operation": "append"
1800 },
1801 "manifest-list": "s3://a/b/2.avro"
1802 }
1803}
1804 "#;
1805
1806 let update = TableUpdate::AddSnapshot {
1807 snapshot: Snapshot::builder()
1808 .with_snapshot_id(3055729675574597000)
1809 .with_parent_snapshot_id(Some(3051729675574597000))
1810 .with_timestamp_ms(1555100955770)
1811 .with_sequence_number(0)
1812 .with_manifest_list("s3://a/b/2.avro")
1813 .with_summary(Summary {
1814 operation: Operation::Append,
1815 additional_properties: HashMap::default(),
1816 })
1817 .build(),
1818 };
1819
1820 let actual: TableUpdate = serde_json::from_str(json).expect("Failed to parse from json");
1821 assert_eq!(actual, update, "Parsed value is not equal to expected");
1822 }
1823
1824 #[test]
1825 fn test_add_snapshot_v3() {
1826 let json = serde_json::json!(
1827 {
1828 "action": "add-snapshot",
1829 "snapshot": {
1830 "snapshot-id": 3055729675574597000i64,
1831 "parent-snapshot-id": 3051729675574597000i64,
1832 "timestamp-ms": 1555100955770i64,
1833 "first-row-id":0,
1834 "added-rows":2,
1835 "key-id":"key123",
1836 "summary": {
1837 "operation": "append"
1838 },
1839 "manifest-list": "s3://a/b/2.avro"
1840 }
1841 });
1842
1843 let update = TableUpdate::AddSnapshot {
1844 snapshot: Snapshot::builder()
1845 .with_snapshot_id(3055729675574597000)
1846 .with_parent_snapshot_id(Some(3051729675574597000))
1847 .with_timestamp_ms(1555100955770)
1848 .with_sequence_number(0)
1849 .with_manifest_list("s3://a/b/2.avro")
1850 .with_row_range(0, 2)
1851 .with_encryption_key_id(Some("key123".to_string()))
1852 .with_summary(Summary {
1853 operation: Operation::Append,
1854 additional_properties: HashMap::default(),
1855 })
1856 .build(),
1857 };
1858
1859 let actual: TableUpdate = serde_json::from_value(json).expect("Failed to parse from json");
1860 assert_eq!(actual, update, "Parsed value is not equal to expected");
1861 let restored: TableUpdate = serde_json::from_str(
1862 &serde_json::to_string(&actual).expect("Failed to serialize to json"),
1863 )
1864 .expect("Failed to parse from serialized json");
1865 assert_eq!(restored, update);
1866 }
1867
1868 #[test]
1869 fn test_remove_snapshots() {
1870 let json = r#"
1871{
1872 "action": "remove-snapshots",
1873 "snapshot-ids": [
1874 1,
1875 2
1876 ]
1877}
1878 "#;
1879
1880 let update = TableUpdate::RemoveSnapshots {
1881 snapshot_ids: vec![1, 2],
1882 };
1883 test_serde_json(json, update);
1884 }
1885
1886 #[test]
1887 fn test_remove_snapshot_ref() {
1888 let json = r#"
1889{
1890 "action": "remove-snapshot-ref",
1891 "ref-name": "snapshot-ref"
1892}
1893 "#;
1894
1895 let update = TableUpdate::RemoveSnapshotRef {
1896 ref_name: "snapshot-ref".to_string(),
1897 };
1898 test_serde_json(json, update);
1899 }
1900
1901 #[test]
1902 fn test_set_snapshot_ref_tag() {
1903 let json = r#"
1904{
1905 "action": "set-snapshot-ref",
1906 "type": "tag",
1907 "ref-name": "hank",
1908 "snapshot-id": 1,
1909 "max-ref-age-ms": 1
1910}
1911 "#;
1912
1913 let update = TableUpdate::SetSnapshotRef {
1914 ref_name: "hank".to_string(),
1915 reference: SnapshotReference {
1916 snapshot_id: 1,
1917 retention: SnapshotRetention::Tag {
1918 max_ref_age_ms: Some(1),
1919 },
1920 },
1921 };
1922
1923 test_serde_json(json, update);
1924 }
1925
1926 #[test]
1927 fn test_set_snapshot_ref_branch() {
1928 let json = r#"
1929{
1930 "action": "set-snapshot-ref",
1931 "type": "branch",
1932 "ref-name": "hank",
1933 "snapshot-id": 1,
1934 "min-snapshots-to-keep": 2,
1935 "max-snapshot-age-ms": 3,
1936 "max-ref-age-ms": 4
1937}
1938 "#;
1939
1940 let update = TableUpdate::SetSnapshotRef {
1941 ref_name: "hank".to_string(),
1942 reference: SnapshotReference {
1943 snapshot_id: 1,
1944 retention: SnapshotRetention::Branch {
1945 min_snapshots_to_keep: Some(2),
1946 max_snapshot_age_ms: Some(3),
1947 max_ref_age_ms: Some(4),
1948 },
1949 },
1950 };
1951
1952 test_serde_json(json, update);
1953 }
1954
1955 #[test]
1956 fn test_set_properties() {
1957 let json = r#"
1958{
1959 "action": "set-properties",
1960 "updates": {
1961 "prop1": "v1",
1962 "prop2": "v2"
1963 }
1964}
1965 "#;
1966
1967 let update = TableUpdate::SetProperties {
1968 updates: vec![
1969 ("prop1".to_string(), "v1".to_string()),
1970 ("prop2".to_string(), "v2".to_string()),
1971 ]
1972 .into_iter()
1973 .collect(),
1974 };
1975
1976 test_serde_json(json, update);
1977 }
1978
1979 #[test]
1980 fn test_remove_properties() {
1981 let json = r#"
1982{
1983 "action": "remove-properties",
1984 "removals": [
1985 "prop1",
1986 "prop2"
1987 ]
1988}
1989 "#;
1990
1991 let update = TableUpdate::RemoveProperties {
1992 removals: vec!["prop1".to_string(), "prop2".to_string()],
1993 };
1994
1995 test_serde_json(json, update);
1996 }
1997
1998 #[test]
1999 fn test_set_location() {
2000 let json = r#"
2001{
2002 "action": "set-location",
2003 "location": "s3://bucket/warehouse/tbl_location"
2004}
2005 "#;
2006
2007 let update = TableUpdate::SetLocation {
2008 location: "s3://bucket/warehouse/tbl_location".to_string(),
2009 };
2010
2011 test_serde_json(json, update);
2012 }
2013
2014 #[test]
2015 fn test_table_update_apply() {
2016 let table_creation = TableCreation::builder()
2017 .location("s3://db/table".to_string())
2018 .name("table".to_string())
2019 .properties(HashMap::new())
2020 .schema(Schema::builder().build().unwrap())
2021 .build();
2022 let table_metadata = TableMetadataBuilder::from_table_creation(table_creation)
2023 .unwrap()
2024 .build()
2025 .unwrap()
2026 .metadata;
2027 let table_metadata_builder = TableMetadataBuilder::new_from_metadata(
2028 table_metadata,
2029 Some("s3://db/table/metadata/metadata1.gz.json".to_string()),
2030 );
2031
2032 let uuid = uuid::Uuid::new_v4();
2033 let update = TableUpdate::AssignUuid { uuid };
2034 let updated_metadata = update
2035 .apply(table_metadata_builder)
2036 .unwrap()
2037 .build()
2038 .unwrap()
2039 .metadata;
2040 assert_eq!(updated_metadata.uuid(), uuid);
2041 }
2042
2043 #[test]
2044 fn test_view_assign_uuid() {
2045 test_serde_json(
2046 r#"
2047{
2048 "action": "assign-uuid",
2049 "uuid": "2cc52516-5e73-41f2-b139-545d41a4e151"
2050}
2051 "#,
2052 ViewUpdate::AssignUuid {
2053 uuid: uuid!("2cc52516-5e73-41f2-b139-545d41a4e151"),
2054 },
2055 );
2056 }
2057
2058 #[test]
2059 fn test_view_upgrade_format_version() {
2060 test_serde_json(
2061 r#"
2062{
2063 "action": "upgrade-format-version",
2064 "format-version": 1
2065}
2066 "#,
2067 ViewUpdate::UpgradeFormatVersion {
2068 format_version: ViewFormatVersion::V1,
2069 },
2070 );
2071 }
2072
2073 #[test]
2074 fn test_view_add_schema() {
2075 let test_schema = Schema::builder()
2076 .with_schema_id(1)
2077 .with_identifier_field_ids(vec![2])
2078 .with_fields(vec![
2079 NestedField::optional(1, "foo", Type::Primitive(PrimitiveType::String)).into(),
2080 NestedField::required(2, "bar", Type::Primitive(PrimitiveType::Int)).into(),
2081 NestedField::optional(3, "baz", Type::Primitive(PrimitiveType::Boolean)).into(),
2082 ])
2083 .build()
2084 .unwrap();
2085 test_serde_json(
2086 r#"
2087{
2088 "action": "add-schema",
2089 "schema": {
2090 "type": "struct",
2091 "schema-id": 1,
2092 "fields": [
2093 {
2094 "id": 1,
2095 "name": "foo",
2096 "required": false,
2097 "type": "string"
2098 },
2099 {
2100 "id": 2,
2101 "name": "bar",
2102 "required": true,
2103 "type": "int"
2104 },
2105 {
2106 "id": 3,
2107 "name": "baz",
2108 "required": false,
2109 "type": "boolean"
2110 }
2111 ],
2112 "identifier-field-ids": [
2113 2
2114 ]
2115 },
2116 "last-column-id": 3
2117}
2118 "#,
2119 ViewUpdate::AddSchema {
2120 schema: test_schema.clone(),
2121 last_column_id: Some(3),
2122 },
2123 );
2124 }
2125
2126 #[test]
2127 fn test_view_set_location() {
2128 test_serde_json(
2129 r#"
2130{
2131 "action": "set-location",
2132 "location": "s3://db/view"
2133}
2134 "#,
2135 ViewUpdate::SetLocation {
2136 location: "s3://db/view".to_string(),
2137 },
2138 );
2139 }
2140
2141 #[test]
2142 fn test_view_set_properties() {
2143 test_serde_json(
2144 r#"
2145{
2146 "action": "set-properties",
2147 "updates": {
2148 "prop1": "v1",
2149 "prop2": "v2"
2150 }
2151}
2152 "#,
2153 ViewUpdate::SetProperties {
2154 updates: vec![
2155 ("prop1".to_string(), "v1".to_string()),
2156 ("prop2".to_string(), "v2".to_string()),
2157 ]
2158 .into_iter()
2159 .collect(),
2160 },
2161 );
2162 }
2163
2164 #[test]
2165 fn test_view_remove_properties() {
2166 test_serde_json(
2167 r#"
2168{
2169 "action": "remove-properties",
2170 "removals": [
2171 "prop1",
2172 "prop2"
2173 ]
2174}
2175 "#,
2176 ViewUpdate::RemoveProperties {
2177 removals: vec!["prop1".to_string(), "prop2".to_string()],
2178 },
2179 );
2180 }
2181
2182 #[test]
2183 fn test_view_add_view_version() {
2184 test_serde_json(
2185 r#"
2186{
2187 "action": "add-view-version",
2188 "view-version": {
2189 "version-id" : 1,
2190 "timestamp-ms" : 1573518431292,
2191 "schema-id" : 1,
2192 "default-catalog" : "prod",
2193 "default-namespace" : [ "default" ],
2194 "summary" : {
2195 "engine-name" : "Spark"
2196 },
2197 "representations" : [ {
2198 "type" : "sql",
2199 "sql" : "SELECT\n COUNT(1), CAST(event_ts AS DATE)\nFROM events\nGROUP BY 2",
2200 "dialect" : "spark"
2201 } ]
2202 }
2203}
2204 "#,
2205 ViewUpdate::AddViewVersion {
2206 view_version: ViewVersion::builder()
2207 .with_version_id(1)
2208 .with_timestamp_ms(1573518431292)
2209 .with_schema_id(1)
2210 .with_default_catalog(Some("prod".to_string()))
2211 .with_default_namespace(NamespaceIdent::from_strs(vec!["default"]).unwrap())
2212 .with_summary(
2213 vec![("engine-name".to_string(), "Spark".to_string())]
2214 .into_iter()
2215 .collect(),
2216 )
2217 .with_representations(ViewRepresentations(vec![ViewRepresentation::Sql(SqlViewRepresentation {
2218 sql: "SELECT\n COUNT(1), CAST(event_ts AS DATE)\nFROM events\nGROUP BY 2".to_string(),
2219 dialect: "spark".to_string(),
2220 })]))
2221 .build(),
2222 },
2223 );
2224 }
2225
2226 #[test]
2227 fn test_view_set_current_view_version() {
2228 test_serde_json(
2229 r#"
2230{
2231 "action": "set-current-view-version",
2232 "view-version-id": 1
2233}
2234 "#,
2235 ViewUpdate::SetCurrentViewVersion { view_version_id: 1 },
2236 );
2237 }
2238
2239 #[test]
2240 fn test_remove_partition_specs_update() {
2241 test_serde_json(
2242 r#"
2243{
2244 "action": "remove-partition-specs",
2245 "spec-ids": [1, 2]
2246}
2247 "#,
2248 TableUpdate::RemovePartitionSpecs {
2249 spec_ids: vec![1, 2],
2250 },
2251 );
2252 }
2253
2254 #[test]
2255 fn test_set_statistics_file() {
2256 test_serde_json(
2257 r#"
2258 {
2259 "action": "set-statistics",
2260 "snapshot-id": 1940541653261589030,
2261 "statistics": {
2262 "snapshot-id": 1940541653261589030,
2263 "statistics-path": "s3://bucket/warehouse/stats.puffin",
2264 "file-size-in-bytes": 124,
2265 "file-footer-size-in-bytes": 27,
2266 "blob-metadata": [
2267 {
2268 "type": "boring-type",
2269 "snapshot-id": 1940541653261589030,
2270 "sequence-number": 2,
2271 "fields": [
2272 1
2273 ],
2274 "properties": {
2275 "prop-key": "prop-value"
2276 }
2277 }
2278 ]
2279 }
2280 }
2281 "#,
2282 TableUpdate::SetStatistics {
2283 statistics: StatisticsFile {
2284 snapshot_id: 1940541653261589030,
2285 statistics_path: "s3://bucket/warehouse/stats.puffin".to_string(),
2286 file_size_in_bytes: 124,
2287 file_footer_size_in_bytes: 27,
2288 key_metadata: None,
2289 blob_metadata: vec![BlobMetadata {
2290 r#type: "boring-type".to_string(),
2291 snapshot_id: 1940541653261589030,
2292 sequence_number: 2,
2293 fields: vec![1],
2294 properties: vec![("prop-key".to_string(), "prop-value".to_string())]
2295 .into_iter()
2296 .collect(),
2297 }],
2298 },
2299 },
2300 );
2301 }
2302
2303 #[test]
2304 fn test_remove_statistics_file() {
2305 test_serde_json(
2306 r#"
2307 {
2308 "action": "remove-statistics",
2309 "snapshot-id": 1940541653261589030
2310 }
2311 "#,
2312 TableUpdate::RemoveStatistics {
2313 snapshot_id: 1940541653261589030,
2314 },
2315 );
2316 }
2317
2318 #[test]
2319 fn test_set_partition_statistics_file() {
2320 test_serde_json(
2321 r#"
2322 {
2323 "action": "set-partition-statistics",
2324 "partition-statistics": {
2325 "snapshot-id": 1940541653261589030,
2326 "statistics-path": "s3://bucket/warehouse/stats1.parquet",
2327 "file-size-in-bytes": 43
2328 }
2329 }
2330 "#,
2331 TableUpdate::SetPartitionStatistics {
2332 partition_statistics: PartitionStatisticsFile {
2333 snapshot_id: 1940541653261589030,
2334 statistics_path: "s3://bucket/warehouse/stats1.parquet".to_string(),
2335 file_size_in_bytes: 43,
2336 },
2337 },
2338 )
2339 }
2340
2341 #[test]
2342 fn test_remove_partition_statistics_file() {
2343 test_serde_json(
2344 r#"
2345 {
2346 "action": "remove-partition-statistics",
2347 "snapshot-id": 1940541653261589030
2348 }
2349 "#,
2350 TableUpdate::RemovePartitionStatistics {
2351 snapshot_id: 1940541653261589030,
2352 },
2353 )
2354 }
2355
2356 #[test]
2357 fn test_remove_schema_update() {
2358 test_serde_json(
2359 r#"
2360 {
2361 "action": "remove-schemas",
2362 "schema-ids": [1, 2]
2363 }
2364 "#,
2365 TableUpdate::RemoveSchemas {
2366 schema_ids: vec![1, 2],
2367 },
2368 );
2369 }
2370
2371 #[test]
2372 fn test_add_encryption_key() {
2373 let key_bytes = "key".as_bytes();
2374 let encoded_key = base64::engine::general_purpose::STANDARD.encode(key_bytes);
2375 test_serde_json(
2376 format!(
2377 r#"
2378 {{
2379 "action": "add-encryption-key",
2380 "encryption-key": {{
2381 "key-id": "a",
2382 "encrypted-key-metadata": "{encoded_key}",
2383 "encrypted-by-id": "b"
2384 }}
2385 }}
2386 "#
2387 ),
2388 TableUpdate::AddEncryptionKey {
2389 encryption_key: EncryptedKey::builder()
2390 .key_id("a")
2391 .encrypted_key_metadata(key_bytes.to_vec())
2392 .encrypted_by_id("b")
2393 .build(),
2394 },
2395 );
2396 }
2397
2398 #[test]
2399 fn test_remove_encryption_key() {
2400 test_serde_json(
2401 r#"
2402 {
2403 "action": "remove-encryption-key",
2404 "key-id": "a"
2405 }
2406 "#,
2407 TableUpdate::RemoveEncryptionKey {
2408 key_id: "a".to_string(),
2409 },
2410 );
2411 }
2412
2413 #[test]
2414 fn test_table_commit() {
2415 let table = {
2416 let file = File::open(format!(
2417 "{}/testdata/table_metadata/{}",
2418 env!("CARGO_MANIFEST_DIR"),
2419 "TableMetadataV2Valid.json"
2420 ))
2421 .unwrap();
2422 let reader = BufReader::new(file);
2423 let resp = serde_json::from_reader::<_, TableMetadata>(reader).unwrap();
2424
2425 Table::builder()
2426 .metadata(resp)
2427 .metadata_location("s3://bucket/test/location/metadata/00000-8a62c37d-4573-4021-952a-c0baef7d21d0.metadata.json")
2428 .identifier(TableIdent::from_strs(["ns1", "test1"]).unwrap())
2429 .file_io(FileIO::new_with_memory())
2430 .runtime(test_runtime())
2431 .build()
2432 .unwrap()
2433 };
2434
2435 let updates = vec![
2436 TableUpdate::SetLocation {
2437 location: "s3://bucket/test/new_location/data".to_string(),
2438 },
2439 TableUpdate::SetProperties {
2440 updates: vec![
2441 ("prop1".to_string(), "v1".to_string()),
2442 ("prop2".to_string(), "v2".to_string()),
2443 ]
2444 .into_iter()
2445 .collect(),
2446 },
2447 ];
2448
2449 let requirements = vec![TableRequirement::UuidMatch {
2450 uuid: table.metadata().table_uuid,
2451 }];
2452
2453 let table_commit = TableCommit::builder()
2454 .ident(table.identifier().to_owned())
2455 .updates(updates)
2456 .requirements(requirements)
2457 .build();
2458
2459 let updated_table = table_commit.apply(table).unwrap();
2460
2461 assert_eq!(
2462 updated_table.metadata().properties.get("prop1").unwrap(),
2463 "v1"
2464 );
2465 assert_eq!(
2466 updated_table.metadata().properties.get("prop2").unwrap(),
2467 "v2"
2468 );
2469
2470 assert!(
2473 updated_table
2474 .metadata_location()
2475 .unwrap()
2476 .starts_with("s3://bucket/test/new_location/data/metadata/00001-")
2477 );
2478
2479 assert_eq!(
2480 updated_table.metadata().location,
2481 "s3://bucket/test/new_location/data",
2482 );
2483 }
2484}