33
44package dev .vortex .spark ;
55
6+ import static org .junit .jupiter .api .Assertions .assertDoesNotThrow ;
67import static org .junit .jupiter .api .Assertions .assertEquals ;
78import static org .junit .jupiter .api .Assertions .assertTrue ;
89
@@ -324,6 +325,73 @@ public void testSpecialCharactersAndNulls() throws IOException {
324325 assertEquals ("special!@#$%^&*()" , specialRows .first ().getString (1 ));
325326 }
326327
328+ @ Test
329+ @ DisplayName ("Write and read date, timestamp, and nested struct columns" )
330+ public void testWriteAndReadTemporalAndStructColumns () throws IOException {
331+ Dataset <Row > originalDf = spark .range (0 , 2 )
332+ .selectExpr (
333+ "cast(id as int) as id" ,
334+ "CASE WHEN id = 0 THEN CAST('2024-01-02' AS DATE) ELSE CAST('2024-02-03' AS DATE) END AS event_date" ,
335+ "CASE WHEN id = 0 THEN CAST('2024-01-02 03:04:05.123456' AS TIMESTAMP) "
336+ + "ELSE CAST('2024-02-03 04:05:06.654321' AS TIMESTAMP) END AS event_ts" ,
337+ "named_struct("
338+ + "'event_date', CASE WHEN id = 0 THEN CAST('2024-01-02' AS DATE) ELSE CAST('2024-02-03' AS DATE) END, "
339+ + "'event_ts', CASE WHEN id = 0 THEN CAST('2024-01-02 03:04:05.123456' AS TIMESTAMP) "
340+ + "ELSE CAST('2024-02-03 04:05:06.654321' AS TIMESTAMP) END, "
341+ + "'label', CASE WHEN id = 0 THEN 'alpha' ELSE 'beta' END"
342+ + ") AS payload" );
343+
344+ Path outputPath = tempDir .resolve ("temporal_struct_output" );
345+ originalDf
346+ .write ()
347+ .format ("vortex" )
348+ .option ("path" , outputPath .toUri ().toString ())
349+ .mode (SaveMode .Overwrite )
350+ .save ();
351+
352+ Dataset <Row > readDf = spark .read ()
353+ .format ("vortex" )
354+ .option ("path" , outputPath .toUri ().toString ())
355+ .load ();
356+
357+ List <String > expectedRows = List .of (
358+ "{\" id\" :0,\" event_date\" :\" 2024-01-02\" ,\" event_ts\" :\" 2024-01-02 03:04:05.123456\" ,"
359+ + "\" payload_event_date\" :\" 2024-01-02\" ,\" payload_event_ts\" :\" 2024-01-02 03:04:05.123456\" ,"
360+ + "\" payload_label\" :\" alpha\" }" ,
361+ "{\" id\" :1,\" event_date\" :\" 2024-02-03\" ,\" event_ts\" :\" 2024-02-03 04:05:06.654321\" ,"
362+ + "\" payload_event_date\" :\" 2024-02-03\" ,\" payload_event_ts\" :\" 2024-02-03 04:05:06.654321\" ,"
363+ + "\" payload_label\" :\" beta\" }" );
364+
365+ assertEquals (DataTypes .DateType , readDf .schema ().fields ()[1 ].dataType ());
366+ assertEquals (DataTypes .TimestampType , readDf .schema ().fields ()[2 ].dataType ());
367+ assertTrue (readDf .schema ().fields ()[3 ].dataType () instanceof StructType );
368+ assertEquals (expectedRows , projectTemporalAndStructRows (readDf ));
369+ }
370+
371+ @ Test
372+ @ DisplayName ("Write TimestampNTZ columns and nested structs" )
373+ public void testWriteTimestampNtzColumns () throws IOException {
374+ Dataset <Row > timestampNtzDf = spark .range (0 , 2 )
375+ .selectExpr (
376+ "cast(id as int) as id" ,
377+ "CASE WHEN id = 0 THEN CAST('2024-01-02 03:04:05.123456' AS TIMESTAMP_NTZ) "
378+ + "ELSE CAST(NULL AS TIMESTAMP_NTZ) END AS event_ntz" ,
379+ "named_struct("
380+ + "'event_ntz', CASE WHEN id = 0 THEN CAST('2024-01-02 03:04:05.123456' AS TIMESTAMP_NTZ) "
381+ + "ELSE CAST('2024-02-03 04:05:06.654321' AS TIMESTAMP_NTZ) END"
382+ + ") AS payload" );
383+
384+ Path outputPath = tempDir .resolve ("timestamp_ntz_output" );
385+ assertDoesNotThrow (() -> timestampNtzDf
386+ .write ()
387+ .format ("vortex" )
388+ .option ("path" , outputPath .toUri ().toString ())
389+ .mode (SaveMode .Overwrite )
390+ .save ());
391+
392+ assertTrue (!findVortexFiles (outputPath ).isEmpty (), "TimestampNTZ write should create Vortex files" );
393+ }
394+
327395 /**
328396 * Creates a test DataFrame with monotonically increasing integers
329397 * and their string representations.
@@ -337,6 +405,23 @@ private Dataset<Row> createTestDataFrame(int numRows) {
337405 "array('Alpha', 'Bravo', 'Charlie') AS elements" );
338406 }
339407
408+ private List <String > projectTemporalAndStructRows (Dataset <Row > df ) {
409+ return df
410+ .orderBy ("id" )
411+ .selectExpr ("to_json(named_struct("
412+ + "'id', id, "
413+ + "'event_date', cast(event_date as string), "
414+ + "'event_ts', date_format(event_ts, 'yyyy-MM-dd HH:mm:ss.SSSSSS'), "
415+ + "'payload_event_date', cast(payload.event_date as string), "
416+ + "'payload_event_ts', date_format(payload.event_ts, 'yyyy-MM-dd HH:mm:ss.SSSSSS'), "
417+ + "'payload_label', payload.label"
418+ + ")) as json" )
419+ .collectAsList ()
420+ .stream ()
421+ .map (row -> row .getString (0 ))
422+ .collect (Collectors .toList ());
423+ }
424+
340425 /**
341426 * Finds all Vortex files in the given directory.
342427 */
0 commit comments