Skip to content

Commit ebac47a

Browse files
committed
feat: enhance table features and write tests
1 parent 3fa9f0b commit ebac47a

2 files changed

Lines changed: 102 additions & 3 deletions

File tree

kernel/src/table_features/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ pub(crate) static SUPPORTED_WRITER_FEATURES: LazyLock<Vec<WriterFeature>> = Lazy
167167
WriterFeature::AppendOnly,
168168
WriterFeature::DeletionVectors,
169169
WriterFeature::Invariants,
170+
WriterFeature::TimestampWithoutTimezone,
170171
]
171172
});
172173

kernel/tests/write.rs

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::HashMap;
22
use std::sync::Arc;
33

44
use delta_kernel::arrow::array::{
5-
Int32Array, MapBuilder, MapFieldNames, StringArray, StringBuilder,
5+
Int32Array, MapBuilder, MapFieldNames, StringArray, StringBuilder, TimestampMicrosecondArray,
66
};
77
use delta_kernel::arrow::datatypes::{DataType as ArrowDataType, Field, Schema as ArrowSchema};
88
use delta_kernel::arrow::error::ArrowError;
@@ -63,17 +63,28 @@ async fn create_table(
6363
schema: SchemaRef,
6464
partition_columns: &[&str],
6565
use_37_protocol: bool,
66+
enable_timestamp_without_timezone: bool,
6667
) -> Result<Table, Box<dyn std::error::Error>> {
6768
let table_id = "test_id";
6869
let schema = serde_json::to_string(&schema)?;
6970

71+
let (reader_features, writer_features) = {
72+
let mut reader_features = vec![];
73+
let mut writer_features = vec![];
74+
if enable_timestamp_without_timezone {
75+
reader_features.push("timestampNtz");
76+
writer_features.push("timestampNtz");
77+
}
78+
(reader_features, writer_features)
79+
};
80+
7081
let protocol = if use_37_protocol {
7182
json!({
7283
"protocol": {
7384
"minReaderVersion": 3,
7485
"minWriterVersion": 7,
75-
"readerFeatures": [],
76-
"writerFeatures": []
86+
"readerFeatures": reader_features,
87+
"writerFeatures": writer_features,
7788
}
7889
})
7990
} else {
@@ -175,6 +186,7 @@ async fn setup_tables(
175186
schema.clone(),
176187
partition_columns,
177188
true,
189+
false,
178190
)
179191
.await?,
180192
engine_37,
@@ -188,6 +200,7 @@ async fn setup_tables(
188200
schema,
189201
partition_columns,
190202
false,
203+
false,
191204
)
192205
.await?,
193206
engine_11,
@@ -837,3 +850,88 @@ async fn test_write_txn_actions() -> Result<(), Box<dyn std::error::Error>> {
837850
}
838851
Ok(())
839852
}
853+
854+
#[tokio::test]
855+
async fn test_append_timestamp_ntz() -> Result<(), Box<dyn std::error::Error>> {
856+
// setup tracing
857+
let _ = tracing_subscriber::fmt::try_init();
858+
859+
// create a table with TIMESTAMP_NTZ column
860+
let schema = Arc::new(StructType::new(vec![StructField::nullable(
861+
"ts_ntz",
862+
DataType::TIMESTAMP_NTZ,
863+
)]));
864+
865+
let (store, engine, table_location) = setup("test_table_timestamp_ntz", true);
866+
let table = create_table(
867+
store.clone(),
868+
table_location,
869+
schema.clone(),
870+
&[],
871+
true,
872+
true, // enable "timestamp without timezone" feature
873+
)
874+
.await?;
875+
876+
let commit_info = new_commit_info()?;
877+
878+
let mut txn = table
879+
.new_transaction(&engine)?
880+
.with_commit_info(commit_info);
881+
882+
// Create Arrow data with TIMESTAMP_NTZ values including edge cases
883+
// These are microseconds since Unix epoch
884+
let timestamp_values = vec![
885+
0i64, // Unix epoch (1970-01-01T00:00:00.000000)
886+
1634567890123456i64, // 2021-10-18T12:31:30.123456
887+
1634567950654321i64, // 2021-10-18T12:32:30.654321
888+
1672531200000000i64, // 2023-01-01T00:00:00.000000
889+
253402300799999999i64, // 9999-12-31T23:59:59.999999 (near max valid timestamp)
890+
-62135596800000000i64, // 0001-01-01T00:00:00.000000 (near min valid timestamp)
891+
];
892+
893+
let data = RecordBatch::try_new(
894+
Arc::new(schema.as_ref().try_into_arrow()?),
895+
vec![Arc::new(TimestampMicrosecondArray::from(timestamp_values))],
896+
)?;
897+
898+
// Write data
899+
let engine = Arc::new(engine);
900+
let write_context = Arc::new(txn.get_write_context());
901+
902+
let write_metadata = engine
903+
.write_parquet(
904+
&ArrowEngineData::new(data.clone()),
905+
write_context.as_ref(),
906+
HashMap::new(),
907+
true,
908+
)
909+
.await?;
910+
911+
txn.add_write_metadata(write_metadata);
912+
913+
// Commit the transaction
914+
txn.commit(engine.as_ref())?;
915+
916+
// Verify the commit was written correctly
917+
let commit1 = store
918+
.get(&Path::from(
919+
"/test_table_timestamp_ntz/_delta_log/00000000000000000001.json",
920+
))
921+
.await?;
922+
923+
let parsed_commits: Vec<_> = Deserializer::from_slice(&commit1.bytes().await?)
924+
.into_iter::<serde_json::Value>()
925+
.try_collect()?;
926+
927+
// Check that we have the expected number of commits (commitInfo + add)
928+
assert_eq!(parsed_commits.len(), 2);
929+
930+
// Check that the add action exists
931+
assert!(parsed_commits[1].get("add").is_some());
932+
933+
// Verify the data can be read back correctly
934+
test_read(&ArrowEngineData::new(data), &table, engine)?;
935+
936+
Ok(())
937+
}

0 commit comments

Comments
 (0)