Skip to content

Commit 91f93bc

Browse files
fix(bigframes): Fix sqlglot backend regressions (#17655)
1 parent 006c947 commit 91f93bc

6 files changed

Lines changed: 125 additions & 11 deletions

File tree

packages/bigframes/bigframes/core/compile/sqlglot/expression_compiler.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class ExpressionCompiler:
4848
sge.LT,
4949
sge.EQ,
5050
sge.NEQ,
51+
sge.Like,
52+
sge.RegexpLike,
53+
sge.In,
54+
sge.Between,
5155
# Logical operations
5256
sge.And,
5357
sge.Or,

packages/bigframes/bigframes/core/compile/sqlglot/expressions/array_ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def _(expr: TypedExpr, op: ops.ArraySliceOp) -> sge.Expression:
105105

106106
@register_unary_op(ops.ArrayToStringOp, pass_op=True)
107107
def _(expr: TypedExpr, op: ops.ArrayToStringOp) -> sge.Expression:
108-
return sge.ArrayToString(this=expr.expr, expression=f"'{op.delimiter}'")
108+
return sge.ArrayToString(this=expr.expr, expression=sge.convert(op.delimiter))
109109

110110

111111
@register_nary_op(ops.ToArrayOp)

packages/bigframes/bigframes/core/compile/sqlglot/expressions/datetime_ops.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,9 +373,11 @@ def _(expr: TypedExpr, op: ops.ToDatetimeOp) -> sge.Expression:
373373
)
374374
return sge.Cast(this=result, to="DATETIME")
375375

