Skip to content

Commit a6195a4

Browse files
committed
Merge branch 'main' into shuowei-anywidget-fix-empty-index
2 parents ee1417a + e1e1141 commit a6195a4

File tree

20 files changed

+203
-71
lines changed

20 files changed

+203
-71
lines changed

.librarian/state.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:c8612d3fffb3f6a32353b2d1abd16b61e87811866f7ec9d65b59b02eb452a620
22
libraries:
33
- id: bigframes
4-
version: 2.31.0
4+
version: 2.32.0
55
last_generated_commit: ""
66
apis: []
77
source_roots:

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@
44

55
[1]: https://pypi.org/project/bigframes/#history
66

7+
## [2.32.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.31.0...bigframes-v2.32.0) (2026-01-05)
8+
9+
10+
### Documentation
11+
12+
* generate sitemap.xml for better search indexing (#2351) ([7d2990f1c48c6d74e2af6bee3af87f90189a3d9b](https://github.com/googleapis/google-cloud-python/commit/7d2990f1c48c6d74e2af6bee3af87f90189a3d9b))
13+
* update supported pandas APIs documentation links (#2330) ([ea71936ce240b2becf21b552d4e41e8ef4418e2d](https://github.com/googleapis/google-cloud-python/commit/ea71936ce240b2becf21b552d4e41e8ef4418e2d))
14+
* Add time series analysis notebook (#2328) ([369f1c0aff29d197b577ec79e401b107985fe969](https://github.com/googleapis/google-cloud-python/commit/369f1c0aff29d197b577ec79e401b107985fe969))
15+
16+
17+
### Features
18+
19+
* Enable multi-column sorting in anywidget mode (#2360) ([1feb956e4762e30276e5b380c0633e6ed7881357](https://github.com/googleapis/google-cloud-python/commit/1feb956e4762e30276e5b380c0633e6ed7881357))
20+
* display series in anywidget mode (#2346) ([7395d418550058c516ad878e13567256f4300a37](https://github.com/googleapis/google-cloud-python/commit/7395d418550058c516ad878e13567256f4300a37))
21+
* Refactor TableWidget and to_pandas_batches (#2250) ([b8f09015a7c8e6987dc124e6df925d4f6951b1da](https://github.com/googleapis/google-cloud-python/commit/b8f09015a7c8e6987dc124e6df925d4f6951b1da))
22+
* Auto-plan complex reduction expressions (#2298) ([4d5de14ccdd05b1ac8f50c3fe71c35ab9e5150c1](https://github.com/googleapis/google-cloud-python/commit/4d5de14ccdd05b1ac8f50c3fe71c35ab9e5150c1))
23+
* Display custom single index column in anywidget mode (#2311) ([f27196260743883ed8131d5fd33a335e311177e4](https://github.com/googleapis/google-cloud-python/commit/f27196260743883ed8131d5fd33a335e311177e4))
24+
* add fit_predict method to ml unsupervised models (#2320) ([59df7f70a12ef702224ad61e597bd775208dac45](https://github.com/googleapis/google-cloud-python/commit/59df7f70a12ef702224ad61e597bd775208dac45))
25+
26+
27+
### Bug Fixes
28+
29+
* vendor sqlglot bigquery dialect and remove package dependency (#2354) ([b321d72d5eb005b6e9295541a002540f05f72209](https://github.com/googleapis/google-cloud-python/commit/b321d72d5eb005b6e9295541a002540f05f72209))
30+
* bigframes.ml fit with eval data in partial mode avoids join on null index (#2355) ([7171d21b8c8d5a2d61081f41fa1109b5c9c4bc5f](https://github.com/googleapis/google-cloud-python/commit/7171d21b8c8d5a2d61081f41fa1109b5c9c4bc5f))
31+
* Improve strictness of nan vs None usage (#2326) ([481d938fb0b840e17047bc4b57e61af15b976e54](https://github.com/googleapis/google-cloud-python/commit/481d938fb0b840e17047bc4b57e61af15b976e54))
32+
* Correct DataFrame widget rendering in Colab (#2319) ([7f1d3df3839ec58f52e48df088057fc0df967da9](https://github.com/googleapis/google-cloud-python/commit/7f1d3df3839ec58f52e48df088057fc0df967da9))
33+
* Fix pd.timedelta handling in polars comipler with polars 1.36 (#2325) ([252644826289d9db7a8548884de880b3a4fccafd](https://github.com/googleapis/google-cloud-python/commit/252644826289d9db7a8548884de880b3a4fccafd))
34+
735
## [2.31.0](https://github.com/googleapis/google-cloud-python/compare/bigframes-v2.30.0...bigframes-v2.31.0) (2025-12-10)
836

937

bigframes/core/compile/sqlglot/expressions/bool_ops.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,60 @@
2626

2727
@register_binary_op(ops.and_op)
2828
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
29+
# For AND, when we encounter a NULL value, we only know when the result is FALSE,
30+
# otherwise the result is unknown (NULL). See: truth table at
31+
# https://en.wikibooks.org/wiki/Structured_Query_Language/NULLs_and_the_Three_Valued_Logic#AND,_OR
32+
if left.expr == sge.null():
33+
condition = sge.EQ(this=right.expr, expression=sge.convert(False))
34+
return sge.If(this=condition, true=right.expr, false=sge.null())
35+
if right.expr == sge.null():
36+
condition = sge.EQ(this=left.expr, expression=sge.convert(False))
37+
return sge.If(this=condition, true=left.expr, false=sge.null())
38+
2939
if left.dtype == dtypes.BOOL_DTYPE and right.dtype == dtypes.BOOL_DTYPE:
3040
return sge.And(this=left.expr, expression=right.expr)
3141
return sge.BitwiseAnd(this=left.expr, expression=right.expr)
3242

3343

3444
@register_binary_op(ops.or_op)
3545
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
46+
# For OR, when we encounter a NULL value, we only know when the result is TRUE,
47+
# otherwise the result is unknown (NULL). See: truth table at
48+
# https://en.wikibooks.org/wiki/Structured_Query_Language/NULLs_and_the_Three_Valued_Logic#AND,_OR
49+
if left.expr == sge.null():
50+
condition = sge.EQ(this=right.expr, expression=sge.convert(True))
51+
return sge.If(this=condition, true=right.expr, false=sge.null())
52+
if right.expr == sge.null():
53+
condition = sge.EQ(this=left.expr, expression=sge.convert(True))
54+
return sge.If(this=condition, true=left.expr, false=sge.null())
55+
3656
if left.dtype == dtypes.BOOL_DTYPE and right.dtype == dtypes.BOOL_DTYPE:
3757
return sge.Or(this=left.expr, expression=right.expr)
3858
return sge.BitwiseOr(this=left.expr, expression=right.expr)
3959

4060

4161
@register_binary_op(ops.xor_op)
4262
def _(left: TypedExpr, right: TypedExpr) -> sge.Expression:
43-
if left.dtype == dtypes.BOOL_DTYPE and right.dtype == dtypes.BOOL_DTYPE:
44-
left_expr = sge.And(this=left.expr, expression=sge.Not(this=right.expr))
45-
right_expr = sge.And(this=sge.Not(this=left.expr), expression=right.expr)
46-
return sge.Or(this=left_expr, expression=right_expr)
63+
# For XOR, cast NULL operands to BOOLEAN to ensure the resulting expression
64+
# maintains the boolean data type.
65+
left_expr = left.expr
66+
left_dtype = left.dtype
67+
if left_expr == sge.null():
68+
left_expr = sge.Cast(this=sge.convert(None), to="BOOLEAN")
69+
left_dtype = dtypes.BOOL_DTYPE
70+
right_expr = right.expr
71+
right_dtype = right.dtype
72+
if right_expr == sge.null():
73+
right_expr = sge.Cast(this=sge.convert(None), to="BOOLEAN")
74+
right_dtype = dtypes.BOOL_DTYPE
75+
76+
if left_dtype == dtypes.BOOL_DTYPE and right_dtype == dtypes.BOOL_DTYPE:
77+
return sge.Or(
78+
this=sge.paren(
79+
sge.And(this=left_expr, expression=sge.Not(this=right_expr))
80+
),
81+
expression=sge.paren(
82+
sge.And(this=sge.Not(this=left_expr), expression=right_expr)
83+
),
84+
)
4785
return sge.BitwiseXor(this=left.expr, expression=right.expr)

bigframes/core/compile/sqlglot/expressions/comparison_ops.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,23 @@
3131
@register_unary_op(ops.IsInOp, pass_op=True)
3232
def _(expr: TypedExpr, op: ops.IsInOp) -> sge.Expression:
3333
values = []
34-
is_numeric_expr = dtypes.is_numeric(expr.dtype)
34+
is_numeric_expr = dtypes.is_numeric(expr.dtype, include_bool=False)
3535
for value in op.values:
36-
if value is None:
36+
if _is_null(value):
3737
continue
3838
dtype = dtypes.bigframes_type(type(value))
39-
if expr.dtype == dtype or is_numeric_expr and dtypes.is_numeric(dtype):
39+
if (
40+
expr.dtype == dtype
41+
or is_numeric_expr
42+
and dtypes.is_numeric(dtype, include_bool=False)
43+
):
4044
values.append(sge.convert(value))
4145

4246
if op.match_nulls:
4347
contains_nulls = any(_is_null(value) for value in op.values)
4448
if contains_nulls:
49+
if len(values) == 0:
50+
return sge.Is(this=expr.expr, expression=sge.Null())
4551
return sge.Is(this=expr.expr, expression=sge.Null()) | sge.In(
4652
this=expr.expr, expressions=values
4753
)

bigframes/core/compile/sqlglot/expressions/geo_ops.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ def _(expr: TypedExpr, op: ops.GeoStSimplifyOp) -> sge.Expression:
108108

109109
@register_unary_op(ops.geo_x_op)
110110
def _(expr: TypedExpr) -> sge.Expression:
111-
return sge.func("SAFE.ST_X", expr.expr)
111+
return sge.func("ST_X", expr.expr)
112112

113113

114114
@register_unary_op(ops.geo_y_op)
115115
def _(expr: TypedExpr) -> sge.Expression:
116-
return sge.func("SAFE.ST_Y", expr.expr)
116+
return sge.func("ST_Y", expr.expr)
117117

118118

119119
@register_binary_op(ops.GeoStDistanceOp, pass_op=True)

bigframes/core/compile/sqlglot/sqlglot_ir.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -558,16 +558,15 @@ def _explode_single_column(
558558
)
559559
selection = sge.Star(replace=[unnested_column_alias.as_(column)])
560560

561-
# TODO: "CROSS" if not keep_empty else "LEFT"
562-
# TODO: overlaps_with_parent to replace existing column.
563561
new_expr = _select_to_cte(
564562
self.expr,
565563
sge.to_identifier(
566564
next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted
567565
),
568566
)
567+
# Use LEFT JOIN to preserve rows when unnesting empty arrays.
569568
new_expr = new_expr.select(selection, append=False).join(
570-
unnest_expr, join_type="CROSS"
569+
unnest_expr, join_type="LEFT"
571570
)
572571
return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen)
573572

@@ -621,8 +620,9 @@ def _explode_multiple_columns(
621620
next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted
622621
),
623622
)
623+
# Use LEFT JOIN to preserve rows when unnesting empty arrays.
624624
new_expr = new_expr.select(selection, append=False).join(
625-
unnest_expr, join_type="CROSS"
625+
unnest_expr, join_type="LEFT"
626626
)
627627
return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen)
628628

bigframes/version.py

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

15-
__version__ = "2.31.0"
15+
__version__ = "2.32.0"
1616

1717
# {x-release-please-start-date}
18-
__release_date__ = "2025-12-10"
18+
__release_date__ = "2026-01-05"
1919
# {x-release-please-end}

biome.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"formatter": {
3+
"indentStyle": "space",
4+
"indentWidth": 2
5+
},
6+
"javascript": {
7+
"formatter": {
8+
"quoteStyle": "single"
9+
}
10+
},
11+
"css": {
12+
"formatter": {
13+
"quoteStyle": "single"
14+
}
15+
}
16+
}

tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_and_op/out.sql

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,22 @@ WITH `bfcte_0` AS (
2121
`bfcol_9` AS `bfcol_17`,
2222
`bfcol_7` AND `bfcol_7` AS `bfcol_18`
2323
FROM `bfcte_1`
24+
), `bfcte_3` AS (
25+
SELECT
26+
*,
27+
`bfcol_14` AS `bfcol_24`,
28+
`bfcol_15` AS `bfcol_25`,
29+
`bfcol_16` AS `bfcol_26`,
30+
`bfcol_17` AS `bfcol_27`,
31+
`bfcol_18` AS `bfcol_28`,
32+
IF(`bfcol_15` = FALSE, `bfcol_15`, NULL) AS `bfcol_29`
33+
FROM `bfcte_2`
2434
)
2535
SELECT
26-
`bfcol_14` AS `rowindex`,
27-
`bfcol_15` AS `bool_col`,
28-
`bfcol_16` AS `int64_col`,
29-
`bfcol_17` AS `int_and_int`,
30-
`bfcol_18` AS `bool_and_bool`
31-
FROM `bfcte_2`
36+
`bfcol_24` AS `rowindex`,
37+
`bfcol_25` AS `bool_col`,
38+
`bfcol_26` AS `int64_col`,
39+
`bfcol_27` AS `int_and_int`,
40+
`bfcol_28` AS `bool_and_bool`,
41+
`bfcol_29` AS `bool_and_null`
42+
FROM `bfcte_3`

tests/unit/core/compile/sqlglot/expressions/snapshots/test_bool_ops/test_or_op/out.sql

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,22 @@ WITH `bfcte_0` AS (
2121
`bfcol_9` AS `bfcol_17`,
2222
`bfcol_7` OR `bfcol_7` AS `bfcol_18`
2323
FROM `bfcte_1`
24+
), `bfcte_3` AS (
25+
SELECT
26+
*,
27+
`bfcol_14` AS `bfcol_24`,
28+
`bfcol_15` AS `bfcol_25`,
29+
`bfcol_16` AS `bfcol_26`,
30+
`bfcol_17` AS `bfcol_27`,
31+
`bfcol_18` AS `bfcol_28`,
32+
IF(`bfcol_15` = TRUE, `bfcol_15`, NULL) AS `bfcol_29`
33+
FROM `bfcte_2`
2434
)
2535
SELECT
26-
`bfcol_14` AS `rowindex`,
27-
`bfcol_15` AS `bool_col`,
28-
`bfcol_16` AS `int64_col`,
29-
`bfcol_17` AS `int_and_int`,
30-
`bfcol_18` AS `bool_and_bool`
31-
FROM `bfcte_2`
36+
`bfcol_24` AS `rowindex`,
37+
`bfcol_25` AS `bool_col`,
38+
`bfcol_26` AS `int64_col`,
39+
`bfcol_27` AS `int_and_int`,
40+
`bfcol_28` AS `bool_and_bool`,
41+
`bfcol_29` AS `bool_and_null`
42+
FROM `bfcte_3`

0 commit comments

Comments
 (0)