@@ -174,12 +174,10 @@ def lower(self, expr: expression.OpExpression) -> expression.Expression:
174174 divisor .output_type
175175 ):
176176 # exact same as floordiv impl for timedelta
177- numeric_result = ops .floordiv_op .as_expr (
177+ numeric_result = ops .div_op .as_expr (
178178 ops .AsTypeOp (to_type = dtypes .INT_DTYPE ).as_expr (dividend ), divisor
179179 )
180- int_result = ops .AsTypeOp (to_type = dtypes .INT_DTYPE ).as_expr (numeric_result )
181- return ops .AsTypeOp (to_type = dtypes .TIMEDELTA_DTYPE ).as_expr (int_result )
182-
180+ return _numeric_to_timedelta (numeric_result )
183181 if (
184182 dividend .output_type == dtypes .BOOL_DTYPE
185183 and divisor .output_type == dtypes .BOOL_DTYPE
@@ -226,11 +224,10 @@ def lower(self, expr: expression.OpExpression) -> expression.Expression:
226224 divisor .output_type
227225 ):
228226 # this is pretty fragile as zero will break it, and must fit back into int
229- numeric_result = expr . op .as_expr (
227+ numeric_result = ops . div_op .as_expr (
230228 ops .AsTypeOp (to_type = dtypes .INT_DTYPE ).as_expr (dividend ), divisor
231229 )
232- int_result = ops .AsTypeOp (to_type = dtypes .INT_DTYPE ).as_expr (numeric_result )
233- return ops .AsTypeOp (to_type = dtypes .TIMEDELTA_DTYPE ).as_expr (int_result )
230+ return _numeric_to_timedelta (numeric_result )
234231
235232 if dividend .output_type == dtypes .BOOL_DTYPE :
236233 dividend = ops .AsTypeOp (to_type = dtypes .INT_DTYPE ).as_expr (dividend )
@@ -319,6 +316,32 @@ def lower(self, expr: expression.OpExpression) -> expression.Expression:
319316 return expr
320317
321318
319+ class LowerCeilOp (op_lowering .OpLoweringRule ):
320+ @property
321+ def op (self ) -> type [ops .ScalarOp ]:
322+ return numeric_ops .CeilOp
323+
324+ def lower (self , expr : expression .OpExpression ) -> expression .Expression :
325+ assert isinstance (expr .op , numeric_ops .CeilOp )
326+ arg = expr .children [0 ]
327+ if arg .output_type in (dtypes .INT_DTYPE , dtypes .BOOL_DTYPE ):
328+ return expr .op .as_expr (ops .AsTypeOp (dtypes .FLOAT_DTYPE ).as_expr (arg ))
329+ return expr
330+
331+
332+ class LowerFloorOp (op_lowering .OpLoweringRule ):
333+ @property
334+ def op (self ) -> type [ops .ScalarOp ]:
335+ return numeric_ops .FloorOp
336+
337+ def lower (self , expr : expression .OpExpression ) -> expression .Expression :
338+ assert isinstance (expr .op , numeric_ops .FloorOp )
339+ arg = expr .children [0 ]
340+ if arg .output_type in (dtypes .INT_DTYPE , dtypes .BOOL_DTYPE ):
341+ return expr .op .as_expr (ops .AsTypeOp (dtypes .FLOAT_DTYPE ).as_expr (arg ))
342+ return expr
343+
344+
322345class LowerIsinOp (op_lowering .OpLoweringRule ):
323346 @property
324347 def op (self ) -> type [ops .ScalarOp ]:
@@ -465,8 +488,21 @@ def _lower_cast(cast_op: ops.AsTypeOp, arg: expression.Expression):
465488 LowerInvertOp (),
466489 LowerIsinOp (),
467490 LowerLenOp (),
491+ LowerCeilOp (),
492+ LowerFloorOp (),
468493)
469494
470495
471496def lower_ops_to_polars (root : bigframe_node .BigFrameNode ) -> bigframe_node .BigFrameNode :
472497 return op_lowering .lower_ops (root , rules = POLARS_LOWERING_RULES )
498+
499+
500+ def _numeric_to_timedelta (expr : expression .Expression ) -> expression .Expression :
501+ """rounding logic used for emulating timedelta ops"""
502+ rounded_value = ops .where_op .as_expr (
503+ ops .floor_op .as_expr (expr ),
504+ ops .gt_op .as_expr (expr , expression .const (0 )),
505+ ops .ceil_op .as_expr (expr ),
506+ )
507+ int_value = ops .AsTypeOp (to_type = dtypes .INT_DTYPE ).as_expr (rounded_value )
508+ return ops .AsTypeOp (to_type = dtypes .TIMEDELTA_DTYPE ).as_expr (int_value )
0 commit comments