@@ -386,6 +386,54 @@ def downstream_partitioned(
386386 assert DeltaTable (saved_path ).metadata ().partition_columns == ["year" ]
387387
388388
389+ def test_polars_delta_native_partitioning_datetime_column_predicate (
390+ polars_delta_io_manager : PolarsDeltaIOManager ,
391+ df_for_delta : pl .DataFrame ,
392+ ):
393+ """The overwrite predicate must work for a ``Datetime`` partition column.
394+
395+ In ``0.27.12`` the predicate emitted a ``DATE '...'`` literal, which fails
396+ to compare against a ``Datetime`` column (`Invalid comparison operation:
397+ Timestamp <= ...`). A plain string literal is coerced by DataFusion to the
398+ column's timestamp type, so overwriting one partition leaves the others
399+ intact.
400+
401+ Note: this exercises the write/overwrite predicate path only. Reading back a
402+ *single* partition of a ``Datetime``-partitioned table is a separate,
403+ pre-existing delta-rs limitation (the partition value cannot be parsed as
404+ ``timestamp_ntz``), so the table is read back in full here rather than via a
405+ partitioned downstream asset.
406+ """
407+ manager = polars_delta_io_manager
408+ df = df_for_delta
409+
410+ partitions_def = DailyPartitionsDefinition (start_date = datetime (2024 , 1 , 1 ))
411+
412+ @asset (
413+ io_manager_def = manager ,
414+ partitions_def = partitions_def ,
415+ metadata = {"partition_by" : "ts" },
416+ )
417+ def upstream_partitioned (context : OpExecutionContext ) -> pl .DataFrame :
418+ return df .with_columns (
419+ pl .lit (context .partition_key )
420+ .str .strptime (pl .Datetime , "%Y-%m-%d" )
421+ .alias ("ts" )
422+ )
423+
424+ saved_path = None
425+ for partition_key in ["2024-01-01" , "2024-01-02" ]:
426+ result = materialize ([upstream_partitioned ], partition_key = partition_key )
427+ saved_path = get_saved_path (result , "upstream_partitioned" )
428+
429+ assert saved_path is not None
430+ assert DeltaTable (saved_path ).metadata ().partition_columns == ["ts" ]
431+
432+ # Both partition writes succeeded and neither predicate clobbered the other.
433+ written = pl .read_delta (saved_path )["ts" ].unique ().sort ().to_list ()
434+ assert written == [datetime (2024 , 1 , 1 ), datetime (2024 , 1 , 2 )]
435+
436+
389437def test_polars_delta_native_multi_partitions (
390438 polars_delta_io_manager : PolarsDeltaIOManager ,
391439 df_for_delta : pl .DataFrame ,
@@ -541,51 +589,28 @@ def asset_schema_2(context: OpExecutionContext) -> pl.DataFrame:
541589
542590
543591@pytest .mark .parametrize (
544- "partition_by, partition_keys, expected_filters, expected_predicate, df " ,
592+ "partition_by, partition_keys, expected_filters, expected_predicate" ,
545593 [
546- ("col_name" , ["a" ], [("col_name" , "in" , ["a" ])], "col_name = 'a'" , None ),
594+ ("col_name" , ["a" ], [("col_name" , "in" , ["a" ])], "col_name = 'a'" ),
547595 (
548596 "col_name" ,
549597 ["a" , "b" ],
550598 [("col_name" , "in" , ["a" , "b" ])],
551- "col_name in ('a', 'b')" ,
552- None ,
599+ "col_name = 'a' OR col_name = 'b'" ,
553600 ),
554601 (
555602 {"col_name" : "mapped_col" },
556603 [{"col_name" : "a" }],
557604 [("mapped_col" , "in" , ["a" ])],
558- "mapped_col in ('a')" ,
559- None ,
605+ "(mapped_col = 'a')" ,
560606 ),
561607 (
562608 {"col_name" : "mapped_col" },
563609 [{"col_name" : "a" }, {"col_name" : "b" }],
564610 [("mapped_col" , "in" , ["a" , "b" ])],
565- "mapped_col in ('a', 'b')" ,
566- None ,
567- ),
568- (None , [], [], None , None ),
569- # Regression (#330): a time-window partition mapped onto a non-temporal
570- # (string) column must stay quoted as a string, not wrapped in `DATE`.
571- (
572- "year" ,
573- ["2025" ],
574- [("year" , "in" , ["2025" ])],
575- "year = '2025'" ,
576- pl .DataFrame ({"year" : ["2025" ]}, schema = {"year" : pl .String }),
577- ),
578- # A genuine `Date` column is emitted as a `DATE '...'` literal so the
579- # predicate compares against the native column type.
580- (
581- "date" ,
582- ["2025-01-01" ],
583- [("date" , "in" , ["2025-01-01" ])],
584- "date = DATE '2025-01-01'" ,
585- pl .DataFrame (
586- {"date" : [datetime (2025 , 1 , 1 ).date ()]}, schema = {"date" : pl .Date }
587- ),
611+ "(mapped_col = 'a' OR mapped_col = 'b')" ,
588612 ),
613+ (None , [], [], None ),
589614 ],
590615)
591616@pytest .mark .parametrize ("context" , [InputContext , OutputContext ])
@@ -595,7 +620,6 @@ def test_partition_filters_predicate(
595620 partition_keys : list [str ] | list [dict [str , str ]],
596621 expected_filters : list [tuple [str , str , list [str ]]],
597622 expected_predicate : str ,
598- df : pl .DataFrame | None ,
599623 context : type [InputContext | OutputContext ],
600624):
601625 """Test that the partition filters and predicate are generated correctly."""
@@ -633,4 +657,4 @@ def test_partition_filters_predicate(
633657 )
634658
635659 assert PolarsDeltaIOManager .get_partition_filters (context ) == expected_filters
636- assert PolarsDeltaIOManager .get_predicate (context , df ) == expected_predicate
660+ assert PolarsDeltaIOManager .get_predicate (context ) == expected_predicate
0 commit comments