2626use std:: rc:: Rc ;
2727use std:: sync:: Arc ;
2828
29+ use datafusion:: arrow:: compute:: kernels:: cast_utils:: parse_interval_month_day_nano;
2930use datafusion:: arrow:: datatypes:: { DataType as ArrowDataType , Field } ;
3031use datafusion:: catalog_common:: MemorySchemaProvider ;
3132use datafusion:: common:: config:: ConfigOptions ;
@@ -40,6 +41,7 @@ use datafusion::logical_expr::function::{PartitionEvaluatorArgs, WindowUDFFieldA
4041use datafusion:: logical_expr:: {
4142 self , lit, AggregateUDF , Case , Distinct , Expr , JoinType , LogicalPlan , PartitionEvaluator ,
4243 ScalarUDF , ScalarUDFImpl , Signature , SimpleAggregateUDF , TypeSignature , Volatility ,
44+ WindowFrameBound as DfWindowFrameBound , WindowFrameUnits as DfWindowFrameUnits ,
4345 WindowFunctionDefinition , WindowUDF , WindowUDFImpl ,
4446} ;
4547use datafusion:: optimizer:: analyzer:: function_rewrite:: ApplyFunctionRewrites ;
@@ -50,7 +52,8 @@ use asap_sql_function_catalog::{AggSemantic, Arity, RewriteKind};
5052use asap_types:: pre_asap:: agg_intent:: AggIntent ;
5153use asap_types:: pre_asap:: query_expr:: {
5254 GroupKeys , Predicate , ProjectItem , Reduction , SortKey , Source ,
53- UnresolvedQueryExpr as Unresolved ,
55+ UnresolvedQueryExpr as Unresolved , WindowFrame , WindowFrameBound , WindowFrameOffset ,
56+ WindowFrameUnits ,
5457} ;
5558use asap_types:: pre_asap:: schema:: { DataType , Schema } ;
5659use asap_types:: pre_asap:: {
@@ -67,7 +70,7 @@ mod types;
6770pub use types:: SqlCatalog ;
6871
6972use self :: expr:: df_expr_to_unresolved;
70- use self :: types:: { arrow_to_dtype, schema_to_arrow} ;
73+ use self :: types:: { arrow_to_dtype, scalar_value_to_asap , schema_to_arrow} ;
7174
7275std:: thread_local! {
7376 static ACCURACY : std:: cell:: RefCell <AccuracyTarget > =
@@ -557,8 +560,8 @@ impl<'a> SqlLowerer<'a> {
557560 } )
558561 }
559562
560- /// `func(args) OVER (PARTITION BY … ORDER BY …)`. One window function per
561- /// plan node; window frames are not modelled yet (default frame assumed) .
563+ /// `func(args) OVER (PARTITION BY … ORDER BY … ROWS/RANGE BETWEEN … )`. One
564+ /// window function per plan node .
562565 fn lower_window ( & self , window : & logical_expr:: Window ) -> Result < Unresolved , LoweringError > {
563566 if window. window_expr . len ( ) > 1 {
564567 return Err ( LoweringError :: UnsupportedFeature ( format ! (
@@ -613,6 +616,7 @@ impl<'a> SqlLowerer<'a> {
613616 } )
614617 } )
615618 . collect :: < Result < Vec < _ > , _ > > ( ) ?;
619+ let frame = lower_window_frame ( & wf. window_frame ) ?;
616620 // The window plan's schema is `[input fields …, window output]`; the last
617621 // field is the window column's name (what an enclosing Project references).
618622 let output_name = window
@@ -626,6 +630,7 @@ impl<'a> SqlLowerer<'a> {
626630 args,
627631 partition_by : partition_by. into ( ) ,
628632 order_by,
633+ frame : Some ( frame) ,
629634 output_name,
630635 child,
631636 } )
@@ -1742,6 +1747,79 @@ fn lower_window_func_kind(fun: &WindowFunctionDefinition) -> Result<WindowFuncKi
17421747 }
17431748}
17441749
1750+ /// Map DataFusion's resolved `WindowFrame` (issue #268) to the canonical
1751+ /// [`WindowFrame`]. DataFusion's planner always fills in the SQL-standard
1752+ /// default frame before the logical plan is built, so this never sees an
1753+ /// "absent" frame — only `ROWS`/`RANGE`/`GROUPS` with concrete bounds.
1754+ /// `GROUPS` is rejected: no query in this repo's SQL corpora uses it, and
1755+ /// nothing downstream interprets frame semantics yet, so it isn't worth
1756+ /// modelling untested.
1757+ fn lower_window_frame (
1758+ frame : & datafusion:: logical_expr:: WindowFrame ,
1759+ ) -> Result < WindowFrame , LoweringError > {
1760+ let units = match frame. units {
1761+ DfWindowFrameUnits :: Rows => WindowFrameUnits :: Rows ,
1762+ DfWindowFrameUnits :: Range => WindowFrameUnits :: Range ,
1763+ DfWindowFrameUnits :: Groups => {
1764+ return Err ( LoweringError :: UnsupportedFeature (
1765+ "window frame unit: GROUPS" . into ( ) ,
1766+ ) )
1767+ }
1768+ } ;
1769+ let offset = |v : & DfScalarValue | -> Result < WindowFrameOffset , LoweringError > {
1770+ Ok ( match v {
1771+ DfScalarValue :: IntervalYearMonth ( Some ( months) ) => WindowFrameOffset :: Interval {
1772+ months : * months,
1773+ days : 0 ,
1774+ nanoseconds : 0 ,
1775+ } ,
1776+ DfScalarValue :: IntervalDayTime ( Some ( value) ) => WindowFrameOffset :: Interval {
1777+ months : 0 ,
1778+ days : value. days ,
1779+ nanoseconds : i64:: from ( value. milliseconds ) * 1_000_000 ,
1780+ } ,
1781+ DfScalarValue :: IntervalMonthDayNano ( Some ( value) ) => WindowFrameOffset :: Interval {
1782+ months : value. months ,
1783+ days : value. days ,
1784+ nanoseconds : value. nanoseconds ,
1785+ } ,
1786+ // DataFusion 43 keeps every RANGE offset as text: both numeric
1787+ // bounds such as `1.5` and normalized interval literals such as
1788+ // `"1 HOUR"`. Arrow's interval parser accepts bare numbers and
1789+ // interprets them as months, so classify numeric text first.
1790+ DfScalarValue :: Utf8 ( Some ( value) ) | DfScalarValue :: LargeUtf8 ( Some ( value) ) => {
1791+ if let Ok ( value) = value. parse :: < i64 > ( ) {
1792+ WindowFrameOffset :: Scalar ( ScalarValue :: Int64 ( value) )
1793+ } else if let Ok ( value) = value. parse :: < f64 > ( ) {
1794+ WindowFrameOffset :: Scalar ( ScalarValue :: Float64 ( value) )
1795+ } else {
1796+ match parse_interval_month_day_nano ( value) {
1797+ Ok ( interval) => WindowFrameOffset :: Interval {
1798+ months : interval. months ,
1799+ days : interval. days ,
1800+ nanoseconds : interval. nanoseconds ,
1801+ } ,
1802+ Err ( _) => WindowFrameOffset :: Scalar ( scalar_value_to_asap ( v) ?) ,
1803+ }
1804+ }
1805+ }
1806+ _ => WindowFrameOffset :: Scalar ( scalar_value_to_asap ( v) ?) ,
1807+ } )
1808+ } ;
1809+ let bound = |b : & DfWindowFrameBound | -> Result < WindowFrameBound , LoweringError > {
1810+ Ok ( match b {
1811+ DfWindowFrameBound :: Preceding ( v) => WindowFrameBound :: Preceding ( offset ( v) ?) ,
1812+ DfWindowFrameBound :: CurrentRow => WindowFrameBound :: CurrentRow ,
1813+ DfWindowFrameBound :: Following ( v) => WindowFrameBound :: Following ( offset ( v) ?) ,
1814+ } )
1815+ } ;
1816+ Ok ( WindowFrame {
1817+ units,
1818+ start_bound : bound ( & frame. start_bound ) ?,
1819+ end_bound : bound ( & frame. end_bound ) ?,
1820+ } )
1821+ }
1822+
17451823// ── Issue #225, item 3: DataFusion registry drift detection ────────────────
17461824//
17471825// `asap_sql_function_catalog::NATIVE_FUNCTIONS` is hand-maintained data
0 commit comments