From eb5f54998cb5821c5f76af06606e595f0cb435be Mon Sep 17 00:00:00 2001 From: my-vegetable-has-exploded Date: Sun, 26 Nov 2023 12:08:37 +0800 Subject: [PATCH 1/5] Implement unbound partition spec. --- .gitignore | 1 + crates/iceberg/src/error.rs | 3 + crates/iceberg/src/spec/partition.rs | 97 ++++++++++++++++++++++- crates/iceberg/src/spec/table_metadata.rs | 2 +- 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 10ed67b31..72c34840c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ /target /Cargo.lock .idea +.vscode **/.DS_Store \ No newline at end of file diff --git a/crates/iceberg/src/error.rs b/crates/iceberg/src/error.rs index 8948c7d6b..2cf3f4969 100644 --- a/crates/iceberg/src/error.rs +++ b/crates/iceberg/src/error.rs @@ -33,6 +33,8 @@ pub enum ErrorKind { /// service error. Unexpected, + /// Iceberg finds some conflict when checking. + Conflict, /// Iceberg data is invalid. /// /// This error is returned when we try to read a table from iceberg but @@ -57,6 +59,7 @@ impl From for &'static str { fn from(v: ErrorKind) -> &'static str { match v { ErrorKind::Unexpected => "Unexpected", + ErrorKind::Conflict => "conflict", ErrorKind::DataInvalid => "DataInvalid", ErrorKind::FeatureUnsupported => "FeatureUnsupported", } diff --git a/crates/iceberg/src/spec/partition.rs b/crates/iceberg/src/spec/partition.rs index cfdbb6f17..5bb92822e 100644 --- a/crates/iceberg/src/spec/partition.rs +++ b/crates/iceberg/src/spec/partition.rs @@ -18,11 +18,13 @@ /*! * Partitioning */ +use crate::error::{Error, ErrorKind, Result}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use typed_builder::TypedBuilder; -use super::transform::Transform; +use super::DEFAULT_SPEC_ID; +use super::{schema::SchemaRef, transform::Transform}; /// Reference to [`PartitionSpec`]. pub type PartitionSpecRef = Arc; @@ -60,6 +62,99 @@ impl PartitionSpec { } } +static PARTITION_DATA_ID_START: i32 = 1000; + +/// Reference to [`UnboundPartitionSpec`]. +pub type UnboundPartitionSpecRef = Arc; +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "kebab-case")] +/// Unbound partition field can be built without a schema and later bound to a schema. +pub struct UnboundPartitionField { + /// A source column id from the table’s schema + pub source_id: i32, + /// A partition field id that is used to identify a partition field and is unique within a partition spec. + /// In v2 table metadata, it is unique across all partition specs. + pub partition_id: Option, + /// A partition name. + pub name: String, + /// A transform that is applied to the source column to produce a partition value. + pub transform: Transform, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default, Builder)] +#[serde(rename_all = "kebab-case")] +#[builder(setter(prefix = "with"))] +/// Unbound partition spec can be built without a schema and later bound to a schema. +pub struct UnboundPartitionSpec { + /// Identifier for PartitionSpec + pub spec_id: Option, + /// Details of the partition spec + #[builder(setter(each(name = "with_unbound_partition_field")))] + pub fields: Vec, +} + +impl UnboundPartitionSpec { + /// last assigned id for partitioned field + pub fn unpartitioned_last_assigned_id() -> i32 { + PARTITION_DATA_ID_START - 1 + } + + /// Create unbound partition spec builer + pub fn builder() -> UnboundPartitionSpecBuilder { + UnboundPartitionSpecBuilder::default() + } + + /// Bind unbound partition spec to a schema + pub fn bind(&self, schema: SchemaRef) -> Result { + let mut fields = Vec::with_capacity(self.fields.len()); + let mut last_assigned_field_id: i32 = + UnboundPartitionSpec::unpartitioned_last_assigned_id(); + for field in &self.fields { + let field_id = match field.partition_id { + Some(id) => id, + None => { + last_assigned_field_id += 1; + last_assigned_field_id + } + }; + match schema.field_by_id(field.source_id) { + Some(f) => { + if f.name != field.name { + return Err(Error::new( + ErrorKind::Conflict, + format!( + "Field name {} in partition spec does not match schema", + field.name + ), + )); + } + } + None => { + return Err(Error::new( + ErrorKind::Conflict, + format!( + "Field id {} in partition spec is not in schema", + field.source_id + ), + )); + } + } + last_assigned_field_id = last_assigned_field_id.max(field_id); + fields.push(PartitionField { + source_id: field.source_id, + field_id, + name: field.name.clone(), + transform: field.transform, + }); + } + let spec_id = match self.spec_id { + Some(id) => id, + None => DEFAULT_SPEC_ID, + }; + Ok(PartitionSpec { spec_id, fields }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/iceberg/src/spec/table_metadata.rs b/crates/iceberg/src/spec/table_metadata.rs index 905c82307..de8eb1134 100644 --- a/crates/iceberg/src/spec/table_metadata.rs +++ b/crates/iceberg/src/spec/table_metadata.rs @@ -35,7 +35,7 @@ use _serde::TableMetadataEnum; use chrono::{DateTime, TimeZone, Utc}; static MAIN_BRANCH: &str = "main"; -static DEFAULT_SPEC_ID: i32 = 0; +pub(crate) static DEFAULT_SPEC_ID: i32 = 0; static DEFAULT_SORT_ORDER_ID: i64 = 0; #[derive(Debug, PartialEq, Serialize, Deserialize, Eq, Clone)] From ec74d1f60f93bec261507525a7e08c9a30675ec8 Mon Sep 17 00:00:00 2001 From: my-vegetable-has-exploded Date: Tue, 28 Nov 2023 23:36:38 +0800 Subject: [PATCH 2/5] little update --- crates/catalog/rest/src/catalog.rs | 4 +-- crates/iceberg/src/catalog/mod.rs | 6 ++-- crates/iceberg/src/error.rs | 3 -- crates/iceberg/src/spec/partition.rs | 49 ---------------------------- 4 files changed, 5 insertions(+), 57 deletions(-) diff --git a/crates/catalog/rest/src/catalog.rs b/crates/catalog/rest/src/catalog.rs index 8037d8c6b..7f182b58e 100644 --- a/crates/catalog/rest/src/catalog.rs +++ b/crates/catalog/rest/src/catalog.rs @@ -521,7 +521,7 @@ mod _serde { use serde_derive::{Deserialize, Serialize}; - use iceberg::spec::{PartitionSpec, Schema, SortOrder, TableMetadata}; + use iceberg::spec::{PartitionSpec, Schema, SortOrder, TableMetadata, UnboundPartitionSpec}; use iceberg::{Error, ErrorKind, Namespace, TableIdent, TableRequirement, TableUpdate}; pub(super) const OK: u16 = 200u16; @@ -660,7 +660,7 @@ mod _serde { pub(super) name: String, pub(super) location: Option, pub(super) schema: Schema, - pub(super) partition_spec: Option, + pub(super) partition_spec: Option, pub(super) write_order: Option, pub(super) stage_create: Option, pub(super) properties: Option>, diff --git a/crates/iceberg/src/catalog/mod.rs b/crates/iceberg/src/catalog/mod.rs index d13a46bdb..5b1117fa5 100644 --- a/crates/iceberg/src/catalog/mod.rs +++ b/crates/iceberg/src/catalog/mod.rs @@ -20,7 +20,7 @@ use serde_derive::{Deserialize, Serialize}; use urlencoding::encode; -use crate::spec::{FormatVersion, PartitionSpec, Schema, Snapshot, SnapshotReference, SortOrder}; +use crate::spec::{FormatVersion, PartitionSpec, Schema, Snapshot, SnapshotReference, SortOrder, UnboundPartitionSpec}; use crate::table::Table; use crate::{Error, ErrorKind, Result}; use async_trait::async_trait; @@ -226,7 +226,7 @@ pub struct TableCreation { pub schema: Schema, /// The partition spec of the table, could be None. #[builder(default, setter(strip_option))] - pub partition_spec: Option, + pub partition_spec: Option, /// The sort order of the table. #[builder(default, setter(strip_option))] pub sort_order: Option, @@ -361,7 +361,7 @@ pub enum TableUpdate { /// Add a new partition spec to the table AddSpec { /// The partition spec to add. - spec: PartitionSpec, + spec: UnboundPartitionSpec, }, /// Set table's default spec #[serde(rename_all = "kebab-case")] diff --git a/crates/iceberg/src/error.rs b/crates/iceberg/src/error.rs index 2cf3f4969..8948c7d6b 100644 --- a/crates/iceberg/src/error.rs +++ b/crates/iceberg/src/error.rs @@ -33,8 +33,6 @@ pub enum ErrorKind { /// service error. Unexpected, - /// Iceberg finds some conflict when checking. - Conflict, /// Iceberg data is invalid. /// /// This error is returned when we try to read a table from iceberg but @@ -59,7 +57,6 @@ impl From for &'static str { fn from(v: ErrorKind) -> &'static str { match v { ErrorKind::Unexpected => "Unexpected", - ErrorKind::Conflict => "conflict", ErrorKind::DataInvalid => "DataInvalid", ErrorKind::FeatureUnsupported => "FeatureUnsupported", } diff --git a/crates/iceberg/src/spec/partition.rs b/crates/iceberg/src/spec/partition.rs index 5bb92822e..e2f67740b 100644 --- a/crates/iceberg/src/spec/partition.rs +++ b/crates/iceberg/src/spec/partition.rs @@ -104,55 +104,6 @@ impl UnboundPartitionSpec { UnboundPartitionSpecBuilder::default() } - /// Bind unbound partition spec to a schema - pub fn bind(&self, schema: SchemaRef) -> Result { - let mut fields = Vec::with_capacity(self.fields.len()); - let mut last_assigned_field_id: i32 = - UnboundPartitionSpec::unpartitioned_last_assigned_id(); - for field in &self.fields { - let field_id = match field.partition_id { - Some(id) => id, - None => { - last_assigned_field_id += 1; - last_assigned_field_id - } - }; - match schema.field_by_id(field.source_id) { - Some(f) => { - if f.name != field.name { - return Err(Error::new( - ErrorKind::Conflict, - format!( - "Field name {} in partition spec does not match schema", - field.name - ), - )); - } - } - None => { - return Err(Error::new( - ErrorKind::Conflict, - format!( - "Field id {} in partition spec is not in schema", - field.source_id - ), - )); - } - } - last_assigned_field_id = last_assigned_field_id.max(field_id); - fields.push(PartitionField { - source_id: field.source_id, - field_id, - name: field.name.clone(), - transform: field.transform, - }); - } - let spec_id = match self.spec_id { - Some(id) => id, - None => DEFAULT_SPEC_ID, - }; - Ok(PartitionSpec { spec_id, fields }) - } } #[cfg(test)] From c858172bf8917fc39c10c9b54e2442b186f29533 Mon Sep 17 00:00:00 2001 From: my-vegetable-has-exploded Date: Thu, 30 Nov 2023 10:19:26 +0800 Subject: [PATCH 3/5] Update in tablecreate & addspec --- crates/catalog/rest/src/catalog.rs | 14 ++++++------ crates/iceberg/src/catalog/mod.rs | 32 +++++++++++----------------- crates/iceberg/src/spec/partition.rs | 16 ++++---------- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/crates/catalog/rest/src/catalog.rs b/crates/catalog/rest/src/catalog.rs index 7f182b58e..1dfbe79e4 100644 --- a/crates/catalog/rest/src/catalog.rs +++ b/crates/catalog/rest/src/catalog.rs @@ -521,7 +521,7 @@ mod _serde { use serde_derive::{Deserialize, Serialize}; - use iceberg::spec::{PartitionSpec, Schema, SortOrder, TableMetadata, UnboundPartitionSpec}; + use iceberg::spec::{Schema, SortOrder, TableMetadata, UnboundPartitionSpec}; use iceberg::{Error, ErrorKind, Namespace, TableIdent, TableRequirement, TableUpdate}; pub(super) const OK: u16 = 200u16; @@ -686,9 +686,9 @@ mod tests { use chrono::{TimeZone, Utc}; use iceberg::spec::ManifestListLocation::ManifestListFile; use iceberg::spec::{ - FormatVersion, NestedField, NullOrder, Operation, PartitionField, PartitionSpec, - PrimitiveType, Schema, Snapshot, SnapshotLog, SortDirection, SortField, SortOrder, Summary, - Transform, Type, + FormatVersion, NestedField, NullOrder, Operation, PrimitiveType, Schema, Snapshot, + SnapshotLog, SortDirection, SortField, SortOrder, Summary, Transform, Type, + UnboundPartitionField, UnboundPartitionSpec, }; use iceberg::transaction::Transaction; use mockito::{Mock, Server, ServerGuard}; @@ -1233,14 +1233,12 @@ mod tests { ) .properties(HashMap::from([("owner".to_string(), "testx".to_string())])) .partition_spec( - PartitionSpec::builder() - .with_fields(vec![PartitionField::builder() + UnboundPartitionSpec::builder() + .with_fields(vec![UnboundPartitionField::builder() .source_id(1) - .field_id(1000) .transform(Transform::Truncate(3)) .name("id".to_string()) .build()]) - .with_spec_id(1) .build() .unwrap(), ) diff --git a/crates/iceberg/src/catalog/mod.rs b/crates/iceberg/src/catalog/mod.rs index 5b1117fa5..2ddeacea8 100644 --- a/crates/iceberg/src/catalog/mod.rs +++ b/crates/iceberg/src/catalog/mod.rs @@ -20,7 +20,9 @@ use serde_derive::{Deserialize, Serialize}; use urlencoding::encode; -use crate::spec::{FormatVersion, PartitionSpec, Schema, Snapshot, SnapshotReference, SortOrder, UnboundPartitionSpec}; +use crate::spec::{ + FormatVersion, Schema, Snapshot, SnapshotReference, SortOrder, UnboundPartitionSpec, +}; use crate::table::Table; use crate::{Error, ErrorKind, Result}; use async_trait::async_trait; @@ -429,9 +431,9 @@ pub enum TableUpdate { mod tests { use crate::spec::ManifestListLocation::ManifestListFile; use crate::spec::{ - FormatVersion, NestedField, NullOrder, Operation, PartitionField, PartitionSpec, - PrimitiveType, Schema, Snapshot, SnapshotReference, SnapshotRetention, SortDirection, - SortField, SortOrder, Summary, Transform, Type, + FormatVersion, NestedField, NullOrder, Operation, PrimitiveType, Schema, Snapshot, + SnapshotReference, SnapshotRetention, SortDirection, SortField, SortOrder, Summary, + Transform, Type, UnboundPartitionField, UnboundPartitionSpec, }; use crate::{NamespaceIdent, TableIdent, TableRequirement, TableUpdate}; use serde::de::DeserializeOwned; @@ -758,23 +760,19 @@ mod tests { { "action": "add-spec", "spec": { - "spec-id": 1, "fields": [ { "source-id": 4, - "field-id": 1000, "name": "ts_day", "transform": "day" }, { "source-id": 1, - "field-id": 1001, "name": "id_bucket", "transform": "bucket[16]" }, { "source-id": 2, - "field-id": 1002, "name": "id_truncate", "transform": "truncate[4]" } @@ -783,28 +781,24 @@ mod tests { } "#, TableUpdate::AddSpec { - spec: PartitionSpec::builder() - .with_spec_id(1) - .with_partition_field( - PartitionField::builder() + spec: UnboundPartitionSpec::builder() + .with_unbound_partition_field( + UnboundPartitionField::builder() .source_id(4) - .field_id(1000) .name("ts_day".to_string()) .transform(Transform::Day) .build(), ) - .with_partition_field( - PartitionField::builder() + .with_unbound_partition_field( + UnboundPartitionField::builder() .source_id(1) - .field_id(1001) .name("id_bucket".to_string()) .transform(Transform::Bucket(16)) .build(), ) - .with_partition_field( - PartitionField::builder() + .with_unbound_partition_field( + UnboundPartitionField::builder() .source_id(2) - .field_id(1002) .name("id_truncate".to_string()) .transform(Transform::Truncate(4)) .build(), diff --git a/crates/iceberg/src/spec/partition.rs b/crates/iceberg/src/spec/partition.rs index e2f67740b..774162dec 100644 --- a/crates/iceberg/src/spec/partition.rs +++ b/crates/iceberg/src/spec/partition.rs @@ -18,13 +18,11 @@ /*! * Partitioning */ -use crate::error::{Error, ErrorKind, Result}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use typed_builder::TypedBuilder; -use super::DEFAULT_SPEC_ID; -use super::{schema::SchemaRef, transform::Transform}; +use super::transform::Transform; /// Reference to [`PartitionSpec`]. pub type PartitionSpecRef = Arc; @@ -62,11 +60,9 @@ impl PartitionSpec { } } -static PARTITION_DATA_ID_START: i32 = 1000; - /// Reference to [`UnboundPartitionSpec`]. pub type UnboundPartitionSpecRef = Arc; -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, TypedBuilder)] #[serde(rename_all = "kebab-case")] /// Unbound partition field can be built without a schema and later bound to a schema. pub struct UnboundPartitionField { @@ -74,6 +70,7 @@ pub struct UnboundPartitionField { pub source_id: i32, /// A partition field id that is used to identify a partition field and is unique within a partition spec. /// In v2 table metadata, it is unique across all partition specs. + #[builder(default, setter(strip_option))] pub partition_id: Option, /// A partition name. pub name: String, @@ -87,6 +84,7 @@ pub struct UnboundPartitionField { /// Unbound partition spec can be built without a schema and later bound to a schema. pub struct UnboundPartitionSpec { /// Identifier for PartitionSpec + #[builder(default, setter(strip_option))] pub spec_id: Option, /// Details of the partition spec #[builder(setter(each(name = "with_unbound_partition_field")))] @@ -94,16 +92,10 @@ pub struct UnboundPartitionSpec { } impl UnboundPartitionSpec { - /// last assigned id for partitioned field - pub fn unpartitioned_last_assigned_id() -> i32 { - PARTITION_DATA_ID_START - 1 - } - /// Create unbound partition spec builer pub fn builder() -> UnboundPartitionSpecBuilder { UnboundPartitionSpecBuilder::default() } - } #[cfg(test)] From b0c1997c7ed5a6df6aa7ebd92e9ce970fd20e026 Mon Sep 17 00:00:00 2001 From: my-vegetable-has-exploded Date: Thu, 30 Nov 2023 11:14:51 +0800 Subject: [PATCH 4/5] fixup: add some tests. --- crates/iceberg/src/spec/partition.rs | 66 +++++++++++++++++++++-- crates/iceberg/src/spec/table_metadata.rs | 2 +- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/crates/iceberg/src/spec/partition.rs b/crates/iceberg/src/spec/partition.rs index 774162dec..3fd7a74f4 100644 --- a/crates/iceberg/src/spec/partition.rs +++ b/crates/iceberg/src/spec/partition.rs @@ -103,8 +103,8 @@ mod tests { use super::*; #[test] - fn partition_spec() { - let sort_order = r#" + fn test_partition_spec() { + let spec = r#" { "spec-id": 1, "fields": [ { @@ -126,7 +126,7 @@ mod tests { } "#; - let partition_spec: PartitionSpec = serde_json::from_str(sort_order).unwrap(); + let partition_spec: PartitionSpec = serde_json::from_str(spec).unwrap(); assert_eq!(4, partition_spec.fields[0].source_id); assert_eq!(1000, partition_spec.fields[0].field_id); assert_eq!("ts_day", partition_spec.fields[0].name); @@ -142,4 +142,64 @@ mod tests { assert_eq!("id_truncate", partition_spec.fields[2].name); assert_eq!(Transform::Truncate(4), partition_spec.fields[2].transform); } + + #[test] + fn test_unbound_partition_spec() { + let spec = r#" + { + "spec-id": 1, + "fields": [ { + "source-id": 4, + "partition-id": 1000, + "name": "ts_day", + "transform": "day" + }, { + "source-id": 1, + "partition-id": 1001, + "name": "id_bucket", + "transform": "bucket[16]" + }, { + "source-id": 2, + "partition-id": 1002, + "name": "id_truncate", + "transform": "truncate[4]" + } ] + } + "#; + + let partition_spec: UnboundPartitionSpec = serde_json::from_str(spec).unwrap(); + assert_eq!(Some(1), partition_spec.spec_id); + + assert_eq!(4, partition_spec.fields[0].source_id); + assert_eq!(Some(1000), partition_spec.fields[0].partition_id); + assert_eq!("ts_day", partition_spec.fields[0].name); + assert_eq!(Transform::Day, partition_spec.fields[0].transform); + + assert_eq!(1, partition_spec.fields[1].source_id); + assert_eq!(Some(1001), partition_spec.fields[1].partition_id); + assert_eq!("id_bucket", partition_spec.fields[1].name); + assert_eq!(Transform::Bucket(16), partition_spec.fields[1].transform); + + assert_eq!(2, partition_spec.fields[2].source_id); + assert_eq!(Some(1002), partition_spec.fields[2].partition_id); + assert_eq!("id_truncate", partition_spec.fields[2].name); + assert_eq!(Transform::Truncate(4), partition_spec.fields[2].transform); + + let spec = r#" + { + "fields": [ { + "source-id": 4, + "name": "ts_day", + "transform": "day" + } ] + } + "#; + let partition_spec: UnboundPartitionSpec = serde_json::from_str(spec).unwrap(); + assert_eq!(None, partition_spec.spec_id); + + assert_eq!(4, partition_spec.fields[0].source_id); + assert_eq!(None, partition_spec.fields[0].partition_id); + assert_eq!("ts_day", partition_spec.fields[0].name); + assert_eq!(Transform::Day, partition_spec.fields[0].transform); + } } diff --git a/crates/iceberg/src/spec/table_metadata.rs b/crates/iceberg/src/spec/table_metadata.rs index de8eb1134..905c82307 100644 --- a/crates/iceberg/src/spec/table_metadata.rs +++ b/crates/iceberg/src/spec/table_metadata.rs @@ -35,7 +35,7 @@ use _serde::TableMetadataEnum; use chrono::{DateTime, TimeZone, Utc}; static MAIN_BRANCH: &str = "main"; -pub(crate) static DEFAULT_SPEC_ID: i32 = 0; +static DEFAULT_SPEC_ID: i32 = 0; static DEFAULT_SORT_ORDER_ID: i64 = 0; #[derive(Debug, PartialEq, Serialize, Deserialize, Eq, Clone)] From 4c044dcfbd14673184537b341d5cd7ddd3b25f43 Mon Sep 17 00:00:00 2001 From: my-vegetable-has-exploded Date: Tue, 5 Dec 2023 11:21:45 +0800 Subject: [PATCH 5/5] Put comments before derive --- crates/iceberg/src/spec/partition.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/iceberg/src/spec/partition.rs b/crates/iceberg/src/spec/partition.rs index 3fd7a74f4..16395dc72 100644 --- a/crates/iceberg/src/spec/partition.rs +++ b/crates/iceberg/src/spec/partition.rs @@ -26,9 +26,9 @@ use super::transform::Transform; /// Reference to [`PartitionSpec`]. pub type PartitionSpecRef = Arc; +/// Partition fields capture the transform from table data to partition values. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, TypedBuilder)] #[serde(rename_all = "kebab-case")] -/// Partition fields capture the transform from table data to partition values. pub struct PartitionField { /// A source column id from the table’s schema pub source_id: i32, @@ -41,10 +41,10 @@ pub struct PartitionField { pub transform: Transform, } +/// Partition spec that defines how to produce a tuple of partition values from a record. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default, Builder)] #[serde(rename_all = "kebab-case")] #[builder(setter(prefix = "with"))] -/// Partition spec that defines how to produce a tuple of partition values from a record. pub struct PartitionSpec { /// Identifier for PartitionSpec pub spec_id: i32, @@ -62,9 +62,9 @@ impl PartitionSpec { /// Reference to [`UnboundPartitionSpec`]. pub type UnboundPartitionSpecRef = Arc; +/// Unbound partition field can be built without a schema and later bound to a schema. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, TypedBuilder)] #[serde(rename_all = "kebab-case")] -/// Unbound partition field can be built without a schema and later bound to a schema. pub struct UnboundPartitionField { /// A source column id from the table’s schema pub source_id: i32, @@ -78,10 +78,10 @@ pub struct UnboundPartitionField { pub transform: Transform, } +/// Unbound partition spec can be built without a schema and later bound to a schema. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default, Builder)] #[serde(rename_all = "kebab-case")] #[builder(setter(prefix = "with"))] -/// Unbound partition spec can be built without a schema and later bound to a schema. pub struct UnboundPartitionSpec { /// Identifier for PartitionSpec #[builder(default, setter(strip_option))]