376+
if expr.dtype == dtypes.TIMESTAMP_DTYPE:
377+
return sge.func("DATETIME", expr.expr, sge.convert("UTC"))
378+
376379
if expr.dtype in (
377380
dtypes.STRING_DTYPE,
378-
dtypes.TIMESTAMP_DTYPE,
379381
dtypes.DATETIME_DTYPE,
380382
dtypes.DATE_DTYPE,
381383
):
@@ -387,9 +389,10 @@ def _(expr: TypedExpr, op: ops.ToDatetimeOp) -> sge.Expression:
387389
if factor != 1:
388390
value = sge.Mul(this=value, expression=sge.convert(factor))
389391
value = sge.func("TRUNC", value)
390-
return sge.Cast(
391-
this=sge.func("TIMESTAMP_MICROS", sge.Cast(this=value, to="INT64")),
392-
to="DATETIME",
392+
return sge.func(
393+
"DATETIME",
394+
sge.func("TIMESTAMP_MICROS", sge.Cast(this=value, to="INT64")),
395+
sge.convert("UTC"),
393396
)
394397

395398

@@ -400,7 +403,7 @@ def _(expr: TypedExpr, op: ops.ToTimestampOp) -> sge.Expression:
400403
if expr.dtype != dtypes.STRING_DTYPE:
401404
result = sge.Cast(this=result, to="STRING")
402405
return sge.func(
403-
"PARSE_TIMESTAMP", sge.convert(op.format), expr.expr, sge.convert("UTC")
406+
"PARSE_TIMESTAMP", sge.convert(op.format), result, sge.convert("UTC")
404407
)
405408

406409
if expr.dtype in (
@@ -683,3 +686,49 @@ def _integer_label_to_datetime_op_yearly_freq(
683686
this=next_month_date, expression=sge.Interval(this=one, unit="DAY")
684687
)
685688
return sge.Cast(this=x_label, to=sqlglot_types.from_bigframes_dtype(y.dtype))
689+
690+
691+
@register_binary_op(ops.timestamp_add_op)
692+
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
693+
return sge.TimestampAdd(
694+
this=left.expr, expression=right.expr, unit=sge.Var(this="MICROSECOND")
695+
)
696+
697+
698+
@register_binary_op(ops.timestamp_sub_op)
699+
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
700+
return sge.TimestampSub(
701+
this=left.expr, expression=right.expr, unit=sge.Var(this="MICROSECOND")
702+
)
703+
704+
705+
@register_binary_op(ops.timestamp_diff_op)
706+
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
707+
return sge.TimestampDiff(
708+
this=left.expr, expression=right.expr, unit=sge.Var(this="MICROSECOND")
709+
)
710+
711+
712+
@register_binary_op(ops.date_add_op)
713+
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
714+
left_expr = sge.Cast(this=left.expr, to="TIMESTAMP")
715+
return sge.TimestampAdd(
716+
this=left_expr, expression=right.expr, unit=sge.Var(this="MICROSECOND")
717+
)
718+
719+
720+
@register_binary_op(ops.date_sub_op)
721+
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
722+
left_expr = sge.Cast(this=left.expr, to="TIMESTAMP")
723+
return sge.TimestampSub(
724+
this=left_expr, expression=right.expr, unit=sge.Var(this="MICROSECOND")
725+
)
726+
727+
728+
@register_binary_op(ops.date_diff_op)
729+
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
730+
diff = sge.DateDiff(this=left.expr, expression=right.expr, unit=sge.Var(this="DAY"))
731+
return sge.Mul(
732+
this=diff,
733+
expression=sge.convert(int(UNIT_TO_US_CONVERSION_FACTORS["d"])),
734+
)

packages/bigframes/tests/system/small/engines/test_comparison_ops.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import asyncio
1516
import itertools
1617

1718
import pytest
1819

1920
import bigframes.operations as ops
2021
from bigframes.core import array_value
2122
from bigframes.session import polars_executor
22-
from bigframes.testing.engine_utils import assert_equivalence_execution
23+
from bigframes.testing.engine_utils import SPEC, assert_equivalence_execution
2324

2425
pytest.importorskip("polars")
2526

@@ -68,3 +69,16 @@ def test_engines_project_comparison_op(
6869
# bool col actually doesn't work properly for bq engine
6970
arr = apply_op_pairwise(scalars_array_value, op, excluded_cols=["string_col"])
7071
assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine)
72+
73+
74+
@pytest.mark.parametrize("engine", ["bq-sqlglot"], indirect=True)
75+
def test_engines_precedence_like_and_in(
76+
scalars_array_value: array_value.ArrayValue, engine
77+
):
78+
exprs = [
79+
ops.eq_op.as_expr("bool_col", ops.StrContainsOp("a").as_expr("string_col")),
80+
]
81+
arr, _ = scalars_array_value.compute_values(exprs)
82+
res = asyncio.run(engine.execute(arr.node, SPEC))
83+
assert res is not None
84+
assert len(res.batches().to_pandas()) > 0

packages/bigframes/tests/system/small/engines/test_temporal_ops.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import asyncio
16+
17+
import pandas as pd
1518
import pytest
1619

1720
import bigframes.operations as ops
21+
from bigframes import dtypes
1822
from bigframes.core import array_value
23+
from bigframes.core import expression as ex
1924
from bigframes.session import polars_executor
20-
from bigframes.testing.engine_utils import assert_equivalence_execution
25+
from bigframes.testing.engine_utils import SPEC, assert_equivalence_execution
2126

2227
pytest.importorskip("polars")
2328

@@ -64,3 +69,45 @@ def test_engines_date_accessors(scalars_array_value: array_value.ArrayValue, eng
6469

6570
arr, _ = scalars_array_value.compute_values(exprs)
6671
assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine)
72+
73+
74+
@pytest.mark.parametrize("engine", ["bq", "bq-sqlglot"], indirect=True)
75+
def test_engines_temporal_arithmetic(
76+
scalars_array_value: array_value.ArrayValue, engine
77+
):
78+
exprs = [
79+
ops.timestamp_add_op.as_expr(
80+
"timestamp_col", ex.const(pd.Timedelta(seconds=1), dtypes.TIMEDELTA_DTYPE)
81+
),
82+
ops.timestamp_sub_op.as_expr(
83+
"timestamp_col", ex.const(pd.Timedelta(seconds=1), dtypes.TIMEDELTA_DTYPE)
84+
),
85+
ops.date_add_op.as_expr(
86+
"date_col", ex.const(pd.Timedelta(days=1), dtypes.TIMEDELTA_DTYPE)
87+
),
88+
ops.date_sub_op.as_expr(
89+
"date_col", ex.const(pd.Timedelta(days=1), dtypes.TIMEDELTA_DTYPE)
90+
),
91+
ops.timestamp_diff_op.as_expr("timestamp_col", "timestamp_col"),
92+
ops.date_diff_op.as_expr("date_col", "date_col"),
93+
]
94+
95+
arr, _ = scalars_array_value.compute_values(exprs)
96+
res = asyncio.run(engine.execute(arr.node, SPEC))
97+
assert res is not None
98+
assert len(res.batches().to_pandas()) > 0
99+
100+
101+
@pytest.mark.parametrize("engine", ["bq", "bq-sqlglot"], indirect=True)
102+
def test_engines_to_datetime(scalars_array_value: array_value.ArrayValue, engine):
103+
exprs = [
104+
ops.ToDatetimeOp().as_expr("timestamp_col"),
105+
]
106+
arr, _ = scalars_array_value.compute_values(exprs)
107+
res = asyncio.run(engine.execute(arr.node, SPEC))
108+
assert res is not None
109+
df = res.batches().to_pandas()
110+
# The input timestamp was: TIMESTAMP('2021-07-21T17:43:43.945289+00:00')
111+
# The output should be naive DATETIME('2021-07-21T17:43:43.945289')
112+
val = df.iloc[0, -1]
113+
assert pd.Timestamp(val) == pd.Timestamp("2021-07-21T17:43:43.945289")
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
SELECT
2-
CAST(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col` * 0.001) AS INT64)) AS DATETIME) AS `int64_col`,
2+
DATETIME(TIMESTAMP_MICROS(CAST(TRUNC(`int64_col` * 0.001) AS INT64)), 'UTC') AS `int64_col`,
33
SAFE_CAST(`string_col` AS DATETIME) AS `string_col`,
4-
CAST(TIMESTAMP_MICROS(CAST(TRUNC(`float64_col` * 0.001) AS INT64)) AS DATETIME) AS `float64_col`,
5-
SAFE_CAST(`timestamp_col` AS DATETIME) AS `timestamp_col`,
4+
DATETIME(TIMESTAMP_MICROS(CAST(TRUNC(`float64_col` * 0.001) AS INT64)), 'UTC') AS `float64_col`,
5+
DATETIME(`timestamp_col`, 'UTC') AS `timestamp_col`,
66
SAFE_CAST(`string_col` AS DATETIME) AS `string_col_fmt`
77
FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` AS `bft_0`

0 commit comments

Comments
 (0)