From 7a8572f93d0212ce7257e3dee3b59bbcd7db961b Mon Sep 17 00:00:00 2001 From: Chelsea Lin Date: Mon, 7 Jul 2025 22:38:32 +0000 Subject: [PATCH 1/3] refactor: add compile_isin_join --- bigframes/core/compile/sqlglot/compiler.py | 22 ++++++++ bigframes/core/compile/sqlglot/sqlglot_ir.py | 50 +++++++++++++++++++ .../test_compile_isin/out.sql | 10 ++++ .../core/compile/sqlglot/test_compile_isin.py | 30 +++++++++++ 4 files changed, 112 insertions(+) create mode 100644 tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql create mode 100644 tests/unit/core/compile/sqlglot/test_compile_isin.py diff --git a/bigframes/core/compile/sqlglot/compiler.py b/bigframes/core/compile/sqlglot/compiler.py index b4dc6174be..0af821a62c 100644 --- a/bigframes/core/compile/sqlglot/compiler.py +++ b/bigframes/core/compile/sqlglot/compiler.py @@ -244,6 +244,28 @@ def compile_join( joins_nulls=node.joins_nulls, ) + @_compile_node.register + def compile_isin_join( + self, node: nodes.InNode, left: ir.SQLGlotIR, right: ir.SQLGlotIR + ) -> ir.SQLGlotIR: + conditions = tuple( + typed_expr.TypedExpr( + scalar_compiler.compile_scalar_expression(node.left_col), + left.output_type, + ), + typed_expr.TypedExpr( + scalar_compiler.compile_scalar_expression(node.right_col), + right.output_type, + ), + ) + + return left.isin_join( + right, + indicator_col=node.indicator_col.sql, + conditions=conditions, + joins_nulls=node.joins_nulls, + ) + @_compile_node.register def compile_concat( self, node: nodes.ConcatNode, *children: ir.SQLGlotIR diff --git a/bigframes/core/compile/sqlglot/sqlglot_ir.py b/bigframes/core/compile/sqlglot/sqlglot_ir.py index 1a00cd0a93..3ee07c69ce 100644 --- a/bigframes/core/compile/sqlglot/sqlglot_ir.py +++ b/bigframes/core/compile/sqlglot/sqlglot_ir.py @@ -336,6 +336,56 @@ def join( return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) + def isin_join( + self, + right: SQLGlotIR, + indicator_col: str, + conditions: tuple[typed_expr.TypedExpr, typed_expr.TypedExpr], + *, + joins_nulls: bool = True, + ) -> SQLGlotIR: + """Joins the current query with another SQLGlotIR instance.""" + # TODO: Optimization similar to Ibis: + # if isinstance(values, ArrayValue): + # return ops.ArrayContains(values, self).to_expr() + # elif isinstance(values, Column): + # return ops.InSubquery(values.as_table(), needle=self).to_expr() + # else: + # return ops.InValues(self, values).to_expr() + + raise NotImplementedError + # left_cte_name = sge.to_identifier( + # next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted + # ) + # right_cte_name = sge.to_identifier( + # next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted + # ) + + # left_select = _select_to_cte(self.expr, left_cte_name) + # right_select = _select_to_cte(right.expr, right_cte_name) + + # left_ctes = left_select.args.pop("with", []) + # right_ctes = right_select.args.pop("with", []) + # merged_ctes = [*left_ctes, *right_ctes] + + + + # join_conditions = [ + # _join_condition(left, right, joins_nulls) for left, right in conditions + # ] + # join_on = sge.And(expressions=join_conditions) if join_conditions else None + + # join_type_str = join_type if join_type != "outer" else "full outer" + # new_expr = ( + # sge.Select() + # .select(sge.Star()) + # .from_(sge.Table(this=left_cte_name)) + # .join(sge.Table(this=right_cte_name), on=join_on, join_type=join_type_str) + # ) + # new_expr.set("with", sge.With(expressions=merged_ctes)) + + # return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) + def explode( self, column_names: tuple[str, ...], diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql new file mode 100644 index 0000000000..b9cced3226 --- /dev/null +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql @@ -0,0 +1,10 @@ +WITH `bfcte_0` AS ( + SELECT + * + FROM UNNEST(ARRAY>[STRUCT(314159.0, 0), STRUCT(2.0, 1), STRUCT(3.0, 2), STRUCT(CAST(NULL AS FLOAT64), 3)]) +) +SELECT + `bfcol_0` AS `0` +FROM `bfcte_0` +ORDER BY + `bfcol_1` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/test_compile_isin.py b/tests/unit/core/compile/sqlglot/test_compile_isin.py new file mode 100644 index 0000000000..6a022873d2 --- /dev/null +++ b/tests/unit/core/compile/sqlglot/test_compile_isin.py @@ -0,0 +1,30 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pandas as pd +import pytest + +import bigframes +import bigframes.pandas as bpd + +pytest.importorskip("pytest_snapshot") + + +def test_compile_isin( + scalar_types_df: bpd.DataFrame, compiler_session: bigframes.Session, snapshot +): + data = [314159, 2.0, 3, pd.NA] + s = bpd.Series(data, session=compiler_session) + bf_isin = scalar_types_df["int64_col"].isin(s).to_frame() + snapshot.assert_match(bf_isin.sql, "out.sql") From e8344b629b6cc8a9bc3a526c195bbbb833decdda Mon Sep 17 00:00:00 2001 From: Chelsea Lin Date: Fri, 29 Aug 2025 21:56:26 +0000 Subject: [PATCH 2/3] complete most implementations --- bigframes/core/compile/sqlglot/compiler.py | 6 +- bigframes/core/compile/sqlglot/sqlglot_ir.py | 93 +++++++++++-------- tests/unit/core/compile/sqlglot/conftest.py | 2 +- .../test_compile_isin/out.sql | 41 ++++++-- .../test_compile_isin_not_nullable/out.sql | 30 ++++++ .../core/compile/sqlglot/test_compile_isin.py | 17 ++-- 6 files changed, 129 insertions(+), 60 deletions(-) create mode 100644 tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql diff --git a/bigframes/core/compile/sqlglot/compiler.py b/bigframes/core/compile/sqlglot/compiler.py index 0af821a62c..8364e757a1 100644 --- a/bigframes/core/compile/sqlglot/compiler.py +++ b/bigframes/core/compile/sqlglot/compiler.py @@ -248,14 +248,14 @@ def compile_join( def compile_isin_join( self, node: nodes.InNode, left: ir.SQLGlotIR, right: ir.SQLGlotIR ) -> ir.SQLGlotIR: - conditions = tuple( + conditions = ( typed_expr.TypedExpr( scalar_compiler.compile_scalar_expression(node.left_col), - left.output_type, + node.left_col.output_type, ), typed_expr.TypedExpr( scalar_compiler.compile_scalar_expression(node.right_col), - right.output_type, + node.right_col.output_type, ), ) diff --git a/bigframes/core/compile/sqlglot/sqlglot_ir.py b/bigframes/core/compile/sqlglot/sqlglot_ir.py index 3ee07c69ce..5f3f07dd3b 100644 --- a/bigframes/core/compile/sqlglot/sqlglot_ir.py +++ b/bigframes/core/compile/sqlglot/sqlglot_ir.py @@ -341,50 +341,61 @@ def isin_join( right: SQLGlotIR, indicator_col: str, conditions: tuple[typed_expr.TypedExpr, typed_expr.TypedExpr], - *, joins_nulls: bool = True, ) -> SQLGlotIR: """Joins the current query with another SQLGlotIR instance.""" - # TODO: Optimization similar to Ibis: - # if isinstance(values, ArrayValue): - # return ops.ArrayContains(values, self).to_expr() - # elif isinstance(values, Column): - # return ops.InSubquery(values.as_table(), needle=self).to_expr() - # else: - # return ops.InValues(self, values).to_expr() - - raise NotImplementedError - # left_cte_name = sge.to_identifier( - # next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted - # ) - # right_cte_name = sge.to_identifier( - # next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted - # ) - - # left_select = _select_to_cte(self.expr, left_cte_name) - # right_select = _select_to_cte(right.expr, right_cte_name) - - # left_ctes = left_select.args.pop("with", []) - # right_ctes = right_select.args.pop("with", []) - # merged_ctes = [*left_ctes, *right_ctes] - - - - # join_conditions = [ - # _join_condition(left, right, joins_nulls) for left, right in conditions - # ] - # join_on = sge.And(expressions=join_conditions) if join_conditions else None - - # join_type_str = join_type if join_type != "outer" else "full outer" - # new_expr = ( - # sge.Select() - # .select(sge.Star()) - # .from_(sge.Table(this=left_cte_name)) - # .join(sge.Table(this=right_cte_name), on=join_on, join_type=join_type_str) - # ) - # new_expr.set("with", sge.With(expressions=merged_ctes)) - - # return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) + left_cte_name = sge.to_identifier( + next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted + ) + right_cte_name = sge.to_identifier( + next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted + ) + + left_select = _select_to_cte(self.expr, left_cte_name) + right_select = _select_to_cte(right.expr, right_cte_name) + + left_ctes = left_select.args.pop("with", []) + right_ctes = right_select.args.pop("with", []) + merged_ctes = [*left_ctes, *right_ctes] + + left_condition = typed_expr.TypedExpr( + sge.Column(this=conditions[0].expr, table=left_cte_name), + conditions[0].dtype, + ) + right_condition = typed_expr.TypedExpr( + sge.Column(this=conditions[1].expr, table=right_cte_name), + conditions[1].dtype, + ) + + new_column: sge.Expression + if joins_nulls: + new_column = sge.Exists( + this=sge.Select() + .select(sge.convert(1)) + .from_(sge.Table(this=right_cte_name)) + .where( + _join_condition(left_condition, right_condition, joins_nulls=True) + ) + ) + else: + new_column = sge.In( + this=left_condition.expr, + expressions=[right_condition.expr], + ) + + new_column = sge.Alias( + this=new_column, + alias=sge.to_identifier(indicator_col, quoted=self.quoted), + ) + + new_expr = ( + sge.Select() + .select(sge.Column(this=sge.Star(), table=left_cte_name), new_column) + .from_(sge.Table(this=left_cte_name)) + ) + new_expr.set("with", sge.With(expressions=merged_ctes)) + + return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) def explode( self, diff --git a/tests/unit/core/compile/sqlglot/conftest.py b/tests/unit/core/compile/sqlglot/conftest.py index f65343fd66..3279b3a259 100644 --- a/tests/unit/core/compile/sqlglot/conftest.py +++ b/tests/unit/core/compile/sqlglot/conftest.py @@ -85,7 +85,7 @@ def scalar_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: bigquery.SchemaField("numeric_col", "NUMERIC"), bigquery.SchemaField("float64_col", "FLOAT"), bigquery.SchemaField("rowindex", "INTEGER"), - bigquery.SchemaField("rowindex_2", "INTEGER"), + bigquery.SchemaField("rowindex_2", "INTEGER", mode="REQUIRED"), bigquery.SchemaField("string_col", "STRING"), bigquery.SchemaField("time_col", "TIME"), bigquery.SchemaField("timestamp_col", "TIMESTAMP"), diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql index b9cced3226..d6b2a9f167 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql @@ -1,10 +1,37 @@ -WITH `bfcte_0` AS ( +WITH `bfcte_1` AS ( SELECT - * - FROM UNNEST(ARRAY>[STRUCT(314159.0, 0), STRUCT(2.0, 1), STRUCT(3.0, 2), STRUCT(CAST(NULL AS FLOAT64), 3)]) + `int64_col` AS `bfcol_0`, + `rowindex` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_2` AS ( + SELECT + `bfcol_1` AS `bfcol_2`, + `bfcol_0` AS `bfcol_3` + FROM `bfcte_1` +), `bfcte_0` AS ( + SELECT + `int64_too` AS `bfcol_4` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_3` AS ( + SELECT + `bfcol_4` + FROM `bfcte_0` + GROUP BY + `bfcol_4` +), `bfcte_4` AS ( + SELECT + `bfcte_2`.*, + EXISTS( + SELECT + 1 + FROM `bfcte_3` + WHERE + COALESCE(`bfcte_2`.`bfcol_3`, 0) = COALESCE(`bfcte_3`.`bfcol_4`, 0) + AND COALESCE(`bfcte_2`.`bfcol_3`, 1) = COALESCE(`bfcte_3`.`bfcol_4`, 1) + ) AS `bfcol_5` + FROM `bfcte_2` ) SELECT - `bfcol_0` AS `0` -FROM `bfcte_0` -ORDER BY - `bfcol_1` ASC NULLS LAST \ No newline at end of file + `bfcol_2` AS `rowindex`, + `bfcol_5` AS `int64_col` +FROM `bfcte_4` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql new file mode 100644 index 0000000000..d2803c828a --- /dev/null +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql @@ -0,0 +1,30 @@ +WITH `bfcte_1` AS ( + SELECT + `rowindex` AS `bfcol_0`, + `rowindex_2` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_2` AS ( + SELECT + `bfcol_0` AS `bfcol_2`, + `bfcol_1` AS `bfcol_3` + FROM `bfcte_1` +), `bfcte_0` AS ( + SELECT + `rowindex_2` AS `bfcol_4` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_3` AS ( + SELECT + `bfcol_4` + FROM `bfcte_0` + GROUP BY + `bfcol_4` +), `bfcte_4` AS ( + SELECT + `bfcte_2`.*, + `bfcte_2`.`bfcol_3` IN (`bfcte_3`.`bfcol_4`) AS `bfcol_5` + FROM `bfcte_2` +) +SELECT + `bfcol_2` AS `rowindex`, + `bfcol_5` AS `rowindex_2` +FROM `bfcte_4` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/test_compile_isin.py b/tests/unit/core/compile/sqlglot/test_compile_isin.py index 6a022873d2..8b3e7f7291 100644 --- a/tests/unit/core/compile/sqlglot/test_compile_isin.py +++ b/tests/unit/core/compile/sqlglot/test_compile_isin.py @@ -12,19 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pandas as pd import pytest -import bigframes import bigframes.pandas as bpd pytest.importorskip("pytest_snapshot") -def test_compile_isin( - scalar_types_df: bpd.DataFrame, compiler_session: bigframes.Session, snapshot -): - data = [314159, 2.0, 3, pd.NA] - s = bpd.Series(data, session=compiler_session) - bf_isin = scalar_types_df["int64_col"].isin(s).to_frame() +def test_compile_isin(scalar_types_df: bpd.DataFrame, snapshot): + bf_isin = scalar_types_df["int64_col"].isin(scalar_types_df["int64_too"]).to_frame() + snapshot.assert_match(bf_isin.sql, "out.sql") + + +def test_compile_isin_not_nullable(scalar_types_df: bpd.DataFrame, snapshot): + bf_isin = ( + scalar_types_df["rowindex_2"].isin(scalar_types_df["rowindex_2"]).to_frame() + ) snapshot.assert_match(bf_isin.sql, "out.sql") From 6fafc086d118e31a06effde0d4515ea46a58e8d0 Mon Sep 17 00:00:00 2001 From: Chelsea Lin Date: Fri, 12 Sep 2025 22:51:37 +0000 Subject: [PATCH 3/3] For isin, use subquery rather than cte for the right selections --- bigframes/core/compile/sqlglot/sqlglot_ir.py | 21 ++++++++++--------- .../test_compile_isin/out.sql | 20 +++++++++--------- .../test_compile_isin_not_nullable/out.sql | 16 +++++++------- .../core/compile/sqlglot/test_compile_isin.py | 8 +++++++ 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/bigframes/core/compile/sqlglot/sqlglot_ir.py b/bigframes/core/compile/sqlglot/sqlglot_ir.py index 5f3f07dd3b..9c81eda044 100644 --- a/bigframes/core/compile/sqlglot/sqlglot_ir.py +++ b/bigframes/core/compile/sqlglot/sqlglot_ir.py @@ -347,12 +347,10 @@ def isin_join( left_cte_name = sge.to_identifier( next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted ) - right_cte_name = sge.to_identifier( - next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted - ) left_select = _select_to_cte(self.expr, left_cte_name) - right_select = _select_to_cte(right.expr, right_cte_name) + # Prefer subquery over CTE for the IN clause's right side to improve SQL readability. + right_select = right.expr left_ctes = left_select.args.pop("with", []) right_ctes = right_select.args.pop("with", []) @@ -362,17 +360,20 @@ def isin_join( sge.Column(this=conditions[0].expr, table=left_cte_name), conditions[0].dtype, ) - right_condition = typed_expr.TypedExpr( - sge.Column(this=conditions[1].expr, table=right_cte_name), - conditions[1].dtype, - ) new_column: sge.Expression if joins_nulls: + right_table_name = sge.to_identifier( + next(self.uid_gen.get_uid_stream("bft_")), quoted=self.quoted + ) + right_condition = typed_expr.TypedExpr( + sge.Column(this=conditions[1].expr, table=right_table_name), + conditions[1].dtype, + ) new_column = sge.Exists( this=sge.Select() .select(sge.convert(1)) - .from_(sge.Table(this=right_cte_name)) + .from_(sge.Alias(this=right_select.subquery(), alias=right_table_name)) .where( _join_condition(left_condition, right_condition, joins_nulls=True) ) @@ -380,7 +381,7 @@ def isin_join( else: new_column = sge.In( this=left_condition.expr, - expressions=[right_condition.expr], + expressions=[right_select.subquery()], ) new_column = sge.Alias( diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql index d6b2a9f167..e3bb0f9eba 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql @@ -13,25 +13,25 @@ WITH `bfcte_1` AS ( `int64_too` AS `bfcol_4` FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ), `bfcte_3` AS ( - SELECT - `bfcol_4` - FROM `bfcte_0` - GROUP BY - `bfcol_4` -), `bfcte_4` AS ( SELECT `bfcte_2`.*, EXISTS( SELECT 1 - FROM `bfcte_3` + FROM ( + SELECT + `bfcol_4` + FROM `bfcte_0` + GROUP BY + `bfcol_4` + ) AS `bft_0` WHERE - COALESCE(`bfcte_2`.`bfcol_3`, 0) = COALESCE(`bfcte_3`.`bfcol_4`, 0) - AND COALESCE(`bfcte_2`.`bfcol_3`, 1) = COALESCE(`bfcte_3`.`bfcol_4`, 1) + COALESCE(`bfcte_2`.`bfcol_3`, 0) = COALESCE(`bft_0`.`bfcol_4`, 0) + AND COALESCE(`bfcte_2`.`bfcol_3`, 1) = COALESCE(`bft_0`.`bfcol_4`, 1) ) AS `bfcol_5` FROM `bfcte_2` ) SELECT `bfcol_2` AS `rowindex`, `bfcol_5` AS `int64_col` -FROM `bfcte_4` \ No newline at end of file +FROM `bfcte_3` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql index d2803c828a..f96a9816dc 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql @@ -13,18 +13,18 @@ WITH `bfcte_1` AS ( `rowindex_2` AS `bfcol_4` FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ), `bfcte_3` AS ( - SELECT - `bfcol_4` - FROM `bfcte_0` - GROUP BY - `bfcol_4` -), `bfcte_4` AS ( SELECT `bfcte_2`.*, - `bfcte_2`.`bfcol_3` IN (`bfcte_3`.`bfcol_4`) AS `bfcol_5` + `bfcte_2`.`bfcol_3` IN (( + SELECT + `bfcol_4` + FROM `bfcte_0` + GROUP BY + `bfcol_4` + )) AS `bfcol_5` FROM `bfcte_2` ) SELECT `bfcol_2` AS `rowindex`, `bfcol_5` AS `rowindex_2` -FROM `bfcte_4` \ No newline at end of file +FROM `bfcte_3` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/test_compile_isin.py b/tests/unit/core/compile/sqlglot/test_compile_isin.py index 8b3e7f7291..94a533abe6 100644 --- a/tests/unit/core/compile/sqlglot/test_compile_isin.py +++ b/tests/unit/core/compile/sqlglot/test_compile_isin.py @@ -12,12 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + import pytest import bigframes.pandas as bpd pytest.importorskip("pytest_snapshot") +if sys.version_info < (3, 12): + pytest.skip( + "Skipping test due to inconsistent SQL formatting on Python < 3.12.", + allow_module_level=True, + ) + def test_compile_isin(scalar_types_df: bpd.DataFrame, snapshot): bf_isin = scalar_types_df["int64_col"].isin(scalar_types_df["int64_too"]).to_frame()