Skip to content

Commit 8adafdd

Browse files
committed
Merge remote-tracking branch 'origin/main' into refactor-isnull-op
2 parents 5990732 + c67a25a commit 8adafdd

File tree

220 files changed

+5627
-721
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

220 files changed

+5627
-721
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ repos:
4646
rev: v2.0.2
4747
hooks:
4848
- id: biome-check
49-
files: '\.js$'
49+
files: '\.(js|css)$'

CHANGELOG.md

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

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

7+
## [2.13.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.12.0...v2.13.0) (2025-07-25)
8+
9+
10+
### Features
11+
12+
* _read_gbq_colab creates hybrid session ([#1901](https://github.com/googleapis/python-bigquery-dataframes/issues/1901)) ([31b17b0](https://github.com/googleapis/python-bigquery-dataframes/commit/31b17b01706ccfcee9a2d838c43a9609ec4dc218))
13+
* Add CSS styling for TableWidget pagination interface ([#1934](https://github.com/googleapis/python-bigquery-dataframes/issues/1934)) ([5b232d7](https://github.com/googleapis/python-bigquery-dataframes/commit/5b232d7e33563196316f5dbb50b28c6be388d440))
14+
* Add row numbering local pushdown in hybrid execution ([#1932](https://github.com/googleapis/python-bigquery-dataframes/issues/1932)) ([92a2377](https://github.com/googleapis/python-bigquery-dataframes/commit/92a237712aa4ce516b1a44748127b34d7780fff6))
15+
* Implement Index.get_loc ([#1921](https://github.com/googleapis/python-bigquery-dataframes/issues/1921)) ([bbbcaf3](https://github.com/googleapis/python-bigquery-dataframes/commit/bbbcaf35df113617fd6bb8ae36468cf3f7ab493b))
16+
17+
18+
### Bug Fixes
19+
20+
* Add license header and correct issues in dbt sample ([#1931](https://github.com/googleapis/python-bigquery-dataframes/issues/1931)) ([ab01b0a](https://github.com/googleapis/python-bigquery-dataframes/commit/ab01b0a236ffc7b667f258e0497105ea5c3d3aab))
21+
22+
23+
### Dependencies
24+
25+
* Replace `google-cloud-iam` with `grpc-google-iam-v1` ([#1864](https://github.com/googleapis/python-bigquery-dataframes/issues/1864)) ([e5ff8f7](https://github.com/googleapis/python-bigquery-dataframes/commit/e5ff8f7d9fdac3ea47dabcc80a2598d601f39e64))
26+
27+
## [2.12.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.11.0...v2.12.0) (2025-07-23)
28+
29+
30+
### Features
31+
32+
* Add code samples for dbt bigframes integration ([#1898](https://github.com/googleapis/python-bigquery-dataframes/issues/1898)) ([7e03252](https://github.com/googleapis/python-bigquery-dataframes/commit/7e03252d31e505731db113eb38af77842bf29b9b))
33+
* Add isin local execution to hybrid engine ([#1915](https://github.com/googleapis/python-bigquery-dataframes/issues/1915)) ([c0cefd3](https://github.com/googleapis/python-bigquery-dataframes/commit/c0cefd36cfd55962b86178d2a612d625ed17f79c))
34+
* Add ml.metrics.mean_absolute_error method ([#1910](https://github.com/googleapis/python-bigquery-dataframes/issues/1910)) ([15b8449](https://github.com/googleapis/python-bigquery-dataframes/commit/15b8449dc5ad0c8190a5cbf47894436de18c8e88))
35+
* Allow local arithmetic execution in hybrid engine ([#1906](https://github.com/googleapis/python-bigquery-dataframes/issues/1906)) ([ebdcd02](https://github.com/googleapis/python-bigquery-dataframes/commit/ebdcd0240f0d8edaef3094b3a4e664b4a84d4a25))
36+
* Provide day_of_year and day_of_week for dt accessor ([#1911](https://github.com/googleapis/python-bigquery-dataframes/issues/1911)) ([40e7638](https://github.com/googleapis/python-bigquery-dataframes/commit/40e76383948a79bde48108f6180fd6ae2b3d0875))
37+
* Support params `max_batching_rows`, `container_cpu`, and `container_memory` for `udf` ([#1897](https://github.com/googleapis/python-bigquery-dataframes/issues/1897)) ([8baa912](https://github.com/googleapis/python-bigquery-dataframes/commit/8baa9126e595ae682469a6bb462244240699f57f))
38+
* Support typed pyarrow.Scalar in assignment ([#1930](https://github.com/googleapis/python-bigquery-dataframes/issues/1930)) ([cd28e12](https://github.com/googleapis/python-bigquery-dataframes/commit/cd28e12b3f70a6934a68963a7f25dbd5e3c67335))
39+
40+
41+
### Bug Fixes
42+
43+
* Correct min field from max() to min() in remote function tests ([#1917](https://github.com/googleapis/python-bigquery-dataframes/issues/1917)) ([d5c54fc](https://github.com/googleapis/python-bigquery-dataframes/commit/d5c54fca32ed75c1aef52c99781db7f8ac7426e1))
44+
* Resolve location reset issue in bigquery options ([#1914](https://github.com/googleapis/python-bigquery-dataframes/issues/1914)) ([c15cb8a](https://github.com/googleapis/python-bigquery-dataframes/commit/c15cb8a1a9c834c2c1c2984930415b246f3f948b))
45+
* Series.str.isdigit in unicode superscripts and fractions ([#1924](https://github.com/googleapis/python-bigquery-dataframes/issues/1924)) ([8d46c36](https://github.com/googleapis/python-bigquery-dataframes/commit/8d46c36da7881a99861166c03a0831beff8ee0dd))
46+
47+
48+
### Documentation
49+
50+
* Add code snippets for session and IO public docs ([#1919](https://github.com/googleapis/python-bigquery-dataframes/issues/1919)) ([6e01cbe](https://github.com/googleapis/python-bigquery-dataframes/commit/6e01cbec0dcf40e528b4a96e944681df18773c11))
51+
* Add snippets for performance optimization doc ([#1923](https://github.com/googleapis/python-bigquery-dataframes/issues/1923)) ([4da309e](https://github.com/googleapis/python-bigquery-dataframes/commit/4da309e27bd58a685e8aca953717da75d4ba5305))
52+
753
## [2.11.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.10.0...v2.11.0) (2025-07-15)
854

955

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# Generated by synthtool. DO NOT EDIT!
1818
include README.rst LICENSE
1919
recursive-include third_party/bigframes_vendored *
20-
recursive-include bigframes *.json *.proto *.js py.typed
20+
recursive-include bigframes *.json *.proto *.js *.css py.typed
2121
recursive-include tests *
2222
global-exclude *.py[co]
2323
global-exclude __pycache__

bigframes/_config/bigquery_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def location(self) -> Optional[str]:
171171

172172
@location.setter
173173
def location(self, value: Optional[str]):
174-
if self._session_started and self._location != value:
174+
if self._session_started and self._location != _get_validated_location(value):
175175
raise ValueError(SESSION_STARTED_MESSAGE.format(attribute="location"))
176176
self._location = _get_validated_location(value)
177177

bigframes/core/compile/ibis_compiler/scalar_op_registry.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,9 @@ def isalpha_op_impl(x: ibis_types.Value):
300300

301301
@scalar_op_compiler.register_unary_op(ops.isdigit_op)
302302
def isdigit_op_impl(x: ibis_types.Value):
303-
# Based on docs, should include superscript/subscript-ed numbers
304-
# Tests however pass only when set to Nd unicode class
305-
return typing.cast(ibis_types.StringValue, x).re_search(r"^(\p{Nd})+$")
303+
return typing.cast(ibis_types.StringValue, x).re_search(
304+
r"^[\p{Nd}\x{00B9}\x{00B2}\x{00B3}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}]+$"
305+
)
306306

307307

308308
@scalar_op_compiler.register_unary_op(ops.isdecimal_op)
@@ -1311,7 +1311,7 @@ def eq_op(
13111311
x: ibis_types.Value,
13121312
y: ibis_types.Value,
13131313
):
1314-
x, y = _coerce_comparables(x, y)
1314+
x, y = _coerce_bools(x, y)
13151315
return x == y
13161316

13171317

@@ -1321,7 +1321,7 @@ def eq_nulls_match_op(
13211321
y: ibis_types.Value,
13221322
):
13231323
"""Variant of eq_op where nulls match each other. Only use where dtypes are known to be same."""
1324-
x, y = _coerce_comparables(x, y)
1324+
x, y = _coerce_bools(x, y)
13251325
literal = ibis_types.literal("$NULL_SENTINEL$")
13261326
if hasattr(x, "fill_null"):
13271327
left = x.cast(ibis_dtypes.str).fill_null(literal)
@@ -1338,7 +1338,7 @@ def ne_op(
13381338
x: ibis_types.Value,
13391339
y: ibis_types.Value,
13401340
):
1341-
x, y = _coerce_comparables(x, y)
1341+
x, y = _coerce_bools(x, y)
13421342
return x != y
13431343

13441344

@@ -1350,13 +1350,10 @@ def _null_or_value(value: ibis_types.Value, where_value: ibis_types.BooleanValue
13501350
)
13511351

13521352

1353-
def _coerce_comparables(
1354-
x: ibis_types.Value,
1355-
y: ibis_types.Value,
1356-
):
1357-
if x.type().is_boolean() and not y.type().is_boolean():
1353+
def _coerce_bools(x: ibis_types.Value, y: ibis_types.Value, *, always: bool = False):
1354+
if x.type().is_boolean() and (always or not y.type().is_boolean()):
13581355
x = x.cast(ibis_dtypes.int64)
1359-
elif y.type().is_boolean() and not x.type().is_boolean():
1356+
if y.type().is_boolean() and (always or not x.type().is_boolean()):
13601357
y = y.cast(ibis_dtypes.int64)
13611358
return x, y
13621359

@@ -1417,8 +1414,18 @@ def add_op(
14171414
x: ibis_types.Value,
14181415
y: ibis_types.Value,
14191416
):
1417+
x, y = _coerce_bools(x, y)
14201418
if isinstance(x, ibis_types.NullScalar) or isinstance(x, ibis_types.NullScalar):
14211419
return ibis_types.null()
1420+
1421+
if x.type().is_boolean() and y.type().is_boolean():
1422+
x, y = _coerce_bools(x, y, always=True)
1423+
return (
1424+
typing.cast(ibis_types.NumericValue, x)
1425+
+ typing.cast(ibis_types.NumericValue, x)
1426+
).cast(ibis_dtypes.Boolean)
1427+
1428+
x, y = _coerce_bools(x, y)
14221429
return x + y # type: ignore
14231430

14241431

@@ -1428,6 +1435,7 @@ def sub_op(
14281435
x: ibis_types.Value,
14291436
y: ibis_types.Value,
14301437
):
1438+
x, y = _coerce_bools(x, y)
14311439
return typing.cast(ibis_types.NumericValue, x) - typing.cast(
14321440
ibis_types.NumericValue, y
14331441
)
@@ -1439,6 +1447,13 @@ def mul_op(
14391447
x: ibis_types.Value,
14401448
y: ibis_types.Value,
14411449
):
1450+
if x.type().is_boolean() and y.type().is_boolean():
1451+
x, y = _coerce_bools(x, y, always=True)
1452+
return (
1453+
typing.cast(ibis_types.NumericValue, x)
1454+
* typing.cast(ibis_types.NumericValue, x)
1455+
).cast(ibis_dtypes.Boolean)
1456+
x, y = _coerce_bools(x, y)
14421457
return typing.cast(ibis_types.NumericValue, x) * typing.cast(
14431458
ibis_types.NumericValue, y
14441459
)
@@ -1450,6 +1465,7 @@ def div_op(
14501465
x: ibis_types.Value,
14511466
y: ibis_types.Value,
14521467
):
1468+
x, y = _coerce_bools(x, y)
14531469
return typing.cast(ibis_types.NumericValue, x) / typing.cast(
14541470
ibis_types.NumericValue, y
14551471
)
@@ -1461,6 +1477,7 @@ def pow_op(
14611477
x: ibis_types.Value,
14621478
y: ibis_types.Value,
14631479
):
1480+
x, y = _coerce_bools(x, y)
14641481
if x.type().is_integer() and y.type().is_integer():
14651482
return _int_pow_op(x, y)
14661483
else:
@@ -1474,6 +1491,7 @@ def unsafe_pow_op(
14741491
y: ibis_types.Value,
14751492
):
14761493
"""For internal use only - where domain and overflow checks are not needed."""
1494+
x, y = _coerce_bools(x, y)
14771495
return typing.cast(ibis_types.NumericValue, x) ** typing.cast(
14781496
ibis_types.NumericValue, y
14791497
)
@@ -1562,7 +1580,7 @@ def lt_op(
15621580
x: ibis_types.Value,
15631581
y: ibis_types.Value,
15641582
):
1565-
x, y = _coerce_comparables(x, y)
1583+
x, y = _coerce_bools(x, y)
15661584
return x < y
15671585

15681586

@@ -1572,7 +1590,7 @@ def le_op(
15721590
x: ibis_types.Value,
15731591
y: ibis_types.Value,
15741592
):
1575-
x, y = _coerce_comparables(x, y)
1593+
x, y = _coerce_bools(x, y)
15761594
return x <= y
15771595

15781596

@@ -1582,7 +1600,7 @@ def gt_op(
15821600
x: ibis_types.Value,
15831601
y: ibis_types.Value,
15841602
):
1585-
x, y = _coerce_comparables(x, y)
1603+
x, y = _coerce_bools(x, y)
15861604
return x > y
15871605

15881606

@@ -1592,7 +1610,7 @@ def ge_op(
15921610
x: ibis_types.Value,
15931611
y: ibis_types.Value,
15941612
):
1595-
x, y = _coerce_comparables(x, y)
1613+
x, y = _coerce_bools(x, y)
15961614
return x >= y
15971615

15981616

@@ -1602,6 +1620,10 @@ def floordiv_op(
16021620
x: ibis_types.Value,
16031621
y: ibis_types.Value,
16041622
):
1623+
if x.type().is_boolean():
1624+
x = x.cast(ibis_dtypes.int64)
1625+
elif y.type().is_boolean():
1626+
y = y.cast(ibis_dtypes.int64)
16051627
x_numeric = typing.cast(ibis_types.NumericValue, x)
16061628
y_numeric = typing.cast(ibis_types.NumericValue, y)
16071629
floordiv_expr = x_numeric // y_numeric
@@ -1640,6 +1662,7 @@ def mod_op(
16401662
if isinstance(op, ibis_generic.Literal) and op.value == 0:
16411663
return ibis_types.null().cast(x.type())
16421664

1665+
x, y = _coerce_bools(x, y)
16431666
if x.type().is_integer() and y.type().is_integer():
16441667
# both are ints, no casting necessary
16451668
return _int_mod(

bigframes/core/compile/polars/compiler.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import bigframes.operations.comparison_ops as comp_ops
3636
import bigframes.operations.generic_ops as gen_ops
3737
import bigframes.operations.numeric_ops as num_ops
38+
import bigframes.operations.string_ops as string_ops
3839

3940
polars_installed = True
4041
if TYPE_CHECKING:
@@ -146,6 +147,14 @@ def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
146147
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
147148
return input.abs()
148149

150+
@compile_op.register(num_ops.FloorOp)
151+
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
152+
return input.floor()
153+
154+
@compile_op.register(num_ops.CeilOp)
155+
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
156+
return input.ceil()
157+
149158
@compile_op.register(num_ops.PosOp)
150159
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
151160
return input.__pos__()
@@ -182,10 +191,6 @@ def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr:
182191
def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr:
183192
return l_input // r_input
184193

185-
@compile_op.register(num_ops.FloorDivOp)
186-
def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr:
187-
return l_input // r_input
188-
189194
@compile_op.register(num_ops.ModOp)
190195
def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr:
191196
return l_input % r_input
@@ -262,6 +267,11 @@ def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
262267
# eg. We want "True" instead of "true" for bool to strin
263268
return input.cast(_DTYPE_MAPPING[op.to_type], strict=not op.safe)
264269

270+
@compile_op.register(string_ops.StrConcatOp)
271+
def _(self, op: ops.ScalarOp, l_input: pl.Expr, r_input: pl.Expr) -> pl.Expr:
272+
assert isinstance(op, string_ops.StrConcatOp)
273+
return pl.concat_str(l_input, r_input)
274+
265275
@dataclasses.dataclass(frozen=True)
266276
class PolarsAggregateCompiler:
267277
scalar_compiler = PolarsExpressionCompiler()
@@ -495,6 +505,30 @@ def compile_join(self, node: nodes.JoinNode):
495505
left, right, node.type, left_on, right_on, node.joins_nulls
496506
)
497507

508+
@compile_node.register
509+
def compile_isin(self, node: nodes.InNode):
510+
left = self.compile_node(node.left_child)
511+
right = self.compile_node(node.right_child).unique(node.right_col.id.sql)
512+
right = right.with_columns(pl.lit(True).alias(node.indicator_col.sql))
513+
514+
left_ex, right_ex = lowering._coerce_comparables(node.left_col, node.right_col)
515+
516+
left_pl_ex = self.expr_compiler.compile_expression(left_ex)
517+
right_pl_ex = self.expr_compiler.compile_expression(right_ex)
518+
519+
joined = left.join(
520+
right,
521+
how="left",
522+
left_on=left_pl_ex,
523+
right_on=right_pl_ex,
524+
# Note: join_nulls renamed to nulls_equal for polars 1.24
525+
join_nulls=node.joins_nulls, # type: ignore
526+
coalesce=False,
527+
)
528+
passthrough = [pl.col(id) for id in left.columns]
529+
indicator = pl.col(node.indicator_col.sql).fill_null(False)
530+
return joined.select((*passthrough, indicator))
531+
498532
def _ordered_join(
499533
self,
500534
left_frame: pl.LazyFrame,

0 commit comments

Comments
 (0)