@@ -2,7 +2,7 @@ use std::collections::HashMap;
22use std:: sync:: Arc ;
33
44use delta_kernel:: arrow:: array:: {
5- Int32Array , MapBuilder , MapFieldNames , StringArray , StringBuilder ,
5+ Int32Array , MapBuilder , MapFieldNames , StringArray , StringBuilder , TimestampMicrosecondArray ,
66} ;
77use delta_kernel:: arrow:: datatypes:: { DataType as ArrowDataType , Field , Schema as ArrowSchema } ;
88use 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