From 4c4c9b14657b7cda1940ef39e7d4db20a9ff5308 Mon Sep 17 00:00:00 2001 From: Shuowei Li Date: Mon, 27 Oct 2025 12:30:54 -0700 Subject: [PATCH 01/21] fix: Resolve AttributeError in TableWidget and improve initialization (#1937) * remove expensive len() call * add testcase * fix a typo * change how row_count is updated * testcase stil fails, need to merged in 1888 * update the method of using PandasBatches.total_rows * change tests in read_gbq_colab * polish comment * fix a test * change code and update more testcase * remove unneeded except * add assert for total_rows * get actual row_counts * avoid two query calls * remove double query when display widget * get row count directly * restore notebook * restore notebook change * remove duplicated code * minor updates * still have zero total rows issue * now large dataset can get the correct row counts * benchmark change * revert a benchmark * revert executor change * raising a NotImplementedError when the row count is none * change return type * Revert accidental change of dataframe.ipynb * remove unnecessary execution in benchmark * remove row_count check * remove extra execute_result * remove unnecessary tests * Fix: Address review comments on PandasBatches and docstring - Reinstated 'Iterator[pd.DataFrame]' inheritance for 'PandasBatches' in 'bigframes/core/blocks.py'. - Removed internal type hint 'bigframes.core.blocks.PandasBatches:' from 'to_pandas_batches' docstring in 'bigframes/dataframe.py' to avoid exposing internal types in public documentation. * Revert: Revert import change in read_gbq_colab benchmark This reverts the import path for the benchmark utils to 'benchmark.utils' to address concerns about google3 imports. * Revert: Revert unnecessary changes in read_gbq_colab benchmarks * Remove notebooks/Untitled-2.ipynb * Remove notebooks/multimodal/audio_transcribe_partial_ordering.ipynb * remove unnecessary change * revert typo * add todo * change docstring * revert changes to tests/benchmark/read_gbq_colab * merge change * update how we handle invalid row count * eliminate duplated flags --- bigframes/display/anywidget.py | 75 ++++++++++----- bigframes/display/table_widget.js | 6 ++ notebooks/dataframes/anywidget_mode.ipynb | 68 +++++++++---- tests/system/small/test_anywidget.py | 110 ++++++++++++++++++++-- 4 files changed, 210 insertions(+), 49 deletions(-) diff --git a/bigframes/display/anywidget.py b/bigframes/display/anywidget.py index 3d12a2032c..a0b4f809d8 100644 --- a/bigframes/display/anywidget.py +++ b/bigframes/display/anywidget.py @@ -23,13 +23,14 @@ import pandas as pd import bigframes +from bigframes.core import blocks import bigframes.dataframe import bigframes.display.html -# anywidget and traitlets are optional dependencies. We don't want the import of this -# module to fail if they aren't installed, though. Instead, we try to limit the surface that -# these packages could affect. This makes unit testing easier and ensures we don't -# accidentally make these required packages. +# anywidget and traitlets are optional dependencies. We don't want the import of +# this module to fail if they aren't installed, though. Instead, we try to +# limit the surface that these packages could affect. This makes unit testing +# easier and ensures we don't accidentally make these required packages. try: import anywidget import traitlets @@ -46,9 +47,21 @@ class TableWidget(WIDGET_BASE): + """An interactive, paginated table widget for BigFrames DataFrames. + + This widget provides a user-friendly way to display and navigate through + large BigQuery DataFrames within a Jupyter environment. """ - An interactive, paginated table widget for BigFrames DataFrames. - """ + + page = traitlets.Int(0).tag(sync=True) + page_size = traitlets.Int(0).tag(sync=True) + row_count = traitlets.Int(0).tag(sync=True) + table_html = traitlets.Unicode().tag(sync=True) + _initial_load_complete = traitlets.Bool(False).tag(sync=True) + _batches: Optional[blocks.PandasBatches] = None + _error_message = traitlets.Unicode(allow_none=True, default_value=None).tag( + sync=True + ) def __init__(self, dataframe: bigframes.dataframe.DataFrame): """Initialize the TableWidget. @@ -61,10 +74,11 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame): "Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use TableWidget." ) - super().__init__() self._dataframe = dataframe - # Initialize attributes that might be needed by observers FIRST + super().__init__() + + # Initialize attributes that might be needed by observers first self._table_id = str(uuid.uuid4()) self._all_data_loaded = False self._batch_iter: Optional[Iterator[pd.DataFrame]] = None @@ -73,9 +87,6 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame): # respect display options for initial page size initial_page_size = bigframes.options.display.max_rows - # Initialize data fetching attributes. - self._batches = dataframe._to_pandas_batches(page_size=initial_page_size) - # set traitlets properties that trigger observers self.page_size = initial_page_size @@ -84,12 +95,21 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame): # TODO(b/428238610): Start iterating over the result of `to_pandas_batches()` # before we get here so that the count might already be cached. # TODO(b/452747934): Allow row_count to be None and check to see if - # there are multiple pages and show "page 1 of many" in this case. - self.row_count = self._batches.total_rows or 0 + # there are multiple pages and show "page 1 of many" in this case + self._reset_batches_for_new_page_size() + if self._batches is None or self._batches.total_rows is None: + self._error_message = "Could not determine total row count. Data might be unavailable or an error occurred." + self.row_count = 0 + else: + self.row_count = self._batches.total_rows # get the initial page self._set_table_html() + # Signals to the frontend that the initial data load is complete. + # Also used as a guard to prevent observers from firing during initialization. + self._initial_load_complete = True + @functools.cached_property def _esm(self): """Load JavaScript code from external file.""" @@ -100,11 +120,6 @@ def _css(self): """Load CSS code from external file.""" return resources.read_text(bigframes.display, "table_widget.css") - page = traitlets.Int(0).tag(sync=True) - page_size = traitlets.Int(25).tag(sync=True) - row_count = traitlets.Int(0).tag(sync=True) - table_html = traitlets.Unicode().tag(sync=True) - @traitlets.validate("page") def _validate_page(self, proposal: Dict[str, Any]) -> int: """Validate and clamp the page number to a valid range. @@ -171,7 +186,10 @@ def _get_next_batch(self) -> bool: def _batch_iterator(self) -> Iterator[pd.DataFrame]: """Lazily initializes and returns the batch iterator.""" if self._batch_iter is None: - self._batch_iter = iter(self._batches) + if self._batches is None: + self._batch_iter = iter([]) + else: + self._batch_iter = iter(self._batches) return self._batch_iter @property @@ -181,15 +199,22 @@ def _cached_data(self) -> pd.DataFrame: return pd.DataFrame(columns=self._dataframe.columns) return pd.concat(self._cached_batches, ignore_index=True) - def _reset_batches_for_new_page_size(self): + def _reset_batches_for_new_page_size(self) -> None: """Reset the batch iterator when page size changes.""" self._batches = self._dataframe._to_pandas_batches(page_size=self.page_size) + self._cached_batches = [] self._batch_iter = None self._all_data_loaded = False - def _set_table_html(self): + def _set_table_html(self) -> None: """Sets the current html data based on the current page and page size.""" + if self._error_message: + self.table_html = ( + f"
{self._error_message}
" + ) + return + start = self.page * self.page_size end = start + self.page_size @@ -211,13 +236,17 @@ def _set_table_html(self): ) @traitlets.observe("page") - def _page_changed(self, _change: Dict[str, Any]): + def _page_changed(self, _change: Dict[str, Any]) -> None: """Handler for when the page number is changed from the frontend.""" + if not self._initial_load_complete: + return self._set_table_html() @traitlets.observe("page_size") - def _page_size_changed(self, _change: Dict[str, Any]): + def _page_size_changed(self, _change: Dict[str, Any]) -> None: """Handler for when the page size is changed from the frontend.""" + if not self._initial_load_complete: + return # Reset the page to 0 when page size changes to avoid invalid page states self.page = 0 diff --git a/bigframes/display/table_widget.js b/bigframes/display/table_widget.js index 6b4d99ff28..801e262cc1 100644 --- a/bigframes/display/table_widget.js +++ b/bigframes/display/table_widget.js @@ -137,6 +137,12 @@ function render({ model, el }) { } }); model.on(Event.CHANGE_TABLE_HTML, handleTableHTMLChange); + model.on(`change:${ModelProperty.ROW_COUNT}`, updateButtonStates); + model.on(`change:_initial_load_complete`, (val) => { + if (val) { + updateButtonStates(); + } + }); // Assemble the DOM paginationContainer.appendChild(prevPage); diff --git a/notebooks/dataframes/anywidget_mode.ipynb b/notebooks/dataframes/anywidget_mode.ipynb index 328d4a05f1..c2af915721 100644 --- a/notebooks/dataframes/anywidget_mode.ipynb +++ b/notebooks/dataframes/anywidget_mode.ipynb @@ -127,12 +127,24 @@ "id": "ce250157", "metadata": {}, "outputs": [ + { + "data": { + "text/html": [ + "✅ Completed. " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "9e3e413eb0774a62818c58d217af8488", + "model_id": "aafd4f912b5f42e0896aa5f0c2c62620", "version_major": 2, - "version_minor": 1 + "version_minor": 0 }, "text/plain": [ "TableWidget(page_size=10, row_count=5552452, table_html='" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "name": "stdout", "output_type": "stream", @@ -181,17 +205,16 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "df5e93f0d03f45cda67aa6da7f9ef1ae", + "model_id": "5ec0ad9f11874d4f9d8edbc903ee7b5d", "version_major": 2, - "version_minor": 1 + "version_minor": 0 }, "text/plain": [ "TableWidget(page_size=10, row_count=5552452, table_html='
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "name": "stdout", "output_type": "stream", @@ -267,17 +304,16 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a4ec5248708442fabc59c446c78a1304", + "model_id": "651b5aac958c408183775152c2573a03", "version_major": 2, - "version_minor": 1 + "version_minor": 0 }, "text/plain": [ "TableWidget(page_size=10, row_count=5, table_html='
Date: Mon, 27 Oct 2025 13:05:03 -0700 Subject: [PATCH 02/21] refactor: migrate ai.forecast from AIAccessor to bbq (#2198) * refactor: remove ai.forecast from AIAccessor to bbq * add warning test for ai accessor * fix notebook test * fix doc * fix lint --- bigframes/bigquery/_operations/ai.py | 92 +++- bigframes/dataframe.py | 6 +- docs/templates/toc.yml | 3 - notebooks/experimental/ai_operators.ipynb | 2 +- .../bq_dataframes_ai_forecast.ipynb | 458 +++++++++--------- tests/system/small/bigquery/test_ai.py | 50 +- tests/unit/test_dataframe.py | 9 + 7 files changed, 384 insertions(+), 236 deletions(-) diff --git a/bigframes/bigquery/_operations/ai.py b/bigframes/bigquery/_operations/ai.py index e0af130016..8579f7f298 100644 --- a/bigframes/bigquery/_operations/ai.py +++ b/bigframes/bigquery/_operations/ai.py @@ -19,12 +19,15 @@ from __future__ import annotations import json -from typing import Any, List, Literal, Mapping, Tuple, Union +from typing import Any, Iterable, List, Literal, Mapping, Tuple, Union import pandas as pd -from bigframes import clients, dtypes, series, session +from bigframes import clients, dataframe, dtypes +from bigframes import pandas as bpd +from bigframes import series, session from bigframes.core import convert, log_adapter +from bigframes.ml import core as ml_core from bigframes.operations import ai_ops, output_schemas PROMPT_TYPE = Union[ @@ -548,6 +551,91 @@ def score( return series_list[0]._apply_nary_op(operator, series_list[1:]) +@log_adapter.method_logger(custom_base_name="bigquery_ai") +def forecast( + df: dataframe.DataFrame | pd.DataFrame, + *, + data_col: str, + timestamp_col: str, + model: str = "TimesFM 2.0", + id_cols: Iterable[str] | None = None, + horizon: int = 10, + confidence_level: float = 0.95, + context_window: int | None = None, +) -> dataframe.DataFrame: + """ + Forecast time series at future horizon. Using Google Research's open source TimesFM(https://github.com/google-research/timesfm) model. + + .. note:: + + This product or feature is subject to the "Pre-GA Offerings Terms" in the General Service Terms section of the + Service Specific Terms(https://cloud.google.com/terms/service-terms#1). Pre-GA products and features are available "as is" + and might have limited support. For more information, see the launch stage descriptions + (https://cloud.google.com/products#product-launch-stages). + + Args: + df (DataFrame): + The dataframe that contains the data that you want to forecast. It could be either a BigFrames Dataframe or + a pandas DataFrame. If it's a pandas DataFrame, the global BigQuery session will be used to load the data. + data_col (str): + A str value that specifies the name of the data column. The data column contains the data to forecast. + The data column must use one of the following data types: INT64, NUMERIC and FLOAT64 + timestamp_col (str): + A str value that specified the name of the time points column. + The time points column provides the time points used to generate the forecast. + The time points column must use one of the following data types: TIMESTAMP, DATE and DATETIME + model (str, default "TimesFM 2.0"): + A str value that specifies the name of the model. TimesFM 2.0 is the only supported value, and is the default value. + id_cols (Iterable[str], optional): + An iterable of str value that specifies the names of one or more ID columns. Each ID identifies a unique time series to forecast. + Specify one or more values for this argument in order to forecast multiple time series using a single query. + The columns that you specify must use one of the following data types: STRING, INT64, ARRAY and ARRAY + horizon (int, default 10): + An int value that specifies the number of time points to forecast. The default value is 10. The valid input range is [1, 10,000]. + confidence_level (float, default 0.95): + A FLOAT64 value that specifies the percentage of the future values that fall in the prediction interval. + The default value is 0.95. The valid input range is [0, 1). + context_window (int, optional): + An int value that specifies the context window length used by BigQuery ML's built-in TimesFM model. + The context window length determines how many of the most recent data points from the input time series are use by the model. + If you don't specify a value, the AI.FORECAST function automatically chooses the smallest possible context window length to use + that is still large enough to cover the number of time series data points in your input data. + + Returns: + DataFrame: + The forecast dataframe matches that of the BigQuery AI.FORECAST function. + See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-forecast + + Raises: + ValueError: when any column ID does not exist in the dataframe. + """ + + if isinstance(df, pd.DataFrame): + # Load the pandas DataFrame with global session + df = bpd.read_pandas(df) + + columns = [timestamp_col, data_col] + if id_cols: + columns += id_cols + for column in columns: + if column not in df.columns: + raise ValueError(f"Column `{column}` not found") + + options: dict[str, Union[int, float, str, Iterable[str]]] = { + "data_col": data_col, + "timestamp_col": timestamp_col, + "model": model, + "horizon": horizon, + "confidence_level": confidence_level, + } + if id_cols: + options["id_cols"] = id_cols + if context_window: + options["context_window"] = context_window + + return ml_core.BaseBqml(df._session).ai_forecast(input_data=df, options=options) + + def _separate_context_and_series( prompt: PROMPT_TYPE, ) -> Tuple[List[str | None], List[series.Series]]: diff --git a/bigframes/dataframe.py b/bigframes/dataframe.py index c3735ca3c2..f016fddd83 100644 --- a/bigframes/dataframe.py +++ b/bigframes/dataframe.py @@ -5328,7 +5328,7 @@ def _throw_if_null_index(self, opname: str): @property def semantics(self): msg = bfe.format_message( - "The 'semantics' property will be removed. Please use 'ai' instead." + "The 'semantics' property will be removed. Please use 'bigframes.bigquery.ai' instead." ) warnings.warn(msg, category=FutureWarning) return bigframes.operations.semantics.Semantics(self) @@ -5336,4 +5336,8 @@ def semantics(self): @property def ai(self): """Returns the accessor for AI operators.""" + msg = bfe.format_message( + "The 'ai' property will be removed. Please use 'bigframes.bigquery.ai' instead." + ) + warnings.warn(msg, category=FutureWarning) return bigframes.operations.ai.AIAccessor(self) diff --git a/docs/templates/toc.yml b/docs/templates/toc.yml index f368cf21ae..5d043fd85f 100644 --- a/docs/templates/toc.yml +++ b/docs/templates/toc.yml @@ -45,9 +45,6 @@ uid: bigframes.operations.plotting.PlotAccessor - name: StructAccessor uid: bigframes.operations.structs.StructFrameAccessor - - name: AI - uid: bigframes.operations.ai.AIAccessor - status: beta name: DataFrame - items: - name: DataFrameGroupBy diff --git a/notebooks/experimental/ai_operators.ipynb b/notebooks/experimental/ai_operators.ipynb index 9878929cd2..e24ec34d86 100644 --- a/notebooks/experimental/ai_operators.ipynb +++ b/notebooks/experimental/ai_operators.ipynb @@ -29,7 +29,7 @@ "id": "rWJnGj2ViouP" }, "source": [ - "All AI operators except for `ai.forecast` have moved to the `bigframes.bigquery.ai` module.\n", + "All AI functions have moved to the `bigframes.bigquery.ai` module.\n", "\n", "The tutorial notebook for AI functions is located at https://github.com/googleapis/python-bigquery-dataframes/blob/main/notebooks/generative_ai/ai_functions.ipynb\n", "\n", diff --git a/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb b/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb index fae6371a89..b9599282b3 100644 --- a/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb +++ b/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb @@ -65,7 +65,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -138,87 +138,111 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", + " \n", + " \n", " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -233,40 +257,40 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -281,99 +305,75 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", - " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -383,76 +383,76 @@ ], "text/plain": [ " trip_id duration_sec start_date \\\n", - "201708211707592427 13010 2017-08-21 17:07:59+00:00 \n", - "201710071741582009 2303 2017-10-07 17:41:58+00:00 \n", - "201803291935061174 552 2018-03-29 19:35:06+00:00 \n", - "201802081152113283 564 2018-02-08 11:52:11+00:00 \n", - "201710101915391238 642 2017-10-10 19:15:39+00:00 \n", - " 20171010191537666 659 2017-10-10 19:15:37+00:00 \n", - "201803241728231437 683 2018-03-24 17:28:23+00:00 \n", - "201801111613101305 858 2018-01-11 16:13:10+00:00 \n", - "201803171534453756 665 2018-03-17 15:34:45+00:00 \n", - "201803021320282858 791 2018-03-02 13:20:28+00:00 \n", + " 20171215164722144 501 2017-12-15 16:47:22+00:00 \n", + "201708052346051585 712 2017-08-05 23:46:05+00:00 \n", + "201711111447202880 272 2017-11-11 14:47:20+00:00 \n", + "201804251726273755 757 2018-04-25 17:26:27+00:00 \n", + " 20180408155601183 1105 2018-04-08 15:56:01+00:00 \n", + "201804191648501560 857 2018-04-19 16:48:50+00:00 \n", + " 20170810204454839 1256 2017-08-10 20:44:54+00:00 \n", + " 20171012204438666 630 2017-10-12 20:44:38+00:00 \n", + "201711181823281960 353 2017-11-18 18:23:28+00:00 \n", + " 20170806183917510 298 2017-08-06 18:39:17+00:00 \n", "\n", - " start_station_name start_station_id end_date \\\n", - " 10th Ave at E 15th St 222 2017-08-21 20:44:50+00:00 \n", - " 10th Ave at E 15th St 222 2017-10-07 18:20:22+00:00 \n", - " 10th St at Fallon St 201 2018-03-29 19:44:19+00:00 \n", - " 13th St at Franklin St 338 2018-02-08 12:01:35+00:00 \n", - " 2nd Ave at E 18th St 200 2017-10-10 19:26:21+00:00 \n", - " 2nd Ave at E 18th St 200 2017-10-10 19:26:37+00:00 \n", - "El Embarcadero at Grand Ave 197 2018-03-24 17:39:46+00:00 \n", - " Frank H Ogawa Plaza 7 2018-01-11 16:27:28+00:00 \n", - " Frank H Ogawa Plaza 7 2018-03-17 15:45:50+00:00 \n", - " Frank H Ogawa Plaza 7 2018-03-02 13:33:39+00:00 \n", + " start_station_name start_station_id end_date \\\n", + " 10th St at Fallon St 201 2017-12-15 16:55:44+00:00 \n", + " 10th St at Fallon St 201 2017-08-05 23:57:57+00:00 \n", + " 12th St at 4th Ave 233 2017-11-11 14:51:53+00:00 \n", + "13th St at Franklin St 338 2018-04-25 17:39:05+00:00 \n", + "13th St at Franklin St 338 2018-04-08 16:14:26+00:00 \n", + "13th St at Franklin St 338 2018-04-19 17:03:08+00:00 \n", + " 2nd Ave at E 18th St 200 2017-08-10 21:05:50+00:00 \n", + " 2nd Ave at E 18th St 200 2017-10-12 20:55:09+00:00 \n", + " 2nd Ave at E 18th St 200 2017-11-18 18:29:22+00:00 \n", + " 2nd Ave at E 18th St 200 2017-08-06 18:44:15+00:00 \n", "\n", " end_station_name end_station_id bike_number zip_code ... \\\n", - "10th Ave at E 15th St 222 2427 ... \n", - "10th Ave at E 15th St 222 2009 ... \n", - "10th Ave at E 15th St 222 1174 ... \n", - "10th Ave at E 15th St 222 3283 ... \n", - "10th Ave at E 15th St 222 1238 ... \n", + "10th Ave at E 15th St 222 144 ... \n", + "10th Ave at E 15th St 222 1585 ... \n", + "10th Ave at E 15th St 222 2880 ... \n", + "10th Ave at E 15th St 222 3755 ... \n", + "10th Ave at E 15th St 222 183 ... \n", + "10th Ave at E 15th St 222 1560 ... \n", + "10th Ave at E 15th St 222 839 ... \n", "10th Ave at E 15th St 222 666 ... \n", - "10th Ave at E 15th St 222 1437 ... \n", - "10th Ave at E 15th St 222 1305 ... \n", - "10th Ave at E 15th St 222 3756 ... \n", - "10th Ave at E 15th St 222 2858 ... \n", + "10th Ave at E 15th St 222 1960 ... \n", + "10th Ave at E 15th St 222 510 ... \n", "\n", "c_subscription_type start_station_latitude start_station_longitude \\\n", - " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 \n", " 37.797673 -122.262997 \n", + " 37.797673 -122.262997 \n", + " 37.795812 -122.255555 \n", + " 37.803189 -122.270579 \n", " 37.803189 -122.270579 \n", + " 37.803189 -122.270579 \n", + " 37.800214 -122.25381 \n", + " 37.800214 -122.25381 \n", " 37.800214 -122.25381 \n", " 37.800214 -122.25381 \n", - " 37.808848 -122.24968 \n", - " 37.804562 -122.271738 \n", - " 37.804562 -122.271738 \n", - " 37.804562 -122.271738 \n", "\n", " end_station_latitude end_station_longitude member_birth_year \\\n", + " 37.792714 -122.24878 1984 \n", " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1979 \n", + " 37.792714 -122.24878 1965 \n", " 37.792714 -122.24878 1982 \n", " 37.792714 -122.24878 1987 \n", + " 37.792714 -122.24878 1982 \n", " 37.792714 -122.24878 \n", " 37.792714 -122.24878 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 1984 \n", - " 37.792714 -122.24878 1987 \n", - " 37.792714 -122.24878 1984 \n", + " 37.792714 -122.24878 1988 \n", + " 37.792714 -122.24878 1969 \n", "\n", " member_gender bike_share_for_all_trip start_station_geom \\\n", - " POINT (-122.24878 37.79271) \n", - " Male POINT (-122.24878 37.79271) \n", - " Other No POINT (-122.263 37.79767) \n", + " Male POINT (-122.263 37.79767) \n", + " POINT (-122.263 37.79767) \n", + " Female POINT (-122.25555 37.79581) \n", + " Other No POINT (-122.27058 37.80319) \n", " Female No POINT (-122.27058 37.80319) \n", + " Other No POINT (-122.27058 37.80319) \n", " POINT (-122.25381 37.80021) \n", " POINT (-122.25381 37.80021) \n", - " Male No POINT (-122.24968 37.80885) \n", - " Male Yes POINT (-122.27174 37.80456) \n", - " Male No POINT (-122.27174 37.80456) \n", - " Male Yes POINT (-122.27174 37.80456) \n", + " Male POINT (-122.25381 37.80021) \n", + " Male POINT (-122.25381 37.80021) \n", "\n", " end_station_geom \n", "POINT (-122.24878 37.79271) \n", @@ -671,92 +671,92 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", @@ -766,28 +766,28 @@ ], "text/plain": [ " forecast_timestamp forecast_value confidence_level \\\n", - "2018-04-24 09:00:00+00:00 429.891174 0.95 \n", - "2018-04-24 19:00:00+00:00 288.039368 0.95 \n", - "2018-04-26 19:00:00+00:00 222.30899 0.95 \n", - "2018-04-29 11:00:00+00:00 133.549408 0.95 \n", + "2018-04-24 12:00:00+00:00 147.023743 0.95 \n", + "2018-04-25 00:00:00+00:00 6.955032 0.95 \n", + "2018-04-26 05:00:00+00:00 -37.196533 0.95 \n", + "2018-04-26 14:00:00+00:00 115.635132 0.95 \n", + "2018-04-27 02:00:00+00:00 2.516006 0.95 \n", + "2018-04-29 03:00:00+00:00 22.503326 0.95 \n", + "2018-04-24 04:00:00+00:00 -12.259079 0.95 \n", + "2018-04-24 14:00:00+00:00 126.519211 0.95 \n", "2018-04-26 11:00:00+00:00 120.90567 0.95 \n", "2018-04-27 13:00:00+00:00 162.023026 0.95 \n", - "2018-04-27 20:00:00+00:00 135.216156 0.95 \n", - "2018-04-28 05:00:00+00:00 5.645325 0.95 \n", - "2018-04-29 12:00:00+00:00 138.966232 0.95 \n", - "2018-04-25 03:00:00+00:00 -0.770828 0.95 \n", "\n", " prediction_interval_lower_bound prediction_interval_upper_bound \\\n", - " 287.352243 572.430105 \n", - " 186.949977 389.128758 \n", - " 87.964205 356.653776 \n", - " 67.082484 200.016332 \n", - " 35.78172 206.029621 \n", + " 98.736624 195.310862 \n", + " -6.094232 20.004297 \n", + " -88.759566 14.366499 \n", + " 30.120832 201.149432 \n", + " -69.095591 74.127604 \n", + " -38.714378 83.721031 \n", + " -45.377262 20.859104 \n", + " 96.837778 156.200644 \n", + " 35.781735 206.029606 \n", " 103.946307 220.099744 \n", - " 57.210032 213.22228 \n", - " -30.675206 41.965855 \n", - " 69.876807 208.055658 \n", - " -28.292754 26.751098 \n", "\n", "ai_forecast_status \n", " \n", @@ -811,8 +811,10 @@ } ], "source": [ + "import bigframes.bigquery as bbq\n", + "\n", "# Using all the data except the last week (2842-168) for training. And predict the last week (168).\n", - "result = df_grouped.head(2842-168).ai.forecast(timestamp_column=\"trip_hour\", data_column=\"num_trips\", horizon=168) \n", + "result = bbq.ai.forecast(df_grouped.head(2842-168), timestamp_col=\"trip_hour\", data_col=\"num_trips\", horizon=168) \n", "result" ] }, @@ -860,7 +862,7 @@ }, { "data": { - "image/png": "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wSpD4IQTTgAA3HLLLc3P9JCVZTGZTFAUhfPf/93f/R3uv//+5vtPf/rTOOqoo3DKKacAqAqa55xzDl75ylfii1/8IiaTCd773vd67zchhBBCCCGEEEIICcPOkOYRAMCePXvwmMc8Br/7u7+L0047DbfffjsuvvjipT/v93//9+O6667DDTfcgKOOOgrHHXdcr7/f2NjAhRdeiIsvvhg33HADXvGKV+D5z38+sizDddddh6uuugpPfOITceKJJ+K6667DHXfcgYc97GFLOhpCCCGEEEIIIYQQ0hcqEXcY73znOzGbzXD22WfjhS98IV796lcv/Tlf8pKXIM9znHHGGTjhhBNw44039vr7JzzhCTj99NPx4z/+4/i3//bf4qd+6qdwySWXAAD279+PT3ziE3jKU56CH/zBH8TFF1+MN7zhDXjyk5+8hCMhhBBCCCGEEEIIIS4IqTfY20HcfffdOProo3HXXXdh//79xu8OHz6Mb37zmzjttNOwvr6eaA8JAFxwwQW488478b73vS/1rgwCfrYJIYQQQgghhBASis3qazZUIhJCCCGEEEIIIYQQQjaFRcRdzo033oijjjpq4b++1mVCCCGEEEIIIYSQVWNalPh/r/82br3rcOpd2bEwWGWXc/LJJ2+a8HzyySd7bf9d73qX198TQgghhBBCCCGE+PLxr96BX//z6/HUR5yMtzzrrNS7syNhEXGXMxqN8JCHPCT1bhBCCCGEEEIIIYQsje8e2qge7zuSeE92LrQzE0IIIYQQQgghhJBBo3KFp8WOzBdeCVhEJIQQQgghhBBCCCGDpqxrh9OiTLsjOxgWEQkhhBBCCCGELJ27D0/xx5/8Bm656/7Uu0II2YWUjRKRRURXWEQkhBBCCCGEELJ03vuFb+PVf/33ePvHv5F6VwghuxClRJzRzuwMi4iEEEIIIYQQQpbOPYenACpFIiGExEb1RNygEtEZFhFJUC655BI88pGPjPp8J510EoQQeN/73hfteQkhhBBCCCH9UCqgsqQKiBASn6Iee6hEdIdFRLIlj3vc4/DCF75wW//3JS95Ca666qrl7lDN3//93+OVr3wl3v72t+OWW27Bk5/85CjPuwz6vMaEEEIIIYTsRFQ/shmLiISQBDBYxZ9R6h0gw0BKiaIocNRRR+Goo46K8pxf//rXAQA//dM/DSGE83am0ynG43Go3SKEEEIIIYR0oCbwBYuIhJAEyCZYhWOQK7tHiSglsHFfmn9y+x/Qxz3ucXjBC16A3/iN38Bxxx2HAwcO4JJLLgEA3HDDDRBC4Prrr2/+/5133gkhBK6++moAwNVXXw0hBD7ykY/grLPOwp49e/D4xz8et99+Oz70oQ/hYQ97GPbv349/9+/+HQ4dOrTl/lxwwQX4+Mc/jje96U0QQkAIgRtuuKF5ng996EM4++yzsba2hr/927+dszNfcMEF+Jmf+Rm88pWvxAknnID9+/fjV3/1V7GxsdH8n//xP/4HzjzzTOzZswfHH388zj33XNx3332b7tcll1yCpz71qQCALMuaImJZlrj00kvxL//lv8Ta2hoe+chH4sMf/nDzd+o1/G//7b/hsY99LNbX1/Fnf/ZnAIA//uM/xsMe9jCsr6/joQ99KN72trcZz/mtb30Lz3rWs3Dcccdh3759eNSjHoXrrrsOQFXQ/Omf/mmcdNJJOOqoo/CjP/qj+OhHP2r8/dve9jacfvrpWF9fx0knnYRnPvOZm77GhBBCCCGEDAlJJSIhJCFMZ/Zn9ygRp4eA15yc5rn/r5uByb5t//d3v/vdePGLX4zrrrsO1157LS644AKcc845OP3007e9jUsuuQRvfetbsXfvXvzcz/0cfu7nfg5ra2t4z3veg3vvvRdPe9rT8Ja3vAW/+Zu/uel23vSmN+FrX/safviHfxiXXnopAOCEE05oily/9Vu/hde//vV48IMfjGOPPbYpZupcddVVWF9fx9VXX40bbrgBv/RLv4Tjjz8ev/M7v4NbbrkFz3rWs/C6170OT3va03DPPffgk5/8ZHODsYiXvOQl+P7v/3780i/9Em655RZjf9/whjfg7W9/O8466yy8853vxE/91E/hy1/+svH6/dZv/Rbe8IY34KyzzmoKiS9/+cvx1re+FWeddRa++MUv4rnPfS727duH5zznObj33nvx2Mc+Ft/3fd+H97///Thw4AC+8IUvoCyrwefee+/FU57yFPzO7/wO1tbW8Cd/8id46lOfiq9+9at40IMehM997nN4wQtegP/yX/4LfuzHfgzf/e538clPfnLT15gQQgghhJAhoSbwVCISQlLQpjOziOjK7iki7iAe/vCH4xWveAUA4PTTT8db3/pWXHXVVb2KiK9+9atxzjnnAAAuvPBCvOxlL8PXv/51PPjBDwYAPPOZz8THPvaxLYuIRx99NCaTCfbu3YsDBw7M/f7SSy/FT/zET2y6jclkgne+853Yu3cvfuiHfgiXXnopXvrSl+JVr3oVbrnlFsxmMzz96U/HqaeeCgA488wztzy+o446CscccwwAGPv1+te/Hr/5m7+Jn//5nwcA/Kf/9J/wsY99DL//+7+Pyy67rPl/L3zhC/H0pz+9+f4Vr3gF3vCGNzQ/O+200/CVr3wFb3/72/Gc5zwH73nPe3DHHXfgs5/9LI477jgAwEMe8pDm7x/xiEfgEY94RPP9q171Krz3ve/F+9//fjz/+c/HjTfeiH379uEnf/In8YAHPACnnnoqzjrrrG29xoQQQgghhAyBZgLPIiIhJAEl7cze7J4i4nhvpQhM9dw9ePjDH258/8AHPhC333678zZOOukk7N27tykgqp995jOf6bXNLh71qEdt+X8e8YhHYO/e9jU4ePAg7r33Xtx00014xCMegSc84Qk488wzcd555+GJT3winvnMZ+LYY4/tvS933303br755qZ4qjjnnHPwd3/3dwv3+7777sPXv/51XHjhhXjuc5/b/Hw2m+Hoo48GAFx//fU466yzmgKizb333otLLrkEf/3Xf90URu+//37ceOONAICf+ImfwKmnnooHP/jBeNKTnoQnPelJeNrTnma8LoQQQgghhAyZVolIFRAhJD4qGX5alpBSemUr7FZ2TxFRiF6W4pTYIR9CCJRliSyrWljqVt/pdLrlNoQQC7fpy759fq9pnue48sorcc011+CKK67AW97yFvz2b/82rrvuOpx22mne+7cIfb/vvfdeAMAf/dEf4dGPfvTc/gHAnj17Nt3eS17yElx55ZV4/etfj4c85CHYs2cPnvnMZza9Hx/wgAfgC1/4Aq6++mpcccUVePnLX45LLrkEn/3sZxtFJSGEEEIIIUNGNlZCqoAIIfFRImgpq7YKo5xFxL7snmCVAaD65Ok9APWQlWUxmUxQFIXz3//d3/0d7r///ub7T3/60zjqqKNwyimnAKgKmueccw5e+cpX4otf/CImkwne+9739n6e/fv34+STT8anPvUp4+ef+tSncMYZZyz8u5NOOgknn3wyvvGNb+AhD3mI8U8VMh/+8Ifj+uuvx3e/+93ObXzqU5/CBRdcgKc97Wk488wzceDAgblwlNFo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cceOHejp6cH8+fMBANXV1Whubh7ye1u2bLH82gRBEARBEETmkIhIEARBEARxEjBt2jSsX78ehw4dQkdHR1ZOwHg8jk996lPYsWMH/vrXv+Kee+7BrbfemlE/xPG45JJLsGjRInzsYx/D5s2bsWHDBtx44404//zztVLriy66CG+//TaeeOIJ7N27F/fccw+2bdtm+bUJgiAIgiCIzCERkSAIgiAI4iTgjjvugMvlwvz581FdXY2mpqaMf/fiiy/GrFmzcN555+G6667D+9//ftx777227JckSfjjH/+I8vJynHfeebjkkkswY8YMPPPMM9pzLr/8cnz961/HnXfeidNPPx39/f248cYbbXl9giAIgiAIIjMkZXiDGYIgCIIgCIJQ+fjHP46enh784Q9/cHpXCIIgCIIgCAchJyJBEARBEARBEARBEARBEGNCIiJBEARBEARhiqamJoRCoVH/ZVMyTRAEQRAEQYgNlTMTBEEQBEEQpkgmkzh06NCoP582bRrcbnf+doggCIIgCILIGSQiEgRBEARBEARBEARBEAQxJlTOTBAEQRAEQRAEQRAEQRDEmJCISBAEQRAEQRAEQRAEQRDEmJCISBAEQRAEQRAEQRAEQRDEmJCISBAEQRAEQRAEQRAEQRDEmJCISBAEQRAEQRAEQRAEQRDEmJCISBAEQRAEQRAEQRAEQRDEmJCISBAEQRAEQRAEQRAEQRDEmJCISBAEQRAEQRAEQRAEQRDEmPx/iE/owHFEEpYAAAAASUVORK5CYII=", + "image/png": "iVBORw0KGgoAAAANSUhEUgAABREAAAKnCAYAAAARNgr5AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs/Xu8LEdd741/qntm1toXdm4n2TvREILEA8EAMfjANio8EBNCRLmJIkeJ5uBLfkEEHlB5zIEQEJADKJegHIWAIsdz9BEOIpeESABJCNcggoICIYFcBZKdZGfvNTNdvz+6q7uqpmet1VU1VbN6fd6v137NuuzV0z0zXd31rc/n+xFSSglCCCGEEEIIIYQQQgiZQ5Z6BwghhBBCCCGEEEIIIcsNi4iEEEIIIYQQQgghhJB1YRGREEIIIYQQQgghhBCyLiwiEkIIIYQQQgghhBBC1oVFREIIIYQQQgghhBBCyLqwiEgIIYQQQgghhBBCCFkXFhEJIYQQQgghhBBCCCHrwiIiIYQQQgghhBBCCCFkXQapd8CVoihw00034T73uQ+EEKl3hxBCCCGEEEIIIYSQLYWUEnfddRdOOOEEZNn6WsMtW0S86aabcOKJJ6beDUIIIYQQQgghhBBCtjQ33ngjfvAHf3Dd/7Nli4j3uc99AJQHuWfPnsR7QwghhBBCCCGEEELI1uLAgQM48cQT6zrbemzZIqKyMO/Zs4dFREIIIYQQQgghhBBCHNlMq0AGqxBCCCGEEEIIIYQQQtaFRURCCCGEEEIIIYQQQsi6sIhICCGEEEIIIYQQQghZly3bE3EzSCkxmUwwnU5T7woh3uR5jsFgsKk+BYQQQgghhBBCCCEh6W0RcW1tDTfffDMOHjyYelcICcbOnTtx/PHHYzQapd4VQgghhBBCCCGEbCN6WUQsigLf/OY3kec5TjjhBIxGI6q3yJZGSom1tTXcfvvt+OY3v4lTTjkFWcZuBIQQQgghhBBCCIlDL4uIa2trKIoCJ554Inbu3Jl6dwgJwo4dOzAcDvGtb30La2trWF1dTb1LhBBCCCGEEEII2Sb0WspEpRbpG/xME0IIIYQQQgghJAWsSBBCCCGEEEIIIYQQQtaFRURCCCGEEEIIIYQQQsi6sIhIgnLxxRfjYQ97WOrdIIQQQgghhBBCCCEBYRGRbMijH/1oPO95z9vU/33hC1+IK6+8crE7RAghhBBCCCGEEEKi0st0ZhIfKSWm0yl2796N3bt3p94dQgghhBBCCCGEEBKQbaNElFLi4NokyT8p5ab389GPfjSe+9zn4rd/+7dx9NFHY9++fbj44osBANdffz2EELjuuuvq/3/HHXdACIGrrroKAHDVVVdBCIEPf/jDOP3007Fjxw485jGPwW233YYPfvCDeNCDHoQ9e/bgl37pl3Dw4MEN9+f888/Hxz72MbzhDW+AEAJCCFx//fX183zwgx/EGWecgZWVFfzjP/7jjJ35/PPPxxOf+ES87GUvw7HHHos9e/bgN37jN7C2tlb/n7/5m7/Baaedhh07duCYY47BWWedhXvuuWfTrxkhhBBCCCGEEEIIWSzbRol473iKU1/y4STP/ZVLzsHO0eZf6ne+8514wQtegGuvvRbXXHMNzj//fJx55pk45ZRTNr2Niy++GG9+85uxc+dOPO1pT8PTnvY0rKys4N3vfjfuvvtuPOlJT8Kb3vQm/M7v/M6623nDG96Ar33ta/iRH/kRXHLJJQCAY489Ftdffz0A4Hd/93fx2te+Fve///1x1FFH1cVMnSuvvBKrq6u46qqrcP311+NXf/VXccwxx+D3f//3cfPNN+PpT386XvOa1+BJT3oS7rrrLnziE5/oVHglhBBCCCGEEEIIIYtl2xQRtxIPechD8NKXvhQAcMopp+DNb34zrrzyyk5FxFe84hU488wzAQAXXHABXvziF+PrX/867n//+wMAnvrUp+KjH/3ohkXEI444AqPRCDt37sS+fftmfn/JJZfgp3/6p9fdxmg0wtvf/nbs3LkTD37wg3HJJZfgRS96EV7+8pfj5ptvxmQywZOf/GScdNJJAIDTTjtt08dJCCGEEEIIIYQQQhbPtiki7hjm+Mol5yR77i485CEPMb4//vjjcdtttzlvY+/evdi5c2ddQFQ/+/SnP91pm208/OEP3/D/PPShD8XOnTvr7/fv34+7774bN954Ix760IfisY99LE477TScc845OPvss/HUpz4VRx11lPe+EUIIIYQQQgghhJAwbJsiohCik6U4JcPh0PheCIGiKJBlZQtL3eo7Ho833IYQYu42fdm1a5fX3+d5jiuuuAJXX301Lr/8crzpTW/C7/3e7+Haa6/FySef7L1/hBBCCCGEEEIIIcSfbROs0geOPfZY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FR/96Ec3LCIeccQRGI1G2LlzJ/bt2zfz+0suuQQ//dM/ve42RqMR3v72t2Pnzp148IMfjEsuuQQvetGL8PKXvxw333wzJpMJnvzkJ+Okk04CAJx22mkbHt/u3btx5JFHAoCxX6997WvxO7/zO/jFX/xFAMAf/MEf4KMf/Sj+6I/+CJdeemn9/573vOfhyU9+cv39S1/6Urzuda+rf3byySfjK1/5Ct761rfimc98Jt797nfj9ttvx2c+8xkcffTRAIAHPOAB9d8/9KEPxUMf+tD6+5e//OV4z3veg/e97314znOegxtuuAG7du3Cz/zMz+A+97kPTjrpJJx++umbeo0JIYQQQgjpA/UEnkVEQkgCCtqZvdk+RcThzlIRmOq5O/CQhzzE+P7444/Hbbfd5ryNvXv3YufOnXUBUf3s05/+dKdttvHwhz98w//z0Ic+FDt3Nq/B/v37cffdd+PGG2/EQx/6UDz2sY/FaaedhnPOOQdnn302nvrUp+Koo47qvC8HDhzATTfdVBdPFWeeeSa++MUvzt3ve+65B1//+tdxwQUX4FnPelb988lkgiOOOAIAcN111+H000+vC4g2d999Ny6++GL8/d//fV0Yvffee3HDDTcAAH76p38aJ510Eu5///vjcY97HB73uMfhSU96kvG6EEIIIYQQ0mcaJSJVQISQ+Khk+HFRQErpla2wXdk+RUQhOlmKU2KHfAghUBQFsqxsYalbfcfj8YbbEELM3aYvu3b5vaZ5nuOKK67A1VdfjcsvvxxvetOb8Hu/93u49tprcfLJJ3vv3zz0/b777rsBAH/6p3+KRzziETP7BwA7duxYd3svfOELccUVV+C1r30tHvCAB2DHjh146lOfWvd+vM997oPPf/7zuOqqq3D55ZfjJS95CS6++GJ85jOfqRWVhBBCCCGE9BlZWwmpAiKExEeJoKUs2yoMchYRu7J9glV6gOqTp/cA1ENWFsVoNMJ0OnX++y9+8Yu499576+8/9alPYffu3TjxxBMBlAXNM888Ey972cvwhS98AaPRCO95z3s6P8+ePXtwwgkn4JOf/KTx809+8pM49dRT5/7d3r17ccIJJ+Ab3/gGHvCABxj/VCHzIQ95CK677jp873vfa93GJz/5SZx//vl40pOehNNOOw379u2bCUcZDAY466yz8JrXvAb/9E//hOuvvx7/8A//AMD/NSaEEEIIIWTZUb0Q2ROREJKCQhNksa2CG9tHidgDduzYgUc+8pF49atfjZNPPhm33XYbLrroooU/7/3udz9ce+21uP7667F79+65lt55rK2t4YILLsBFF12E66+/Hi996UvxnOc8B1mW4dprr8WVV16Js88+G8cddxyuvfZa3H777XjQgx7ktK8vetGL8NKXvhQ/9EM/hIc97GG47LLLcN1119UJzPN42ctehuc+97k44ogj8LjHPQ6HDx/GZz/7WXz/+9/HC17wAjz96U/HK1/5SjzxiU/Eq171Khx//PH4whe+gBNOOAH79+/HKaecgr/927/FE57wBAgh8N/+238zlJ7vf//78Y1vfAM/9VM/haOOOgof+MAHUBQF/vN//s8A2l9jpTwlhBBCCCGkD9R25g1CFAkhZBHodcO1aYHVYZ5uZ7YorFJsMd7+9rdjMpngjDPOwPOe9zy84hWvWPhzvvCFL0Se5zj11FNx7LHH1n3+NstjH/tYnHLKKfipn/op/MIv/AJ+9md/FhdffDGAUj348Y9/HI9//OPxwz/8w7jooovwute9Dueee67Tvj73uc/FC17wAvw//8//g9NOOw0f+tCH8L73vW/DUJr/+l//K/7sz/4Ml112GU477TQ86lGPwjve8Y5aiTgajXD55ZfjuOOOw+Mf/3icdtppePWrX13bnV//+tfjqKOOwo//+I/jCU94As455xz86I/+aL39I488En/7t3+LxzzmMXjQgx6EP/mTP8H//J//Ew9+8IMB+L/GhBBCCCGELDuqdkglIiEkBXprOLZVcENIuTWXgQ4cOIAjjjgCd955J/bs2WP87tChQ/jmN7+Jk08+Gaurq4n2kADA+eefjzvuuAPvfe97U+9KL+BnmxBCCCGEbFVe8n/+GX9+zbdw6vF78IHf+snUu0MI2Wb89w//Ky796NcBANf+v4/F3j2cUwPr19dsqEQkhBBCCCGEELJwmnTmLaljWZe1CROnCVl29KFnPOU56wKLiNucG264Abt37577L6Wtdr39+sQnPpFsvwghhBBCyPLwr7ccwL/eciD1bpBNoCbwk6Jfk/e3fuzreMjLPozrbrwj9a4QQtah0KqIY9qZnWCwyjbnhBNOWDfh+YQTTvDa/jve8Q7nv11vv37gB37AebuEEEIIIaQfjKcFfv6PrwEE8Pn/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", "text/plain": [ "
" ] @@ -877,7 +879,7 @@ ], "metadata": { "kernelspec": { - "display_name": "venv", + "display_name": "venv (3.10.17)", "language": "python", "name": "python3" }, @@ -891,7 +893,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.16" + "version": "3.10.17" } }, "nbformat": 4, diff --git a/tests/system/small/bigquery/test_ai.py b/tests/system/small/bigquery/test_ai.py index f2aa477168..0c7c40031b 100644 --- a/tests/system/small/bigquery/test_ai.py +++ b/tests/system/small/bigquery/test_ai.py @@ -20,9 +20,10 @@ import pytest import sqlglot -from bigframes import dtypes, series +from bigframes import dataframe, dtypes, series import bigframes.bigquery as bbq import bigframes.pandas as bpd +from bigframes.testing import utils as test_utils def test_ai_function_pandas_input(session): @@ -325,5 +326,52 @@ def test_ai_score_multi_model(session): assert result.dtype == dtypes.FLOAT_DTYPE +def test_forecast_default_params(time_series_df_default_index: dataframe.DataFrame): + df = time_series_df_default_index[time_series_df_default_index["id"] == "1"] + + result = bbq.ai.forecast(df, timestamp_col="parsed_date", data_col="total_visits") + + expected_columns = [ + "forecast_timestamp", + "forecast_value", + "confidence_level", + "prediction_interval_lower_bound", + "prediction_interval_upper_bound", + "ai_forecast_status", + ] + test_utils.check_pandas_df_schema_and_index( + result, + columns=expected_columns, + index=10, + ) + + +def test_forecast_w_params(time_series_df_default_index: dataframe.DataFrame): + result = bbq.ai.forecast( + time_series_df_default_index, + timestamp_col="parsed_date", + data_col="total_visits", + id_cols=["id"], + horizon=20, + confidence_level=0.98, + context_window=64, + ) + + expected_columns = [ + "id", + "forecast_timestamp", + "forecast_value", + "confidence_level", + "prediction_interval_lower_bound", + "prediction_interval_upper_bound", + "ai_forecast_status", + ] + test_utils.check_pandas_df_schema_and_index( + result, + columns=expected_columns, + index=20 * 2, # 20 for each id + ) + + def _contains_no_nulls(s: series.Series) -> bool: return len(s) == s.count() diff --git a/tests/unit/test_dataframe.py b/tests/unit/test_dataframe.py index 6aaccd644e..2326f2595b 100644 --- a/tests/unit/test_dataframe.py +++ b/tests/unit/test_dataframe.py @@ -172,3 +172,12 @@ def test_dataframe_semantics_property_future_warning( FutureWarning ): dataframe.semantics + + +def test_dataframe_ai_property_future_warning( + monkeypatch: pytest.MonkeyPatch, +): + dataframe = mocks.create_dataframe(monkeypatch) + + with pytest.warns(FutureWarning): + dataframe.ai From 6eab1217e5d59d826547ebe342198aeeea21d5ee Mon Sep 17 00:00:00 2001 From: Chelsea Lin Date: Mon, 27 Oct 2025 15:32:13 -0700 Subject: [PATCH 03/21] refactor: add parenthesization for binary operations (#2193) --- .../core/compile/sqlglot/scalar_compiler.py | 43 ++++++++++++++++++- .../compile/sqlglot/test_scalar_compiler.py | 37 ++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/bigframes/core/compile/sqlglot/scalar_compiler.py b/bigframes/core/compile/sqlglot/scalar_compiler.py index 8167f40fc3..1da58871c7 100644 --- a/bigframes/core/compile/sqlglot/scalar_compiler.py +++ b/bigframes/core/compile/sqlglot/scalar_compiler.py @@ -31,6 +31,37 @@ class ScalarOpCompiler: typing.Callable[[typing.Sequence[TypedExpr], ops.RowOp], sge.Expression], ] = {} + # A set of SQLGlot classes that may need to be parenthesized + SQLGLOT_NEEDS_PARENS = { + # Numeric operations + sge.Add, + sge.Sub, + sge.Mul, + sge.Div, + sge.Mod, + sge.Pow, + # Comparison operations + sge.GTE, + sge.GT, + sge.LTE, + sge.LT, + sge.EQ, + sge.NEQ, + # Logical operations + sge.And, + sge.Or, + sge.Xor, + # Bitwise operations + sge.BitwiseAnd, + sge.BitwiseOr, + sge.BitwiseXor, + sge.BitwiseLeftShift, + sge.BitwiseRightShift, + sge.BitwiseNot, + # Other operations + sge.Is, + } + @functools.singledispatchmethod def compile_expression( self, @@ -110,10 +141,12 @@ def register_binary_op( def decorator(impl: typing.Callable[..., sge.Expression]): def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): + left = self._add_parentheses(args[0]) + right = self._add_parentheses(args[1]) if pass_op: - return impl(args[0], args[1], op) + return impl(left, right, op) else: - return impl(args[0], args[1]) + return impl(left, right) self._register(key, normalized_impl) return impl @@ -177,6 +210,12 @@ def _register( raise ValueError(f"Operation name {op_name} already registered") self._registry[op_name] = impl + @classmethod + def _add_parentheses(cls, expr: TypedExpr) -> TypedExpr: + if type(expr.expr) in cls.SQLGLOT_NEEDS_PARENS: + return TypedExpr(sge.paren(expr.expr, copy=False), expr.dtype) + return expr + # Singleton compiler scalar_op_compiler = ScalarOpCompiler() diff --git a/tests/unit/core/compile/sqlglot/test_scalar_compiler.py b/tests/unit/core/compile/sqlglot/test_scalar_compiler.py index a2ee2c6331..14d7b47389 100644 --- a/tests/unit/core/compile/sqlglot/test_scalar_compiler.py +++ b/tests/unit/core/compile/sqlglot/test_scalar_compiler.py @@ -170,6 +170,43 @@ def _(*args: TypedExpr, op: ops.NaryOp) -> sge.Expression: mock_impl.assert_called_once_with(arg1, arg2, arg3, arg4, op=mock_op) +def test_binary_op_parentheses(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockAddOp(ops.BinaryOp): + name = "mock_add_op" + + class MockMulOp(ops.BinaryOp): + name = "mock_mul_op" + + add_op = MockAddOp() + mul_op = MockMulOp() + + @compiler.register_binary_op(add_op) + def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: + return sge.Add(this=left.expr, expression=right.expr) + + @compiler.register_binary_op(mul_op) + def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: + return sge.Mul(this=left.expr, expression=right.expr) + + a = TypedExpr(sge.Identifier(this="a"), "int") + b = TypedExpr(sge.Identifier(this="b"), "int") + c = TypedExpr(sge.Identifier(this="c"), "int") + + # (a + b) * c + add_expr = compiler.compile_row_op(add_op, [a, b]) + add_typed_expr = TypedExpr(add_expr, "int") + result1 = compiler.compile_row_op(mul_op, [add_typed_expr, c]) + assert result1.sql() == "(a + b) * c" + + # a * (b + c) + add_expr_2 = compiler.compile_row_op(add_op, [b, c]) + add_typed_expr_2 = TypedExpr(add_expr_2, "int") + result2 = compiler.compile_row_op(mul_op, [a, add_typed_expr_2]) + assert result2.sql() == "a * (b + c)" + + def test_register_duplicate_op_raises(): compiler = scalar_compiler.ScalarOpCompiler() From 86a27564b48b854a32b3d11cd2105aa0fa496279 Mon Sep 17 00:00:00 2001 From: TrevorBergeron Date: Mon, 27 Oct 2025 15:34:23 -0700 Subject: [PATCH 04/21] feat: Support some python standard lib callables in apply/combine (#2187) --- bigframes/core/compile/polars/compiler.py | 20 +++ bigframes/core/compile/polars/lowering.py | 21 ++- .../compile/polars/operations/numeric_ops.py | 81 +++++++++-- bigframes/operations/python_op_maps.py | 85 ++++++++++++ bigframes/operations/string_ops.py | 17 +++ bigframes/series.py | 130 ++++++++++-------- tests/system/small/operations/test_lists.py | 30 ++++ tests/system/small/test_polars_execution.py | 13 +- tests/unit/test_series_polars.py | 92 +++++++++++++ 9 files changed, 412 insertions(+), 77 deletions(-) create mode 100644 bigframes/operations/python_op_maps.py diff --git a/bigframes/core/compile/polars/compiler.py b/bigframes/core/compile/polars/compiler.py index acaf1b8f22..d48ddba0cc 100644 --- a/bigframes/core/compile/polars/compiler.py +++ b/bigframes/core/compile/polars/compiler.py @@ -328,6 +328,26 @@ def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: assert isinstance(op, string_ops.StrContainsRegexOp) return input.str.contains(pattern=op.pat, literal=False) + @compile_op.register(string_ops.UpperOp) + def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: + assert isinstance(op, string_ops.UpperOp) + return input.str.to_uppercase() + + @compile_op.register(string_ops.LowerOp) + def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: + assert isinstance(op, string_ops.LowerOp) + return input.str.to_lowercase() + + @compile_op.register(string_ops.ArrayLenOp) + def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: + assert isinstance(op, string_ops.ArrayLenOp) + return input.list.len() + + @compile_op.register(string_ops.StrLenOp) + def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: + assert isinstance(op, string_ops.StrLenOp) + return input.str.len_chars() + @compile_op.register(string_ops.StartsWithOp) def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr: assert isinstance(op, string_ops.StartsWithOp) diff --git a/bigframes/core/compile/polars/lowering.py b/bigframes/core/compile/polars/lowering.py index 876ff2794f..bf617d6879 100644 --- a/bigframes/core/compile/polars/lowering.py +++ b/bigframes/core/compile/polars/lowering.py @@ -27,6 +27,7 @@ generic_ops, json_ops, numeric_ops, + string_ops, ) import bigframes.operations as ops @@ -347,11 +348,28 @@ def lower(self, expr: expression.OpExpression) -> expression.Expression: return ops.coalesce_op.as_expr(new_isin, expression.const(False)) +class LowerLenOp(op_lowering.OpLoweringRule): + @property + def op(self) -> type[ops.ScalarOp]: + return string_ops.LenOp + + def lower(self, expr: expression.OpExpression) -> expression.Expression: + assert isinstance(expr.op, string_ops.LenOp) + arg = expr.children[0] + + if dtypes.is_string_like(arg.output_type): + return string_ops.StrLenOp().as_expr(arg) + elif dtypes.is_array_like(arg.output_type): + return string_ops.ArrayLenOp().as_expr(arg) + else: + raise ValueError(f"Unexpected type: {arg.output_type}") + + def _coerce_comparables( expr1: expression.Expression, expr2: expression.Expression, *, - bools_only: bool = False + bools_only: bool = False, ): if bools_only: if ( @@ -446,6 +464,7 @@ def _lower_cast(cast_op: ops.AsTypeOp, arg: expression.Expression): LowerAsTypeRule(), LowerInvertOp(), LowerIsinOp(), + LowerLenOp(), ) diff --git a/bigframes/core/compile/polars/operations/numeric_ops.py b/bigframes/core/compile/polars/operations/numeric_ops.py index 8e44f15955..440415014e 100644 --- a/bigframes/core/compile/polars/operations/numeric_ops.py +++ b/bigframes/core/compile/polars/operations/numeric_ops.py @@ -29,15 +29,6 @@ import polars as pl -@polars_compiler.register_op(numeric_ops.CosOp) -def cos_op_impl( - compiler: polars_compiler.PolarsExpressionCompiler, - op: numeric_ops.CosOp, # type: ignore - input: pl.Expr, -) -> pl.Expr: - return input.cos() - - @polars_compiler.register_op(numeric_ops.LnOp) def ln_op_impl( compiler: polars_compiler.PolarsExpressionCompiler, @@ -80,6 +71,78 @@ def sin_op_impl( return input.sin() +@polars_compiler.register_op(numeric_ops.CosOp) +def cos_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.CosOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.cos() + + +@polars_compiler.register_op(numeric_ops.TanOp) +def tan_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.SinOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.tan() + + +@polars_compiler.register_op(numeric_ops.SinhOp) +def sinh_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.SinOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.sinh() + + +@polars_compiler.register_op(numeric_ops.CoshOp) +def cosh_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.CosOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.cosh() + + +@polars_compiler.register_op(numeric_ops.TanhOp) +def tanh_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.SinOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.tanh() + + +@polars_compiler.register_op(numeric_ops.ArcsinOp) +def asin_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.ArcsinOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.arcsin() + + +@polars_compiler.register_op(numeric_ops.ArccosOp) +def acos_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.ArccosOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.arccos() + + +@polars_compiler.register_op(numeric_ops.ArctanOp) +def atan_op_impl( + compiler: polars_compiler.PolarsExpressionCompiler, + op: numeric_ops.ArctanOp, # type: ignore + input: pl.Expr, +) -> pl.Expr: + return input.arctan() + + @polars_compiler.register_op(numeric_ops.SqrtOp) def sqrt_op_impl( compiler: polars_compiler.PolarsExpressionCompiler, diff --git a/bigframes/operations/python_op_maps.py b/bigframes/operations/python_op_maps.py new file mode 100644 index 0000000000..39f153ec05 --- /dev/null +++ b/bigframes/operations/python_op_maps.py @@ -0,0 +1,85 @@ +# 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 math +import operator +from typing import Optional + +import bigframes.operations +from bigframes.operations import ( + aggregations, + array_ops, + bool_ops, + comparison_ops, + numeric_ops, + string_ops, +) + +PYTHON_TO_BIGFRAMES = { + ## operators + operator.add: numeric_ops.add_op, + operator.sub: numeric_ops.sub_op, + operator.mul: numeric_ops.mul_op, + operator.truediv: numeric_ops.div_op, + operator.floordiv: numeric_ops.floordiv_op, + operator.mod: numeric_ops.mod_op, + operator.pow: numeric_ops.pow_op, + operator.pos: numeric_ops.pos_op, + operator.neg: numeric_ops.neg_op, + operator.abs: numeric_ops.abs_op, + operator.eq: comparison_ops.eq_op, + operator.ne: comparison_ops.ne_op, + operator.gt: comparison_ops.gt_op, + operator.lt: comparison_ops.lt_op, + operator.ge: comparison_ops.ge_op, + operator.le: comparison_ops.le_op, + operator.and_: bool_ops.and_op, + operator.or_: bool_ops.or_op, + operator.xor: bool_ops.xor_op, + ## math + math.log: numeric_ops.ln_op, + math.log10: numeric_ops.log10_op, + math.log1p: numeric_ops.log1p_op, + math.expm1: numeric_ops.expm1_op, + math.sin: numeric_ops.sin_op, + math.cos: numeric_ops.cos_op, + math.tan: numeric_ops.tan_op, + math.sinh: numeric_ops.sinh_op, + math.cosh: numeric_ops.cosh_op, + math.tanh: numeric_ops.tanh_op, + math.asin: numeric_ops.arcsin_op, + math.acos: numeric_ops.arccos_op, + math.atan: numeric_ops.arctan_op, + math.floor: numeric_ops.floor_op, + math.ceil: numeric_ops.ceil_op, + ## str + str.upper: string_ops.upper_op, + str.lower: string_ops.lower_op, + ## builtins + len: string_ops.len_op, + abs: numeric_ops.abs_op, + pow: numeric_ops.pow_op, + ### builtins -- iterable + all: array_ops.ArrayReduceOp(aggregations.all_op), + any: array_ops.ArrayReduceOp(aggregations.any_op), + sum: array_ops.ArrayReduceOp(aggregations.sum_op), + min: array_ops.ArrayReduceOp(aggregations.min_op), + max: array_ops.ArrayReduceOp(aggregations.max_op), +} + + +def python_callable_to_op(obj) -> Optional[bigframes.operations.RowOp]: + if obj in PYTHON_TO_BIGFRAMES: + return PYTHON_TO_BIGFRAMES[obj] + return None diff --git a/bigframes/operations/string_ops.py b/bigframes/operations/string_ops.py index f937ed23b6..a50a1b39f6 100644 --- a/bigframes/operations/string_ops.py +++ b/bigframes/operations/string_ops.py @@ -30,6 +30,23 @@ ) len_op = LenOp() +## Specialized len ops for compile-time lowering +StrLenOp = base_ops.create_unary_op( + name="strlen", + type_signature=op_typing.FixedOutputType( + dtypes.is_string_like, dtypes.INT_DTYPE, description="string-like" + ), +) +str_len_op = StrLenOp() + +ArrayLenOp = base_ops.create_unary_op( + name="arraylen", + type_signature=op_typing.FixedOutputType( + dtypes.is_array_like, dtypes.INT_DTYPE, description="array-like" + ), +) +array_len_op = ArrayLenOp() + ReverseOp = base_ops.create_unary_op( name="reverse", type_signature=op_typing.STRING_TRANSFORM ) diff --git a/bigframes/series.py b/bigframes/series.py index ad1f091803..ef0da32dfc 100644 --- a/bigframes/series.py +++ b/bigframes/series.py @@ -74,6 +74,7 @@ import bigframes.operations.datetimes as dt import bigframes.operations.lists as lists import bigframes.operations.plotting as plotting +import bigframes.operations.python_op_maps as python_ops import bigframes.operations.structs as structs import bigframes.session @@ -2033,88 +2034,97 @@ def apply( if by_row not in ["compat", False]: raise ValueError("Param by_row must be one of 'compat' or False") - if not callable(func): + if not callable(func) and not isinstance(func, numpy.ufunc): raise ValueError( "Only a ufunc (a function that applies to the entire Series) or" " a BigFrames BigQuery function that only works on single values" " are supported." ) - if not isinstance(func, bigframes.functions.BigqueryCallableRoutine): - # It is neither a remote function nor a managed function. - # Then it must be a vectorized function that applies to the Series - # as a whole. - if by_row: - raise ValueError( - "You have passed a function as-is. If your intention is to " - "apply this function in a vectorized way (i.e. to the " - "entire Series as a whole, and you are sure that it " - "performs only the operations that are implemented for a " - "Series (e.g. a chain of arithmetic/logical operations, " - "such as `def foo(s): return s % 2 == 1`), please also " - "specify `by_row=False`. If your function contains " - "arbitrary code, it can only be applied to every element " - "in the Series individually, in which case you must " - "convert it to a BigFrames BigQuery function using " - "`bigframes.pandas.udf`, " - "or `bigframes.pandas.remote_function` before passing." + if isinstance(func, bigframes.functions.BigqueryCallableRoutine): + # We are working with bigquery function at this point + if args: + result_series = self._apply_nary_op( + ops.NaryRemoteFunctionOp(function_def=func.udf_def), args + ) + # TODO(jialuo): Investigate why `_apply_nary_op` drops the series + # `name`. Manually reassigning it here as a temporary fix. + result_series.name = self.name + else: + result_series = self._apply_unary_op( + ops.RemoteFunctionOp(function_def=func.udf_def, apply_on_null=True) ) + result_series = func._post_process_series(result_series) - try: - return func(self) - except Exception as ex: - # This could happen if any of the operators in func is not - # supported on a Series. Let's guide the customer to use a - # bigquery function instead - if hasattr(ex, "message"): - ex.message += f"\n{_bigquery_function_recommendation_message}" - raise - - # We are working with bigquery function at this point - if args: - result_series = self._apply_nary_op( - ops.NaryRemoteFunctionOp(function_def=func.udf_def), args - ) - # TODO(jialuo): Investigate why `_apply_nary_op` drops the series - # `name`. Manually reassigning it here as a temporary fix. - result_series.name = self.name - else: - result_series = self._apply_unary_op( - ops.RemoteFunctionOp(function_def=func.udf_def, apply_on_null=True) + return result_series + + bf_op = python_ops.python_callable_to_op(func) + if bf_op and isinstance(bf_op, ops.UnaryOp): + return self._apply_unary_op(bf_op) + + # It is neither a remote function nor a managed function. + # Then it must be a vectorized function that applies to the Series + # as a whole. + if by_row: + raise ValueError( + "You have passed a function as-is. If your intention is to " + "apply this function in a vectorized way (i.e. to the " + "entire Series as a whole, and you are sure that it " + "performs only the operations that are implemented for a " + "Series (e.g. a chain of arithmetic/logical operations, " + "such as `def foo(s): return s % 2 == 1`), please also " + "specify `by_row=False`. If your function contains " + "arbitrary code, it can only be applied to every element " + "in the Series individually, in which case you must " + "convert it to a BigFrames BigQuery function using " + "`bigframes.pandas.udf`, " + "or `bigframes.pandas.remote_function` before passing." ) - result_series = func._post_process_series(result_series) - return result_series + try: + return func(self) # type: ignore + except Exception as ex: + # This could happen if any of the operators in func is not + # supported on a Series. Let's guide the customer to use a + # bigquery function instead + if hasattr(ex, "message"): + ex.message += f"\n{_bigquery_function_recommendation_message}" + raise def combine( self, other, func, ) -> Series: - if not callable(func): + if not callable(func) and not isinstance(func, numpy.ufunc): raise ValueError( "Only a ufunc (a function that applies to the entire Series) or" " a BigFrames BigQuery function that only works on single values" " are supported." ) - if not isinstance(func, bigframes.functions.BigqueryCallableRoutine): - # Keep this in sync with .apply - try: - return func(self, other) - except Exception as ex: - # This could happen if any of the operators in func is not - # supported on a Series. Let's guide the customer to use a - # bigquery function instead - if hasattr(ex, "message"): - ex.message += f"\n{_bigquery_function_recommendation_message}" - raise - - result_series = self._apply_binary_op( - other, ops.BinaryRemoteFunctionOp(function_def=func.udf_def) - ) - result_series = func._post_process_series(result_series) - return result_series + if isinstance(func, bigframes.functions.BigqueryCallableRoutine): + result_series = self._apply_binary_op( + other, ops.BinaryRemoteFunctionOp(function_def=func.udf_def) + ) + result_series = func._post_process_series(result_series) + return result_series + + bf_op = python_ops.python_callable_to_op(func) + if bf_op and isinstance(bf_op, ops.BinaryOp): + result_series = self._apply_binary_op(other, bf_op) + return result_series + + # Keep this in sync with .apply + try: + return func(self, other) + except Exception as ex: + # This could happen if any of the operators in func is not + # supported on a Series. Let's guide the customer to use a + # bigquery function instead + if hasattr(ex, "message"): + ex.message += f"\n{_bigquery_function_recommendation_message}" + raise @validations.requires_index def add_prefix(self, prefix: str, axis: int | str | None = None) -> Series: diff --git a/tests/system/small/operations/test_lists.py b/tests/system/small/operations/test_lists.py index fda01a5dae..16a6802572 100644 --- a/tests/system/small/operations/test_lists.py +++ b/tests/system/small/operations/test_lists.py @@ -106,3 +106,33 @@ def test_len(column_name, dtype, repeated_df, repeated_pandas_df): check_index_type=False, check_names=False, ) + + +@pytest.mark.parametrize( + ("column_name", "dtype"), + [ + pytest.param("int_list_col", pd.ArrowDtype(pa.list_(pa.int64()))), + pytest.param("float_list_col", pd.ArrowDtype(pa.list_(pa.float64()))), + ], +) +@pytest.mark.parametrize( + ("func",), + [ + pytest.param(len), + pytest.param(all), + pytest.param(any), + pytest.param(min), + pytest.param(max), + pytest.param(sum), + ], +) +def test_list_apply_callable(column_name, dtype, repeated_df, repeated_pandas_df, func): + bf_result = repeated_df[column_name].apply(func).to_pandas() + pd_result = repeated_pandas_df[column_name].astype(dtype).apply(func) + pd_result.index = pd_result.index.astype("Int64") + + assert_series_equal( + pd_result, + bf_result, + check_dtype=False, + ) diff --git a/tests/system/small/test_polars_execution.py b/tests/system/small/test_polars_execution.py index 916780b1ce..46eb59260b 100644 --- a/tests/system/small/test_polars_execution.py +++ b/tests/system/small/test_polars_execution.py @@ -11,9 +11,12 @@ # 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 math + import pytest import bigframes +import bigframes.bigquery from bigframes.testing.utils import assert_pandas_df_equal polars = pytest.importorskip("polars") @@ -63,13 +66,9 @@ def test_polar_execution_unsupported_sql_fallback( execution_count_before = session_w_polars._metrics.execution_count bf_df = session_w_polars.read_pandas(scalars_pandas_df_index) - pd_df = scalars_pandas_df_index.copy() - pd_df["str_len_col"] = pd_df.string_col.str.len() - pd_result = pd_df - - bf_df["str_len_col"] = bf_df.string_col.str.len() + bf_df["geo_area"] = bigframes.bigquery.st_length(bf_df.geography_col) bf_result = bf_df.to_pandas() - # str len not supported by polar engine yet, so falls back to bq execution + # geo fns not supported by polar engine yet, so falls back to bq execution assert session_w_polars._metrics.execution_count == (execution_count_before + 1) - assert_pandas_df_equal(bf_result, pd_result) + assert math.isclose(bf_result.geo_area.sum(), 70.52332050, rel_tol=0.00001) diff --git a/tests/unit/test_series_polars.py b/tests/unit/test_series_polars.py index e978ed43da..55bc048bcd 100644 --- a/tests/unit/test_series_polars.py +++ b/tests/unit/test_series_polars.py @@ -15,6 +15,7 @@ import datetime as dt import json import math +import operator import pathlib import re import tempfile @@ -4653,6 +4654,74 @@ def test_apply_numpy_ufunc(scalars_dfs, ufunc): assert_series_equal(bf_result, pd_result) +@pytest.mark.parametrize( + ("ufunc",), + [ + pytest.param(math.log), + pytest.param(math.log10), + pytest.param(math.sin), + pytest.param(math.cos), + pytest.param(math.tan), + pytest.param(math.sinh), + pytest.param(math.cosh), + pytest.param(math.tanh), + pytest.param(math.asin), + pytest.param(math.acos), + pytest.param(math.atan), + pytest.param(abs), + ], +) +@pytest.mark.parametrize( + ("col",), + [pytest.param("float64_col"), pytest.param("int64_col")], +) +def test_series_apply_python_numeric_fns(scalars_dfs, ufunc, col): + scalars_df, scalars_pandas_df = scalars_dfs + + bf_col = scalars_df[col] + bf_result = bf_col.apply(ufunc).to_pandas() + + pd_col = scalars_pandas_df[col] + + def wrapped(x): + try: + return ufunc(x) + except ValueError: + return pd.NA + except OverflowError: + if ufunc == math.sinh and x < 0: + return float("-inf") + return float("inf") + + pd_result = pd_col.apply(wrapped) + + assert_series_equal(bf_result, pd_result, check_dtype=False) + + +@pytest.mark.parametrize( + ("ufunc",), + [ + pytest.param(str.upper), + pytest.param(str.lower), + pytest.param(len), + ], +) +def test_series_apply_python_string_fns(scalars_dfs, ufunc): + scalars_df, scalars_pandas_df = scalars_dfs + + bf_col = scalars_df["string_col"] + bf_result = bf_col.apply(ufunc).to_pandas() + + pd_col = scalars_pandas_df["string_col"] + + def wrapped(x): + return ufunc(x) if isinstance(x, str) else None + + pd_result = pd_col.apply(wrapped) + + assert_series_equal(bf_result, pd_result, check_dtype=False) + + @pytest.mark.parametrize( ("ufunc",), [ @@ -4676,6 +4745,29 @@ def test_combine_series_ufunc(scalars_dfs, ufunc): assert_series_equal(bf_result, pd_result, check_dtype=False) +@pytest.mark.parametrize( + ("func",), + [ + pytest.param(operator.add), + pytest.param(operator.truediv), + ], + ids=[ + "add", + "divide", + ], +) +def test_combine_series_pyfunc(scalars_dfs, func): + scalars_df, scalars_pandas_df = scalars_dfs + + bf_col = scalars_df["int64_col"].dropna() + bf_result = bf_col.combine(bf_col, func).to_pandas() + + pd_col = scalars_pandas_df["int64_col"].dropna() + pd_result = pd_col.combine(pd_col, func) + + assert_series_equal(bf_result, pd_result, check_dtype=False) + + def test_combine_scalar_ufunc(scalars_dfs): scalars_df, scalars_pandas_df = scalars_dfs From 3e6299fee9204a63e97eb1910e97718ad5d23df5 Mon Sep 17 00:00:00 2001 From: Chelsea Lin Date: Mon, 27 Oct 2025 15:42:46 -0700 Subject: [PATCH 05/21] refactor: enable engine tests for datetime ops (#2196) --- .../compile/sqlglot/expressions/date_ops.py | 21 ++++++--- .../sqlglot/expressions/datetime_ops.py | 24 +++++++++- .../system/small/engines/test_temporal_ops.py | 4 +- .../test_datetime_ops/test_dayofweek/out.sql | 12 +++-- .../test_datetime_ops/test_floor_dt/out.sql | 29 ++++++++++-- .../test_datetime_ops/test_iso_day/out.sql | 2 +- .../sqlglot/expressions/test_datetime_ops.py | 44 ++++++++++++++----- 7 files changed, 110 insertions(+), 26 deletions(-) diff --git a/bigframes/core/compile/sqlglot/expressions/date_ops.py b/bigframes/core/compile/sqlglot/expressions/date_ops.py index f5922ecc8d..be772d978d 100644 --- a/bigframes/core/compile/sqlglot/expressions/date_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/date_ops.py @@ -35,10 +35,7 @@ def _(expr: TypedExpr) -> sge.Expression: @register_unary_op(ops.dayofweek_op) def _(expr: TypedExpr) -> sge.Expression: - # Adjust the 1-based day-of-week index (from SQL) to a 0-based index. - return sge.Extract( - this=sge.Identifier(this="DAYOFWEEK"), expression=expr.expr - ) - sge.convert(1) + return dayofweek_op_impl(expr) @register_unary_op(ops.dayofyear_op) @@ -48,7 +45,8 @@ def _(expr: TypedExpr) -> sge.Expression: @register_unary_op(ops.iso_day_op) def _(expr: TypedExpr) -> sge.Expression: - return sge.Extract(this=sge.Identifier(this="DAYOFWEEK"), expression=expr.expr) + # Plus 1 because iso day of week uses 1-based indexing + return dayofweek_op_impl(expr) + sge.convert(1) @register_unary_op(ops.iso_week_op) @@ -59,3 +57,16 @@ def _(expr: TypedExpr) -> sge.Expression: @register_unary_op(ops.iso_year_op) def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="ISOYEAR"), expression=expr.expr) + + +# Helpers +def dayofweek_op_impl(expr: TypedExpr) -> sge.Expression: + # BigQuery SQL Extract(DAYOFWEEK) returns 1 for Sunday through 7 for Saturday. + # We want 0 for Monday through 6 for Sunday to be compatible with Pandas. + extract_expr = sge.Extract( + this=sge.Identifier(this="DAYOFWEEK"), expression=expr.expr + ) + return sge.Cast( + this=sge.Mod(this=extract_expr + sge.convert(5), expression=sge.convert(7)), + to="INT64", + ) diff --git a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py b/bigframes/core/compile/sqlglot/expressions/datetime_ops.py index 77f4233e1c..949b122a1d 100644 --- a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/datetime_ops.py @@ -25,8 +25,28 @@ @register_unary_op(ops.FloorDtOp, pass_op=True) def _(expr: TypedExpr, op: ops.FloorDtOp) -> sge.Expression: - # TODO: Remove this method when it is covered by ops.FloorOp - return sge.TimestampTrunc(this=expr.expr, unit=sge.Identifier(this=op.freq)) + pandas_to_bq_freq_map = { + "Y": "YEAR", + "Q": "QUARTER", + "M": "MONTH", + "W": "WEEK(MONDAY)", + "D": "DAY", + "h": "HOUR", + "min": "MINUTE", + "s": "SECOND", + "ms": "MILLISECOND", + "us": "MICROSECOND", + "ns": "NANOSECOND", + } + if op.freq not in pandas_to_bq_freq_map.keys(): + raise NotImplementedError( + f"Unsupported freq paramater: {op.freq}" + + " Supported freq parameters are: " + + ",".join(pandas_to_bq_freq_map.keys()) + ) + + bq_freq = pandas_to_bq_freq_map[op.freq] + return sge.TimestampTrunc(this=expr.expr, unit=sge.Identifier(this=bq_freq)) @register_unary_op(ops.hour_op) diff --git a/tests/system/small/engines/test_temporal_ops.py b/tests/system/small/engines/test_temporal_ops.py index 5a39587886..66edfeddcc 100644 --- a/tests/system/small/engines/test_temporal_ops.py +++ b/tests/system/small/engines/test_temporal_ops.py @@ -25,7 +25,7 @@ REFERENCE_ENGINE = polars_executor.PolarsExecutor() -@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) def test_engines_dt_floor(scalars_array_value: array_value.ArrayValue, engine): arr, _ = scalars_array_value.compute_values( [ @@ -46,7 +46,7 @@ def test_engines_dt_floor(scalars_array_value: array_value.ArrayValue, engine): assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) -@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +@pytest.mark.parametrize("engine", ["polars", "bq", "bq-sqlglot"], indirect=True) def test_engines_date_accessors(scalars_array_value: array_value.ArrayValue, engine): datelike_cols = ["datetime_col", "timestamp_col", "date_col"] accessors = [ diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofweek/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofweek/out.sql index e6c17587d0..55d3832c1f 100644 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofweek/out.sql +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_dayofweek/out.sql @@ -1,13 +1,19 @@ WITH `bfcte_0` AS ( SELECT - `timestamp_col` AS `bfcol_0` + `date_col` AS `bfcol_0`, + `datetime_col` AS `bfcol_1`, + `timestamp_col` AS `bfcol_2` FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ), `bfcte_1` AS ( SELECT *, - EXTRACT(DAYOFWEEK FROM `bfcol_0`) - 1 AS `bfcol_1` + CAST(MOD(EXTRACT(DAYOFWEEK FROM `bfcol_1`) + 5, 7) AS INT64) AS `bfcol_6`, + CAST(MOD(EXTRACT(DAYOFWEEK FROM `bfcol_2`) + 5, 7) AS INT64) AS `bfcol_7`, + CAST(MOD(EXTRACT(DAYOFWEEK FROM `bfcol_0`) + 5, 7) AS INT64) AS `bfcol_8` FROM `bfcte_0` ) SELECT - `bfcol_1` AS `timestamp_col` + `bfcol_6` AS `datetime_col`, + `bfcol_7` AS `timestamp_col`, + `bfcol_8` AS `date_col` FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_floor_dt/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_floor_dt/out.sql index ad4fdb23a1..a8877f8cfa 100644 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_floor_dt/out.sql +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_floor_dt/out.sql @@ -1,13 +1,36 @@ WITH `bfcte_0` AS ( SELECT - `timestamp_col` AS `bfcol_0` + `datetime_col` AS `bfcol_0`, + `timestamp_col` AS `bfcol_1` FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ), `bfcte_1` AS ( SELECT *, - TIMESTAMP_TRUNC(`bfcol_0`, D) AS `bfcol_1` + TIMESTAMP_TRUNC(`bfcol_1`, MICROSECOND) AS `bfcol_2`, + TIMESTAMP_TRUNC(`bfcol_1`, MILLISECOND) AS `bfcol_3`, + TIMESTAMP_TRUNC(`bfcol_1`, SECOND) AS `bfcol_4`, + TIMESTAMP_TRUNC(`bfcol_1`, MINUTE) AS `bfcol_5`, + TIMESTAMP_TRUNC(`bfcol_1`, HOUR) AS `bfcol_6`, + TIMESTAMP_TRUNC(`bfcol_1`, DAY) AS `bfcol_7`, + TIMESTAMP_TRUNC(`bfcol_1`, WEEK(MONDAY)) AS `bfcol_8`, + TIMESTAMP_TRUNC(`bfcol_1`, MONTH) AS `bfcol_9`, + TIMESTAMP_TRUNC(`bfcol_1`, QUARTER) AS `bfcol_10`, + TIMESTAMP_TRUNC(`bfcol_1`, YEAR) AS `bfcol_11`, + TIMESTAMP_TRUNC(`bfcol_0`, MICROSECOND) AS `bfcol_12`, + TIMESTAMP_TRUNC(`bfcol_0`, MICROSECOND) AS `bfcol_13` FROM `bfcte_0` ) SELECT - `bfcol_1` AS `timestamp_col` + `bfcol_2` AS `timestamp_col_us`, + `bfcol_3` AS `timestamp_col_ms`, + `bfcol_4` AS `timestamp_col_s`, + `bfcol_5` AS `timestamp_col_min`, + `bfcol_6` AS `timestamp_col_h`, + `bfcol_7` AS `timestamp_col_D`, + `bfcol_8` AS `timestamp_col_W`, + `bfcol_9` AS `timestamp_col_M`, + `bfcol_10` AS `timestamp_col_Q`, + `bfcol_11` AS `timestamp_col_Y`, + `bfcol_12` AS `datetime_col_q`, + `bfcol_13` AS `datetime_col_us` FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_day/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_day/out.sql index d389172fda..f7203fc930 100644 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_day/out.sql +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_iso_day/out.sql @@ -5,7 +5,7 @@ WITH `bfcte_0` AS ( ), `bfcte_1` AS ( SELECT *, - EXTRACT(DAYOFWEEK FROM `bfcol_0`) AS `bfcol_1` + CAST(MOD(EXTRACT(DAYOFWEEK FROM `bfcol_0`) + 5, 7) AS INT64) + 1 AS `bfcol_1` FROM `bfcte_0` ) SELECT diff --git a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py index 91926e7bdd..3261113806 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py @@ -39,12 +39,11 @@ def test_day(scalar_types_df: bpd.DataFrame, snapshot): def test_dayofweek(scalar_types_df: bpd.DataFrame, snapshot): - col_name = "timestamp_col" - bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( - bf_df, [ops.dayofweek_op.as_expr(col_name)], [col_name] - ) + col_names = ["datetime_col", "timestamp_col", "date_col"] + bf_df = scalar_types_df[col_names] + ops_map = {col_name: ops.dayofweek_op.as_expr(col_name) for col_name in col_names} + sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -59,13 +58,38 @@ def test_dayofyear(scalar_types_df: bpd.DataFrame, snapshot): def test_floor_dt(scalar_types_df: bpd.DataFrame, snapshot): + col_names = ["datetime_col", "timestamp_col", "date_col"] + bf_df = scalar_types_df[col_names] + ops_map = { + "timestamp_col_us": ops.FloorDtOp("us").as_expr("timestamp_col"), + "timestamp_col_ms": ops.FloorDtOp("ms").as_expr("timestamp_col"), + "timestamp_col_s": ops.FloorDtOp("s").as_expr("timestamp_col"), + "timestamp_col_min": ops.FloorDtOp("min").as_expr("timestamp_col"), + "timestamp_col_h": ops.FloorDtOp("h").as_expr("timestamp_col"), + "timestamp_col_D": ops.FloorDtOp("D").as_expr("timestamp_col"), + "timestamp_col_W": ops.FloorDtOp("W").as_expr("timestamp_col"), + "timestamp_col_M": ops.FloorDtOp("M").as_expr("timestamp_col"), + "timestamp_col_Q": ops.FloorDtOp("Q").as_expr("timestamp_col"), + "timestamp_col_Y": ops.FloorDtOp("Y").as_expr("timestamp_col"), + "datetime_col_q": ops.FloorDtOp("us").as_expr("datetime_col"), + "datetime_col_us": ops.FloorDtOp("us").as_expr("datetime_col"), + } + + sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + snapshot.assert_match(sql, "out.sql") + + +def test_floor_dt_op_invalid_freq(scalar_types_df: bpd.DataFrame): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( - bf_df, [ops.FloorDtOp("D").as_expr(col_name)], [col_name] - ) - - snapshot.assert_match(sql, "out.sql") + with pytest.raises( + NotImplementedError, match="Unsupported freq paramater: invalid" + ): + utils._apply_unary_ops( + bf_df, + [ops.FloorDtOp(freq="invalid").as_expr(col_name)], # type:ignore + [col_name], + ) def test_hour(scalar_types_df: bpd.DataFrame, snapshot): From 4c98c95596ee4099aad68e9314296f669020139d Mon Sep 17 00:00:00 2001 From: Chelsea Lin Date: Mon, 27 Oct 2025 16:31:18 -0700 Subject: [PATCH 06/21] refactor: add struct_op and sql_scalar_op for the sqlglot compiler (#2197) --- .../sqlglot/expressions/generic_ops.py | 11 +++++++ .../compile/sqlglot/expressions/struct_ops.py | 11 +++++++ bigframes/testing/utils.py | 14 ++++++-- .../test_sql_scalar_op/out.sql | 14 ++++++++ .../test_struct_ops/test_struct_op/out.sql | 21 ++++++++++++ .../sqlglot/expressions/test_generic_ops.py | 11 +++++++ .../sqlglot/expressions/test_struct_ops.py | 32 +++++++++++++++++++ 7 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_sql_scalar_op/out.sql create mode 100644 tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_op/out.sql diff --git a/bigframes/core/compile/sqlglot/expressions/generic_ops.py b/bigframes/core/compile/sqlglot/expressions/generic_ops.py index 9782ef11d4..7572a1e801 100644 --- a/bigframes/core/compile/sqlglot/expressions/generic_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/generic_ops.py @@ -14,6 +14,7 @@ from __future__ import annotations +import sqlglot as sg import sqlglot.expressions as sge from bigframes import dtypes @@ -80,6 +81,16 @@ def _(expr: TypedExpr) -> sge.Expression: return sge.BitwiseNot(this=sge.paren(expr.expr)) +@register_nary_op(ops.SqlScalarOp, pass_op=True) +def _(*operands: TypedExpr, op: ops.SqlScalarOp) -> sge.Expression: + return sg.parse_one( + op.sql_template.format( + *[operand.expr.sql(dialect="bigquery") for operand in operands] + ), + dialect="bigquery", + ) + + @register_unary_op(ops.isnull_op) def _(expr: TypedExpr) -> sge.Expression: return sge.Is(this=expr.expr, expression=sge.Null()) diff --git a/bigframes/core/compile/sqlglot/expressions/struct_ops.py b/bigframes/core/compile/sqlglot/expressions/struct_ops.py index ebd3a38397..b6ec101eb1 100644 --- a/bigframes/core/compile/sqlglot/expressions/struct_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/struct_ops.py @@ -24,6 +24,7 @@ from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr import bigframes.core.compile.sqlglot.scalar_compiler as scalar_compiler +register_nary_op = scalar_compiler.scalar_op_compiler.register_nary_op register_unary_op = scalar_compiler.scalar_op_compiler.register_unary_op @@ -40,3 +41,13 @@ def _(expr: TypedExpr, op: ops.StructFieldOp) -> sge.Expression: this=sge.to_identifier(name, quoted=True), catalog=expr.expr, ) + + +@register_nary_op(ops.StructOp, pass_op=True) +def _(*exprs: TypedExpr, op: ops.StructOp) -> sge.Struct: + return sge.Struct( + expressions=[ + sge.PropertyEQ(this=sge.to_identifier(col), expression=expr.expr) + for col, expr in zip(op.column_names, exprs) + ] + ) diff --git a/bigframes/testing/utils.py b/bigframes/testing/utils.py index b4daab7aad..a0bfc9e648 100644 --- a/bigframes/testing/utils.py +++ b/bigframes/testing/utils.py @@ -475,13 +475,23 @@ def _apply_binary_op( ) -> str: """Applies a binary op to the given DataFrame and return the SQL representing the resulting DataFrame.""" + return _apply_nary_op(obj, op, l_arg, r_arg) + + +def _apply_nary_op( + obj: bpd.DataFrame, + op: Union[ops.BinaryOp, ops.NaryOp], + *args: Union[str, ex.Expression], +) -> str: + """Applies a nary op to the given DataFrame and return the SQL representing + the resulting DataFrame.""" array_value = obj._block.expr - op_expr = op.as_expr(l_arg, r_arg) + op_expr = op.as_expr(*args) result, col_ids = array_value.compute_values([op_expr]) # Rename columns for deterministic golden SQL results. assert len(col_ids) == 1 - result = result.rename_columns({col_ids[0]: l_arg}).select_columns([l_arg]) + result = result.rename_columns({col_ids[0]: args[0]}).select_columns([args[0]]) sql = result.session._executor.to_sql(result, enable_cache=False) return sql diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_sql_scalar_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_sql_scalar_op/out.sql new file mode 100644 index 0000000000..a79e006885 --- /dev/null +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_sql_scalar_op/out.sql @@ -0,0 +1,14 @@ +WITH `bfcte_0` AS ( + SELECT + `bool_col` AS `bfcol_0`, + `bytes_col` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_1` AS ( + SELECT + *, + CAST(`bfcol_0` AS INT64) + BYTE_LENGTH(`bfcol_1`) AS `bfcol_2` + FROM `bfcte_0` +) +SELECT + `bfcol_2` AS `bool_col` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_op/out.sql new file mode 100644 index 0000000000..f7f741a523 --- /dev/null +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_struct_ops/test_struct_op/out.sql @@ -0,0 +1,21 @@ +WITH `bfcte_0` AS ( + SELECT + `bool_col` AS `bfcol_0`, + `int64_col` AS `bfcol_1`, + `float64_col` AS `bfcol_2`, + `string_col` AS `bfcol_3` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_1` AS ( + SELECT + *, + STRUCT( + `bfcol_0` AS bool_col, + `bfcol_1` AS int64_col, + `bfcol_2` AS float64_col, + `bfcol_3` AS string_col + ) AS `bfcol_4` + FROM `bfcte_0` +) +SELECT + `bfcol_4` AS `result_col` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py index b7abc63213..075416d664 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py @@ -261,6 +261,17 @@ def test_notnull(scalar_types_df: bpd.DataFrame, snapshot): snapshot.assert_match(sql, "out.sql") +def test_sql_scalar_op(scalar_types_df: bpd.DataFrame, snapshot): + bf_df = scalar_types_df[["bool_col", "bytes_col"]] + sql = utils._apply_nary_op( + bf_df, + ops.SqlScalarOp(dtypes.INT_DTYPE, "CAST({0} AS INT64) + BYTE_LENGTH({1})"), + "bool_col", + "bytes_col", + ) + snapshot.assert_match(sql, "out.sql") + + def test_map(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] diff --git a/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py index 19156ead99..7e67e44cd3 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py @@ -12,15 +12,39 @@ # See the License for the specific language governing permissions and # limitations under the License. +import typing + import pytest from bigframes import operations as ops +from bigframes.core import expression as ex import bigframes.pandas as bpd from bigframes.testing import utils pytest.importorskip("pytest_snapshot") +def _apply_nary_op( + obj: bpd.DataFrame, + op: ops.NaryOp, + *args: typing.Union[str, ex.Expression], +) -> str: + """Applies a nary op to the given DataFrame and return the SQL representing + the resulting DataFrame.""" + array_value = obj._block.expr + op_expr = op.as_expr(*args) + result, col_ids = array_value.compute_values([op_expr]) + + # Rename columns for deterministic golden SQL results. + assert len(col_ids) == 1 + result = result.rename_columns({col_ids[0]: "result_col"}).select_columns( + ["result_col"] + ) + + sql = result.session._executor.to_sql(result, enable_cache=False) + return sql + + def test_struct_field(nested_structs_types_df: bpd.DataFrame, snapshot): col_name = "people" bf_df = nested_structs_types_df[[col_name]] @@ -34,3 +58,11 @@ def test_struct_field(nested_structs_types_df: bpd.DataFrame, snapshot): sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") + + +def test_struct_op(scalar_types_df: bpd.DataFrame, snapshot): + bf_df = scalar_types_df[["bool_col", "int64_col", "float64_col", "string_col"]] + op = ops.StructOp(column_names=tuple(bf_df.columns.tolist())) + sql = _apply_nary_op(bf_df, op, *bf_df.columns.tolist()) + + snapshot.assert_match(sql, "out.sql") From 3de8995ab658f81eabb6af22d64d2c88d8c9e5a8 Mon Sep 17 00:00:00 2001 From: Chelsea Lin Date: Tue, 28 Oct 2025 16:50:09 -0700 Subject: [PATCH 07/21] refactor: add rowkey to the sqlglot compiler (#2202) --- .../sqlglot/expressions/generic_ops.py | 53 ++++++++++++++ .../test_generic_ops/test_row_key/out.sql | 70 +++++++++++++++++++ .../sqlglot/expressions/test_generic_ops.py | 8 +++ 3 files changed, 131 insertions(+) create mode 100644 tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_row_key/out.sql diff --git a/bigframes/core/compile/sqlglot/expressions/generic_ops.py b/bigframes/core/compile/sqlglot/expressions/generic_ops.py index 7572a1e801..07505855e1 100644 --- a/bigframes/core/compile/sqlglot/expressions/generic_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/generic_ops.py @@ -159,6 +159,30 @@ def _(*cases_and_outputs: TypedExpr) -> sge.Expression: ) +@register_nary_op(ops.RowKey) +def _(*values: TypedExpr) -> sge.Expression: + # All inputs into hash must be non-null or resulting hash will be null + str_values = [_convert_to_nonnull_string_sqlglot(value) for value in values] + + full_row_hash_p1 = sge.func("FARM_FINGERPRINT", sge.Concat(expressions=str_values)) + + # By modifying value slightly, we get another hash uncorrelated with the first + full_row_hash_p2 = sge.func( + "FARM_FINGERPRINT", sge.Concat(expressions=[*str_values, sge.convert("_")]) + ) + + # Used to disambiguate between identical rows (which will have identical hash) + random_hash_p3 = sge.func("RAND") + + return sge.Concat( + expressions=[ + sge.Cast(this=full_row_hash_p1, to="STRING"), + sge.Cast(this=full_row_hash_p2, to="STRING"), + sge.Cast(this=random_hash_p3, to="STRING"), + ] + ) + + # Helper functions def _cast_to_json(expr: TypedExpr, op: ops.AsTypeOp) -> sge.Expression: from_type = expr.dtype @@ -218,3 +242,32 @@ def _cast(expr: sge.Expression, to: str, safe: bool): return sge.TryCast(this=expr, to=to) else: return sge.Cast(this=expr, to=to) + + +def _convert_to_nonnull_string_sqlglot(expr: TypedExpr) -> sge.Expression: + col_type = expr.dtype + sg_expr = expr.expr + + if col_type == dtypes.STRING_DTYPE: + result = sg_expr + elif ( + dtypes.is_numeric(col_type) + or dtypes.is_time_or_date_like(col_type) + or col_type == dtypes.BYTES_DTYPE + ): + result = sge.Cast(this=sg_expr, to="STRING") + elif col_type == dtypes.GEO_DTYPE: + result = sge.func("ST_ASTEXT", sg_expr) + else: + # TO_JSON_STRING works with all data types, but isn't the most efficient + # Needed for JSON, STRUCT and ARRAY datatypes + result = sge.func("TO_JSON_STRING", sg_expr) + + # Escape backslashes and use backslash as delineator + escaped = sge.func( + "REPLACE", + sge.func("COALESCE", result, sge.convert("")), + sge.convert("\\"), + sge.convert("\\\\"), + ) + return sge.Concat(expressions=[sge.convert("\\"), escaped]) diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_row_key/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_row_key/out.sql new file mode 100644 index 0000000000..080e35f68e --- /dev/null +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_generic_ops/test_row_key/out.sql @@ -0,0 +1,70 @@ +WITH `bfcte_0` AS ( + SELECT + `bool_col` AS `bfcol_0`, + `bytes_col` AS `bfcol_1`, + `date_col` AS `bfcol_2`, + `datetime_col` AS `bfcol_3`, + `geography_col` AS `bfcol_4`, + `int64_col` AS `bfcol_5`, + `int64_too` AS `bfcol_6`, + `numeric_col` AS `bfcol_7`, + `float64_col` AS `bfcol_8`, + `rowindex` AS `bfcol_9`, + `rowindex_2` AS `bfcol_10`, + `string_col` AS `bfcol_11`, + `time_col` AS `bfcol_12`, + `timestamp_col` AS `bfcol_13`, + `duration_col` AS `bfcol_14` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_1` AS ( + SELECT + *, + CONCAT( + CAST(FARM_FINGERPRINT( + CONCAT( + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_9` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_0` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_1` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_2` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_3` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(ST_ASTEXT(`bfcol_4`), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_5` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_6` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_7` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_8` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_9` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_10` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(`bfcol_11`, ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_12` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_13` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_14` AS STRING), ''), '\\', '\\\\')) + ) + ) AS STRING), + CAST(FARM_FINGERPRINT( + CONCAT( + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_9` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_0` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_1` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_2` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_3` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(ST_ASTEXT(`bfcol_4`), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_5` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_6` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_7` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_8` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_9` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_10` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(`bfcol_11`, ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_12` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_13` AS STRING), ''), '\\', '\\\\')), + CONCAT('\\', REPLACE(COALESCE(CAST(`bfcol_14` AS STRING), ''), '\\', '\\\\')), + '_' + ) + ) AS STRING), + CAST(RAND() AS STRING) + ) AS `bfcol_31` + FROM `bfcte_0` +) +SELECT + `bfcol_31` AS `row_key` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py index 075416d664..fd9732bf89 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py @@ -261,6 +261,14 @@ def test_notnull(scalar_types_df: bpd.DataFrame, snapshot): snapshot.assert_match(sql, "out.sql") +def test_row_key(scalar_types_df: bpd.DataFrame, snapshot): + column_ids = (col for col in scalar_types_df._block.expr.column_ids) + sql = utils._apply_unary_ops( + scalar_types_df, [ops.RowKey().as_expr(*column_ids)], ["row_key"] + ) + snapshot.assert_match(sql, "out.sql") + + def test_sql_scalar_op(scalar_types_df: bpd.DataFrame, snapshot): bf_df = scalar_types_df[["bool_col", "bytes_col"]] sql = utils._apply_nary_op( From 917f778e59c9a8c847b41dc72c8e6ca90eb51193 Mon Sep 17 00:00:00 2001 From: jialuoo Date: Wed, 29 Oct 2025 10:47:43 -0700 Subject: [PATCH 08/21] chore: Migrate minimum_op operator to SQLGlot (#2205) --- .../compile/sqlglot/expressions/comparison_ops.py | 5 +++++ .../test_comparison_ops/test_minimum_op/out.sql | 14 ++++++++++++++ .../sqlglot/expressions/test_comparison_ops.py | 7 +++++++ 3 files changed, 26 insertions(+) create mode 100644 tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_minimum_op/out.sql diff --git a/bigframes/core/compile/sqlglot/expressions/comparison_ops.py b/bigframes/core/compile/sqlglot/expressions/comparison_ops.py index eb08144b8a..e77b8b50a5 100644 --- a/bigframes/core/compile/sqlglot/expressions/comparison_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/comparison_ops.py @@ -109,6 +109,11 @@ def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: return sge.LTE(this=left_expr, expression=right_expr) +@register_binary_op(ops.minimum_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: + return sge.Least(this=left.expr, expressions=right.expr) + + @register_binary_op(ops.ne_op) def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_minimum_op/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_minimum_op/out.sql new file mode 100644 index 0000000000..429c3d2861 --- /dev/null +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_comparison_ops/test_minimum_op/out.sql @@ -0,0 +1,14 @@ +WITH `bfcte_0` AS ( + SELECT + `int64_col` AS `bfcol_0`, + `float64_col` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_1` AS ( + SELECT + *, + LEAST(`bfcol_0`, `bfcol_1`) AS `bfcol_2` + FROM `bfcte_0` +) +SELECT + `bfcol_2` AS `int64_col` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py index 6c3eb64414..f278a15f3c 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py @@ -110,6 +110,13 @@ def test_le_numeric(scalar_types_df: bpd.DataFrame, snapshot): snapshot.assert_match(bf_df.sql, "out.sql") +def test_minimum_op(scalar_types_df: bpd.DataFrame, snapshot): + bf_df = scalar_types_df[["int64_col", "float64_col"]] + sql = utils._apply_binary_op(bf_df, ops.minimum_op, "int64_col", "float64_col") + + snapshot.assert_match(sql, "out.sql") + + def test_ne_numeric(scalar_types_df: bpd.DataFrame, snapshot): bf_df = scalar_types_df[["int64_col", "bool_col"]] From 5ea640fa99e9ea874ebd6a49bbf882c65d2005cd Mon Sep 17 00:00:00 2001 From: jialuoo Date: Wed, 29 Oct 2025 10:50:49 -0700 Subject: [PATCH 09/21] chore: Migrate round_op operator to SQLGlot (#2204) This commit migrates the `round_op` operator from the Ibis compiler to the SQLGlot compiler. --- .../sqlglot/expressions/numeric_ops.py | 8 ++ .../test_numeric_ops/test_round/out.sql | 81 +++++++++++++++++++ .../sqlglot/expressions/test_numeric_ops.py | 14 ++++ 3 files changed, 103 insertions(+) create mode 100644 tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_round/out.sql diff --git a/bigframes/core/compile/sqlglot/expressions/numeric_ops.py b/bigframes/core/compile/sqlglot/expressions/numeric_ops.py index 8ca884b900..afc0d9d01c 100644 --- a/bigframes/core/compile/sqlglot/expressions/numeric_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/numeric_ops.py @@ -377,6 +377,14 @@ def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: return result +@register_binary_op(ops.round_op) +def _(expr: TypedExpr, n_digits: TypedExpr) -> sge.Expression: + rounded = sge.Round(this=expr.expr, decimals=n_digits.expr) + if expr.dtype == dtypes.INT_DTYPE: + return sge.Cast(this=rounded, to="INT64") + return rounded + + @register_binary_op(ops.sub_op) def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: if dtypes.is_numeric(left.dtype) and dtypes.is_numeric(right.dtype): diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_round/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_round/out.sql new file mode 100644 index 0000000000..8513c8d63f --- /dev/null +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_numeric_ops/test_round/out.sql @@ -0,0 +1,81 @@ +WITH `bfcte_0` AS ( + SELECT + `int64_col` AS `bfcol_0`, + `float64_col` AS `bfcol_1`, + `rowindex` AS `bfcol_2` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_1` AS ( + SELECT + *, + `bfcol_2` AS `bfcol_6`, + `bfcol_0` AS `bfcol_7`, + `bfcol_1` AS `bfcol_8`, + CAST(ROUND(`bfcol_0`, 0) AS INT64) AS `bfcol_9` + FROM `bfcte_0` +), `bfcte_2` AS ( + SELECT + *, + `bfcol_6` AS `bfcol_14`, + `bfcol_7` AS `bfcol_15`, + `bfcol_8` AS `bfcol_16`, + `bfcol_9` AS `bfcol_17`, + CAST(ROUND(`bfcol_7`, 1) AS INT64) AS `bfcol_18` + FROM `bfcte_1` +), `bfcte_3` AS ( + SELECT + *, + `bfcol_14` AS `bfcol_24`, + `bfcol_15` AS `bfcol_25`, + `bfcol_16` AS `bfcol_26`, + `bfcol_17` AS `bfcol_27`, + `bfcol_18` AS `bfcol_28`, + CAST(ROUND(`bfcol_15`, -1) AS INT64) AS `bfcol_29` + FROM `bfcte_2` +), `bfcte_4` AS ( + SELECT + *, + `bfcol_24` AS `bfcol_36`, + `bfcol_25` AS `bfcol_37`, + `bfcol_26` AS `bfcol_38`, + `bfcol_27` AS `bfcol_39`, + `bfcol_28` AS `bfcol_40`, + `bfcol_29` AS `bfcol_41`, + ROUND(`bfcol_26`, 0) AS `bfcol_42` + FROM `bfcte_3` +), `bfcte_5` AS ( + SELECT + *, + `bfcol_36` AS `bfcol_50`, + `bfcol_37` AS `bfcol_51`, + `bfcol_38` AS `bfcol_52`, + `bfcol_39` AS `bfcol_53`, + `bfcol_40` AS `bfcol_54`, + `bfcol_41` AS `bfcol_55`, + `bfcol_42` AS `bfcol_56`, + ROUND(`bfcol_38`, 1) AS `bfcol_57` + FROM `bfcte_4` +), `bfcte_6` AS ( + SELECT + *, + `bfcol_50` AS `bfcol_66`, + `bfcol_51` AS `bfcol_67`, + `bfcol_52` AS `bfcol_68`, + `bfcol_53` AS `bfcol_69`, + `bfcol_54` AS `bfcol_70`, + `bfcol_55` AS `bfcol_71`, + `bfcol_56` AS `bfcol_72`, + `bfcol_57` AS `bfcol_73`, + ROUND(`bfcol_52`, -1) AS `bfcol_74` + FROM `bfcte_5` +) +SELECT + `bfcol_66` AS `rowindex`, + `bfcol_67` AS `int64_col`, + `bfcol_68` AS `float64_col`, + `bfcol_69` AS `int_round_0`, + `bfcol_70` AS `int_round_1`, + `bfcol_71` AS `int_round_m1`, + `bfcol_72` AS `float_round_0`, + `bfcol_73` AS `float_round_1`, + `bfcol_74` AS `float_round_m1` +FROM `bfcte_6` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py index fe9a53a558..ab9fe53092 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py @@ -167,6 +167,20 @@ def test_pos(scalar_types_df: bpd.DataFrame, snapshot): snapshot.assert_match(sql, "out.sql") +def test_round(scalar_types_df: bpd.DataFrame, snapshot): + bf_df = scalar_types_df[["int64_col", "float64_col"]] + + bf_df["int_round_0"] = bf_df["int64_col"].round(0) + bf_df["int_round_1"] = bf_df["int64_col"].round(1) + bf_df["int_round_m1"] = bf_df["int64_col"].round(-1) + + bf_df["float_round_0"] = bf_df["float64_col"].round(0) + bf_df["float_round_1"] = bf_df["float64_col"].round(1) + bf_df["float_round_m1"] = bf_df["float64_col"].round(-1) + + snapshot.assert_match(bf_df.sql, "out.sql") + + def test_sqrt(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] From d4100466612df0523d01ed01ca1e115dabd6ef45 Mon Sep 17 00:00:00 2001 From: Shuowei Li Date: Wed, 29 Oct 2025 12:03:45 -0700 Subject: [PATCH 10/21] fix: Improve error handling in blob operations (#2194) * add error handling for audio_transcribe * add error handling for pdf functions * add eror handling for image functions * final touch * restore rename * update notebook to better reflect our new code change * return None on error with verbose=False for image functions * define typing module in udf * only use local variable * Refactor code --- bigframes/blob/_functions.py | 285 ++++++++---- bigframes/operations/blob.py | 137 ++++-- .../multimodal/multimodal_dataframe.ipynb | 426 ++++++++++++++---- 3 files changed, 625 insertions(+), 223 deletions(-) diff --git a/bigframes/blob/_functions.py b/bigframes/blob/_functions.py index 2a11974b8d..3dfe38811b 100644 --- a/bigframes/blob/_functions.py +++ b/bigframes/blob/_functions.py @@ -14,6 +14,7 @@ from dataclasses import dataclass import inspect +import typing from typing import Callable, Iterable, Union import google.cloud.bigquery as bigquery @@ -70,6 +71,12 @@ def _input_bq_signature(self): def _output_bq_type(self): sig = inspect.signature(self._func) + return_annotation = sig.return_annotation + origin = typing.get_origin(return_annotation) + if origin is Union: + args = typing.get_args(return_annotation) + if len(args) == 2 and args[1] is type(None): + return _PYTHON_TO_BQ_TYPES[args[0]] return _PYTHON_TO_BQ_TYPES[sig.return_annotation] def _create_udf(self): @@ -78,7 +85,7 @@ def _create_udf(self): self._session._anon_dataset_manager.generate_unique_resource_id() ) - func_body = inspect.getsource(self._func) + func_body = "import typing\n" + inspect.getsource(self._func) func_name = self._func.__name__ packages = str(list(self._requirements)) @@ -120,43 +127,50 @@ def udf(self): def exif_func(src_obj_ref_rt: str, verbose: bool) -> str: - import io - import json + try: + import io + import json - from PIL import ExifTags, Image - import requests - from requests import adapters + from PIL import ExifTags, Image + import requests + from requests import adapters - result_dict = {"status": "", "content": "{}"} - try: session = requests.Session() session.mount("https://", adapters.HTTPAdapter(max_retries=3)) src_obj_ref_rt_json = json.loads(src_obj_ref_rt) - src_url = src_obj_ref_rt_json["access_urls"]["read_url"] response = session.get(src_url, timeout=30) + response.raise_for_status() bts = response.content image = Image.open(io.BytesIO(bts)) exif_data = image.getexif() exif_dict = {} + if exif_data: for tag, value in exif_data.items(): tag_name = ExifTags.TAGS.get(tag, tag) - # Pillow might return bytes, which are not serializable. - if isinstance(value, bytes): - value = value.decode("utf-8", "replace") - exif_dict[tag_name] = value - result_dict["content"] = json.dumps(exif_dict) - except Exception as e: - result_dict["status"] = str(e) + # Convert non-serializable types to strings + try: + json.dumps(value) + exif_dict[tag_name] = value + except (TypeError, ValueError): + exif_dict[tag_name] = str(value) + + if verbose: + return json.dumps({"status": "", "content": json.dumps(exif_dict)}) + else: + return json.dumps(exif_dict) - if verbose: - return json.dumps(result_dict) - else: - return result_dict["content"] + except Exception as e: + # Return error as JSON with error field + error_result = {"status": f"{type(e).__name__}: {str(e)}", "content": "{}"} + if verbose: + return json.dumps(error_result) + else: + return "{}" exif_func_def = FunctionDef(exif_func, ["pillow", "requests"]) @@ -170,12 +184,10 @@ def image_blur_func( ksize_y: int, ext: str, verbose: bool, -) -> str: - import json - - result_dict = {"status": "", "content": dst_obj_ref_rt} - +) -> typing.Optional[str]: try: + import json + import cv2 as cv # type: ignore import numpy as np import requests @@ -193,35 +205,52 @@ def image_blur_func( dst_url = dst_obj_ref_rt_json["access_urls"]["write_url"] response = session.get(src_url, timeout=30) + response.raise_for_status() # Raise exception for HTTP errors bts = response.content nparr = np.frombuffer(bts, np.uint8) img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED) + + if img is None: + raise ValueError( + "Failed to decode image - possibly corrupted or unsupported format" + ) + img_blurred = cv.blur(img, ksize=(ksize_x, ksize_y)) - bts = cv.imencode(ext, img_blurred)[1].tobytes() + success, encoded = cv.imencode(ext, img_blurred) + if not success: + raise ValueError(f"Failed to encode image with extension {ext}") + + bts = encoded.tobytes() ext = ext.replace(".", "") ext_mappings = {"jpg": "jpeg", "tif": "tiff"} ext = ext_mappings.get(ext, ext) content_type = "image/" + ext - session.put( + put_response = session.put( url=dst_url, data=bts, - headers={ - "Content-Type": content_type, - }, + headers={"Content-Type": content_type}, timeout=30, ) + put_response.raise_for_status() - except Exception as e: - result_dict["status"] = str(e) + if verbose: + return json.dumps({"status": "", "content": dst_obj_ref_rt}) + else: + return dst_obj_ref_rt - if verbose: - return json.dumps(result_dict) - else: - return result_dict["content"] + except Exception as e: + if verbose: + error_result = { + "status": f"Error: {type(e).__name__}: {str(e)}", + "content": "", + } + return json.dumps(error_result) + else: + return None image_blur_def = FunctionDef(image_blur_func, ["opencv-python", "numpy", "requests"]) @@ -233,9 +262,6 @@ def image_blur_to_bytes_func( import base64 import json - status = "" - content = b"" - try: import cv2 as cv # type: ignore import numpy as np @@ -251,22 +277,36 @@ def image_blur_to_bytes_func( src_url = src_obj_ref_rt_json["access_urls"]["read_url"] response = session.get(src_url, timeout=30) + response.raise_for_status() bts = response.content nparr = np.frombuffer(bts, np.uint8) img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED) + if img is None: + raise ValueError( + "Failed to decode image - possibly corrupted or unsupported format" + ) img_blurred = cv.blur(img, ksize=(ksize_x, ksize_y)) - content = cv.imencode(ext, img_blurred)[1].tobytes() + success, encoded = cv.imencode(ext, img_blurred) + if not success: + raise ValueError(f"Failed to encode image with extension {ext}") + content = encoded.tobytes() + + encoded_content = base64.b64encode(content).decode("utf-8") + result_dict = {"status": "", "content": encoded_content} + if verbose: + return json.dumps(result_dict) + else: + return result_dict["content"] except Exception as e: - status = str(e) - - encoded_content = base64.b64encode(content).decode("utf-8") - result_dict = {"status": status, "content": encoded_content} - if verbose: - return json.dumps(result_dict) - else: - return result_dict["content"] + status = f"Error: {type(e).__name__}: {str(e)}" + encoded_content = base64.b64encode(b"").decode("utf-8") + result_dict = {"status": status, "content": encoded_content} + if verbose: + return json.dumps(result_dict) + else: + return result_dict["content"] image_blur_to_bytes_def = FunctionDef( @@ -283,12 +323,10 @@ def image_resize_func( fy: float, ext: str, verbose: bool, -) -> str: - import json - - result_dict = {"status": "", "content": dst_obj_ref_rt} - +) -> typing.Optional[str]: try: + import json + import cv2 as cv # type: ignore import numpy as np import requests @@ -306,20 +344,28 @@ def image_resize_func( dst_url = dst_obj_ref_rt_json["access_urls"]["write_url"] response = session.get(src_url, timeout=30) + response.raise_for_status() bts = response.content nparr = np.frombuffer(bts, np.uint8) img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED) + if img is None: + raise ValueError( + "Failed to decode image - possibly corrupted or unsupported format" + ) img_resized = cv.resize(img, dsize=(dsize_x, dsize_y), fx=fx, fy=fy) - bts = cv.imencode(ext, img_resized)[1].tobytes() + success, encoded = cv.imencode(ext, img_resized) + if not success: + raise ValueError(f"Failed to encode image with extension {ext}") + bts = encoded.tobytes() ext = ext.replace(".", "") ext_mappings = {"jpg": "jpeg", "tif": "tiff"} ext = ext_mappings.get(ext, ext) content_type = "image/" + ext - session.put( + put_response = session.put( url=dst_url, data=bts, headers={ @@ -327,14 +373,22 @@ def image_resize_func( }, timeout=30, ) + put_response.raise_for_status() - except Exception as e: - result_dict["status"] = str(e) + if verbose: + return json.dumps({"status": "", "content": dst_obj_ref_rt}) + else: + return dst_obj_ref_rt - if verbose: - return json.dumps(result_dict) - else: - return result_dict["content"] + except Exception as e: + if verbose: + error_result = { + "status": f"Error: {type(e).__name__}: {str(e)}", + "content": "", + } + return json.dumps(error_result) + else: + return None image_resize_def = FunctionDef( @@ -354,9 +408,6 @@ def image_resize_to_bytes_func( import base64 import json - status = "" - content = b"" - try: import cv2 as cv # type: ignore import numpy as np @@ -372,22 +423,36 @@ def image_resize_to_bytes_func( src_url = src_obj_ref_rt_json["access_urls"]["read_url"] response = session.get(src_url, timeout=30) + response.raise_for_status() bts = response.content nparr = np.frombuffer(bts, np.uint8) img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED) + if img is None: + raise ValueError( + "Failed to decode image - possibly corrupted or unsupported format" + ) img_resized = cv.resize(img, dsize=(dsize_x, dsize_y), fx=fx, fy=fy) - content = cv.imencode(".jpeg", img_resized)[1].tobytes() + success, encoded = cv.imencode(ext, img_resized) + if not success: + raise ValueError(f"Failed to encode image with extension {ext}") + content = encoded.tobytes() + + encoded_content = base64.b64encode(content).decode("utf-8") + result_dict = {"status": "", "content": encoded_content} + if verbose: + return json.dumps(result_dict) + else: + return result_dict["content"] except Exception as e: - status = str(e) - - encoded_content = base64.b64encode(content).decode("utf-8") - result_dict = {"status": status, "content": encoded_content} - if verbose: - return json.dumps(result_dict) - else: - return result_dict["content"] + status = f"Error: {type(e).__name__}: {str(e)}" + encoded_content = base64.b64encode(b"").decode("utf-8") + result_dict = {"status": status, "content": encoded_content} + if verbose: + return json.dumps(result_dict) + else: + return result_dict["content"] image_resize_to_bytes_def = FunctionDef( @@ -403,12 +468,10 @@ def image_normalize_func( norm_type: str, ext: str, verbose: bool, -) -> str: - import json - - result_dict = {"status": "", "content": dst_obj_ref_rt} - +) -> typing.Optional[str]: try: + import json + import cv2 as cv # type: ignore import numpy as np import requests @@ -433,22 +496,30 @@ def image_normalize_func( dst_url = dst_obj_ref_rt_json["access_urls"]["write_url"] response = session.get(src_url, timeout=30) + response.raise_for_status() bts = response.content nparr = np.frombuffer(bts, np.uint8) img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED) + if img is None: + raise ValueError( + "Failed to decode image - possibly corrupted or unsupported format" + ) img_normalized = cv.normalize( img, None, alpha=alpha, beta=beta, norm_type=norm_type_mapping[norm_type] ) - bts = cv.imencode(ext, img_normalized)[1].tobytes() + success, encoded = cv.imencode(ext, img_normalized) + if not success: + raise ValueError(f"Failed to encode image with extension {ext}") + bts = encoded.tobytes() ext = ext.replace(".", "") ext_mappings = {"jpg": "jpeg", "tif": "tiff"} ext = ext_mappings.get(ext, ext) content_type = "image/" + ext - session.put( + put_response = session.put( url=dst_url, data=bts, headers={ @@ -456,14 +527,22 @@ def image_normalize_func( }, timeout=30, ) + put_response.raise_for_status() - except Exception as e: - result_dict["status"] = str(e) + if verbose: + return json.dumps({"status": "", "content": dst_obj_ref_rt}) + else: + return dst_obj_ref_rt - if verbose: - return json.dumps(result_dict) - else: - return result_dict["content"] + except Exception as e: + if verbose: + error_result = { + "status": f"Error: {type(e).__name__}: {str(e)}", + "content": "", + } + return json.dumps(error_result) + else: + return None image_normalize_def = FunctionDef( @@ -482,8 +561,6 @@ def image_normalize_to_bytes_func( import base64 import json - result_dict = {"status": "", "content": ""} - try: import cv2 as cv # type: ignore import numpy as np @@ -506,25 +583,39 @@ def image_normalize_to_bytes_func( src_url = src_obj_ref_rt_json["access_urls"]["read_url"] response = session.get(src_url, timeout=30) + response.raise_for_status() bts = response.content nparr = np.frombuffer(bts, np.uint8) img = cv.imdecode(nparr, cv.IMREAD_UNCHANGED) + if img is None: + raise ValueError( + "Failed to decode image - possibly corrupted or unsupported format" + ) img_normalized = cv.normalize( img, None, alpha=alpha, beta=beta, norm_type=norm_type_mapping[norm_type] ) - bts = cv.imencode(".jpeg", img_normalized)[1].tobytes() + success, encoded = cv.imencode(ext, img_normalized) + if not success: + raise ValueError(f"Failed to encode image with extension {ext}") + content = encoded.tobytes() - content_b64 = base64.b64encode(bts).decode("utf-8") - result_dict["content"] = content_b64 + encoded_content = base64.b64encode(content).decode("utf-8") + result_dict = {"status": "", "content": encoded_content} - except Exception as e: - result_dict["status"] = str(e) + if verbose: + return json.dumps(result_dict) + else: + return result_dict["content"] - if verbose: - return json.dumps(result_dict) - else: - return result_dict["content"] + except Exception as e: + status = f"Error: {type(e).__name__}: {str(e)}" + encoded_content = base64.b64encode(b"").decode("utf-8") + result_dict = {"status": status, "content": encoded_content} + if verbose: + return json.dumps(result_dict) + else: + return result_dict["content"] image_normalize_to_bytes_def = FunctionDef( diff --git a/bigframes/operations/blob.py b/bigframes/operations/blob.py index 1f6b75a8f5..577de458f4 100644 --- a/bigframes/operations/blob.py +++ b/bigframes/operations/blob.py @@ -193,6 +193,20 @@ def _df_apply_udf( return s + def _apply_udf_or_raise_error( + self, df: bigframes.dataframe.DataFrame, udf, operation_name: str + ) -> bigframes.series.Series: + """Helper to apply UDF with consistent error handling.""" + try: + res = self._df_apply_udf(df, udf) + except Exception as e: + raise RuntimeError(f"{operation_name} UDF execution failed: {e}") from e + + if res is None: + raise RuntimeError(f"{operation_name} returned None result") + + return res + def read_url(self) -> bigframes.series.Series: """Retrieve the read URL of the Blob. @@ -343,6 +357,10 @@ def exif( Returns: bigframes.series.Series: JSON series of key-value pairs if verbose=False, or struct with status and content if verbose=True. + + Raises: + ValueError: If engine is not 'pillow'. + RuntimeError: If EXIF extraction fails or returns invalid structure. """ if engine is None or engine.casefold() != "pillow": raise ValueError("Must specify the engine, supported value is 'pillow'.") @@ -364,22 +382,28 @@ def exif( container_memory=container_memory, ).udf() - res = self._df_apply_udf(df, exif_udf) + res = self._apply_udf_or_raise_error(df, exif_udf, "EXIF extraction") if verbose: - exif_content_series = bbq.parse_json( - res._apply_unary_op(ops.JSONValue(json_path="$.content")) - ).rename("exif_content") - exif_status_series = res._apply_unary_op( - ops.JSONValue(json_path="$.status") - ) + try: + exif_content_series = bbq.parse_json( + res._apply_unary_op(ops.JSONValue(json_path="$.content")) + ).rename("exif_content") + exif_status_series = res._apply_unary_op( + ops.JSONValue(json_path="$.status") + ) + except Exception as e: + raise RuntimeError(f"Failed to parse EXIF JSON result: {e}") from e results_df = bpd.DataFrame( {"status": exif_status_series, "content": exif_content_series} ) results_struct = bbq.struct(results_df).rename("exif_results") return results_struct else: - return bbq.parse_json(res) + try: + return bbq.parse_json(res) + except Exception as e: + raise RuntimeError(f"Failed to parse EXIF JSON result: {e}") from e def image_blur( self, @@ -411,6 +435,10 @@ def image_blur( Returns: bigframes.series.Series: blob Series if destination is GCS. Or bytes Series if destination is BQ. If verbose=True, returns struct with status and content. + + Raises: + ValueError: If engine is not 'opencv' or parameters are invalid. + RuntimeError: If image blur operation fails. """ if engine is None or engine.casefold() != "opencv": raise ValueError("Must specify the engine, supported value is 'opencv'.") @@ -437,7 +465,7 @@ def image_blur( df["ksize_x"], df["ksize_y"] = ksize df["ext"] = ext # type: ignore df["verbose"] = verbose - res = self._df_apply_udf(df, image_blur_udf) + res = self._apply_udf_or_raise_error(df, image_blur_udf, "Image blur") if verbose: blurred_content_b64_series = res._apply_unary_op( @@ -486,7 +514,7 @@ def image_blur( df["ext"] = ext # type: ignore df["verbose"] = verbose - res = self._df_apply_udf(df, image_blur_udf) + res = self._apply_udf_or_raise_error(df, image_blur_udf, "Image blur") res.cache() # to execute the udf if verbose: @@ -540,6 +568,10 @@ def image_resize( Returns: bigframes.series.Series: blob Series if destination is GCS. Or bytes Series if destination is BQ. If verbose=True, returns struct with status and content. + + Raises: + ValueError: If engine is not 'opencv' or parameters are invalid. + RuntimeError: If image resize operation fails. """ if engine is None or engine.casefold() != "opencv": raise ValueError("Must specify the engine, supported value is 'opencv'.") @@ -570,11 +602,11 @@ def image_resize( container_memory=container_memory, ).udf() - df["dsize_x"], df["dsizye_y"] = dsize + df["dsize_x"], df["dsize_y"] = dsize df["fx"], df["fy"] = fx, fy df["ext"] = ext # type: ignore df["verbose"] = verbose - res = self._df_apply_udf(df, image_resize_udf) + res = self._apply_udf_or_raise_error(df, image_resize_udf, "Image resize") if verbose: resized_content_b64_series = res._apply_unary_op( @@ -620,12 +652,12 @@ def image_resize( dst_rt = dst.blob.get_runtime_json_str(mode="RW") df = df.join(dst_rt, how="outer") - df["dsize_x"], df["dsizye_y"] = dsize + df["dsize_x"], df["dsize_y"] = dsize df["fx"], df["fy"] = fx, fy df["ext"] = ext # type: ignore df["verbose"] = verbose - res = self._df_apply_udf(df, image_resize_udf) + res = self._apply_udf_or_raise_error(df, image_resize_udf, "Image resize") res.cache() # to execute the udf if verbose: @@ -679,6 +711,10 @@ def image_normalize( Returns: bigframes.series.Series: blob Series if destination is GCS. Or bytes Series if destination is BQ. If verbose=True, returns struct with status and content. + + Raises: + ValueError: If engine is not 'opencv' or parameters are invalid. + RuntimeError: If image normalize operation fails. """ if engine is None or engine.casefold() != "opencv": raise ValueError("Must specify the engine, supported value is 'opencv'.") @@ -707,7 +743,9 @@ def image_normalize( df["norm_type"] = norm_type df["ext"] = ext # type: ignore df["verbose"] = verbose - res = self._df_apply_udf(df, image_normalize_udf) + res = self._apply_udf_or_raise_error( + df, image_normalize_udf, "Image normalize" + ) if verbose: normalized_content_b64_series = res._apply_unary_op( @@ -758,7 +796,7 @@ def image_normalize( df["ext"] = ext # type: ignore df["verbose"] = verbose - res = self._df_apply_udf(df, image_normalize_udf) + res = self._apply_udf_or_raise_error(df, image_normalize_udf, "Image normalize") res.cache() # to execute the udf if verbose: @@ -809,6 +847,10 @@ def pdf_extract( depend on the "verbose" parameter. Contains the extracted text from the PDF file. Includes error messages if verbosity is enabled. + + Raises: + ValueError: If engine is not 'pypdf'. + RuntimeError: If PDF extraction fails or returns invalid structure. """ if engine is None or engine.casefold() != "pypdf": raise ValueError("Must specify the engine, supported value is 'pypdf'.") @@ -830,18 +872,29 @@ def pdf_extract( df = self.get_runtime_json_str(mode="R").to_frame() df["verbose"] = verbose - res = self._df_apply_udf(df, pdf_extract_udf) + + res = self._apply_udf_or_raise_error(df, pdf_extract_udf, "PDF extraction") if verbose: - extracted_content_series = res._apply_unary_op( - ops.JSONValue(json_path="$.content") - ) - status_series = res._apply_unary_op(ops.JSONValue(json_path="$.status")) - results_df = bpd.DataFrame( - {"status": status_series, "content": extracted_content_series} - ) - results_struct = bbq.struct(results_df).rename("extracted_results") - return results_struct + # Extract content with error handling + try: + content_series = res._apply_unary_op( + ops.JSONValue(json_path="$.content") + ) + except Exception as e: + raise RuntimeError( + f"Failed to extract content field from PDF result: {e}" + ) from e + try: + status_series = res._apply_unary_op(ops.JSONValue(json_path="$.status")) + except Exception as e: + raise RuntimeError( + f"Failed to extract status field from PDF result: {e}" + ) from e + + res_df = bpd.DataFrame({"status": status_series, "content": content_series}) + struct_series = bbq.struct(res_df).rename("extracted_results") + return struct_series else: return res.rename("extracted_content") @@ -884,6 +937,10 @@ def pdf_chunk( depend on the "verbose" parameter. where each string is a chunk of text extracted from PDF. Includes error messages if verbosity is enabled. + + Raises: + ValueError: If engine is not 'pypdf'. + RuntimeError: If PDF chunking fails or returns invalid structure. """ if engine is None or engine.casefold() != "pypdf": raise ValueError("Must specify the engine, supported value is 'pypdf'.") @@ -915,13 +972,25 @@ def pdf_chunk( df["overlap_size"] = overlap_size df["verbose"] = verbose - res = self._df_apply_udf(df, pdf_chunk_udf) + res = self._apply_udf_or_raise_error(df, pdf_chunk_udf, "PDF chunking") + + try: + content_series = bbq.json_extract_string_array(res, "$.content") + except Exception as e: + raise RuntimeError( + f"Failed to extract content array from PDF chunk result: {e}" + ) from e if verbose: - chunked_content_series = bbq.json_extract_string_array(res, "$.content") - status_series = res._apply_unary_op(ops.JSONValue(json_path="$.status")) + try: + status_series = res._apply_unary_op(ops.JSONValue(json_path="$.status")) + except Exception as e: + raise RuntimeError( + f"Failed to extract status field from PDF chunk result: {e}" + ) from e + results_df = bpd.DataFrame( - {"status": status_series, "content": chunked_content_series} + {"status": status_series, "content": content_series} ) resultes_struct = bbq.struct(results_df).rename("chunked_results") return resultes_struct @@ -962,6 +1031,10 @@ def audio_transcribe( depend on the "verbose" parameter. Contains the transcribed text from the audio file. Includes error messages if verbosity is enabled. + + Raises: + ValueError: If engine is not 'bigquery'. + RuntimeError: If the transcription result structure is invalid. """ if engine.casefold() != "bigquery": raise ValueError("Must specify the engine, supported value is 'bigquery'.") @@ -984,6 +1057,10 @@ def audio_transcribe( model_params={"generationConfig": {"temperature": 0.0}}, ) + # Validate that the result is not None + if transcribed_results is None: + raise RuntimeError("Transcription returned None result") + transcribed_content_series = transcribed_results.struct.field("result").rename( "transcribed_content" ) diff --git a/notebooks/multimodal/multimodal_dataframe.ipynb b/notebooks/multimodal/multimodal_dataframe.ipynb index c04463fc4c..0822ee4c2d 100644 --- a/notebooks/multimodal/multimodal_dataframe.ipynb +++ b/notebooks/multimodal/multimodal_dataframe.ipynb @@ -60,7 +60,8 @@ "2. Combine unstructured data with structured data\n", "3. Conduct image transformations\n", "4. Use LLM models to ask questions and generate embeddings on images\n", - "5. PDF chunking function" + "5. PDF chunking function\n", + "6. Transcribe audio" ] }, { @@ -215,23 +216,23 @@ "
\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", "
0201708211707592427130102017-08-21 17:07:59+00:0010th Ave at E 15th St2222017-08-21 20:44:50+00:00201712151647221445012017-12-15 16:47:22+00:0010th St at Fallon St2012017-12-15 16:55:44+00:0010th Ave at E 15th St2222427144<NA>...<NA>37.797673-122.26299737.792714-122.248781984Male<NA>POINT (-122.263 37.79767)POINT (-122.24878 37.79271)
12017080523460515857122017-08-05 23:46:05+00:0010th St at Fallon St2012017-08-05 23:57:57+00:0010th Ave at E 15th St2221585<NA>...<NA>37.797673-122.26299737.792714-122.24878<NA><NA><NA>POINT (-122.24878 37.79271)POINT (-122.263 37.79767)POINT (-122.24878 37.79271)
120171007174158200923032017-10-07 17:41:58+00:0010th Ave at E 15th St2222017-10-07 18:20:22+00:0022017111114472028802722017-11-11 14:47:20+00:0012th St at 4th Ave2332017-11-11 14:51:53+00:0010th Ave at E 15th St22220092880<NA>...<NA>37.795812-122.25555537.792714-122.2487837.792714-122.248781979Male1965Female<NA>POINT (-122.24878 37.79271)POINT (-122.25555 37.79581)POINT (-122.24878 37.79271)
22018032919350611745522018-03-29 19:35:06+00:0010th St at Fallon St2012018-03-29 19:44:19+00:0032018042517262737557572018-04-25 17:26:27+00:0013th St at Franklin St3382018-04-25 17:39:05+00:0010th Ave at E 15th St22211743755<NA>...<NA>37.797673-122.26299737.803189-122.27057937.792714-122.248781982OtherNoPOINT (-122.263 37.79767)POINT (-122.27058 37.80319)POINT (-122.24878 37.79271)
32018020811521132835642018-02-08 11:52:11+00:0042018040815560118311052018-04-08 15:56:01+00:0013th St at Franklin St3382018-02-08 12:01:35+00:002018-04-08 16:14:26+00:0010th Ave at E 15th St2223283183<NA>...<NA>POINT (-122.24878 37.79271)
42017101019153912386422017-10-10 19:15:39+00:002nd Ave at E 18th St2002017-10-10 19:26:21+00:0052018041916485015608572018-04-19 16:48:50+00:0013th St at Franklin St3382018-04-19 17:03:08+00:0010th Ave at E 15th St22212381560<NA>...<NA>37.800214-122.2538137.803189-122.27057937.792714-122.24878<NA><NA><NA>POINT (-122.25381 37.80021)1982OtherNoPOINT (-122.27058 37.80319)POINT (-122.24878 37.79271)
5201710101915376666592017-10-10 19:15:37+00:0062017081020445483912562017-08-10 20:44:54+00:002nd Ave at E 18th St2002017-10-10 19:26:37+00:002017-08-10 21:05:50+00:0010th Ave at E 15th St222666839<NA>...<NA>POINT (-122.24878 37.79271)
62018032417282314376832018-03-24 17:28:23+00:00El Embarcadero at Grand Ave1972018-03-24 17:39:46+00:007201710122044386666302017-10-12 20:44:38+00:002nd Ave at E 18th St2002017-10-12 20:55:09+00:0010th Ave at E 15th St2221437666<NA>...<NA>37.808848-122.2496837.800214-122.2538137.792714-122.248781987MaleNoPOINT (-122.24968 37.80885)POINT (-122.24878 37.79271)
72018011116131013058582018-01-11 16:13:10+00:00Frank H Ogawa Plaza72018-01-11 16:27:28+00:0010th Ave at E 15th St2221305<NA>...<NA>37.804562-122.27173837.792714-122.248781984MaleYesPOINT (-122.27174 37.80456)<NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
82018031715344537566652018-03-17 15:34:45+00:00Frank H Ogawa Plaza72018-03-17 15:45:50+00:002017111818232819603532017-11-18 18:23:28+00:002nd Ave at E 18th St2002017-11-18 18:29:22+00:0010th Ave at E 15th St22237561960<NA>...<NA>37.804562-122.27173837.800214-122.2538137.792714-122.2487819871988MaleNoPOINT (-122.27174 37.80456)<NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
92018030213202828587912018-03-02 13:20:28+00:00Frank H Ogawa Plaza72018-03-02 13:33:39+00:00201708061839175102982017-08-06 18:39:17+00:002nd Ave at E 18th St2002017-08-06 18:44:15+00:0010th Ave at E 15th St2222858510<NA>...<NA>37.804562-122.27173837.800214-122.2538137.792714-122.2487819841969MaleYesPOINT (-122.27174 37.80456)<NA>POINT (-122.25381 37.80021)POINT (-122.24878 37.79271)
02018-04-24 09:00:00+00:00429.8911742018-04-24 12:00:00+00:00147.0237430.95287.352243572.43010598.736624195.310862
12018-04-24 19:00:00+00:00288.0393682018-04-25 00:00:00+00:006.9550320.95186.949977389.128758-6.09423220.004297
22018-04-26 19:00:00+00:00222.308992018-04-26 05:00:00+00:00-37.1965330.9587.964205356.653776-88.75956614.366499
32018-04-29 11:00:00+00:00133.5494082018-04-26 14:00:00+00:00115.6351320.9567.082484200.01633230.120832201.149432
42018-04-26 11:00:00+00:00120.905672018-04-27 02:00:00+00:002.5160060.9535.78172206.029621-69.09559174.127604
52018-04-27 13:00:00+00:00162.0230262018-04-29 03:00:00+00:0022.5033260.95103.946307220.099744-38.71437883.721031
62018-04-27 20:00:00+00:00135.2161562018-04-24 04:00:00+00:00-12.2590790.9557.210032213.22228-45.37726220.859104
72018-04-28 05:00:00+00:005.6453252018-04-24 14:00:00+00:00126.5192110.95-30.67520641.96585596.837778156.200644
82018-04-29 12:00:00+00:00138.9662322018-04-26 11:00:00+00:00120.905670.9569.876807208.05565835.781735206.029606
92018-04-25 03:00:00+00:00-0.7708282018-04-27 13:00:00+00:00162.0230260.95-28.29275426.751098103.946307220.099744
0
1
2
3
4
\n", @@ -297,21 +298,21 @@ "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:124: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:121: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", "version. Use `json_query` instead.\n", " warnings.warn(bfe.format_message(msg), category=UserWarning)\n", "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:124: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:121: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", "version. Use `json_query` instead.\n", " warnings.warn(bfe.format_message(msg), category=UserWarning)\n", "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:124: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:121: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", "version. Use `json_query` instead.\n", " warnings.warn(bfe.format_message(msg), category=UserWarning)\n", "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", @@ -351,7 +352,7 @@ " \n", " \n", " 0\n", - " \n", + " \n", " alice\n", " image/png\n", " 1591240\n", @@ -359,7 +360,7 @@ " \n", " \n", " 1\n", - " \n", + " \n", " bob\n", " image/png\n", " 1182951\n", @@ -367,7 +368,7 @@ " \n", " \n", " 2\n", - " \n", + " \n", " bob\n", " image/png\n", " 1520884\n", @@ -375,7 +376,7 @@ " \n", " \n", " 3\n", - " \n", + " \n", " alice\n", " image/png\n", " 1235401\n", @@ -383,7 +384,7 @@ " \n", " \n", " 4\n", - " \n", + " \n", " bob\n", " image/png\n", " 1591923\n", @@ -463,7 +464,7 @@ "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:124: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:121: UserWarning: The `json_extract` is deprecated and will be removed in a future\n", "version. Use `json_query` instead.\n", " warnings.warn(bfe.format_message(msg), category=UserWarning)\n" ] @@ -471,7 +472,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -483,7 +484,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -527,19 +528,19 @@ "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:180: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", " return method(*args, **kwargs)\n", "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:180: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", " return method(*args, **kwargs)\n", "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:180: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", " return method(*args, **kwargs)\n" ] } @@ -579,7 +580,7 @@ "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:180: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", " return method(*args, **kwargs)\n" ] } @@ -589,9 +590,119 @@ "df_image[\"blur_resized\"] = df_image[\"blurred\"].blob.image_resize((300, 200), dst=f\"gs://{OUTPUT_BUCKET}/image_blur_resize_transformed/\", engine=\"opencv\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using `verbose` mode for detailed output\\n\n", + "\\n\n", + "All multimodal functions support a `verbose` parameter, which defaults to `False`.\\n\n", + "\\n\n", + "* When `verbose=False` (the default), the function will only return the main content of the result (e.g., the transformed image, the extracted text).\\n\n", + "* When `verbose=True`, the function returns a `STRUCT` containing two fields:\\n\n", + " * `content`: The main result of the operation.\\n\n", + " * `status`: An informational field. If the operation is successful, this will be empty. If an error occurs during the processing of a specific row, this field will contain the error message, allowing the overall job to complete without failing.\\n\n", + "\\n\n", + "Using `verbose=True` is highly recommended for debugging and for workflows where you need to handle potential failures on a row-by-row basis. Let's see it in action with the `image_blur` function." + ] + }, { "cell_type": "code", "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", + "instead of using `db_dtypes` in the future when available in pandas\n", + "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", + " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", + " return method(*args, **kwargs)\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", + "instead of using `db_dtypes` in the future when available in pandas\n", + "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", + " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
blurred_verbose
0{'status': '', 'content': {'uri': 'gs://bigfra...
1{'status': '', 'content': {'uri': 'gs://bigfra...
2{'status': '', 'content': {'uri': 'gs://bigfra...
3{'status': '', 'content': {'uri': 'gs://bigfra...
4{'status': '', 'content': {'uri': 'gs://bigfra...
\n", + "

5 rows × 1 columns

\n", + "
[5 rows x 1 columns in total]" + ], + "text/plain": [ + " blurred_verbose\n", + "0 {'status': '', 'content': {'uri': 'gs://bigfra...\n", + "1 {'status': '', 'content': {'uri': 'gs://bigfra...\n", + "2 {'status': '', 'content': {'uri': 'gs://bigfra...\n", + "3 {'status': '', 'content': {'uri': 'gs://bigfra...\n", + "4 {'status': '', 'content': {'uri': 'gs://bigfra...\n", + "\n", + "[5 rows x 1 columns]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_image[\"blurred_verbose\"] = df_image[\"image\"].blob.image_blur(\n", + " (20, 20), dst=f\"gs://{OUTPUT_BUCKET}/image_blur_transformed_verbose/\", engine=\"opencv\", verbose=True\n", + ")\n", + "df_image[[\"blurred_verbose\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -657,73 +768,79 @@ " resized\n", " normalized\n", " blur_resized\n", + " blurred_verbose\n", " \n", " \n", " \n", " \n", " 0\n", - " \n", + " \n", " alice\n", " image/png\n", " 1591240\n", " 2025-03-20 17:45:04+00:00\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " {'status': '', 'content': {'uri': 'gs://bigframes_blob_test/image_blur_transformed_verbose/k9-guard-dog-paw-balm.png', 'version': None, 'authorizer': 'bigframes-dev.us.bigframes-default-connection', 'details': None}}\n", " \n", " \n", " 1\n", - " \n", + " \n", " bob\n", " image/png\n", " 1182951\n", " 2025-03-20 17:45:02+00:00\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " {'status': '', 'content': {'uri': 'gs://bigframes_blob_test/image_blur_transformed_verbose/k9-guard-dog-hot-spot-spray.png', 'version': None, 'authorizer': 'bigframes-dev.us.bigframes-default-connection', 'details': None}}\n", " \n", " \n", " 2\n", - " \n", + " \n", " bob\n", " image/png\n", " 1520884\n", " 2025-03-20 17:44:55+00:00\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " {'status': '', 'content': {'uri': 'gs://bigframes_blob_test/image_blur_transformed_verbose/fluffy-buns-chinchilla-food-variety-pack.png', 'version': None, 'authorizer': 'bigframes-dev.us.bigframes-default-connection', 'details': None}}\n", " \n", " \n", " 3\n", - " \n", + " \n", " alice\n", " image/png\n", " 1235401\n", " 2025-03-20 17:45:19+00:00\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " {'status': '', 'content': {'uri': 'gs://bigframes_blob_test/image_blur_transformed_verbose/purrfect-perch-cat-scratcher.png', 'version': None, 'authorizer': 'bigframes-dev.us.bigframes-default-connection', 'details': None}}\n", " \n", " \n", " 4\n", - " \n", + " \n", " bob\n", " image/png\n", " 1591923\n", " 2025-03-20 17:44:47+00:00\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " {'status': '', 'content': {'uri': 'gs://bigframes_blob_test/image_blur_transformed_verbose/chirpy-seed-deluxe-bird-food.png', 'version': None, 'authorizer': 'bigframes-dev.us.bigframes-default-connection', 'details': None}}\n", " \n", " \n", "\n", - "

5 rows × 9 columns

\n", - "[5 rows x 9 columns in total]" + "

5 rows × 10 columns

\n", + "[5 rows x 10 columns in total]" ], "text/plain": [ " image author content_type \\\n", @@ -761,17 +878,24 @@ "3 {'uri': 'gs://bigframes_blob_test/image_normal... \n", "4 {'uri': 'gs://bigframes_blob_test/image_normal... \n", "\n", - " blur_resized \n", - "0 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", - "1 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", - "2 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", - "3 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", - "4 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", + " blur_resized \\\n", + "0 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", + "1 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", + "2 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", + "3 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", + "4 {'uri': 'gs://bigframes_blob_test/image_blur_r... \n", "\n", - "[5 rows x 9 columns]" + " blurred_verbose \n", + "0 {'status': '', 'content': {'uri': 'gs://bigfra... \n", + "1 {'status': '', 'content': {'uri': 'gs://bigfra... \n", + "2 {'status': '', 'content': {'uri': 'gs://bigfra... \n", + "3 {'status': '', 'content': {'uri': 'gs://bigfra... \n", + "4 {'status': '', 'content': {'uri': 'gs://bigfra... \n", + "\n", + "[5 rows x 10 columns]" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -791,7 +915,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": { "id": "mRUGfcaFVW-3" }, @@ -800,7 +924,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:180: FutureWarning: Since upgrading the default model can cause unintended breakages, the\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FutureWarning: Since upgrading the default model can cause unintended breakages, the\n", "default model will be removed in BigFrames 3.0. Please supply an\n", "explicit model to avoid this message.\n", " return method(*args, **kwargs)\n" @@ -814,7 +938,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -874,13 +998,13 @@ " \n", " \n", " 0\n", - " The item is a tin of K9Guard Dog Paw Balm.\n", - " \n", + " The item is a tin of K9 Guard dog paw balm.\n", + " \n", " \n", " \n", " 1\n", - " The item is a bottle of K9 Guard Dog Hot Spot Spray.\n", - " \n", + " The item is K9 Guard Dog Hot Spot Spray.\n", + " \n", " \n", " \n", "\n", @@ -888,9 +1012,9 @@ "[2 rows x 2 columns in total]" ], "text/plain": [ - " ml_generate_text_llm_result \\\n", - "0 The item is a tin of K9Guard Dog Paw Balm. \n", - "1 The item is a bottle of K9 Guard Dog Hot Spot ... \n", + " ml_generate_text_llm_result \\\n", + "0 The item is a tin of K9 Guard dog paw balm. \n", + "1 The item is K9 Guard Dog Hot Spot Spray. \n", "\n", " image \n", "0 {'uri': 'gs://cloud-samples-data/bigquery/tuto... \n", @@ -899,7 +1023,7 @@ "[2 rows x 2 columns]" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -913,7 +1037,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": { "id": "IG3J3HsKhyBY" }, @@ -936,7 +1060,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -996,13 +1120,13 @@ " \n", " \n", " 0\n", - " The item is dog paw balm.\n", - " \n", + " The item is a tin of K9Guard Dog Paw Balm.\n", + " \n", " \n", " \n", " 1\n", - " The picture features a white bottle with a light blue spray nozzle and accents. The background is a neutral gray.\\n\n", - " \n", + " The bottle is mostly white, with a light blue accents. The background is a light gray. There are also black and green elements on the bottle's label.\n", + " \n", " \n", " \n", "\n", @@ -1011,8 +1135,8 @@ ], "text/plain": [ " ml_generate_text_llm_result \\\n", - "0 The item is dog paw balm. \n", - "1 The picture features a white bottle with a lig... \n", + "0 The item is a tin of K9Guard Dog Paw Balm. \n", + "1 The bottle is mostly white, with a light blue ... \n", "\n", " image \n", "0 {'uri': 'gs://cloud-samples-data/bigquery/tuto... \n", @@ -1021,7 +1145,7 @@ "[2 rows x 2 columns]" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -1033,7 +1157,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -1047,7 +1171,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:180: FutureWarning: Since upgrading the default model can cause unintended breakages, the\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FutureWarning: Since upgrading the default model can cause unintended breakages, the\n", "default model will be removed in BigFrames 3.0. Please supply an\n", "explicit model to avoid this message.\n", " return method(*args, **kwargs)\n", @@ -1096,19 +1220,19 @@ " \n", " \n", " 0\n", - " [ 0.00638846 0.01666372 0.00451786 ... -0.02...\n", + " [ 0.00638842 0.01666344 0.00451782 ... -0.02...\n", " \n", " <NA>\n", " <NA>\n", - " {\"access_urls\":{\"expiry_time\":\"2025-10-09T12:2...\n", + " {\"access_urls\":{\"expiry_time\":\"2025-10-25T00:2...\n", " \n", " \n", " 1\n", - " [ 0.0097399 0.0214815 0.00244266 ... 0.00...\n", + " [ 0.00973689 0.02148374 0.00244311 ... 0.00...\n", " \n", " <NA>\n", " <NA>\n", - " {\"access_urls\":{\"expiry_time\":\"2025-10-09T12:2...\n", + " {\"access_urls\":{\"expiry_time\":\"2025-10-25T00:2...\n", " \n", " \n", "\n", @@ -1117,8 +1241,8 @@ ], "text/plain": [ " ml_generate_embedding_result \\\n", - "0 [ 0.00638846 0.01666372 0.00451786 ... -0.02... \n", - "1 [ 0.0097399 0.0214815 0.00244266 ... 0.00... \n", + "0 [ 0.00638842 0.01666344 0.00451782 ... -0.02... \n", + "1 [ 0.00973689 0.02148374 0.00244311 ... 0.00... \n", "\n", " ml_generate_embedding_status ml_generate_embedding_start_sec \\\n", "0 \n", @@ -1129,13 +1253,13 @@ "1 \n", "\n", " content \n", - "0 {\"access_urls\":{\"expiry_time\":\"2025-10-09T12:2... \n", - "1 {\"access_urls\":{\"expiry_time\":\"2025-10-09T12:2... \n", + "0 {\"access_urls\":{\"expiry_time\":\"2025-10-25T00:2... \n", + "1 {\"access_urls\":{\"expiry_time\":\"2025-10-25T00:2... \n", "\n", "[2 rows x 5 columns]" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -1158,7 +1282,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": { "id": "oDDuYtUm5Yiy" }, @@ -1180,7 +1304,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1197,9 +1321,12 @@ "instead of using `db_dtypes` in the future when available in pandas\n", "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:180: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", " return method(*args, **kwargs)\n", - "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:244: UserWarning: The `json_extract_string_array` is deprecated and will be removed in a\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:239: UserWarning: The `json_extract_string_array` is deprecated and will be removed in a\n", + "future version. Use `json_value_array` instead.\n", + " warnings.warn(bfe.format_message(msg), category=UserWarning)\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:239: UserWarning: The `json_extract_string_array` is deprecated and will be removed in a\n", "future version. Use `json_value_array` instead.\n", " warnings.warn(bfe.format_message(msg), category=UserWarning)\n" ] @@ -1211,7 +1338,78 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", + "instead of using `db_dtypes` in the future when available in pandas\n", + "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", + " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/core/log_adapter.py:182: FunctionAxisOnePreviewWarning: Blob Functions use bigframes DataFrame Managed function with axis=1 senario, which is a preview feature.\n", + " return method(*args, **kwargs)\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/bigquery/_operations/json.py:239: UserWarning: The `json_extract_string_array` is deprecated and will be removed in a\n", + "future version. Use `json_value_array` instead.\n", + " warnings.warn(bfe.format_message(msg), category=UserWarning)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
chunked_verbose
0{'status': '', 'content': array([\"CritterCuisi...
\n", + "

1 rows × 1 columns

\n", + "
[1 rows x 1 columns in total]" + ], + "text/plain": [ + " chunked_verbose\n", + "0 {'status': '', 'content': array([\"CritterCuisi...\n", + "\n", + "[1 rows x 1 columns]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_pdf[\"chunked_verbose\"] = df_pdf[\"pdf\"].blob.pdf_chunk(engine=\"pypdf\", verbose=True)\n", + "df_pdf[[\"chunked_verbose\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, "metadata": { "id": "kaPvJATN7zlw" }, @@ -1239,7 +1437,7 @@ "Name: chunked, dtype: string" ] }, - "execution_count": 18, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1258,7 +1456,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -1279,7 +1477,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -1303,7 +1501,7 @@ "Name: transcribed_content, dtype: string" ] }, - "execution_count": 20, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1312,6 +1510,42 @@ "transcribed_series = df['audio'].blob.audio_transcribe(model_name=\"gemini-2.0-flash-001\", verbose=False)\n", "transcribed_series" ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", + "instead of using `db_dtypes` in the future when available in pandas\n", + "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", + " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n", + "/usr/local/google/home/shuowei/src/github.com/googleapis/python-bigquery-dataframes/bigframes/dtypes.py:959: JSONDtypeWarning: JSON columns will be represented as pandas.ArrowDtype(pyarrow.json_())\n", + "instead of using `db_dtypes` in the future when available in pandas\n", + "(https://github.com/pandas-dev/pandas/issues/60958) and pyarrow.\n", + " warnings.warn(msg, bigframes.exceptions.JSONDtypeWarning)\n" + ] + }, + { + "data": { + "text/plain": [ + "0 {'status': '', 'content': 'Now, as all books, ...\n", + "Name: transcription_results, dtype: struct[pyarrow]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "transcribed_series_verbose = df['audio'].blob.audio_transcribe(model_name=\"gemini-2.0-flash-001\", verbose=True)\n", + "transcribed_series_verbose" + ] } ], "metadata": { From d69ba8871a9d36ce26429846375b0f21515db6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a=20=28Swast=29?= Date: Wed, 29 Oct 2025 16:52:45 -0500 Subject: [PATCH 11/21] refactor: update geo "spec" and split geo ops in ibis compiler (#2208) --- .../core/compile/ibis_compiler/__init__.py | 1 + .../ibis_compiler/operations/geo_ops.py | 159 ++++++++++++++++++ .../ibis_compiler/scalar_op_registry.py | 134 --------------- specs/2025-08-04-geoseries-scalars.md | 13 +- 4 files changed, 168 insertions(+), 139 deletions(-) create mode 100644 bigframes/core/compile/ibis_compiler/operations/geo_ops.py diff --git a/bigframes/core/compile/ibis_compiler/__init__.py b/bigframes/core/compile/ibis_compiler/__init__.py index aef0ed9267..6b9d284c53 100644 --- a/bigframes/core/compile/ibis_compiler/__init__.py +++ b/bigframes/core/compile/ibis_compiler/__init__.py @@ -21,4 +21,5 @@ from __future__ import annotations import bigframes.core.compile.ibis_compiler.operations.generic_ops # noqa: F401 +import bigframes.core.compile.ibis_compiler.operations.geo_ops # noqa: F401 import bigframes.core.compile.ibis_compiler.scalar_op_registry # noqa: F401 diff --git a/bigframes/core/compile/ibis_compiler/operations/geo_ops.py b/bigframes/core/compile/ibis_compiler/operations/geo_ops.py new file mode 100644 index 0000000000..f9155fed5a --- /dev/null +++ b/bigframes/core/compile/ibis_compiler/operations/geo_ops.py @@ -0,0 +1,159 @@ +# 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. + +from __future__ import annotations + +from typing import cast + +from bigframes_vendored.ibis.expr import types as ibis_types +import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes +import bigframes_vendored.ibis.expr.operations.udf as ibis_udf + +from bigframes.core.compile.ibis_compiler import scalar_op_compiler +from bigframes.operations import geo_ops as ops + +register_unary_op = scalar_op_compiler.scalar_op_compiler.register_unary_op +register_binary_op = scalar_op_compiler.scalar_op_compiler.register_binary_op + + +# Geo Ops +@register_unary_op(ops.geo_area_op) +def geo_area_op_impl(x: ibis_types.Value): + return cast(ibis_types.GeoSpatialValue, x).area() + + +@register_unary_op(ops.geo_st_astext_op) +def geo_st_astext_op_impl(x: ibis_types.Value): + return cast(ibis_types.GeoSpatialValue, x).as_text() + + +@register_unary_op(ops.geo_st_boundary_op, pass_op=False) +def geo_st_boundary_op_impl(x: ibis_types.Value): + return st_boundary(x) + + +@register_unary_op(ops.GeoStBufferOp, pass_op=True) +def geo_st_buffer_op_impl(x: ibis_types.Value, op: ops.GeoStBufferOp): + return st_buffer( + x, + op.buffer_radius, + op.num_seg_quarter_circle, + op.use_spheroid, + ) + + +@register_unary_op(ops.geo_st_centroid_op, pass_op=False) +def geo_st_centroid_op_impl(x: ibis_types.Value): + return cast(ibis_types.GeoSpatialValue, x).centroid() + + +@register_unary_op(ops.geo_st_convexhull_op, pass_op=False) +def geo_st_convexhull_op_impl(x: ibis_types.Value): + return st_convexhull(x) + + +@register_binary_op(ops.geo_st_difference_op, pass_op=False) +def geo_st_difference_op_impl(x: ibis_types.Value, y: ibis_types.Value): + return cast(ibis_types.GeoSpatialValue, x).difference( + cast(ibis_types.GeoSpatialValue, y) + ) + + +@register_binary_op(ops.GeoStDistanceOp, pass_op=True) +def geo_st_distance_op_impl( + x: ibis_types.Value, y: ibis_types.Value, op: ops.GeoStDistanceOp +): + return st_distance(x, y, op.use_spheroid) + + +@register_unary_op(ops.geo_st_geogfromtext_op) +def geo_st_geogfromtext_op_impl(x: ibis_types.Value): + # Ibis doesn't seem to provide a dedicated method to cast from string to geography, + # so we use a BigQuery scalar function, st_geogfromtext(), directly. + return st_geogfromtext(x) + + +@register_binary_op(ops.geo_st_geogpoint_op, pass_op=False) +def geo_st_geogpoint_op_impl(x: ibis_types.Value, y: ibis_types.Value): + return cast(ibis_types.NumericValue, x).point(cast(ibis_types.NumericValue, y)) + + +@register_binary_op(ops.geo_st_intersection_op, pass_op=False) +def geo_st_intersection_op_impl(x: ibis_types.Value, y: ibis_types.Value): + return cast(ibis_types.GeoSpatialValue, x).intersection( + cast(ibis_types.GeoSpatialValue, y) + ) + + +@register_unary_op(ops.geo_st_isclosed_op, pass_op=False) +def geo_st_isclosed_op_impl(x: ibis_types.Value): + return st_isclosed(x) + + +@register_unary_op(ops.geo_x_op) +def geo_x_op_impl(x: ibis_types.Value): + return cast(ibis_types.GeoSpatialValue, x).x() + + +@register_unary_op(ops.GeoStLengthOp, pass_op=True) +def geo_length_op_impl(x: ibis_types.Value, op: ops.GeoStLengthOp): + # Call the st_length UDF defined in this file (or imported) + return st_length(x, op.use_spheroid) + + +@register_unary_op(ops.geo_y_op) +def geo_y_op_impl(x: ibis_types.Value): + return cast(ibis_types.GeoSpatialValue, x).y() + + +@ibis_udf.scalar.builtin +def st_convexhull(x: ibis_dtypes.geography) -> ibis_dtypes.geography: # type: ignore + """ST_CONVEXHULL""" + ... + + +@ibis_udf.scalar.builtin +def st_geogfromtext(a: str) -> ibis_dtypes.geography: # type: ignore + """Convert string to geography.""" + + +@ibis_udf.scalar.builtin +def st_boundary(a: ibis_dtypes.geography) -> ibis_dtypes.geography: # type: ignore + """Find the boundary of a geography.""" + + +@ibis_udf.scalar.builtin +def st_buffer( + geography: ibis_dtypes.geography, # type: ignore + buffer_radius: ibis_dtypes.Float64, + num_seg_quarter_circle: ibis_dtypes.Float64, + use_spheroid: ibis_dtypes.Boolean, +) -> ibis_dtypes.geography: # type: ignore + ... + + +@ibis_udf.scalar.builtin +def st_distance(a: ibis_dtypes.geography, b: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.float: # type: ignore + """Convert string to geography.""" + + +@ibis_udf.scalar.builtin +def st_length(geog: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.float: # type: ignore + """ST_LENGTH BQ builtin. This body is never executed.""" + pass + + +@ibis_udf.scalar.builtin +def st_isclosed(a: ibis_dtypes.geography) -> ibis_dtypes.boolean: # type: ignore + """Checks if a geography is closed.""" diff --git a/bigframes/core/compile/ibis_compiler/scalar_op_registry.py b/bigframes/core/compile/ibis_compiler/scalar_op_registry.py index e983fc7e21..0876722990 100644 --- a/bigframes/core/compile/ibis_compiler/scalar_op_registry.py +++ b/bigframes/core/compile/ibis_compiler/scalar_op_registry.py @@ -837,98 +837,6 @@ def normalize_op_impl(x: ibis_types.Value): return result.cast(result_type) -# Geo Ops -@scalar_op_compiler.register_unary_op(ops.geo_area_op) -def geo_area_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.GeoSpatialValue, x).area() - - -@scalar_op_compiler.register_unary_op(ops.geo_st_astext_op) -def geo_st_astext_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.GeoSpatialValue, x).as_text() - - -@scalar_op_compiler.register_unary_op(ops.geo_st_boundary_op, pass_op=False) -def geo_st_boundary_op_impl(x: ibis_types.Value): - return st_boundary(x) - - -@scalar_op_compiler.register_unary_op(ops.GeoStBufferOp, pass_op=True) -def geo_st_buffer_op_impl(x: ibis_types.Value, op: ops.GeoStBufferOp): - return st_buffer( - x, - op.buffer_radius, - op.num_seg_quarter_circle, - op.use_spheroid, - ) - - -@scalar_op_compiler.register_unary_op(ops.geo_st_centroid_op, pass_op=False) -def geo_st_centroid_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.GeoSpatialValue, x).centroid() - - -@scalar_op_compiler.register_unary_op(ops.geo_st_convexhull_op, pass_op=False) -def geo_st_convexhull_op_impl(x: ibis_types.Value): - return st_convexhull(x) - - -@scalar_op_compiler.register_binary_op(ops.geo_st_difference_op, pass_op=False) -def geo_st_difference_op_impl(x: ibis_types.Value, y: ibis_types.Value): - return typing.cast(ibis_types.GeoSpatialValue, x).difference( - typing.cast(ibis_types.GeoSpatialValue, y) - ) - - -@scalar_op_compiler.register_binary_op(ops.GeoStDistanceOp, pass_op=True) -def geo_st_distance_op_impl( - x: ibis_types.Value, y: ibis_types.Value, op: ops.GeoStDistanceOp -): - return st_distance(x, y, op.use_spheroid) - - -@scalar_op_compiler.register_unary_op(ops.geo_st_geogfromtext_op) -def geo_st_geogfromtext_op_impl(x: ibis_types.Value): - # Ibis doesn't seem to provide a dedicated method to cast from string to geography, - # so we use a BigQuery scalar function, st_geogfromtext(), directly. - return st_geogfromtext(x) - - -@scalar_op_compiler.register_binary_op(ops.geo_st_geogpoint_op, pass_op=False) -def geo_st_geogpoint_op_impl(x: ibis_types.Value, y: ibis_types.Value): - return typing.cast(ibis_types.NumericValue, x).point( - typing.cast(ibis_types.NumericValue, y) - ) - - -@scalar_op_compiler.register_binary_op(ops.geo_st_intersection_op, pass_op=False) -def geo_st_intersection_op_impl(x: ibis_types.Value, y: ibis_types.Value): - return typing.cast(ibis_types.GeoSpatialValue, x).intersection( - typing.cast(ibis_types.GeoSpatialValue, y) - ) - - -@scalar_op_compiler.register_unary_op(ops.geo_st_isclosed_op, pass_op=False) -def geo_st_isclosed_op_impl(x: ibis_types.Value): - return st_isclosed(x) - - -@scalar_op_compiler.register_unary_op(ops.geo_x_op) -def geo_x_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.GeoSpatialValue, x).x() - - -@scalar_op_compiler.register_unary_op(ops.GeoStLengthOp, pass_op=True) -def geo_length_op_impl(x: ibis_types.Value, op: ops.GeoStLengthOp): - # Call the st_length UDF defined in this file (or imported) - return st_length(x, op.use_spheroid) - - -@scalar_op_compiler.register_unary_op(ops.geo_y_op) -def geo_y_op_impl(x: ibis_types.Value): - return typing.cast(ibis_types.GeoSpatialValue, x).y() - - # Parameterized ops @scalar_op_compiler.register_unary_op(ops.StructFieldOp, pass_op=True) def struct_field_op_impl(x: ibis_types.Value, op: ops.StructFieldOp): @@ -2092,17 +2000,6 @@ def _ibis_num(number: float): return typing.cast(ibis_types.NumericValue, ibis_types.literal(number)) -@ibis_udf.scalar.builtin -def st_convexhull(x: ibis_dtypes.geography) -> ibis_dtypes.geography: # type: ignore - """ST_CONVEXHULL""" - ... - - -@ibis_udf.scalar.builtin -def st_geogfromtext(a: str) -> ibis_dtypes.geography: # type: ignore - """Convert string to geography.""" - - @ibis_udf.scalar.builtin def timestamp(a: str) -> ibis_dtypes.timestamp: # type: ignore """Convert string to timestamp.""" @@ -2113,32 +2010,6 @@ def unix_millis(a: ibis_dtypes.timestamp) -> int: # type: ignore """Convert a timestamp to milliseconds""" -@ibis_udf.scalar.builtin -def st_boundary(a: ibis_dtypes.geography) -> ibis_dtypes.geography: # type: ignore - """Find the boundary of a geography.""" - - -@ibis_udf.scalar.builtin -def st_buffer( - geography: ibis_dtypes.geography, # type: ignore - buffer_radius: ibis_dtypes.Float64, - num_seg_quarter_circle: ibis_dtypes.Float64, - use_spheroid: ibis_dtypes.Boolean, -) -> ibis_dtypes.geography: # type: ignore - ... - - -@ibis_udf.scalar.builtin -def st_distance(a: ibis_dtypes.geography, b: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.float: # type: ignore - """Convert string to geography.""" - - -@ibis_udf.scalar.builtin -def st_length(geog: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.float: # type: ignore - """ST_LENGTH BQ builtin. This body is never executed.""" - pass - - @ibis_udf.scalar.builtin def unix_micros(a: ibis_dtypes.timestamp) -> int: # type: ignore """Convert a timestamp to microseconds""" @@ -2272,11 +2143,6 @@ def str_lstrip_op( # type: ignore[empty-body] """Remove leading and trailing characters.""" -@ibis_udf.scalar.builtin -def st_isclosed(a: ibis_dtypes.geography) -> ibis_dtypes.boolean: # type: ignore - """Checks if a geography is closed.""" - - @ibis_udf.scalar.builtin(name="rtrim") def str_rstrip_op( # type: ignore[empty-body] x: ibis_dtypes.String, to_strip: ibis_dtypes.String diff --git a/specs/2025-08-04-geoseries-scalars.md b/specs/2025-08-04-geoseries-scalars.md index 38dc77c4cf..66ed77d0dd 100644 --- a/specs/2025-08-04-geoseries-scalars.md +++ b/specs/2025-08-04-geoseries-scalars.md @@ -267,11 +267,14 @@ Raster functions: Functions for analyzing geospatial rasters using geographies. - [ ] **Export the new operation:** - [ ] In `bigframes/operations/__init__.py`, import your new operation dataclass and add it to the `__all__` list. - [ ] **Implement the compilation logic:** - - [ ] In `bigframes/core/compile/scalar_op_compiler.py`: - - [ ] If the BigQuery function has a direct equivalent in Ibis, you can often reuse an existing Ibis method. - - [ ] If not, define a new Ibis UDF using `@ibis_udf.scalar.builtin` to map to the specific BigQuery function signature. - - [ ] Create a new compiler implementation function (e.g., `geo_length_op_impl`). - - [ ] Register this function to your operation dataclass using `@scalar_op_compiler.register_unary_op` or `@scalar_op_compiler.register_binary_op`. + - [ ] In `bigframes/core/compile/ibis_compiler/operations/geo_ops.py`: + - [ ] If the BigQuery function has a direct equivalent in Ibis, you can often reuse an existing Ibis method. + - [ ] If not, define a new Ibis UDF using `@ibis_udf.scalar.builtin` to map to the specific BigQuery function signature. + - [ ] Create a new compiler implementation function (e.g., `geo_length_op_impl`). + - [ ] Register this function to your operation dataclass using `@register_unary_op` or `@register_binary_op`. + - [ ] In `bigframes/core/compile/sqlglot/expressions/geo_ops.py`: + - [ ] Create a new compiler implementation function that generates the appropriate `sqlglot.exp` expression. + - [ ] Register this function to your operation dataclass using `@register_unary_op` or `@register_binary_op`. - [ ] **Implement the user-facing function or property:** - [ ] For a `bigframes.bigquery` function: - [ ] In `bigframes/bigquery/_operations/geo.py`, create the user-facing function (e.g., `st_length`). From d97cafcb5921fca2351b18011b0e54e2631cc53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a=20=28Swast=29?= Date: Thu, 30 Oct 2025 09:36:05 -0500 Subject: [PATCH 12/21] feat: support INFORMATION_SCHEMA views in `read_gbq` (#1895) * feat: support INFORMATION_SCHEMA tables in read_gbq * avoid storage semi executor * use faster tables for peek tests * more tests * fix mypy * Update bigframes/session/_io/bigquery/read_gbq_table.py * immediately query for information_schema tables * Fix mypy errors and temporarily update python version * snapshot * snapshot again --- .../session/_io/bigquery/read_gbq_table.py | 96 +++++++++++++++++-- bigframes/session/loader.py | 30 +++--- bigframes/session/read_api_execution.py | 3 + .../test_read_gbq_information_schema.py | 50 ++++++++++ tests/unit/session/test_session.py | 4 +- 5 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 tests/system/small/pandas/test_read_gbq_information_schema.py diff --git a/bigframes/session/_io/bigquery/read_gbq_table.py b/bigframes/session/_io/bigquery/read_gbq_table.py index f8a379aee9..465fa08187 100644 --- a/bigframes/session/_io/bigquery/read_gbq_table.py +++ b/bigframes/session/_io/bigquery/read_gbq_table.py @@ -28,6 +28,7 @@ import google.cloud.bigquery as bigquery import google.cloud.bigquery.table +import bigframes.core import bigframes.core.events import bigframes.exceptions as bfe import bigframes.session._io.bigquery @@ -37,18 +38,79 @@ import bigframes.session +def _convert_information_schema_table_id_to_table_reference( + table_id: str, + default_project: Optional[str], +) -> bigquery.TableReference: + """Squeeze an INFORMATION_SCHEMA reference into a TableReference. + This is kind-of a hack. INFORMATION_SCHEMA is a view that isn't available + via the tables.get REST API. + """ + parts = table_id.split(".") + parts_casefold = [part.casefold() for part in parts] + dataset_index = parts_casefold.index("INFORMATION_SCHEMA".casefold()) + + if dataset_index == 0: + project = default_project + else: + project = ".".join(parts[:dataset_index]) + + if project is None: + message = ( + "Could not determine project ID. " + "Please provide a project or region in your INFORMATION_SCHEMA table ID, " + "For example, 'region-REGION_NAME.INFORMATION_SCHEMA.JOBS'." + ) + raise ValueError(message) + + dataset = "INFORMATION_SCHEMA" + table_id_short = ".".join(parts[dataset_index + 1 :]) + return bigquery.TableReference( + bigquery.DatasetReference(project, dataset), + table_id_short, + ) + + +def get_information_schema_metadata( + bqclient: bigquery.Client, + table_id: str, + default_project: Optional[str], +) -> bigquery.Table: + job_config = bigquery.QueryJobConfig(dry_run=True) + job = bqclient.query( + f"SELECT * FROM `{table_id}`", + job_config=job_config, + ) + table_ref = _convert_information_schema_table_id_to_table_reference( + table_id=table_id, + default_project=default_project, + ) + table = bigquery.Table.from_api_repr( + { + "tableReference": table_ref.to_api_repr(), + "location": job.location, + # Prevent ourselves from trying to read the table with the BQ + # Storage API. + "type": "VIEW", + } + ) + table.schema = job.schema + return table + + def get_table_metadata( bqclient: bigquery.Client, - table_ref: google.cloud.bigquery.table.TableReference, - bq_time: datetime.datetime, *, - cache: Dict[bigquery.TableReference, Tuple[datetime.datetime, bigquery.Table]], + table_id: str, + default_project: Optional[str], + bq_time: datetime.datetime, + cache: Dict[str, Tuple[datetime.datetime, bigquery.Table]], use_cache: bool = True, publisher: bigframes.core.events.Publisher, ) -> Tuple[datetime.datetime, google.cloud.bigquery.table.Table]: """Get the table metadata, either from cache or via REST API.""" - cached_table = cache.get(table_ref) + cached_table = cache.get(table_id) if use_cache and cached_table is not None: snapshot_timestamp, table = cached_table @@ -90,7 +152,16 @@ def get_table_metadata( return cached_table - table = bqclient.get_table(table_ref) + if is_information_schema(table_id): + table = get_information_schema_metadata( + bqclient=bqclient, table_id=table_id, default_project=default_project + ) + else: + table_ref = google.cloud.bigquery.table.TableReference.from_string( + table_id, default_project=default_project + ) + table = bqclient.get_table(table_ref) + # local time will lag a little bit do to network latency # make sure it is at least table creation time. # This is relevant if the table was created immediately before loading it here. @@ -98,10 +169,21 @@ def get_table_metadata( bq_time = table.created cached_table = (bq_time, table) - cache[table_ref] = cached_table + cache[table_id] = cached_table return cached_table +def is_information_schema(table_id: str): + table_id_casefold = table_id.casefold() + # Include the "."s to ensure we don't have false positives for some user + # defined dataset like MY_INFORMATION_SCHEMA or tables called + # INFORMATION_SCHEMA. + return ( + ".INFORMATION_SCHEMA.".casefold() in table_id_casefold + or table_id_casefold.startswith("INFORMATION_SCHEMA.".casefold()) + ) + + def is_time_travel_eligible( bqclient: bigquery.Client, table: google.cloud.bigquery.table.Table, @@ -168,6 +250,8 @@ def is_time_travel_eligible( msg, category=bfe.TimeTravelDisabledWarning, stacklevel=stacklevel ) return False + elif table.table_type == "VIEW": + return False # table might support time travel, lets do a dry-run query with time travel if should_dry_run: diff --git a/bigframes/session/loader.py b/bigframes/session/loader.py index 940fdc1352..2d5dec13e6 100644 --- a/bigframes/session/loader.py +++ b/bigframes/session/loader.py @@ -47,6 +47,8 @@ import pandas import pyarrow as pa +import bigframes._tools +import bigframes._tools.strings from bigframes.core import guid, identifiers, local_data, nodes, ordering, utils import bigframes.core as core import bigframes.core.blocks as blocks @@ -272,9 +274,7 @@ def __init__( self._default_index_type = default_index_type self._scan_index_uniqueness = scan_index_uniqueness self._force_total_order = force_total_order - self._df_snapshot: Dict[ - bigquery.TableReference, Tuple[datetime.datetime, bigquery.Table] - ] = {} + self._df_snapshot: Dict[str, Tuple[datetime.datetime, bigquery.Table]] = {} self._metrics = metrics self._publisher = publisher # Unfortunate circular reference, but need to pass reference when constructing objects @@ -629,10 +629,6 @@ def read_gbq_table( _check_duplicates("columns", columns) - table_ref = google.cloud.bigquery.table.TableReference.from_string( - table_id, default_project=self._bqclient.project - ) - columns = list(columns) include_all_columns = columns is None or len(columns) == 0 filters = typing.cast(list, list(filters)) @@ -643,7 +639,8 @@ def read_gbq_table( time_travel_timestamp, table = bf_read_gbq_table.get_table_metadata( self._bqclient, - table_ref=table_ref, + table_id=table_id, + default_project=self._bqclient.project, bq_time=self._clock.get_time(), cache=self._df_snapshot, use_cache=use_cache, @@ -706,18 +703,23 @@ def read_gbq_table( # Optionally, execute the query # ----------------------------- - # max_results introduces non-determinism and limits the cost on - # clustered tables, so fallback to a query. We do this here so that - # the index is consistent with tables that have primary keys, even - # when max_results is set. - if max_results is not None: + if ( + # max_results introduces non-determinism and limits the cost on + # clustered tables, so fallback to a query. We do this here so that + # the index is consistent with tables that have primary keys, even + # when max_results is set. + max_results is not None + # Views such as INFORMATION_SCHEMA can introduce non-determinism. + # They can update frequently and don't support time travel. + or bf_read_gbq_table.is_information_schema(table_id) + ): # TODO(b/338111344): If we are running a query anyway, we might as # well generate ROW_NUMBER() at the same time. all_columns: Iterable[str] = ( itertools.chain(index_cols, columns) if columns else () ) query = bf_io_bigquery.to_query( - table_id, + f"{table.project}.{table.dataset_id}.{table.table_id}", columns=all_columns, sql_predicate=bf_io_bigquery.compile_filters(filters) if filters diff --git a/bigframes/session/read_api_execution.py b/bigframes/session/read_api_execution.py index 2530a1dc8d..136c279c08 100644 --- a/bigframes/session/read_api_execution.py +++ b/bigframes/session/read_api_execution.py @@ -46,6 +46,9 @@ def execute( if node.explicitly_ordered and ordered: return None + if not node.source.table.is_physically_stored: + return None + if limit is not None: if peek is None or limit < peek: peek = limit diff --git a/tests/system/small/pandas/test_read_gbq_information_schema.py b/tests/system/small/pandas/test_read_gbq_information_schema.py new file mode 100644 index 0000000000..32e2dc4712 --- /dev/null +++ b/tests/system/small/pandas/test_read_gbq_information_schema.py @@ -0,0 +1,50 @@ +# 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 pytest + + +@pytest.mark.parametrize("include_project", [True, False]) +@pytest.mark.parametrize( + "view_id", + [ + # https://cloud.google.com/bigquery/docs/information-schema-intro + "region-US.INFORMATION_SCHEMA.SESSIONS_BY_USER", + "region-US.INFORMATION_SCHEMA.SCHEMATA", + ], +) +def test_read_gbq_jobs_by_user_returns_schema( + unordered_session, view_id: str, include_project: bool +): + if include_project: + table_id = unordered_session.bqclient.project + "." + view_id + else: + table_id = view_id + + df = unordered_session.read_gbq(table_id, max_results=10) + assert df.dtypes is not None + + +def test_read_gbq_schemata_can_be_peeked(unordered_session): + df = unordered_session.read_gbq("region-US.INFORMATION_SCHEMA.SCHEMATA") + result = df.peek() + assert result is not None + + +def test_read_gbq_schemata_four_parts_can_be_peeked(unordered_session): + df = unordered_session.read_gbq( + f"{unordered_session.bqclient.project}.region-US.INFORMATION_SCHEMA.SCHEMATA" + ) + result = df.peek() + assert result is not None diff --git a/tests/unit/session/test_session.py b/tests/unit/session/test_session.py index d05957b941..f003398706 100644 --- a/tests/unit/session/test_session.py +++ b/tests/unit/session/test_session.py @@ -242,7 +242,7 @@ def test_read_gbq_cached_table(): table._properties["numRows"] = "1000000000" table._properties["location"] = session._location table._properties["type"] = "TABLE" - session._loader._df_snapshot[table_ref] = ( + session._loader._df_snapshot[str(table_ref)] = ( datetime.datetime(1999, 1, 2, 3, 4, 5, 678901, tzinfo=datetime.timezone.utc), table, ) @@ -273,7 +273,7 @@ def test_read_gbq_cached_table_doesnt_warn_for_anonymous_tables_and_doesnt_inclu table._properties["numRows"] = "1000000000" table._properties["location"] = session._location table._properties["type"] = "TABLE" - session._loader._df_snapshot[table_ref] = ( + session._loader._df_snapshot[str(table_ref)] = ( datetime.datetime(1999, 1, 2, 3, 4, 5, 678901, tzinfo=datetime.timezone.utc), table, ) From 316ba9f557d792117d5a7845d7567498f78dd513 Mon Sep 17 00:00:00 2001 From: ccarpentiere Date: Fri, 31 Oct 2025 07:04:34 -0700 Subject: [PATCH 13/21] docs: update bq_dataframes_llm_output_schema.ipynb (#2004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated output schema tutorial notebook * Cleaned up language and structure. * Added set up and clean up sections. * Additional small spelling fixes for bq_dataframes_llm_output_schema.ipynb. * Removed Vertex AI import link * Remove placeholder project name that is breaking tests --------- Co-authored-by: Tim Sweña (Swast) --- .../bq_dataframes_llm_output_schema.ipynb | 175 ++++++++++++++++-- 1 file changed, 155 insertions(+), 20 deletions(-) diff --git a/notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb b/notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb index 70714c823c..5399363e34 100644 --- a/notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb +++ b/notebooks/generative_ai/bq_dataframes_llm_output_schema.ipynb @@ -25,7 +25,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# BigFrames LLM Output Schema\n", + "# Format LLM output using an output schema\n", "\n", "\n", "\n", @@ -43,7 +43,7 @@ " \n", "
\n", " \n", " \"BQ\n", - " Open in BQ Studio\n", + " Open in BigQuery Studio\n", " \n", "
\n" @@ -53,26 +53,124 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This Notebook introduces BigFrames LLM with output schema to generate structured output dataframes." + "This notebook shows you how to create structured LLM output by specifying an output schema when generating predictions with a Gemini model." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Setup" + "## Costs\n", + "\n", + "This tutorial uses billable components of Google Cloud:\n", + "\n", + "* BigQuery (compute)\n", + "* BigQuery ML\n", + "* Generative AI support on Vertex AI\n", + "\n", + "Learn about [BigQuery compute pricing](https://cloud.google.com/bigquery/pricing#analysis_pricing_models), [Generative AI support on Vertex AI pricing](https://cloud.google.com/vertex-ai/generative-ai/pricing),\n", + "and [BigQuery ML pricing](https://cloud.google.com/bigquery/pricing#section-11),\n", + "and use the [Pricing Calculator](https://cloud.google.com/products/calculator/)\n", + "to generate a cost estimate based on your projected usage." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Before you begin\n", + "\n", + "Complete the tasks in this section to set up your environment." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up your Google Cloud project\n", + "\n", + "**The following steps are required, regardless of your notebook environment.**\n", + "\n", + "1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 credit towards your compute/storage costs.\n", + "\n", + "2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n", + "\n", + "3. [Click here](https://console.cloud.google.com/flows/enableapi?apiid=bigquery.googleapis.com,bigqueryconnection.googleapis.com,aiplatform.googleapis.com) to enable the following APIs:\n", + "\n", + " * BigQuery API\n", + " * BigQuery Connection API\n", + " * Vertex AI API\n", + "\n", + "4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Authenticate your Google Cloud account\n", + "\n", + "Depending on your Jupyter environment, you might have to manually authenticate. Follow the relevant instructions below.\n", + "\n", + "**BigQuery Studio** or **Vertex AI Workbench**\n", + "\n", + "Do nothing, you are already authenticated.\n", + "\n", + "**Local JupyterLab instance**\n", + "\n", + "Uncomment and run the following cell:" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "PROJECT = \"bigframes-dev\" # replace with your project\n", + "# ! gcloud auth login" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Colab**\n", "\n", + "Uncomment and run the following cell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# from google.colab import auth\n", + "# auth.authenticate_user()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up your project" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set your project and import necessary modules. If you don't know your project ID, see [Locate the project ID](https://support.google.com/googleapi/answer/7014113)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT = \"\" # replace with your project\n", "import bigframes\n", - "# Setup project\n", "bigframes.options.bigquery.project = PROJECT\n", "bigframes.options.display.progress_bar = None\n", "\n", @@ -84,8 +182,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1. Create a BigFrames DataFrame and a Gemini model\n", - "Starting from creating a simple dataframe of several cities and a Gemini model in BigFrames" + "## Create a DataFrame and a Gemini model\n", + "Create a simple [DataFrame](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.dataframe.DataFrame) of several cities:" ] }, { @@ -162,6 +260,13 @@ "df" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Connect to a Gemini model using the [`GeminiTextGenerator` class](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator):" + ] + }, { "cell_type": "code", "execution_count": 4, @@ -186,8 +291,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 2. Generate structured output data\n", - "Before, llm models can only generate text output. Saying if you want to know whether the city is a US city, for example:" + "## Generate structured output data\n", + "Previously, LLMs could only generate text output. For example, you could generate output that identifies whether a given city is a US city:" ] }, { @@ -273,9 +378,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The outputs are text results that human can read. But if want the output data to be more useful for analysis, it is better to transfer to structured data like boolean, int or float values. Usually the process wasn't easy.\n", + "The output is text that a human can read. However, if you want the output to be more useful for analysis, it is better to format the output as structured data. This is especially true when you want to have Boolean, integer, or float values to work with instead of string values. Previously, formatting the output in this way wasn't easy.\n", "\n", - "Now you can get structured output out-of-the-box by specifying the output_schema parameter in Gemini model predict method. In below example, the outputs are only boolean values." + "Now, you can get structured output out-of-the-box by specifying the `output_schema` parameter when calling the Gemini model's [`predict` method](https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.ml.llm.GeminiTextGenerator#bigframes_ml_llm_GeminiTextGenerator_predict). In the following example, the model output is formatted as Boolean values:" ] }, { @@ -361,7 +466,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can also get float or int values, for example, to get populations in millions:" + "You can also format model output as float or integer values. In the following example, the model output is formatted as float values to show the city's population in millions:" ] }, { @@ -447,7 +552,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "And yearly rainy days:" + "In the following example, the model output is formatted as integer values to show the count of the city's rainy days:" ] }, { @@ -533,10 +638,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3. Generate all types of data in one prediction\n", - "You can get the different output columns and types in one prediction. \n", + "### Format output as multiple data types in one prediction\n", + "Within a single prediction, you can generate multiple columns of output that use different data types. \n", "\n", - "Note it doesn't require dedicated prompts, as long as the output column names are informative to the model." + "The input doesn't have to be dedicated prompts as long as the output column names are informative to the model." ] }, { @@ -630,14 +735,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 4. Generate composite data types" + "### Format output as a composite data type" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Composite datatypes like array and struct can also be generated. Here the example generates a places_to_visit column as array of strings and a gps_coordinates as struct of floats. Along with previous fields, all in one prediction." + "You can generate composite data types like arrays and structs. The following example generates a `places_to_visit` column as an array of strings and a `gps_coordinates` column as a struct of floats:" ] }, { @@ -744,6 +849,36 @@ "result = gemini.predict(df, prompt=[df[\"city\"]], output_schema={\"is_US_city\": \"bool\", \"population_in_millions\": \"float64\", \"rainy_days_per_year\": \"int64\", \"places_to_visit\": \"array\", \"gps_coordinates\": \"struct\"})\n", "result[[\"city\", \"is_US_city\", \"population_in_millions\", \"rainy_days_per_year\", \"places_to_visit\", \"gps_coordinates\"]]" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Clean up\n", + "\n", + "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n", + "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n", + "\n", + "Otherwise, run the following cell to delete the temporary cloud artifacts created during the BigFrames session:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bpd.close_session()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "Learn more about BigQuery DataFrames in the [documentation](https://cloud.google.com/python/docs/reference/bigframes/latest) and find more sample notebooks in the [GitHub repo](https://github.com/googleapis/python-bigquery-dataframes/tree/main/notebooks)." + ] } ], "metadata": { From ecee2bc6ada0bc968fc56ed7194dc8c043547e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a=20=28Swast=29?= Date: Fri, 31 Oct 2025 12:13:32 -0500 Subject: [PATCH 14/21] feat: add bigframes.bigquery.st_simplify (#2210) * feat: add bigframes.bigquery.st_simplify * fix mypy errors * add docstring * fix mypy again * fix typos --- bigframes/bigquery/__init__.py | 2 ++ bigframes/bigquery/_operations/geo.py | 20 +++++++++++++ .../ibis_compiler/operations/geo_ops.py | 14 ++++++++++ bigframes/geopandas/geoseries.py | 5 ++++ bigframes/operations/__init__.py | 2 ++ bigframes/operations/geo_ops.py | 9 ++++++ specs/2025-08-04-geoseries-scalars.md | 15 +++++++--- tests/system/small/bigquery/test_geo.py | 9 ++++++ .../bigframes_vendored/geopandas/geoseries.py | 28 +++++++++++++++++++ 9 files changed, 100 insertions(+), 4 deletions(-) diff --git a/bigframes/bigquery/__init__.py b/bigframes/bigquery/__init__.py index e8c7a524d9..c599a4b543 100644 --- a/bigframes/bigquery/__init__.py +++ b/bigframes/bigquery/__init__.py @@ -40,6 +40,7 @@ st_intersection, st_isclosed, st_length, + st_simplify, ) from bigframes.bigquery._operations.json import ( json_extract, @@ -80,6 +81,7 @@ st_intersection, st_isclosed, st_length, + st_simplify, # json ops json_extract, json_extract_array, diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py index 254d2ae13f..6b7e5d88a2 100644 --- a/bigframes/bigquery/_operations/geo.py +++ b/bigframes/bigquery/_operations/geo.py @@ -675,3 +675,23 @@ def st_length( series = series._apply_unary_op(ops.GeoStLengthOp(use_spheroid=use_spheroid)) series.name = None return series + + +def st_simplify( + geography: "bigframes.series.Series", + tolerance_meters: float, +) -> "bigframes.series.Series": + """Returns a simplified version of the input geography. + + Args: + geography (bigframes.series.Series): + A Series containing GEOGRAPHY data. + tolerance_meters (float): + A float64 value indicating the tolerance in meters. + + Returns: + a Series containing the simplified GEOGRAPHY data. + """ + return geography._apply_unary_op( + ops.GeoStSimplifyOp(tolerance_meters=tolerance_meters) + ) diff --git a/bigframes/core/compile/ibis_compiler/operations/geo_ops.py b/bigframes/core/compile/ibis_compiler/operations/geo_ops.py index f9155fed5a..2f06c76768 100644 --- a/bigframes/core/compile/ibis_compiler/operations/geo_ops.py +++ b/bigframes/core/compile/ibis_compiler/operations/geo_ops.py @@ -101,6 +101,12 @@ def geo_st_isclosed_op_impl(x: ibis_types.Value): return st_isclosed(x) +@register_unary_op(ops.GeoStSimplifyOp, pass_op=True) +def st_simplify_op_impl(x: ibis_types.Value, op: ops.GeoStSimplifyOp): + x = cast(ibis_types.GeoSpatialValue, x) + return st_simplify(x, op.tolerance_meters) + + @register_unary_op(ops.geo_x_op) def geo_x_op_impl(x: ibis_types.Value): return cast(ibis_types.GeoSpatialValue, x).x() @@ -157,3 +163,11 @@ def st_length(geog: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.fl @ibis_udf.scalar.builtin def st_isclosed(a: ibis_dtypes.geography) -> ibis_dtypes.boolean: # type: ignore """Checks if a geography is closed.""" + + +@ibis_udf.scalar.builtin +def st_simplify( + geography: ibis_dtypes.geography, # type: ignore + tolerance_meters: ibis_dtypes.float, # type: ignore +) -> ibis_dtypes.geography: # type: ignore + ... diff --git a/bigframes/geopandas/geoseries.py b/bigframes/geopandas/geoseries.py index f3558e4b34..660f1939a9 100644 --- a/bigframes/geopandas/geoseries.py +++ b/bigframes/geopandas/geoseries.py @@ -123,3 +123,8 @@ def distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # t def intersection(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore return self._apply_binary_op(other, ops.geo_st_intersection_op) + + def simplify(self, tolerance, preserve_topology=True): + raise NotImplementedError( + f"GeoSeries.simplify is not supported. Use bigframes.bigquery.st_simplify(series, tolerance_meters), instead. {constants.FEEDBACK_LINK}" + ) diff --git a/bigframes/operations/__init__.py b/bigframes/operations/__init__.py index 24a7d6542f..cb03943ada 100644 --- a/bigframes/operations/__init__.py +++ b/bigframes/operations/__init__.py @@ -121,6 +121,7 @@ GeoStBufferOp, GeoStDistanceOp, GeoStLengthOp, + GeoStSimplifyOp, ) from bigframes.operations.json_ops import ( JSONExtract, @@ -416,6 +417,7 @@ "geo_st_isclosed_op", "GeoStBufferOp", "GeoStLengthOp", + "GeoStSimplifyOp", "geo_x_op", "geo_y_op", "GeoStDistanceOp", diff --git a/bigframes/operations/geo_ops.py b/bigframes/operations/geo_ops.py index 3b7754a47a..86e913d543 100644 --- a/bigframes/operations/geo_ops.py +++ b/bigframes/operations/geo_ops.py @@ -133,3 +133,12 @@ class GeoStLengthOp(base_ops.UnaryOp): def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: return dtypes.FLOAT_DTYPE + + +@dataclasses.dataclass(frozen=True) +class GeoStSimplifyOp(base_ops.UnaryOp): + name = "st_simplify" + tolerance_meters: float + + def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: + return dtypes.GEO_DTYPE diff --git a/specs/2025-08-04-geoseries-scalars.md b/specs/2025-08-04-geoseries-scalars.md index 66ed77d0dd..e7bc6c61e1 100644 --- a/specs/2025-08-04-geoseries-scalars.md +++ b/specs/2025-08-04-geoseries-scalars.md @@ -261,7 +261,10 @@ Raster functions: Functions for analyzing geospatial rasters using geographies. ### Implementing a new scalar geography operation - [ ] **Define the operation dataclass:** - - [ ] In `bigframes/operations/geo_ops.py`, create a new dataclass inheriting from `base_ops.UnaryOp` or `base_ops.BinaryOp`. + - [ ] In `bigframes/operations/geo_ops.py`, create a new dataclass + inheriting from `base_ops.UnaryOp` or `base_ops.BinaryOp`. Note that + BinaryOp is for methods that take two **columns**. Any literal values can + be passed as parameters to a UnaryOp. - [ ] Define the `name` of the operation and any parameters it requires. - [ ] Implement the `output_type` method to specify the data type of the result. - [ ] **Export the new operation:** @@ -283,13 +286,17 @@ Raster functions: Functions for analyzing geospatial rasters using geographies. - [ ] Add a comprehensive docstring with examples. - [ ] In `bigframes/bigquery/__init__.py`, import your new user-facing function and add it to the `__all__` list. - [ ] For a `GeoSeries` property or method: - - [ ] In `bigframes/geopandas/geoseries.py`, create the property or method. + - [ ] In `bigframes/geopandas/geoseries.py`, create the property or + method. Omit the docstring. - [ ] If the operation is not possible to be supported, such as if the geopandas method returns values in units corresponding to the coordinate system rather than meters that BigQuery uses, raise a - `NotImplementedError` with a helpful message. + `NotImplementedError` with a helpful message. Likewise, if a + required parameter takes a value in terms of the coordinate + system, but BigQuery uses meters, raise a `NotImplementedError`. - [ ] Otherwise, call `series._apply_unary_op` or `series._apply_binary_op`, passing the operation dataclass. - - [ ] Add a comprehensive docstring with examples. + - [ ] Add a comprehensive docstring with examples to the superclass in + `third_party/bigframes_vendored/geopandas/geoseries.py`. - [ ] **Add Tests:** - [ ] Add system tests in `tests/system/small/bigquery/test_geo.py` or `tests/system/small/geopandas/test_geoseries.py` to verify the end-to-end functionality. Test various inputs, including edge cases and `NULL` values. - [ ] If you are overriding a pandas or GeoPandas property and raising `NotImplementedError`, add a unit test to ensure the correct error is raised. diff --git a/tests/system/small/bigquery/test_geo.py b/tests/system/small/bigquery/test_geo.py index c89ca59aca..28db58c711 100644 --- a/tests/system/small/bigquery/test_geo.py +++ b/tests/system/small/bigquery/test_geo.py @@ -480,3 +480,12 @@ def test_st_buffer(session): result = bbq.st_buffer(geoseries, 1000).to_pandas() assert result.iloc[0].geom_type == "Polygon" assert result.iloc[1].geom_type == "Polygon" + + +def test_st_simplify(session): + geoseries = bigframes.geopandas.GeoSeries( + [LineString([(0, 0), (1, 1), (2, 0)])], session=session + ) + result = bbq.st_simplify(geoseries, 100000).to_pandas() + assert len(result.index) == 1 + assert result.isna().sum() == 0 diff --git a/third_party/bigframes_vendored/geopandas/geoseries.py b/third_party/bigframes_vendored/geopandas/geoseries.py index 20587b4d57..642cf2fc90 100644 --- a/third_party/bigframes_vendored/geopandas/geoseries.py +++ b/third_party/bigframes_vendored/geopandas/geoseries.py @@ -496,3 +496,31 @@ def is_closed(self: GeoSeries) -> bigframes.series.Series: ``bigframes.bigquery.st_isclosed(series)``, instead. """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + + def simplify(self, tolerance: float, preserve_topology: bool = True) -> bigframes.series.Series: # type: ignore + """[Not Implemented] Use ``bigframes.bigquery.st_simplify(series, tolerance_meters)``, + instead to set the tolerance in meters. + + In GeoPandas, this returns a GeoSeries containing a simplified + representation of each geometry. + + Args: + tolerance (float): + All parts of a simplified geometry will be no more than + tolerance distance from the original. It has the same units as + the coordinate reference system of the GeoSeries. For example, + using tolerance=100 in a projected CRS with meters as units + means a distance of 100 meters in reality. + preserve_topology (bool): + Default True. False uses a quicker algorithm, but may produce + self-intersecting or otherwise invalid geometries. + + Returns: + bigframes.geopandas.GeoSeries: + Series of simplified geometries. + + Raises: + NotImplementedError: + GeoSeries.simplify is not supported. Use bigframes.bigquery.st_simplify(series, tolerance_meters), instead. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) From b23cf83d37e518ef45d8c919a7b6038beb889d50 Mon Sep 17 00:00:00 2001 From: TrevorBergeron Date: Fri, 31 Oct 2025 10:44:48 -0700 Subject: [PATCH 15/21] refactor: ExecuteResult is reusable, sampleable (#2159) --- bigframes/core/array_value.py | 18 +- bigframes/core/blocks.py | 75 +++--- bigframes/core/bq_data.py | 221 ++++++++++++++++++ .../compile/ibis_compiler/ibis_compiler.py | 6 +- bigframes/core/indexes/base.py | 32 ++- bigframes/core/local_data.py | 47 ++-- bigframes/core/nodes.py | 86 ++----- bigframes/core/pyarrow_utils.py | 7 + bigframes/core/rewrite/fold_row_count.py | 6 +- bigframes/core/schema.py | 25 +- bigframes/dtypes.py | 18 ++ bigframes/session/bq_caching_executor.py | 114 ++++----- bigframes/session/direct_gbq_execution.py | 13 +- bigframes/session/executor.py | 201 ++++++++++++++-- bigframes/session/loader.py | 41 ++-- bigframes/session/local_scan_executor.py | 9 +- bigframes/session/polars_executor.py | 22 +- bigframes/session/read_api_execution.py | 79 ++----- bigframes/testing/engine_utils.py | 4 +- bigframes/testing/polars_session.py | 8 +- tests/system/small/engines/test_read_local.py | 12 +- tests/system/small/test_anywidget.py | 31 ++- tests/system/small/test_session.py | 4 +- tests/unit/core/rewrite/conftest.py | 2 - tests/unit/core/rewrite/test_identifiers.py | 2 - .../unit/session/test_local_scan_executor.py | 6 +- tests/unit/test_planner.py | 1 - 27 files changed, 721 insertions(+), 369 deletions(-) create mode 100644 bigframes/core/bq_data.py diff --git a/bigframes/core/array_value.py b/bigframes/core/array_value.py index a89d224ad1..e2948cdd05 100644 --- a/bigframes/core/array_value.py +++ b/bigframes/core/array_value.py @@ -23,7 +23,7 @@ import pandas import pyarrow as pa -from bigframes.core import agg_expressions +from bigframes.core import agg_expressions, bq_data import bigframes.core.expression as ex import bigframes.core.guid import bigframes.core.identifiers as ids @@ -63,7 +63,7 @@ def from_pyarrow(cls, arrow_table: pa.Table, session: Session): def from_managed(cls, source: local_data.ManagedArrowTable, session: Session): scan_list = nodes.ScanList( tuple( - nodes.ScanItem(ids.ColumnId(item.column), item.dtype, item.column) + nodes.ScanItem(ids.ColumnId(item.column), item.column) for item in source.schema.items ) ) @@ -88,9 +88,9 @@ def from_range(cls, start, end, step): def from_table( cls, table: google.cloud.bigquery.Table, - schema: schemata.ArraySchema, session: Session, *, + columns: Optional[Sequence[str]] = None, predicate: Optional[str] = None, at_time: Optional[datetime.datetime] = None, primary_key: Sequence[str] = (), @@ -100,7 +100,7 @@ def from_table( if offsets_col and primary_key: raise ValueError("must set at most one of 'offests', 'primary_key'") # define data source only for needed columns, this makes row-hashing cheaper - table_def = nodes.GbqTable.from_table(table, columns=schema.names) + table_def = bq_data.GbqTable.from_table(table, columns=columns or ()) # create ordering from info ordering = None @@ -111,15 +111,17 @@ def from_table( [ids.ColumnId(key_part) for key_part in primary_key] ) + bf_schema = schemata.ArraySchema.from_bq_table(table, columns=columns) # Scan all columns by default, we define this list as it can be pruned while preserving source_def scan_list = nodes.ScanList( tuple( - nodes.ScanItem(ids.ColumnId(item.column), item.dtype, item.column) - for item in schema.items + nodes.ScanItem(ids.ColumnId(item.column), item.column) + for item in bf_schema.items ) ) - source_def = nodes.BigqueryDataSource( + source_def = bq_data.BigqueryDataSource( table=table_def, + schema=bf_schema, at_time=at_time, sql_predicate=predicate, ordering=ordering, @@ -130,7 +132,7 @@ def from_table( @classmethod def from_bq_data_source( cls, - source: nodes.BigqueryDataSource, + source: bq_data.BigqueryDataSource, scan_list: nodes.ScanList, session: Session, ): diff --git a/bigframes/core/blocks.py b/bigframes/core/blocks.py index 1900b7208a..41986ce5df 100644 --- a/bigframes/core/blocks.py +++ b/bigframes/core/blocks.py @@ -37,7 +37,6 @@ Optional, Sequence, Tuple, - TYPE_CHECKING, Union, ) import warnings @@ -70,9 +69,6 @@ from bigframes.session import dry_runs, execution_spec from bigframes.session import executor as executors -if TYPE_CHECKING: - from bigframes.session.executor import ExecuteResult - # Type constraint for wherever column labels are used Label = typing.Hashable @@ -98,7 +94,6 @@ LevelsType = typing.Union[LevelType, typing.Sequence[LevelType]] -@dataclasses.dataclass class PandasBatches(Iterator[pd.DataFrame]): """Interface for mutable objects with state represented by a block value object.""" @@ -271,10 +266,14 @@ def shape(self) -> typing.Tuple[int, int]: except Exception: pass - row_count = self.session._executor.execute( - self.expr.row_count(), - execution_spec.ExecutionSpec(promise_under_10gb=True, ordered=False), - ).to_py_scalar() + row_count = ( + self.session._executor.execute( + self.expr.row_count(), + execution_spec.ExecutionSpec(promise_under_10gb=True, ordered=False), + ) + .batches() + .to_py_scalar() + ) return (row_count, len(self.value_columns)) @property @@ -584,7 +583,7 @@ def to_arrow( ordered=ordered, ), ) - pa_table = execute_result.to_arrow_table() + pa_table = execute_result.batches().to_arrow_table() pa_index_labels = [] for index_level, index_label in enumerate(self._index_labels): @@ -636,17 +635,13 @@ def to_pandas( max_download_size, sampling_method, random_state ) - ex_result = self._materialize_local( + return self._materialize_local( materialize_options=MaterializationOptions( downsampling=sampling, allow_large_results=allow_large_results, ordered=ordered, ) ) - df = ex_result.to_pandas() - df = self._copy_index_to_pandas(df) - df.set_axis(self.column_labels, axis=1, copy=False) - return df, ex_result.query_job def _get_sampling_option( self, @@ -683,7 +678,7 @@ def try_peek( self.expr, execution_spec.ExecutionSpec(promise_under_10gb=under_10gb, peek=n), ) - df = result.to_pandas() + df = result.batches().to_pandas() return self._copy_index_to_pandas(df) else: return None @@ -704,13 +699,14 @@ def to_pandas_batches( if (allow_large_results is not None) else not bigframes.options._allow_large_results ) - execute_result = self.session._executor.execute( + execution_result = self.session._executor.execute( self.expr, execution_spec.ExecutionSpec( promise_under_10gb=under_10gb, ordered=True, ), ) + result_batches = execution_result.batches() # To reduce the number of edge cases to consider when working with the # results of this, always return at least one DataFrame. See: @@ -724,19 +720,21 @@ def to_pandas_batches( dfs = map( lambda a: a[0], itertools.zip_longest( - execute_result.to_pandas_batches(page_size, max_results), + result_batches.to_pandas_batches(page_size, max_results), [0], fillvalue=empty_val, ), ) dfs = iter(map(self._copy_index_to_pandas, dfs)) - total_rows = execute_result.total_rows + total_rows = result_batches.approx_total_rows if (total_rows is not None) and (max_results is not None): total_rows = min(total_rows, max_results) return PandasBatches( - dfs, total_rows, total_bytes_processed=execute_result.total_bytes_processed + dfs, + total_rows, + total_bytes_processed=execution_result.total_bytes_processed, ) def _copy_index_to_pandas(self, df: pd.DataFrame) -> pd.DataFrame: @@ -754,7 +752,7 @@ def _copy_index_to_pandas(self, df: pd.DataFrame) -> pd.DataFrame: def _materialize_local( self, materialize_options: MaterializationOptions = MaterializationOptions() - ) -> ExecuteResult: + ) -> tuple[pd.DataFrame, Optional[bigquery.QueryJob]]: """Run query and download results as a pandas DataFrame. Return the total number of results as well.""" # TODO(swast): Allow for dry run and timeout. under_10gb = ( @@ -769,9 +767,11 @@ def _materialize_local( ordered=materialize_options.ordered, ), ) + result_batches = execute_result.batches() + sample_config = materialize_options.downsampling - if execute_result.total_bytes is not None: - table_mb = execute_result.total_bytes / _BYTES_TO_MEGABYTES + if result_batches.approx_total_bytes is not None: + table_mb = result_batches.approx_total_bytes / _BYTES_TO_MEGABYTES max_download_size = sample_config.max_download_size fraction = ( max_download_size / table_mb @@ -792,7 +792,7 @@ def _materialize_local( # TODO: Maybe materialize before downsampling # Some downsampling methods - if fraction < 1 and (execute_result.total_rows is not None): + if fraction < 1 and (result_batches.approx_total_rows is not None): if not sample_config.enable_downsampling: raise RuntimeError( f"The data size ({table_mb:.2f} MB) exceeds the maximum download limit of " @@ -811,7 +811,7 @@ def _materialize_local( "the downloading limit." ) warnings.warn(msg, category=UserWarning) - total_rows = execute_result.total_rows + total_rows = result_batches.approx_total_rows # Remove downsampling config from subsequent invocations, as otherwise could result in many # iterations if downsampling undershoots return self._downsample( @@ -823,7 +823,10 @@ def _materialize_local( MaterializationOptions(ordered=materialize_options.ordered) ) else: - return execute_result + df = result_batches.to_pandas() + df = self._copy_index_to_pandas(df) + df.set_axis(self.column_labels, axis=1, copy=False) + return df, execute_result.query_job def _downsample( self, total_rows: int, sampling_method: str, fraction: float, random_state @@ -1662,15 +1665,19 @@ def retrieve_repr_request_results( ordered=True, ), ) - row_count = self.session._executor.execute( - self.expr.row_count(), - execution_spec.ExecutionSpec( - promise_under_10gb=True, - ordered=False, - ), - ).to_py_scalar() + row_count = ( + self.session._executor.execute( + self.expr.row_count(), + execution_spec.ExecutionSpec( + promise_under_10gb=True, + ordered=False, + ), + ) + .batches() + .to_py_scalar() + ) - head_df = head_result.to_pandas() + head_df = head_result.batches().to_pandas() return self._copy_index_to_pandas(head_df), row_count, head_result.query_job def promote_offsets(self, label: Label = None) -> typing.Tuple[Block, str]: diff --git a/bigframes/core/bq_data.py b/bigframes/core/bq_data.py new file mode 100644 index 0000000000..c72de6ead6 --- /dev/null +++ b/bigframes/core/bq_data.py @@ -0,0 +1,221 @@ +# Copyright 2023 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. + +from __future__ import annotations + +import concurrent.futures +import dataclasses +import datetime +import functools +import os +import queue +import threading +import typing +from typing import Any, Iterator, Optional, Sequence, Tuple + +from google.cloud import bigquery_storage_v1 +import google.cloud.bigquery as bq +import google.cloud.bigquery_storage_v1.types as bq_storage_types +from google.protobuf import timestamp_pb2 +import pyarrow as pa + +from bigframes.core import pyarrow_utils +import bigframes.core.schema + +if typing.TYPE_CHECKING: + import bigframes.core.ordering as orderings + + +@dataclasses.dataclass(frozen=True) +class GbqTable: + project_id: str = dataclasses.field() + dataset_id: str = dataclasses.field() + table_id: str = dataclasses.field() + physical_schema: Tuple[bq.SchemaField, ...] = dataclasses.field() + is_physically_stored: bool = dataclasses.field() + cluster_cols: typing.Optional[Tuple[str, ...]] + + @staticmethod + def from_table(table: bq.Table, columns: Sequence[str] = ()) -> GbqTable: + # Subsetting fields with columns can reduce cost of row-hash default ordering + if columns: + schema = tuple(item for item in table.schema if item.name in columns) + else: + schema = tuple(table.schema) + return GbqTable( + project_id=table.project, + dataset_id=table.dataset_id, + table_id=table.table_id, + physical_schema=schema, + is_physically_stored=(table.table_type in ["TABLE", "MATERIALIZED_VIEW"]), + cluster_cols=None + if table.clustering_fields is None + else tuple(table.clustering_fields), + ) + + def get_table_ref(self) -> bq.TableReference: + return bq.TableReference( + bq.DatasetReference(self.project_id, self.dataset_id), self.table_id + ) + + @property + @functools.cache + def schema_by_id(self): + return {col.name: col for col in self.physical_schema} + + +@dataclasses.dataclass(frozen=True) +class BigqueryDataSource: + """ + Google BigQuery Data source. + + This should not be modified once defined, as all attributes contribute to the default ordering. + """ + + def __post_init__(self): + # not all columns need be in schema, eg so can exclude unsupported column types (eg RANGE) + assert set(field.name for field in self.table.physical_schema).issuperset( + self.schema.names + ) + + table: GbqTable + schema: bigframes.core.schema.ArraySchema + at_time: typing.Optional[datetime.datetime] = None + # Added for backwards compatibility, not validated + sql_predicate: typing.Optional[str] = None + ordering: typing.Optional[orderings.RowOrdering] = None + # Optimization field + n_rows: Optional[int] = None + + +_WORKER_TIME_INCREMENT = 0.05 + + +def _iter_stream( + stream_name: str, + storage_read_client: bigquery_storage_v1.BigQueryReadClient, + result_queue: queue.Queue, + stop_event: threading.Event, +): + reader = storage_read_client.read_rows(stream_name) + for page in reader.rows().pages: + while True: # Alternate between put attempt and checking stop event + try: + result_queue.put(page.to_arrow(), timeout=_WORKER_TIME_INCREMENT) + break + except queue.Full: + if stop_event.is_set(): + return + continue + + +def _iter_streams( + streams: Sequence[bq_storage_types.ReadStream], + storage_read_client: bigquery_storage_v1.BigQueryReadClient, +) -> Iterator[pa.RecordBatch]: + stop_event = threading.Event() + result_queue: queue.Queue = queue.Queue( + len(streams) + ) # each response is large, so small queue is appropriate + + in_progress: list[concurrent.futures.Future] = [] + with concurrent.futures.ThreadPoolExecutor(max_workers=len(streams)) as pool: + try: + for stream in streams: + in_progress.append( + pool.submit( + _iter_stream, + stream.name, + storage_read_client, + result_queue, + stop_event, + ) + ) + + while in_progress: + try: + yield result_queue.get(timeout=0.1) + except queue.Empty: + new_in_progress = [] + for future in in_progress: + if future.done(): + # Call to raise any exceptions + future.result() + else: + new_in_progress.append(future) + in_progress = new_in_progress + finally: + stop_event.set() + + +@dataclasses.dataclass +class ReadResult: + iter: Iterator[pa.RecordBatch] + approx_rows: int + approx_bytes: int + + +def get_arrow_batches( + data: BigqueryDataSource, + columns: Sequence[str], + storage_read_client: bigquery_storage_v1.BigQueryReadClient, + project_id: str, +) -> ReadResult: + table_mod_options = {} + read_options_dict: dict[str, Any] = {"selected_fields": list(columns)} + if data.sql_predicate: + read_options_dict["row_restriction"] = data.sql_predicate + read_options = bq_storage_types.ReadSession.TableReadOptions(**read_options_dict) + + if data.at_time: + snapshot_time = timestamp_pb2.Timestamp() + snapshot_time.FromDatetime(data.at_time) + table_mod_options["snapshot_time"] = snapshot_time + table_mods = bq_storage_types.ReadSession.TableModifiers(**table_mod_options) + + requested_session = bq_storage_types.stream.ReadSession( + table=data.table.get_table_ref().to_bqstorage(), + data_format=bq_storage_types.DataFormat.ARROW, + read_options=read_options, + table_modifiers=table_mods, + ) + if data.ordering is not None: + max_streams = 1 + else: + max_streams = os.cpu_count() or 8 + + # Single stream to maintain ordering + request = bq_storage_types.CreateReadSessionRequest( + parent=f"projects/{project_id}", + read_session=requested_session, + max_stream_count=max_streams, + ) + + session = storage_read_client.create_read_session(request=request) + + if not session.streams: + batches: Iterator[pa.RecordBatch] = iter([]) + else: + batches = _iter_streams(session.streams, storage_read_client) + + def process_batch(pa_batch): + return pyarrow_utils.cast_batch( + pa_batch.select(columns), data.schema.select(columns).to_pyarrow() + ) + + batches = map(process_batch, batches) + + return ReadResult( + batches, session.estimated_row_count, session.estimated_total_bytes_scanned + ) diff --git a/bigframes/core/compile/ibis_compiler/ibis_compiler.py b/bigframes/core/compile/ibis_compiler/ibis_compiler.py index ff0441ea22..0436e05559 100644 --- a/bigframes/core/compile/ibis_compiler/ibis_compiler.py +++ b/bigframes/core/compile/ibis_compiler/ibis_compiler.py @@ -24,7 +24,7 @@ import bigframes_vendored.ibis.expr.types as ibis_types from bigframes import dtypes, operations -from bigframes.core import expression, pyarrow_utils +from bigframes.core import bq_data, expression, pyarrow_utils import bigframes.core.compile.compiled as compiled import bigframes.core.compile.concat as concat_impl import bigframes.core.compile.configs as configs @@ -186,7 +186,7 @@ def compile_readtable(node: nodes.ReadTableNode, *args): # TODO(b/395912450): Remove workaround solution once b/374784249 got resolved. for scan_item in node.scan_list.items: if ( - scan_item.dtype == dtypes.JSON_DTYPE + node.source.schema.get_type(scan_item.source_id) == dtypes.JSON_DTYPE and ibis_table[scan_item.source_id].type() == ibis_dtypes.string ): json_column = scalar_op_registry.parse_json( @@ -204,7 +204,7 @@ def compile_readtable(node: nodes.ReadTableNode, *args): def _table_to_ibis( - source: nodes.BigqueryDataSource, + source: bq_data.BigqueryDataSource, scan_cols: typing.Sequence[str], ) -> ibis_types.Table: full_table_name = ( diff --git a/bigframes/core/indexes/base.py b/bigframes/core/indexes/base.py index 0e82b6dea7..41b32d99e4 100644 --- a/bigframes/core/indexes/base.py +++ b/bigframes/core/indexes/base.py @@ -297,9 +297,13 @@ def get_loc(self, key) -> typing.Union[int, slice, "bigframes.series.Series"]: count_agg = ex_types.UnaryAggregation(agg_ops.count_op, ex.deref(offsets_id)) count_result = filtered_block._expr.aggregate([(count_agg, "count")]) - count_scalar = self._block.session._executor.execute( - count_result, ex_spec.ExecutionSpec(promise_under_10gb=True) - ).to_py_scalar() + count_scalar = ( + self._block.session._executor.execute( + count_result, ex_spec.ExecutionSpec(promise_under_10gb=True) + ) + .batches() + .to_py_scalar() + ) if count_scalar == 0: raise KeyError(f"'{key}' is not in index") @@ -308,9 +312,13 @@ def get_loc(self, key) -> typing.Union[int, slice, "bigframes.series.Series"]: if count_scalar == 1: min_agg = ex_types.UnaryAggregation(agg_ops.min_op, ex.deref(offsets_id)) position_result = filtered_block._expr.aggregate([(min_agg, "position")]) - position_scalar = self._block.session._executor.execute( - position_result, ex_spec.ExecutionSpec(promise_under_10gb=True) - ).to_py_scalar() + position_scalar = ( + self._block.session._executor.execute( + position_result, ex_spec.ExecutionSpec(promise_under_10gb=True) + ) + .batches() + .to_py_scalar() + ) return int(position_scalar) # Handle multiple matches based on index monotonicity @@ -342,10 +350,14 @@ def _get_monotonic_slice( combined_result = filtered_block._expr.aggregate(min_max_aggs) # Execute query and extract positions - result_df = self._block.session._executor.execute( - combined_result, - execution_spec=ex_spec.ExecutionSpec(promise_under_10gb=True), - ).to_pandas() + result_df = ( + self._block.session._executor.execute( + combined_result, + execution_spec=ex_spec.ExecutionSpec(promise_under_10gb=True), + ) + .batches() + .to_pandas() + ) min_pos = int(result_df["min_pos"].iloc[0]) max_pos = int(result_df["max_pos"].iloc[0]) diff --git a/bigframes/core/local_data.py b/bigframes/core/local_data.py index c214d0bb7e..fa18f00483 100644 --- a/bigframes/core/local_data.py +++ b/bigframes/core/local_data.py @@ -83,20 +83,39 @@ def from_pandas(cls, dataframe: pd.DataFrame) -> ManagedArrowTable: return mat @classmethod - def from_pyarrow(self, table: pa.Table) -> ManagedArrowTable: - columns: list[pa.ChunkedArray] = [] - fields: list[schemata.SchemaItem] = [] - for name, arr in zip(table.column_names, table.columns): - new_arr, bf_type = _adapt_chunked_array(arr) - columns.append(new_arr) - fields.append(schemata.SchemaItem(name, bf_type)) - - mat = ManagedArrowTable( - pa.table(columns, names=table.column_names), - schemata.ArraySchema(tuple(fields)), - ) - mat.validate() - return mat + def from_pyarrow( + cls, table: pa.Table, schema: Optional[schemata.ArraySchema] = None + ) -> ManagedArrowTable: + if schema is not None: + pa_fields = [] + for item in schema.items: + pa_type = _get_managed_storage_type(item.dtype) + pa_fields.append( + pyarrow.field( + item.column, + pa_type, + nullable=not pyarrow.types.is_list(pa_type), + ) + ) + pa_schema = pyarrow.schema(pa_fields) + # assumption: needed transformations can be handled by simple cast. + mat = ManagedArrowTable(table.cast(pa_schema), schema) + mat.validate() + return mat + else: # infer bigframes schema + columns: list[pa.ChunkedArray] = [] + fields: list[schemata.SchemaItem] = [] + for name, arr in zip(table.column_names, table.columns): + new_arr, bf_type = _adapt_chunked_array(arr) + columns.append(new_arr) + fields.append(schemata.SchemaItem(name, bf_type)) + + mat = ManagedArrowTable( + pa.table(columns, names=table.column_names), + schemata.ArraySchema(tuple(fields)), + ) + mat.validate() + return mat def to_arrow( self, diff --git a/bigframes/core/nodes.py b/bigframes/core/nodes.py index 0d20509877..9e0fcb3ace 100644 --- a/bigframes/core/nodes.py +++ b/bigframes/core/nodes.py @@ -16,7 +16,6 @@ import abc import dataclasses -import datetime import functools import itertools import typing @@ -31,9 +30,7 @@ Tuple, ) -import google.cloud.bigquery as bq - -from bigframes.core import agg_expressions, identifiers, local_data, sequences +from bigframes.core import agg_expressions, bq_data, identifiers, local_data, sequences from bigframes.core.bigframe_node import BigFrameNode, COLUMN_SET import bigframes.core.expression as ex from bigframes.core.field import Field @@ -599,14 +596,13 @@ def transform_children(self, t: Callable[[BigFrameNode], BigFrameNode]) -> LeafN class ScanItem(typing.NamedTuple): id: identifiers.ColumnId - dtype: bigframes.dtypes.Dtype # Might be multiple logical types for a given physical source type source_id: str # Flexible enough for both local data and bq data def with_id(self, id: identifiers.ColumnId) -> ScanItem: - return ScanItem(id, self.dtype, self.source_id) + return ScanItem(id, self.source_id) def with_source_id(self, source_id: str) -> ScanItem: - return ScanItem(self.id, self.dtype, source_id) + return ScanItem(self.id, source_id) @dataclasses.dataclass(frozen=True) @@ -661,7 +657,7 @@ def remap_source_ids( def append( self, source_id: str, dtype: bigframes.dtypes.Dtype, id: identifiers.ColumnId ) -> ScanList: - return ScanList((*self.items, ScanItem(id, dtype, source_id))) + return ScanList((*self.items, ScanItem(id, source_id))) @dataclasses.dataclass(frozen=True, eq=False) @@ -677,8 +673,10 @@ class ReadLocalNode(LeafNode): @property def fields(self) -> Sequence[Field]: fields = tuple( - Field(col_id, dtype) for col_id, dtype, _ in self.scan_list.items + Field(col_id, self.local_data_source.schema.get_type(source_id)) + for col_id, source_id in self.scan_list.items ) + if self.offsets_col is not None: return tuple( itertools.chain( @@ -726,7 +724,7 @@ def remap_vars( ) -> ReadLocalNode: new_scan_list = ScanList( tuple( - ScanItem(mappings.get(item.id, item.id), item.dtype, item.source_id) + ScanItem(mappings.get(item.id, item.id), item.source_id) for item in self.scan_list.items ) ) @@ -745,64 +743,9 @@ def remap_refs( return self -@dataclasses.dataclass(frozen=True) -class GbqTable: - project_id: str = dataclasses.field() - dataset_id: str = dataclasses.field() - table_id: str = dataclasses.field() - physical_schema: Tuple[bq.SchemaField, ...] = dataclasses.field() - is_physically_stored: bool = dataclasses.field() - cluster_cols: typing.Optional[Tuple[str, ...]] - - @staticmethod - def from_table(table: bq.Table, columns: Sequence[str] = ()) -> GbqTable: - # Subsetting fields with columns can reduce cost of row-hash default ordering - if columns: - schema = tuple(item for item in table.schema if item.name in columns) - else: - schema = tuple(table.schema) - return GbqTable( - project_id=table.project, - dataset_id=table.dataset_id, - table_id=table.table_id, - physical_schema=schema, - is_physically_stored=(table.table_type in ["TABLE", "MATERIALIZED_VIEW"]), - cluster_cols=None - if table.clustering_fields is None - else tuple(table.clustering_fields), - ) - - def get_table_ref(self) -> bq.TableReference: - return bq.TableReference( - bq.DatasetReference(self.project_id, self.dataset_id), self.table_id - ) - - @property - @functools.cache - def schema_by_id(self): - return {col.name: col for col in self.physical_schema} - - -@dataclasses.dataclass(frozen=True) -class BigqueryDataSource: - """ - Google BigQuery Data source. - - This should not be modified once defined, as all attributes contribute to the default ordering. - """ - - table: GbqTable - at_time: typing.Optional[datetime.datetime] = None - # Added for backwards compatibility, not validated - sql_predicate: typing.Optional[str] = None - ordering: typing.Optional[orderings.RowOrdering] = None - n_rows: Optional[int] = None - - -## Put ordering in here or just add order_by node above? @dataclasses.dataclass(frozen=True, eq=False) class ReadTableNode(LeafNode): - source: BigqueryDataSource + source: bq_data.BigqueryDataSource # Subset of physical schema column # Mapping of table schema ids to bfet id. scan_list: ScanList @@ -826,8 +769,12 @@ def session(self): @property def fields(self) -> Sequence[Field]: return tuple( - Field(col_id, dtype, self.source.table.schema_by_id[source_id].is_nullable) - for col_id, dtype, source_id in self.scan_list.items + Field( + col_id, + self.source.schema.get_type(source_id), + self.source.table.schema_by_id[source_id].is_nullable, + ) + for col_id, source_id in self.scan_list.items ) @property @@ -886,7 +833,7 @@ def remap_vars( ) -> ReadTableNode: new_scan_list = ScanList( tuple( - ScanItem(mappings.get(item.id, item.id), item.dtype, item.source_id) + ScanItem(mappings.get(item.id, item.id), item.source_id) for item in self.scan_list.items ) ) @@ -907,7 +854,6 @@ def with_order_cols(self): new_scan_cols = [ ScanItem( identifiers.ColumnId.unique(), - dtype=bigframes.dtypes.convert_schema_field(field)[1], source_id=field.name, ) for field in self.source.table.physical_schema diff --git a/bigframes/core/pyarrow_utils.py b/bigframes/core/pyarrow_utils.py index b9dc2ea2b3..bdbb220b95 100644 --- a/bigframes/core/pyarrow_utils.py +++ b/bigframes/core/pyarrow_utils.py @@ -84,6 +84,13 @@ def cast_batch(batch: pa.RecordBatch, schema: pa.Schema) -> pa.RecordBatch: ) +def rename_batch(batch: pa.RecordBatch, names: list[str]) -> pa.RecordBatch: + if batch.schema.names == names: + return batch + # TODO: Use RecordBatch.rename_columns once min pyarrow>=16.0 + return pa.RecordBatch.from_arrays(batch.columns, names) + + def truncate_pyarrow_iterable( batches: Iterable[pa.RecordBatch], max_results: int ) -> Iterator[pa.RecordBatch]: diff --git a/bigframes/core/rewrite/fold_row_count.py b/bigframes/core/rewrite/fold_row_count.py index 583343d68a..cc0b818fb9 100644 --- a/bigframes/core/rewrite/fold_row_count.py +++ b/bigframes/core/rewrite/fold_row_count.py @@ -15,7 +15,6 @@ import pyarrow as pa -from bigframes import dtypes from bigframes.core import local_data, nodes from bigframes.operations import aggregations @@ -34,10 +33,7 @@ def fold_row_counts(node: nodes.BigFrameNode) -> nodes.BigFrameNode: pa.table({"count": pa.array([node.child.row_count], type=pa.int64())}) ) scan_list = nodes.ScanList( - tuple( - nodes.ScanItem(out_id, dtypes.INT_DTYPE, "count") - for _, out_id in node.aggregations - ) + tuple(nodes.ScanItem(out_id, "count") for _, out_id in node.aggregations) ) return nodes.ReadLocalNode( local_data_source=local_data_source, scan_list=scan_list, session=node.session diff --git a/bigframes/core/schema.py b/bigframes/core/schema.py index b1a77d1259..395ad55f49 100644 --- a/bigframes/core/schema.py +++ b/bigframes/core/schema.py @@ -17,7 +17,7 @@ from dataclasses import dataclass import functools import typing -from typing import Dict, List, Sequence +from typing import Dict, List, Optional, Sequence import google.cloud.bigquery import pyarrow @@ -35,7 +35,7 @@ class SchemaItem: @dataclass(frozen=True) class ArraySchema: - items: Sequence[SchemaItem] + items: tuple[SchemaItem, ...] def __iter__(self): yield from self.items @@ -44,21 +44,26 @@ def __iter__(self): def from_bq_table( cls, table: google.cloud.bigquery.Table, - column_type_overrides: typing.Optional[ + column_type_overrides: Optional[ typing.Dict[str, bigframes.dtypes.Dtype] ] = None, + columns: Optional[Sequence[str]] = None, ): + if not columns: + fields = table.schema + else: + lookup = {field.name: field for field in table.schema} + fields = [lookup[col] for col in columns] + return ArraySchema.from_bq_schema( - table.schema, column_type_overrides=column_type_overrides + fields, column_type_overrides=column_type_overrides ) @classmethod def from_bq_schema( cls, schema: List[google.cloud.bigquery.SchemaField], - column_type_overrides: typing.Optional[ - Dict[str, bigframes.dtypes.Dtype] - ] = None, + column_type_overrides: Optional[Dict[str, bigframes.dtypes.Dtype]] = None, ): if column_type_overrides is None: column_type_overrides = {} @@ -90,14 +95,16 @@ def to_bigquery( for item in self.items ) - def to_pyarrow(self) -> pyarrow.Schema: + def to_pyarrow(self, use_storage_types: bool = False) -> pyarrow.Schema: fields = [] for item in self.items: pa_type = bigframes.dtypes.bigframes_dtype_to_arrow_dtype(item.dtype) + if use_storage_types: + pa_type = bigframes.dtypes.to_storage_type(pa_type) fields.append( pyarrow.field( item.column, - pa_type, + type=pa_type, nullable=not pyarrow.types.is_list(pa_type), ) ) diff --git a/bigframes/dtypes.py b/bigframes/dtypes.py index 6c05b6f4a3..29e1be1ace 100644 --- a/bigframes/dtypes.py +++ b/bigframes/dtypes.py @@ -553,6 +553,24 @@ def bigframes_dtype_to_arrow_dtype( ) +def to_storage_type( + arrow_type: pa.DataType, +): + """Some pyarrow versions don't support extension types fully, such as for empty table generation.""" + if isinstance(arrow_type, pa.ExtensionType): + return arrow_type.storage_type + if pa.types.is_list(arrow_type): + assert isinstance(arrow_type, pa.ListType) + return pa.list_(to_storage_type(arrow_type.value_type)) + if pa.types.is_struct(arrow_type): + assert isinstance(arrow_type, pa.StructType) + return pa.struct( + field.with_type(to_storage_type(field.type)) + for field in bigframes.core.backports.pyarrow_struct_type_fields(arrow_type) + ) + return arrow_type + + def arrow_type_to_literal( arrow_type: pa.DataType, ) -> Any: diff --git a/bigframes/session/bq_caching_executor.py b/bigframes/session/bq_caching_executor.py index c830ca1e29..736dbf7be1 100644 --- a/bigframes/session/bq_caching_executor.py +++ b/bigframes/session/bq_caching_executor.py @@ -15,10 +15,8 @@ from __future__ import annotations import math -import os import threading from typing import Literal, Mapping, Optional, Sequence, Tuple -import warnings import weakref import google.api_core.exceptions @@ -31,13 +29,12 @@ from bigframes import exceptions as bfe import bigframes.constants import bigframes.core -from bigframes.core import compile, local_data, rewrite +from bigframes.core import bq_data, compile, local_data, rewrite import bigframes.core.compile.sqlglot.sqlglot_ir as sqlglot_ir import bigframes.core.events import bigframes.core.guid import bigframes.core.identifiers import bigframes.core.nodes as nodes -import bigframes.core.ordering as order import bigframes.core.schema as schemata import bigframes.core.tree_properties as tree_properties import bigframes.dtypes @@ -60,7 +57,6 @@ MAX_SUBTREE_FACTORINGS = 5 _MAX_CLUSTER_COLUMNS = 4 MAX_SMALL_RESULT_BYTES = 10 * 1024 * 1024 * 1024 # 10G -_MAX_READ_STREAMS = os.cpu_count() SourceIdMapping = Mapping[str, str] @@ -74,7 +70,7 @@ def __init__(self): ] = weakref.WeakKeyDictionary() self._uploaded_local_data: weakref.WeakKeyDictionary[ local_data.ManagedArrowTable, - tuple[nodes.BigqueryDataSource, SourceIdMapping], + tuple[bq_data.BigqueryDataSource, SourceIdMapping], ] = weakref.WeakKeyDictionary() @property @@ -84,23 +80,16 @@ def mapping(self) -> Mapping[nodes.BigFrameNode, nodes.BigFrameNode]: def cache_results_table( self, original_root: nodes.BigFrameNode, - table: bigquery.Table, - ordering: order.RowOrdering, - num_rows: Optional[int] = None, + data: bq_data.BigqueryDataSource, ): # Assumption: GBQ cached table uses field name as bq column name scan_list = nodes.ScanList( tuple( - nodes.ScanItem(field.id, field.dtype, field.id.sql) - for field in original_root.fields + nodes.ScanItem(field.id, field.id.sql) for field in original_root.fields ) ) cached_replacement = nodes.CachedTableNode( - source=nodes.BigqueryDataSource( - nodes.GbqTable.from_table(table), - ordering=ordering, - n_rows=num_rows, - ), + source=data, scan_list=scan_list, table_session=original_root.session, original_node=original_root, @@ -111,7 +100,7 @@ def cache_results_table( def cache_remote_replacement( self, local_data: local_data.ManagedArrowTable, - bq_data: nodes.BigqueryDataSource, + bq_data: bq_data.BigqueryDataSource, ): # bq table has one extra column for offsets, those are implicit for local data assert len(local_data.schema.items) + 1 == len(bq_data.table.physical_schema) @@ -331,7 +320,7 @@ def _export_gbq( # TODO(swast): plumb through the api_name of the user-facing api that # caused this query. - row_iter, query_job = self._run_execute_query( + iterator, job = self._run_execute_query( sql=sql, job_config=job_config, ) @@ -347,14 +336,11 @@ def _export_gbq( table.schema = array_value.schema.to_bigquery() self.bqclient.update_table(table, ["schema"]) - return executor.ExecuteResult( - row_iter.to_arrow_iterable( - bqstorage_client=self.bqstoragereadclient, - max_stream_count=_MAX_READ_STREAMS, + return executor.EmptyExecuteResult( + bf_schema=array_value.schema, + execution_metadata=executor.ExecutionMetadata.from_iterator_and_job( + iterator, job ), - array_value.schema, - query_job, - total_bytes_processed=row_iter.total_bytes_processed, ) def dry_run( @@ -637,6 +623,7 @@ def _execute_plan_gbq( create_table = True if not cache_spec.cluster_cols: + offsets_id = bigframes.core.identifiers.ColumnId( bigframes.core.guid.generate_guid() ) @@ -676,41 +663,62 @@ def _execute_plan_gbq( query_with_job=(destination_table is not None), ) - table_info: Optional[bigquery.Table] = None + # we could actually cache even when caching is not explicitly requested, but being conservative for now + result_bq_data = None if query_job and query_job.destination: - table_info = self.bqclient.get_table(query_job.destination) - size_bytes = table_info.num_bytes - else: - size_bytes = None + # we might add extra sql columns in compilation, esp if caching w ordering, infer a bigframes type for them + result_bf_schema = _result_schema(og_schema, list(compiled.sql_schema)) + dst = query_job.destination + result_bq_data = bq_data.BigqueryDataSource( + table=bq_data.GbqTable( + dst.project, + dst.dataset_id, + dst.table_id, + tuple(compiled_schema), + is_physically_stored=True, + cluster_cols=tuple(cluster_cols), + ), + schema=result_bf_schema, + ordering=compiled.row_order, + n_rows=iterator.total_rows, + ) - # we could actually cache even when caching is not explicitly requested, but being conservative for now if cache_spec is not None: - assert table_info is not None + assert result_bq_data is not None assert compiled.row_order is not None - self.cache.cache_results_table( - og_plan, table_info, compiled.row_order, num_rows=table_info.num_rows - ) + self.cache.cache_results_table(og_plan, result_bq_data) - if size_bytes is not None and size_bytes >= MAX_SMALL_RESULT_BYTES: - msg = bfe.format_message( - "The query result size has exceeded 10 GB. In BigFrames 2.0 and " - "later, you might need to manually set `allow_large_results=True` in " - "the IO method or adjust the BigFrames option: " - "`bigframes.options.compute.allow_large_results=True`." + execution_metadata = executor.ExecutionMetadata.from_iterator_and_job( + iterator, query_job + ) + result_mostly_cached = ( + hasattr(iterator, "_is_almost_completely_cached") + and iterator._is_almost_completely_cached() + ) + if result_bq_data is not None and not result_mostly_cached: + return executor.BQTableExecuteResult( + data=result_bq_data, + project_id=self.bqclient.project, + storage_client=self.bqstoragereadclient, + execution_metadata=execution_metadata, + selected_fields=tuple((col, col) for col in og_schema.names), + ) + else: + return executor.LocalExecuteResult( + data=iterator.to_arrow().select(og_schema.names), + bf_schema=plan.schema, + execution_metadata=execution_metadata, ) - warnings.warn(msg, FutureWarning) - return executor.ExecuteResult( - _arrow_batches=iterator.to_arrow_iterable( - bqstorage_client=self.bqstoragereadclient, - max_stream_count=_MAX_READ_STREAMS, - ), - schema=og_schema, - query_job=query_job, - total_bytes=size_bytes, - total_rows=iterator.total_rows, - total_bytes_processed=iterator.total_bytes_processed, - ) + +def _result_schema( + logical_schema: schemata.ArraySchema, sql_schema: list[bigquery.SchemaField] +) -> schemata.ArraySchema: + inferred_schema = bigframes.dtypes.bf_type_from_type_kind(sql_schema) + inferred_schema.update(logical_schema._mapping) + return schemata.ArraySchema( + tuple(schemata.SchemaItem(col, dtype) for col, dtype in inferred_schema.items()) + ) def _if_schema_match( diff --git a/bigframes/session/direct_gbq_execution.py b/bigframes/session/direct_gbq_execution.py index 9e7db87301..d76a1a7630 100644 --- a/bigframes/session/direct_gbq_execution.py +++ b/bigframes/session/direct_gbq_execution.py @@ -64,12 +64,13 @@ def execute( sql=compiled.sql, ) - return executor.ExecuteResult( - _arrow_batches=iterator.to_arrow_iterable(), - schema=plan.schema, - query_job=query_job, - total_rows=iterator.total_rows, - total_bytes_processed=iterator.total_bytes_processed, + # just immediately downlaod everything for simplicity + return executor.LocalExecuteResult( + data=iterator.to_arrow(), + bf_schema=plan.schema, + execution_metadata=executor.ExecutionMetadata.from_iterator_and_job( + iterator, query_job + ), ) def _run_execute_query( diff --git a/bigframes/session/executor.py b/bigframes/session/executor.py index d0cfe5f4f7..bca98bfb2f 100644 --- a/bigframes/session/executor.py +++ b/bigframes/session/executor.py @@ -18,16 +18,19 @@ import dataclasses import functools import itertools -from typing import Iterator, Literal, Optional, Union +from typing import Iterator, Literal, Optional, Sequence, Union -from google.cloud import bigquery +from google.cloud import bigquery, bigquery_storage_v1 +import google.cloud.bigquery.table as bq_table import pandas as pd import pyarrow +import pyarrow as pa import bigframes import bigframes.core -from bigframes.core import pyarrow_utils +from bigframes.core import bq_data, local_data, pyarrow_utils import bigframes.core.schema +import bigframes.dtypes import bigframes.session._io.pandas as io_pandas import bigframes.session.execution_spec as ex_spec @@ -38,21 +41,39 @@ ) -@dataclasses.dataclass(frozen=True) -class ExecuteResult: - _arrow_batches: Iterator[pyarrow.RecordBatch] - schema: bigframes.core.schema.ArraySchema - query_job: Optional[bigquery.QueryJob] = None - total_bytes: Optional[int] = None - total_rows: Optional[int] = None - total_bytes_processed: Optional[int] = None +class ResultsIterator(Iterator[pa.RecordBatch]): + """ + Iterator for query results, with some extra metadata attached. + """ + + def __init__( + self, + batches: Iterator[pa.RecordBatch], + schema: bigframes.core.schema.ArraySchema, + total_rows: Optional[int] = 0, + total_bytes: Optional[int] = 0, + ): + self._batches = batches + self._schema = schema + self._total_rows = total_rows + self._total_bytes = total_bytes + + @property + def approx_total_rows(self) -> Optional[int]: + return self._total_rows + + @property + def approx_total_bytes(self) -> Optional[int]: + return self._total_bytes + + def __next__(self) -> pa.RecordBatch: + return next(self._batches) @property def arrow_batches(self) -> Iterator[pyarrow.RecordBatch]: result_rows = 0 - for batch in self._arrow_batches: - batch = pyarrow_utils.cast_batch(batch, self.schema.to_pyarrow()) + for batch in self._batches: result_rows += batch.num_rows maximum_result_rows = bigframes.options.compute.maximum_result_rows @@ -80,10 +101,14 @@ def to_arrow_table(self) -> pyarrow.Table: itertools.chain(peek_value, batches), # reconstruct ) else: - return self.schema.to_pyarrow().empty_table() + try: + return self._schema.to_pyarrow().empty_table() + except pa.ArrowNotImplementedError: + # Bug with some pyarrow versions, empty_table only supports base storage types, not extension types. + return self._schema.to_pyarrow(use_storage_types=True).empty_table() def to_pandas(self) -> pd.DataFrame: - return io_pandas.arrow_to_pandas(self.to_arrow_table(), self.schema) + return io_pandas.arrow_to_pandas(self.to_arrow_table(), self._schema) def to_pandas_batches( self, page_size: Optional[int] = None, max_results: Optional[int] = None @@ -105,7 +130,7 @@ def to_pandas_batches( ) yield from map( - functools.partial(io_pandas.arrow_to_pandas, schema=self.schema), + functools.partial(io_pandas.arrow_to_pandas, schema=self._schema), batch_iter, ) @@ -121,6 +146,150 @@ def to_py_scalar(self): return column[0] +class ExecuteResult(abc.ABC): + @property + @abc.abstractmethod + def execution_metadata(self) -> ExecutionMetadata: + ... + + @property + @abc.abstractmethod + def schema(self) -> bigframes.core.schema.ArraySchema: + ... + + @abc.abstractmethod + def batches(self) -> ResultsIterator: + ... + + @property + def query_job(self) -> Optional[bigquery.QueryJob]: + return self.execution_metadata.query_job + + @property + def total_bytes_processed(self) -> Optional[int]: + return self.execution_metadata.bytes_processed + + +@dataclasses.dataclass(frozen=True) +class ExecutionMetadata: + query_job: Optional[bigquery.QueryJob] = None + bytes_processed: Optional[int] = None + + @classmethod + def from_iterator_and_job( + cls, iterator: bq_table.RowIterator, job: Optional[bigquery.QueryJob] + ) -> ExecutionMetadata: + return cls(query_job=job, bytes_processed=iterator.total_bytes_processed) + + +class LocalExecuteResult(ExecuteResult): + def __init__( + self, + data: pa.Table, + bf_schema: bigframes.core.schema.ArraySchema, + execution_metadata: ExecutionMetadata = ExecutionMetadata(), + ): + self._data = local_data.ManagedArrowTable.from_pyarrow(data, bf_schema) + self._execution_metadata = execution_metadata + + @property + def execution_metadata(self) -> ExecutionMetadata: + return self._execution_metadata + + @property + def schema(self) -> bigframes.core.schema.ArraySchema: + return self._data.schema + + def batches(self) -> ResultsIterator: + return ResultsIterator( + iter(self._data.to_arrow()[1]), + self.schema, + self._data.metadata.row_count, + self._data.metadata.total_bytes, + ) + + +class EmptyExecuteResult(ExecuteResult): + def __init__( + self, + bf_schema: bigframes.core.schema.ArraySchema, + execution_metadata: ExecutionMetadata = ExecutionMetadata(), + ): + self._schema = bf_schema + self._execution_metadata = execution_metadata + + @property + def execution_metadata(self) -> ExecutionMetadata: + return self._execution_metadata + + @property + def schema(self) -> bigframes.core.schema.ArraySchema: + return self._schema + + def batches(self) -> ResultsIterator: + return ResultsIterator(iter([]), self.schema, 0, 0) + + +class BQTableExecuteResult(ExecuteResult): + def __init__( + self, + data: bq_data.BigqueryDataSource, + storage_client: bigquery_storage_v1.BigQueryReadClient, + project_id: str, + *, + execution_metadata: ExecutionMetadata = ExecutionMetadata(), + limit: Optional[int] = None, + selected_fields: Optional[Sequence[tuple[str, str]]] = None, + ): + self._data = data + self._project_id = project_id + self._execution_metadata = execution_metadata + self._storage_client = storage_client + self._limit = limit + self._selected_fields = selected_fields or [ + (name, name) for name in data.schema.names + ] + + @property + def execution_metadata(self) -> ExecutionMetadata: + return self._execution_metadata + + @property + @functools.cache + def schema(self) -> bigframes.core.schema.ArraySchema: + source_ids = [selection[0] for selection in self._selected_fields] + return self._data.schema.select(source_ids).rename(dict(self._selected_fields)) + + def batches(self) -> ResultsIterator: + read_batches = bq_data.get_arrow_batches( + self._data, + [x[0] for x in self._selected_fields], + self._storage_client, + self._project_id, + ) + arrow_batches: Iterator[pa.RecordBatch] = map( + functools.partial( + pyarrow_utils.rename_batch, names=list(self.schema.names) + ), + read_batches.iter, + ) + approx_bytes: Optional[int] = read_batches.approx_bytes + approx_rows: Optional[int] = self._data.n_rows or read_batches.approx_rows + + if self._limit is not None: + if approx_rows is not None: + approx_rows = min(approx_rows, self._limit) + arrow_batches = pyarrow_utils.truncate_pyarrow_iterable( + arrow_batches, self._limit + ) + + if self._data.sql_predicate: + approx_bytes = None + approx_rows = None + + return ResultsIterator(arrow_batches, self.schema, approx_rows, approx_bytes) + + @dataclasses.dataclass(frozen=True) class HierarchicalKey: columns: tuple[str, ...] diff --git a/bigframes/session/loader.py b/bigframes/session/loader.py index 2d5dec13e6..6b16fe6bfd 100644 --- a/bigframes/session/loader.py +++ b/bigframes/session/loader.py @@ -49,7 +49,15 @@ import bigframes._tools import bigframes._tools.strings -from bigframes.core import guid, identifiers, local_data, nodes, ordering, utils +from bigframes.core import ( + bq_data, + guid, + identifiers, + local_data, + nodes, + ordering, + utils, +) import bigframes.core as core import bigframes.core.blocks as blocks import bigframes.core.events @@ -324,9 +332,7 @@ def read_managed_data( source=gbq_source, scan_list=nodes.ScanList( tuple( - nodes.ScanItem( - identifiers.ColumnId(item.column), item.dtype, item.column - ) + nodes.ScanItem(identifiers.ColumnId(item.column), item.column) for item in data.schema.items ) ), @@ -337,7 +343,7 @@ def load_data( self, data: local_data.ManagedArrowTable, offsets_col: str, - ) -> nodes.BigqueryDataSource: + ) -> bq_data.BigqueryDataSource: """Load managed data into bigquery""" # JSON support incomplete @@ -379,8 +385,9 @@ def load_data( self._start_generic_job(load_job) # must get table metadata after load job for accurate metadata destination_table = self._bqclient.get_table(load_table_destination) - return nodes.BigqueryDataSource( - nodes.GbqTable.from_table(destination_table), + return bq_data.BigqueryDataSource( + bq_data.GbqTable.from_table(destination_table), + schema=schema_w_offsets, ordering=ordering.TotalOrdering.from_offset_col(offsets_col), n_rows=data.metadata.row_count, ) @@ -389,7 +396,7 @@ def stream_data( self, data: local_data.ManagedArrowTable, offsets_col: str, - ) -> nodes.BigqueryDataSource: + ) -> bq_data.BigqueryDataSource: """Load managed data into bigquery""" schema_w_offsets = data.schema.append( schemata.SchemaItem(offsets_col, bigframes.dtypes.INT_DTYPE) @@ -415,8 +422,9 @@ def stream_data( f"Problem loading at least one row from DataFrame: {errors}. {constants.FEEDBACK_LINK}" ) destination_table = self._bqclient.get_table(load_table_destination) - return nodes.BigqueryDataSource( - nodes.GbqTable.from_table(destination_table), + return bq_data.BigqueryDataSource( + bq_data.GbqTable.from_table(destination_table), + schema=schema_w_offsets, ordering=ordering.TotalOrdering.from_offset_col(offsets_col), n_rows=data.metadata.row_count, ) @@ -425,7 +433,7 @@ def write_data( self, data: local_data.ManagedArrowTable, offsets_col: str, - ) -> nodes.BigqueryDataSource: + ) -> bq_data.BigqueryDataSource: """Load managed data into bigquery""" schema_w_offsets = data.schema.append( schemata.SchemaItem(offsets_col, bigframes.dtypes.INT_DTYPE) @@ -469,8 +477,9 @@ def request_gen() -> Generator[bq_storage_types.AppendRowsRequest, None, None]: assert response.row_count == data.data.num_rows destination_table = self._bqclient.get_table(bq_table_ref) - return nodes.BigqueryDataSource( - nodes.GbqTable.from_table(destination_table), + return bq_data.BigqueryDataSource( + bq_data.GbqTable.from_table(destination_table), + schema=schema_w_offsets, ordering=ordering.TotalOrdering.from_offset_col(offsets_col), n_rows=data.metadata.row_count, ) @@ -804,12 +813,10 @@ def read_gbq_table( bigframes.core.events.ExecutionFinished(), ) - schema = schemata.ArraySchema.from_bq_table(table) - if not include_all_columns: - schema = schema.select(index_cols + columns) + selected_cols = None if include_all_columns else index_cols + columns array_value = core.ArrayValue.from_table( table, - schema=schema, + columns=selected_cols, predicate=filter_str, at_time=time_travel_timestamp if enable_snapshot else None, primary_key=primary_key, diff --git a/bigframes/session/local_scan_executor.py b/bigframes/session/local_scan_executor.py index 65f088e8a1..fee0f557ea 100644 --- a/bigframes/session/local_scan_executor.py +++ b/bigframes/session/local_scan_executor.py @@ -57,10 +57,7 @@ def execute( if (peek is not None) and (total_rows is not None): total_rows = min(peek, total_rows) - return executor.ExecuteResult( - _arrow_batches=arrow_table.to_batches(), - schema=plan.schema, - query_job=None, - total_bytes=None, - total_rows=total_rows, + return executor.LocalExecuteResult( + data=arrow_table, + bf_schema=plan.schema, ) diff --git a/bigframes/session/polars_executor.py b/bigframes/session/polars_executor.py index a1e1d436e1..00f8f37934 100644 --- a/bigframes/session/polars_executor.py +++ b/bigframes/session/polars_executor.py @@ -16,14 +16,11 @@ import itertools from typing import Optional, TYPE_CHECKING -import pyarrow as pa - from bigframes.core import ( agg_expressions, array_value, bigframe_node, expression, - local_data, nodes, ) import bigframes.operations @@ -153,23 +150,10 @@ def execute( if peek is not None: lazy_frame = lazy_frame.limit(peek) pa_table = lazy_frame.collect().to_arrow() - return executor.ExecuteResult( - _arrow_batches=iter(map(self._adapt_batch, pa_table.to_batches())), - schema=plan.schema, - total_bytes=pa_table.nbytes, - total_rows=pa_table.num_rows, + return executor.LocalExecuteResult( + data=pa_table, + bf_schema=plan.schema, ) def _can_execute(self, plan: bigframe_node.BigFrameNode): return all(_is_node_polars_executable(node) for node in plan.unique_nodes()) - - def _adapt_array(self, array: pa.Array) -> pa.Array: - target_type = local_data.logical_type_replacements(array.type) - if target_type != array.type: - # Safe is false to handle weird polars decimal scaling - return array.cast(target_type, safe=False) - return array - - def _adapt_batch(self, batch: pa.RecordBatch) -> pa.RecordBatch: - new_arrays = [self._adapt_array(arr) for arr in batch.columns] - return pa.RecordBatch.from_arrays(new_arrays, names=batch.column_names) diff --git a/bigframes/session/read_api_execution.py b/bigframes/session/read_api_execution.py index 136c279c08..c7138f7b30 100644 --- a/bigframes/session/read_api_execution.py +++ b/bigframes/session/read_api_execution.py @@ -13,12 +13,11 @@ # limitations under the License. from __future__ import annotations -from typing import Any, Iterator, Optional +from typing import Optional from google.cloud import bigquery_storage_v1 -import pyarrow as pa -from bigframes.core import bigframe_node, nodes, pyarrow_utils, rewrite +from bigframes.core import bigframe_node, nodes, rewrite from bigframes.session import executor, semi_executor @@ -28,7 +27,9 @@ class ReadApiSemiExecutor(semi_executor.SemiExecutor): """ def __init__( - self, bqstoragereadclient: bigquery_storage_v1.BigQueryReadClient, project: str + self, + bqstoragereadclient: bigquery_storage_v1.BigQueryReadClient, + project: str, ): self.bqstoragereadclient = bqstoragereadclient self.project = project @@ -53,68 +54,14 @@ def execute( if peek is None or limit < peek: peek = limit - import google.cloud.bigquery_storage_v1.types as bq_storage_types - from google.protobuf import timestamp_pb2 - - bq_table = node.source.table.get_table_ref() - read_options: dict[str, Any] = { - "selected_fields": [item.source_id for item in node.scan_list.items] - } - if node.source.sql_predicate: - read_options["row_restriction"] = node.source.sql_predicate - read_options = bq_storage_types.ReadSession.TableReadOptions(**read_options) - - table_mod_options = {} - if node.source.at_time: - snapshot_time = timestamp_pb2.Timestamp() - snapshot_time.FromDatetime(node.source.at_time) - table_mod_options["snapshot_time"] = snapshot_time = snapshot_time - table_mods = bq_storage_types.ReadSession.TableModifiers(**table_mod_options) - - requested_session = bq_storage_types.stream.ReadSession( - table=bq_table.to_bqstorage(), - data_format=bq_storage_types.DataFormat.ARROW, - read_options=read_options, - table_modifiers=table_mods, - ) - # Single stream to maintain ordering - request = bq_storage_types.CreateReadSessionRequest( - parent=f"projects/{self.project}", - read_session=requested_session, - max_stream_count=1, - ) - session = self.bqstoragereadclient.create_read_session(request=request) - - if not session.streams: - batches: Iterator[pa.RecordBatch] = iter([]) - else: - reader = self.bqstoragereadclient.read_rows(session.streams[0].name) - rowstream = reader.rows() - - def process_page(page): - pa_batch = page.to_arrow() - pa_batch = pa_batch.select( - [item.source_id for item in node.scan_list.items] - ) - return pa.RecordBatch.from_arrays( - pa_batch.columns, names=[id.sql for id in node.ids] - ) - - batches = map(process_page, rowstream.pages) - - if peek: - batches = pyarrow_utils.truncate_pyarrow_iterable(batches, max_results=peek) - - rows = node.source.n_rows or session.estimated_row_count - if peek and rows: - rows = min(peek, rows) - - return executor.ExecuteResult( - _arrow_batches=batches, - schema=plan.schema, - query_job=None, - total_bytes=None, - total_rows=rows, + return executor.BQTableExecuteResult( + data=node.source, + project_id=self.project, + storage_client=self.bqstoragereadclient, + limit=peek, + selected_fields=[ + (item.source_id, item.id.sql) for item in node.scan_list.items + ], ) def _try_adapt_plan( diff --git a/bigframes/testing/engine_utils.py b/bigframes/testing/engine_utils.py index 625d1727ee..edb68c3a9b 100644 --- a/bigframes/testing/engine_utils.py +++ b/bigframes/testing/engine_utils.py @@ -29,6 +29,6 @@ def assert_equivalence_execution( assert e2_result is not None # Convert to pandas, as pandas has better comparison utils than arrow assert e1_result.schema == e2_result.schema - e1_table = e1_result.to_pandas() - e2_table = e2_result.to_pandas() + e1_table = e1_result.batches().to_pandas() + e2_table = e2_result.batches().to_pandas() pandas.testing.assert_frame_equal(e1_table, e2_table, rtol=1e-5) diff --git a/bigframes/testing/polars_session.py b/bigframes/testing/polars_session.py index ba6d502fcc..ca1fa329a2 100644 --- a/bigframes/testing/polars_session.py +++ b/bigframes/testing/polars_session.py @@ -51,11 +51,9 @@ def execute( pa_table = lazy_frame.collect().to_arrow() # Currently, pyarrow types might not quite be exactly the ones in the bigframes schema. # Nullability may be different, and might use large versions of list, string datatypes. - return bigframes.session.executor.ExecuteResult( - _arrow_batches=pa_table.to_batches(), - schema=array_value.schema, - total_bytes=pa_table.nbytes, - total_rows=pa_table.num_rows, + return bigframes.session.executor.LocalExecuteResult( + data=pa_table, + bf_schema=array_value.schema, ) def cached( diff --git a/tests/system/small/engines/test_read_local.py b/tests/system/small/engines/test_read_local.py index bf1a10beec..abdd29c4ac 100644 --- a/tests/system/small/engines/test_read_local.py +++ b/tests/system/small/engines/test_read_local.py @@ -31,7 +31,7 @@ def test_engines_read_local( engine, ): scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.dtype, item.column) + nodes.ScanItem(identifiers.ColumnId(item.column), item.column) for item in managed_data_source.schema.items ) local_node = nodes.ReadLocalNode( @@ -46,7 +46,7 @@ def test_engines_read_local_w_offsets( engine, ): scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.dtype, item.column) + nodes.ScanItem(identifiers.ColumnId(item.column), item.column) for item in managed_data_source.schema.items ) local_node = nodes.ReadLocalNode( @@ -64,7 +64,7 @@ def test_engines_read_local_w_col_subset( engine, ): scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.dtype, item.column) + nodes.ScanItem(identifiers.ColumnId(item.column), item.column) for item in managed_data_source.schema.items[::-2] ) local_node = nodes.ReadLocalNode( @@ -79,7 +79,7 @@ def test_engines_read_local_w_zero_row_source( engine, ): scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.dtype, item.column) + nodes.ScanItem(identifiers.ColumnId(item.column), item.column) for item in zero_row_source.schema.items ) local_node = nodes.ReadLocalNode( @@ -96,7 +96,7 @@ def test_engines_read_local_w_nested_source( engine, ): scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.dtype, item.column) + nodes.ScanItem(identifiers.ColumnId(item.column), item.column) for item in nested_data_source.schema.items ) local_node = nodes.ReadLocalNode( @@ -111,7 +111,7 @@ def test_engines_read_local_w_repeated_source( engine, ): scan_list = nodes.ScanList.from_items( - nodes.ScanItem(identifiers.ColumnId(item.column), item.dtype, item.column) + nodes.ScanItem(identifiers.ColumnId(item.column), item.column) for item in repeated_data_source.schema.items ) local_node = nodes.ReadLocalNode( diff --git a/tests/system/small/test_anywidget.py b/tests/system/small/test_anywidget.py index 8944ee5365..51c39c2aec 100644 --- a/tests/system/small/test_anywidget.py +++ b/tests/system/small/test_anywidget.py @@ -117,16 +117,31 @@ def mock_execute_result_with_params( """ Mocks an execution result with configurable total_rows and arrow_batches. """ - from bigframes.session.executor import ExecuteResult - - return ExecuteResult( - iter(arrow_batches_val), - schema=schema, - query_job=None, - total_bytes=None, - total_rows=total_rows_val, + from bigframes.session.executor import ( + ExecuteResult, + ExecutionMetadata, + ResultsIterator, ) + class MockExecuteResult(ExecuteResult): + @property + def execution_metadata(self) -> ExecutionMetadata: + return ExecutionMetadata() + + @property + def schema(self): + return schema + + def batches(self) -> ResultsIterator: + return ResultsIterator( + arrow_batches_val, + self.schema, + total_rows_val, + None, + ) + + return MockExecuteResult() + def _assert_html_matches_pandas_slice( table_html: str, diff --git a/tests/system/small/test_session.py b/tests/system/small/test_session.py index d3e646dc92..698f531d57 100644 --- a/tests/system/small/test_session.py +++ b/tests/system/small/test_session.py @@ -122,7 +122,7 @@ def test_read_gbq_tokyo( assert exec_result.query_job is not None assert exec_result.query_job.location == tokyo_location - assert len(expected) == exec_result.total_rows + assert len(expected) == exec_result.batches().approx_total_rows @pytest.mark.parametrize( @@ -951,7 +951,7 @@ def test_read_pandas_tokyo( assert result.query_job is not None assert result.query_job.location == tokyo_location - assert len(expected) == result.total_rows + assert len(expected) == result.batches().approx_total_rows @all_write_engines diff --git a/tests/unit/core/rewrite/conftest.py b/tests/unit/core/rewrite/conftest.py index bbfbde46f3..8c7ee290ae 100644 --- a/tests/unit/core/rewrite/conftest.py +++ b/tests/unit/core/rewrite/conftest.py @@ -72,7 +72,6 @@ def leaf(fake_session, table): return core.ArrayValue.from_table( session=fake_session, table=table, - schema=bigframes.core.schema.ArraySchema.from_bq_table(table), ).node @@ -81,5 +80,4 @@ def leaf_too(fake_session, table_too): return core.ArrayValue.from_table( session=fake_session, table=table_too, - schema=bigframes.core.schema.ArraySchema.from_bq_table(table_too), ).node diff --git a/tests/unit/core/rewrite/test_identifiers.py b/tests/unit/core/rewrite/test_identifiers.py index f95cd696d0..c23d69c9b9 100644 --- a/tests/unit/core/rewrite/test_identifiers.py +++ b/tests/unit/core/rewrite/test_identifiers.py @@ -56,7 +56,6 @@ def test_remap_variables_nested_join_stability(leaf, fake_session, table): leaf2_uncached = core.ArrayValue.from_table( session=fake_session, table=table, - schema=leaf.schema, ).node leaf2 = leaf2_uncached.remap_vars( { @@ -67,7 +66,6 @@ def test_remap_variables_nested_join_stability(leaf, fake_session, table): leaf3_uncached = core.ArrayValue.from_table( session=fake_session, table=table, - schema=leaf.schema, ).node leaf3 = leaf3_uncached.remap_vars( { diff --git a/tests/unit/session/test_local_scan_executor.py b/tests/unit/session/test_local_scan_executor.py index 30b1b5f78d..fc59253153 100644 --- a/tests/unit/session/test_local_scan_executor.py +++ b/tests/unit/session/test_local_scan_executor.py @@ -16,7 +16,6 @@ import pyarrow import pytest -from bigframes import dtypes from bigframes.core import identifiers, local_data, nodes from bigframes.session import local_scan_executor from bigframes.testing import mocks @@ -37,9 +36,6 @@ def create_read_local_node(arrow_table: pyarrow.Table): items=tuple( nodes.ScanItem( id=identifiers.ColumnId(column_name), - dtype=dtypes.arrow_dtype_to_bigframes_dtype( - arrow_table.field(column_name).type - ), source_id=column_name, ) for column_name in arrow_table.column_names @@ -77,7 +73,7 @@ def test_local_scan_executor_with_slice(start, stop, expected_rows, object_under ) result = object_under_test.execute(plan, ordered=True) - result_table = pyarrow.Table.from_batches(result.arrow_batches) + result_table = pyarrow.Table.from_batches(result.batches().arrow_batches) assert result_table.num_rows == expected_rows diff --git a/tests/unit/test_planner.py b/tests/unit/test_planner.py index c64b50395b..66d83f362d 100644 --- a/tests/unit/test_planner.py +++ b/tests/unit/test_planner.py @@ -39,7 +39,6 @@ LEAF: core.ArrayValue = core.ArrayValue.from_table( session=FAKE_SESSION, table=TABLE, - schema=bigframes.core.schema.ArraySchema.from_bq_table(TABLE), ) From 0ff1395f2fcde0dceaf00aaf3cccd30478b9b37c Mon Sep 17 00:00:00 2001 From: jialuoo Date: Fri, 31 Oct 2025 10:59:24 -0700 Subject: [PATCH 16/21] refactor: rename _apply_unary_ops to _apply_ops_to_sql (#2214) * refactor: rename _apply_unary_ops to _apply_ops_to_sql * fix lint * fix mypy --- bigframes/testing/utils.py | 13 +--- .../sqlglot/expressions/test_ai_ops.py | 24 +++---- .../sqlglot/expressions/test_array_ops.py | 8 +-- .../expressions/test_comparison_ops.py | 2 +- .../sqlglot/expressions/test_datetime_ops.py | 50 ++++++++------- .../sqlglot/expressions/test_generic_ops.py | 30 ++++----- .../sqlglot/expressions/test_geo_ops.py | 24 +++---- .../sqlglot/expressions/test_json_ops.py | 18 +++--- .../sqlglot/expressions/test_numeric_ops.py | 46 +++++++------- .../sqlglot/expressions/test_string_ops.py | 62 ++++++++++--------- .../sqlglot/expressions/test_struct_ops.py | 2 +- .../sqlglot/expressions/test_timedelta_ops.py | 2 +- 12 files changed, 142 insertions(+), 139 deletions(-) diff --git a/bigframes/testing/utils.py b/bigframes/testing/utils.py index a0bfc9e648..cf9c9fc031 100644 --- a/bigframes/testing/utils.py +++ b/bigframes/testing/utils.py @@ -448,12 +448,12 @@ def get_function_name(func, package_requirements=None, is_row_processor=False): return f"bigframes_{function_hash}" -def _apply_unary_ops( +def _apply_ops_to_sql( obj: bpd.DataFrame, ops_list: Sequence[ex.Expression], new_names: Sequence[str], ) -> str: - """Applies a list of unary ops to the given DataFrame and returns the SQL + """Applies a list of ops to the given DataFrame and returns the SQL representing the resulting DataFrame.""" array_value = obj._block.expr result, old_names = array_value.compute_values(ops_list) @@ -485,13 +485,6 @@ def _apply_nary_op( ) -> str: """Applies a nary op to the given DataFrame and return the SQL representing the resulting DataFrame.""" - array_value = obj._block.expr op_expr = op.as_expr(*args) - result, col_ids = array_value.compute_values([op_expr]) - - # Rename columns for deterministic golden SQL results. - assert len(col_ids) == 1 - result = result.rename_columns({col_ids[0]: args[0]}).select_columns([args[0]]) - - sql = result.session._executor.to_sql(result, enable_cache=False) + sql = _apply_ops_to_sql(obj, [op_expr], [args[0]]) # type: ignore return sql diff --git a/tests/unit/core/compile/sqlglot/expressions/test_ai_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_ai_ops.py index 13481d88c6..45024fc691 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_ai_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_ai_ops.py @@ -39,7 +39,7 @@ def test_ai_generate(scalar_types_df: dataframe.DataFrame, snapshot): output_schema=None, ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -58,7 +58,7 @@ def test_ai_generate_with_output_schema(scalar_types_df: dataframe.DataFrame, sn output_schema="x INT64, y FLOAT64", ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -82,7 +82,7 @@ def test_ai_generate_with_model_param(scalar_types_df: dataframe.DataFrame, snap output_schema=None, ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -100,7 +100,7 @@ def test_ai_generate_bool(scalar_types_df: dataframe.DataFrame, snapshot): model_params=None, ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -125,7 +125,7 @@ def test_ai_generate_bool_with_model_param( model_params=json.dumps(dict()), ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -144,7 +144,7 @@ def test_ai_generate_int(scalar_types_df: dataframe.DataFrame, snapshot): model_params=None, ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -170,7 +170,7 @@ def test_ai_generate_int_with_model_param( model_params=json.dumps(dict()), ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -189,7 +189,7 @@ def test_ai_generate_double(scalar_types_df: dataframe.DataFrame, snapshot): model_params=None, ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -215,7 +215,7 @@ def test_ai_generate_double_with_model_param( model_params=json.dumps(dict()), ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -230,7 +230,7 @@ def test_ai_if(scalar_types_df: dataframe.DataFrame, snapshot): connection_id=CONNECTION_ID, ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) @@ -246,7 +246,7 @@ def test_ai_classify(scalar_types_df: dataframe.DataFrame, snapshot): connection_id=CONNECTION_ID, ) - sql = utils._apply_unary_ops(scalar_types_df, [op.as_expr(col_name)], ["result"]) + sql = utils._apply_ops_to_sql(scalar_types_df, [op.as_expr(col_name)], ["result"]) snapshot.assert_match(sql, "out.sql") @@ -259,7 +259,7 @@ def test_ai_score(scalar_types_df: dataframe.DataFrame, snapshot): connection_id=CONNECTION_ID, ) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [op.as_expr(col_name, col_name)], ["result"] ) diff --git a/tests/unit/core/compile/sqlglot/expressions/test_array_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_array_ops.py index 407c7bbb3c..61b8b99479 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_array_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_array_ops.py @@ -25,7 +25,7 @@ def test_array_to_string(repeated_types_df: bpd.DataFrame, snapshot): col_name = "string_list_col" bf_df = repeated_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.ArrayToStringOp(delimiter=".").as_expr(col_name)], [col_name] ) @@ -35,7 +35,7 @@ def test_array_to_string(repeated_types_df: bpd.DataFrame, snapshot): def test_array_index(repeated_types_df: bpd.DataFrame, snapshot): col_name = "string_list_col" bf_df = repeated_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [convert_index(1).as_expr(col_name)], [col_name] ) @@ -45,7 +45,7 @@ def test_array_index(repeated_types_df: bpd.DataFrame, snapshot): def test_array_slice_with_only_start(repeated_types_df: bpd.DataFrame, snapshot): col_name = "string_list_col" bf_df = repeated_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [convert_slice(slice(1, None)).as_expr(col_name)], [col_name] ) @@ -55,7 +55,7 @@ def test_array_slice_with_only_start(repeated_types_df: bpd.DataFrame, snapshot) def test_array_slice_with_start_and_stop(repeated_types_df: bpd.DataFrame, snapshot): col_name = "string_list_col" bf_df = repeated_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [convert_slice(slice(1, 5)).as_expr(col_name)], [col_name] ) diff --git a/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py index f278a15f3c..52b57623b3 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_comparison_ops.py @@ -40,7 +40,7 @@ def test_is_in(scalar_types_df: bpd.DataFrame, snapshot): "float_in_ints": ops.IsInOp(values=(1, 2, 3, None)).as_expr(float_col), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py index 3261113806..6384dc79a9 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py @@ -25,7 +25,7 @@ def test_date(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.date_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.date_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -33,7 +33,7 @@ def test_date(scalar_types_df: bpd.DataFrame, snapshot): def test_day(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.day_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.day_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -43,14 +43,14 @@ def test_dayofweek(scalar_types_df: bpd.DataFrame, snapshot): bf_df = scalar_types_df[col_names] ops_map = {col_name: ops.dayofweek_op.as_expr(col_name) for col_name in col_names} - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") def test_dayofyear(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.dayofyear_op.as_expr(col_name)], [col_name] ) @@ -75,7 +75,7 @@ def test_floor_dt(scalar_types_df: bpd.DataFrame, snapshot): "datetime_col_us": ops.FloorDtOp("us").as_expr("datetime_col"), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -85,7 +85,7 @@ def test_floor_dt_op_invalid_freq(scalar_types_df: bpd.DataFrame): with pytest.raises( NotImplementedError, match="Unsupported freq paramater: invalid" ): - utils._apply_unary_ops( + utils._apply_ops_to_sql( bf_df, [ops.FloorDtOp(freq="invalid").as_expr(col_name)], # type:ignore [col_name], @@ -95,7 +95,7 @@ def test_floor_dt_op_invalid_freq(scalar_types_df: bpd.DataFrame): def test_hour(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.hour_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.hour_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -103,7 +103,7 @@ def test_hour(scalar_types_df: bpd.DataFrame, snapshot): def test_minute(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.minute_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.minute_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -111,7 +111,7 @@ def test_minute(scalar_types_df: bpd.DataFrame, snapshot): def test_month(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.month_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.month_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -119,7 +119,7 @@ def test_month(scalar_types_df: bpd.DataFrame, snapshot): def test_normalize(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.normalize_op.as_expr(col_name)], [col_name] ) @@ -129,7 +129,7 @@ def test_normalize(scalar_types_df: bpd.DataFrame, snapshot): def test_quarter(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.quarter_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.quarter_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -137,7 +137,7 @@ def test_quarter(scalar_types_df: bpd.DataFrame, snapshot): def test_second(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.second_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.second_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -145,7 +145,7 @@ def test_second(scalar_types_df: bpd.DataFrame, snapshot): def test_strftime(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrftimeOp("%Y-%m-%d").as_expr(col_name)], [col_name] ) @@ -155,7 +155,7 @@ def test_strftime(scalar_types_df: bpd.DataFrame, snapshot): def test_time(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.time_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.time_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -163,7 +163,7 @@ def test_time(scalar_types_df: bpd.DataFrame, snapshot): def test_to_datetime(scalar_types_df: bpd.DataFrame, snapshot): col_name = "int64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.ToDatetimeOp().as_expr(col_name)], [col_name] ) @@ -173,7 +173,7 @@ def test_to_datetime(scalar_types_df: bpd.DataFrame, snapshot): def test_to_timestamp(scalar_types_df: bpd.DataFrame, snapshot): col_name = "int64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.ToTimestampOp().as_expr(col_name)], [col_name] ) @@ -183,7 +183,7 @@ def test_to_timestamp(scalar_types_df: bpd.DataFrame, snapshot): def test_unix_micros(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.UnixMicros().as_expr(col_name)], [col_name] ) @@ -193,7 +193,7 @@ def test_unix_micros(scalar_types_df: bpd.DataFrame, snapshot): def test_unix_millis(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.UnixMillis().as_expr(col_name)], [col_name] ) @@ -203,7 +203,7 @@ def test_unix_millis(scalar_types_df: bpd.DataFrame, snapshot): def test_unix_seconds(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.UnixSeconds().as_expr(col_name)], [col_name] ) @@ -213,7 +213,7 @@ def test_unix_seconds(scalar_types_df: bpd.DataFrame, snapshot): def test_year(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.year_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.year_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -221,7 +221,7 @@ def test_year(scalar_types_df: bpd.DataFrame, snapshot): def test_iso_day(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.iso_day_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.iso_day_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -229,7 +229,9 @@ def test_iso_day(scalar_types_df: bpd.DataFrame, snapshot): def test_iso_week(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.iso_week_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql( + bf_df, [ops.iso_week_op.as_expr(col_name)], [col_name] + ) snapshot.assert_match(sql, "out.sql") @@ -237,7 +239,9 @@ def test_iso_week(scalar_types_df: bpd.DataFrame, snapshot): def test_iso_year(scalar_types_df: bpd.DataFrame, snapshot): col_name = "timestamp_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.iso_year_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql( + bf_df, [ops.iso_year_op.as_expr(col_name)], [col_name] + ) snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py index fd9732bf89..aa40c21fd9 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_generic_ops.py @@ -43,7 +43,7 @@ def test_astype_int(scalar_types_df: bpd.DataFrame, snapshot): "str_const": ops.AsTypeOp(to_type=to_type).as_expr(ex.const("100")), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -56,7 +56,7 @@ def test_astype_float(scalar_types_df: bpd.DataFrame, snapshot): "str_const": ops.AsTypeOp(to_type=to_type).as_expr(ex.const("1.34235e4")), "bool_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr("bool_col"), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -71,7 +71,7 @@ def test_astype_bool(scalar_types_df: bpd.DataFrame, snapshot): "float64_col" ), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -90,7 +90,7 @@ def test_astype_time_like(scalar_types_df: bpd.DataFrame, snapshot): to_type=dtypes.TIME_DTYPE, safe=True ).as_expr("int64_col"), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -103,7 +103,7 @@ def test_astype_string(scalar_types_df: bpd.DataFrame, snapshot): "bool_col": ops.AsTypeOp(to_type=to_type).as_expr("bool_col"), "bool_w_safe": ops.AsTypeOp(to_type=to_type, safe=True).as_expr("bool_col"), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -122,7 +122,7 @@ def test_astype_json(scalar_types_df: bpd.DataFrame, snapshot): "string_col" ), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -138,7 +138,7 @@ def test_astype_from_json(json_types_df: bpd.DataFrame, snapshot): "json_col" ), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -152,7 +152,7 @@ def test_astype_json_invalid( "datetime_col" ), } - utils._apply_unary_ops( + utils._apply_ops_to_sql( scalar_types_df, list(ops_map_to.values()), list(ops_map_to.keys()) ) @@ -163,7 +163,7 @@ def test_astype_json_invalid( "json_col" ), } - utils._apply_unary_ops( + utils._apply_ops_to_sql( json_types_df, list(ops_map_from.values()), list(ops_map_from.keys()) ) @@ -228,7 +228,7 @@ def test_clip(scalar_types_df: bpd.DataFrame, snapshot): def test_hash(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.hash_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.hash_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -240,7 +240,7 @@ def test_invert(scalar_types_df: bpd.DataFrame, snapshot): "bytes_col": ops.invert_op.as_expr("bytes_col"), "bool_col": ops.invert_op.as_expr("bool_col"), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -248,7 +248,7 @@ def test_invert(scalar_types_df: bpd.DataFrame, snapshot): def test_isnull(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.isnull_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.isnull_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -256,14 +256,14 @@ def test_isnull(scalar_types_df: bpd.DataFrame, snapshot): def test_notnull(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.notnull_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.notnull_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") def test_row_key(scalar_types_df: bpd.DataFrame, snapshot): column_ids = (col for col in scalar_types_df._block.expr.column_ids) - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( scalar_types_df, [ops.RowKey().as_expr(*column_ids)], ["row_key"] ) snapshot.assert_match(sql, "out.sql") @@ -283,7 +283,7 @@ def test_sql_scalar_op(scalar_types_df: bpd.DataFrame, snapshot): def test_map(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.MapOp(mappings=(("value1", "mapped1"),)).as_expr(col_name)], [col_name], diff --git a/tests/unit/core/compile/sqlglot/expressions/test_geo_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_geo_ops.py index e136d172f6..9b99b37fb6 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_geo_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_geo_ops.py @@ -24,7 +24,9 @@ def test_geo_area(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.geo_area_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql( + bf_df, [ops.geo_area_op.as_expr(col_name)], [col_name] + ) snapshot.assert_match(sql, "out.sql") @@ -32,7 +34,7 @@ def test_geo_area(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_astext(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.geo_st_astext_op.as_expr(col_name)], [col_name] ) @@ -42,7 +44,7 @@ def test_geo_st_astext(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_boundary(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.geo_st_boundary_op.as_expr(col_name)], [col_name] ) @@ -52,7 +54,7 @@ def test_geo_st_boundary(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_buffer(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.GeoStBufferOp(1.0, 8.0, False).as_expr(col_name)], [col_name] ) @@ -62,7 +64,7 @@ def test_geo_st_buffer(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_centroid(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.geo_st_centroid_op.as_expr(col_name)], [col_name] ) @@ -72,7 +74,7 @@ def test_geo_st_centroid(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_convexhull(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.geo_st_convexhull_op.as_expr(col_name)], [col_name] ) @@ -82,7 +84,7 @@ def test_geo_st_convexhull(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_geogfromtext(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.geo_st_geogfromtext_op.as_expr(col_name)], [col_name] ) @@ -92,7 +94,7 @@ def test_geo_st_geogfromtext(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_isclosed(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.geo_st_isclosed_op.as_expr(col_name)], [col_name] ) @@ -102,7 +104,7 @@ def test_geo_st_isclosed(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_st_length(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.GeoStLengthOp(True).as_expr(col_name)], [col_name] ) @@ -112,7 +114,7 @@ def test_geo_st_length(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_x(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.geo_x_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.geo_x_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -120,6 +122,6 @@ def test_geo_x(scalar_types_df: bpd.DataFrame, snapshot): def test_geo_y(scalar_types_df: bpd.DataFrame, snapshot): col_name = "geography_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.geo_y_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.geo_y_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_json_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_json_ops.py index 75206091e0..ca0896bd03 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_json_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_json_ops.py @@ -25,7 +25,7 @@ def test_json_extract(json_types_df: bpd.DataFrame, snapshot): col_name = "json_col" bf_df = json_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.JSONExtract(json_path="$").as_expr(col_name)], [col_name] ) @@ -35,7 +35,7 @@ def test_json_extract(json_types_df: bpd.DataFrame, snapshot): def test_json_extract_array(json_types_df: bpd.DataFrame, snapshot): col_name = "json_col" bf_df = json_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.JSONExtractArray(json_path="$").as_expr(col_name)], [col_name] ) @@ -45,7 +45,7 @@ def test_json_extract_array(json_types_df: bpd.DataFrame, snapshot): def test_json_extract_string_array(json_types_df: bpd.DataFrame, snapshot): col_name = "json_col" bf_df = json_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.JSONExtractStringArray(json_path="$").as_expr(col_name)], [col_name] ) @@ -55,7 +55,7 @@ def test_json_extract_string_array(json_types_df: bpd.DataFrame, snapshot): def test_json_query(json_types_df: bpd.DataFrame, snapshot): col_name = "json_col" bf_df = json_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.JSONQuery(json_path="$").as_expr(col_name)], [col_name] ) @@ -65,7 +65,7 @@ def test_json_query(json_types_df: bpd.DataFrame, snapshot): def test_json_query_array(json_types_df: bpd.DataFrame, snapshot): col_name = "json_col" bf_df = json_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.JSONQueryArray(json_path="$").as_expr(col_name)], [col_name] ) @@ -75,7 +75,7 @@ def test_json_query_array(json_types_df: bpd.DataFrame, snapshot): def test_json_value(json_types_df: bpd.DataFrame, snapshot): col_name = "json_col" bf_df = json_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.JSONValue(json_path="$").as_expr(col_name)], [col_name] ) @@ -85,7 +85,9 @@ def test_json_value(json_types_df: bpd.DataFrame, snapshot): def test_parse_json(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.ParseJSON().as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql( + bf_df, [ops.ParseJSON().as_expr(col_name)], [col_name] + ) snapshot.assert_match(sql, "out.sql") @@ -93,7 +95,7 @@ def test_parse_json(scalar_types_df: bpd.DataFrame, snapshot): def test_to_json_string(json_types_df: bpd.DataFrame, snapshot): col_name = "json_col" bf_df = json_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.ToJSONString().as_expr(col_name)], [col_name] ) diff --git a/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py index ab9fe53092..c66fe15c16 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_numeric_ops.py @@ -26,7 +26,7 @@ def test_arccosh(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.arccosh_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.arccosh_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -34,7 +34,7 @@ def test_arccosh(scalar_types_df: bpd.DataFrame, snapshot): def test_arccos(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.arccos_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.arccos_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -42,7 +42,7 @@ def test_arccos(scalar_types_df: bpd.DataFrame, snapshot): def test_arcsin(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.arcsin_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.arcsin_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -50,7 +50,7 @@ def test_arcsin(scalar_types_df: bpd.DataFrame, snapshot): def test_arcsinh(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.arcsinh_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.arcsinh_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -58,7 +58,7 @@ def test_arcsinh(scalar_types_df: bpd.DataFrame, snapshot): def test_arctan(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.arctan_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.arctan_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -66,7 +66,7 @@ def test_arctan(scalar_types_df: bpd.DataFrame, snapshot): def test_arctanh(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.arctanh_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.arctanh_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -74,7 +74,7 @@ def test_arctanh(scalar_types_df: bpd.DataFrame, snapshot): def test_abs(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.abs_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.abs_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -82,7 +82,7 @@ def test_abs(scalar_types_df: bpd.DataFrame, snapshot): def test_ceil(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.ceil_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.ceil_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -90,7 +90,7 @@ def test_ceil(scalar_types_df: bpd.DataFrame, snapshot): def test_cos(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.cos_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.cos_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -98,7 +98,7 @@ def test_cos(scalar_types_df: bpd.DataFrame, snapshot): def test_cosh(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.cosh_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.cosh_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -106,7 +106,7 @@ def test_cosh(scalar_types_df: bpd.DataFrame, snapshot): def test_exp(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.exp_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.exp_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -114,7 +114,7 @@ def test_exp(scalar_types_df: bpd.DataFrame, snapshot): def test_expm1(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.expm1_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.expm1_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -122,7 +122,7 @@ def test_expm1(scalar_types_df: bpd.DataFrame, snapshot): def test_floor(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.floor_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.floor_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -130,7 +130,7 @@ def test_floor(scalar_types_df: bpd.DataFrame, snapshot): def test_ln(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.ln_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.ln_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -138,7 +138,7 @@ def test_ln(scalar_types_df: bpd.DataFrame, snapshot): def test_log10(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.log10_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.log10_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -146,7 +146,7 @@ def test_log10(scalar_types_df: bpd.DataFrame, snapshot): def test_log1p(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.log1p_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.log1p_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -154,7 +154,7 @@ def test_log1p(scalar_types_df: bpd.DataFrame, snapshot): def test_neg(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.neg_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.neg_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -162,7 +162,7 @@ def test_neg(scalar_types_df: bpd.DataFrame, snapshot): def test_pos(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.pos_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.pos_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -184,7 +184,7 @@ def test_round(scalar_types_df: bpd.DataFrame, snapshot): def test_sqrt(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.sqrt_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.sqrt_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -192,7 +192,7 @@ def test_sqrt(scalar_types_df: bpd.DataFrame, snapshot): def test_sin(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.sin_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.sin_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -200,7 +200,7 @@ def test_sin(scalar_types_df: bpd.DataFrame, snapshot): def test_sinh(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.sinh_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.sinh_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -208,7 +208,7 @@ def test_sinh(scalar_types_df: bpd.DataFrame, snapshot): def test_tan(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.tan_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.tan_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -216,7 +216,7 @@ def test_tan(scalar_types_df: bpd.DataFrame, snapshot): def test_tanh(scalar_types_df: bpd.DataFrame, snapshot): col_name = "float64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.tanh_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.tanh_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_string_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_string_ops.py index 99dbce9410..b20c038ed0 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_string_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_string_ops.py @@ -25,7 +25,7 @@ def test_capitalize(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.capitalize_op.as_expr(col_name)], [col_name] ) @@ -40,14 +40,14 @@ def test_endswith(scalar_types_df: bpd.DataFrame, snapshot): "double": ops.EndsWithOp(pat=("ab", "cd")).as_expr(col_name), "empty": ops.EndsWithOp(pat=()).as_expr(col_name), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") def test_isalnum(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.isalnum_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.isalnum_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -55,7 +55,7 @@ def test_isalnum(scalar_types_df: bpd.DataFrame, snapshot): def test_isalpha(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.isalpha_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.isalpha_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -63,7 +63,7 @@ def test_isalpha(scalar_types_df: bpd.DataFrame, snapshot): def test_isdecimal(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.isdecimal_op.as_expr(col_name)], [col_name] ) @@ -73,7 +73,7 @@ def test_isdecimal(scalar_types_df: bpd.DataFrame, snapshot): def test_isdigit(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.isdigit_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.isdigit_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -81,7 +81,7 @@ def test_isdigit(scalar_types_df: bpd.DataFrame, snapshot): def test_islower(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.islower_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.islower_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -89,7 +89,7 @@ def test_islower(scalar_types_df: bpd.DataFrame, snapshot): def test_isnumeric(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.isnumeric_op.as_expr(col_name)], [col_name] ) @@ -99,7 +99,7 @@ def test_isnumeric(scalar_types_df: bpd.DataFrame, snapshot): def test_isspace(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.isspace_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.isspace_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -107,7 +107,7 @@ def test_isspace(scalar_types_df: bpd.DataFrame, snapshot): def test_isupper(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.isupper_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.isupper_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -115,7 +115,7 @@ def test_isupper(scalar_types_df: bpd.DataFrame, snapshot): def test_len(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.len_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.len_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -123,7 +123,7 @@ def test_len(scalar_types_df: bpd.DataFrame, snapshot): def test_lower(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.lower_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.lower_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -131,7 +131,7 @@ def test_lower(scalar_types_df: bpd.DataFrame, snapshot): def test_lstrip(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrLstripOp(" ").as_expr(col_name)], [col_name] ) @@ -141,7 +141,7 @@ def test_lstrip(scalar_types_df: bpd.DataFrame, snapshot): def test_replace_str(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.ReplaceStrOp("e", "a").as_expr(col_name)], [col_name] ) snapshot.assert_match(sql, "out.sql") @@ -150,7 +150,7 @@ def test_replace_str(scalar_types_df: bpd.DataFrame, snapshot): def test_regex_replace_str(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.RegexReplaceStrOp(r"e", "a").as_expr(col_name)], [col_name] ) snapshot.assert_match(sql, "out.sql") @@ -159,7 +159,7 @@ def test_regex_replace_str(scalar_types_df: bpd.DataFrame, snapshot): def test_reverse(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.reverse_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.reverse_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -167,7 +167,7 @@ def test_reverse(scalar_types_df: bpd.DataFrame, snapshot): def test_rstrip(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrRstripOp(" ").as_expr(col_name)], [col_name] ) @@ -183,14 +183,16 @@ def test_startswith(scalar_types_df: bpd.DataFrame, snapshot): "double": ops.StartsWithOp(pat=("ab", "cd")).as_expr(col_name), "empty": ops.StartsWithOp(pat=()).as_expr(col_name), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") def test_str_get(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.StrGetOp(1).as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql( + bf_df, [ops.StrGetOp(1).as_expr(col_name)], [col_name] + ) snapshot.assert_match(sql, "out.sql") @@ -203,14 +205,14 @@ def test_str_pad(scalar_types_df: bpd.DataFrame, snapshot): "right": ops.StrPadOp(length=10, fillchar="-", side="right").as_expr(col_name), "both": ops.StrPadOp(length=10, fillchar="-", side="both").as_expr(col_name), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") def test_str_slice(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrSliceOp(1, 3).as_expr(col_name)], [col_name] ) @@ -220,7 +222,7 @@ def test_str_slice(scalar_types_df: bpd.DataFrame, snapshot): def test_strip(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrStripOp(" ").as_expr(col_name)], [col_name] ) @@ -230,7 +232,7 @@ def test_strip(scalar_types_df: bpd.DataFrame, snapshot): def test_str_contains(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrContainsOp("e").as_expr(col_name)], [col_name] ) @@ -240,7 +242,7 @@ def test_str_contains(scalar_types_df: bpd.DataFrame, snapshot): def test_str_contains_regex(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrContainsRegexOp("e").as_expr(col_name)], [col_name] ) @@ -250,7 +252,7 @@ def test_str_contains_regex(scalar_types_df: bpd.DataFrame, snapshot): def test_str_extract(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrExtractOp(r"([a-z]*)", 1).as_expr(col_name)], [col_name] ) @@ -260,7 +262,7 @@ def test_str_extract(scalar_types_df: bpd.DataFrame, snapshot): def test_str_repeat(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StrRepeatOp(2).as_expr(col_name)], [col_name] ) snapshot.assert_match(sql, "out.sql") @@ -275,7 +277,7 @@ def test_str_find(scalar_types_df: bpd.DataFrame, snapshot): "none_end": ops.StrFindOp("e", start=None, end=5).as_expr(col_name), "start_end": ops.StrFindOp("e", start=2, end=5).as_expr(col_name), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") @@ -283,7 +285,7 @@ def test_str_find(scalar_types_df: bpd.DataFrame, snapshot): def test_string_split(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.StringSplitOp(pat=",").as_expr(col_name)], [col_name] ) snapshot.assert_match(sql, "out.sql") @@ -292,7 +294,7 @@ def test_string_split(scalar_types_df: bpd.DataFrame, snapshot): def test_upper(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops(bf_df, [ops.upper_op.as_expr(col_name)], [col_name]) + sql = utils._apply_ops_to_sql(bf_df, [ops.upper_op.as_expr(col_name)], [col_name]) snapshot.assert_match(sql, "out.sql") @@ -300,7 +302,7 @@ def test_upper(scalar_types_df: bpd.DataFrame, snapshot): def test_zfill(scalar_types_df: bpd.DataFrame, snapshot): col_name = "string_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.ZfillOp(width=10).as_expr(col_name)], [col_name] ) snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py index 7e67e44cd3..0e24426fe8 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_struct_ops.py @@ -55,7 +55,7 @@ def test_struct_field(nested_structs_types_df: bpd.DataFrame, snapshot): # When an index integer is provided. "int": ops.StructFieldOp(0).as_expr(col_name), } - sql = utils._apply_unary_ops(bf_df, list(ops_map.values()), list(ops_map.keys())) + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) snapshot.assert_match(sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/expressions/test_timedelta_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_timedelta_ops.py index 1f01047ba9..8675b42bec 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_timedelta_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_timedelta_ops.py @@ -33,7 +33,7 @@ def test_to_timedelta(scalar_types_df: bpd.DataFrame, snapshot): def test_timedelta_floor(scalar_types_df: bpd.DataFrame, snapshot): col_name = "int64_col" bf_df = scalar_types_df[[col_name]] - sql = utils._apply_unary_ops( + sql = utils._apply_ops_to_sql( bf_df, [ops.timedelta_floor_op.as_expr(col_name)], [col_name] ) From 38abfd8750dc8980d5e0183b7312498d506636bd Mon Sep 17 00:00:00 2001 From: Shenyang Cai Date: Fri, 31 Oct 2025 11:29:03 -0700 Subject: [PATCH 17/21] refactor: move main merge logic from df to reshape package (#2217) * refactor: move main merge logic from df to reshape package * fix mypy and tests --- bigframes/core/reshape/merge.py | 115 ++++++++++++++++++++++++++------ bigframes/dataframe.py | 88 ++---------------------- 2 files changed, 102 insertions(+), 101 deletions(-) diff --git a/bigframes/core/reshape/merge.py b/bigframes/core/reshape/merge.py index e1750d5c7a..5c6cba4915 100644 --- a/bigframes/core/reshape/merge.py +++ b/bigframes/core/reshape/merge.py @@ -18,20 +18,17 @@ from __future__ import annotations -import typing -from typing import Literal, Optional +from typing import Literal, Sequence import bigframes_vendored.pandas.core.reshape.merge as vendored_pandas_merge -# Avoid cirular imports. -if typing.TYPE_CHECKING: - import bigframes.dataframe - import bigframes.series +from bigframes import dataframe, series +from bigframes.core import blocks, utils def merge( - left: bigframes.dataframe.DataFrame, - right: bigframes.dataframe.DataFrame, + left: dataframe.DataFrame, + right: dataframe.DataFrame, how: Literal[ "inner", "left", @@ -39,33 +36,75 @@ def merge( "right", "cross", ] = "inner", - on: Optional[str] = None, + on: blocks.Label | Sequence[blocks.Label] | None = None, *, - left_on: Optional[str] = None, - right_on: Optional[str] = None, + left_on: blocks.Label | Sequence[blocks.Label] | None = None, + right_on: blocks.Label | Sequence[blocks.Label] | None = None, sort: bool = False, suffixes: tuple[str, str] = ("_x", "_y"), -) -> bigframes.dataframe.DataFrame: +) -> dataframe.DataFrame: left = _validate_operand(left) right = _validate_operand(right) - return left.merge( - right, - how=how, - on=on, - left_on=left_on, - right_on=right_on, + if how == "cross": + if on is not None: + raise ValueError("'on' is not supported for cross join.") + result_block = left._block.merge( + right._block, + left_join_ids=[], + right_join_ids=[], + suffixes=suffixes, + how=how, + sort=True, + ) + return dataframe.DataFrame(result_block) + + left_on, right_on = _validate_left_right_on( + left, right, on, left_on=left_on, right_on=right_on + ) + + if utils.is_list_like(left_on): + left_on = list(left_on) # type: ignore + else: + left_on = [left_on] + + if utils.is_list_like(right_on): + right_on = list(right_on) # type: ignore + else: + right_on = [right_on] + + left_join_ids = [] + for label in left_on: # type: ignore + left_col_id = left._resolve_label_exact(label) + # 0 elements already throws an exception + if not left_col_id: + raise ValueError(f"No column {label} found in self.") + left_join_ids.append(left_col_id) + + right_join_ids = [] + for label in right_on: # type: ignore + right_col_id = right._resolve_label_exact(label) + if not right_col_id: + raise ValueError(f"No column {label} found in other.") + right_join_ids.append(right_col_id) + + block = left._block.merge( + right._block, + how, + left_join_ids, + right_join_ids, sort=sort, suffixes=suffixes, ) + return dataframe.DataFrame(block) merge.__doc__ = vendored_pandas_merge.merge.__doc__ def _validate_operand( - obj: bigframes.dataframe.DataFrame | bigframes.series.Series, -) -> bigframes.dataframe.DataFrame: + obj: dataframe.DataFrame | series.Series, +) -> dataframe.DataFrame: import bigframes.dataframe import bigframes.series @@ -79,3 +118,39 @@ def _validate_operand( raise TypeError( f"Can only merge bigframes.series.Series or bigframes.dataframe.DataFrame objects, a {type(obj)} was passed" ) + + +def _validate_left_right_on( + left: dataframe.DataFrame, + right: dataframe.DataFrame, + on: blocks.Label | Sequence[blocks.Label] | None = None, + *, + left_on: blocks.Label | Sequence[blocks.Label] | None = None, + right_on: blocks.Label | Sequence[blocks.Label] | None = None, +): + if on is not None: + if left_on is not None or right_on is not None: + raise ValueError( + "Can not pass both `on` and `left_on` + `right_on` params." + ) + return on, on + + if left_on is not None and right_on is not None: + return left_on, right_on + + left_cols = left.columns + right_cols = right.columns + common_cols = left_cols.intersection(right_cols) + if len(common_cols) == 0: + raise ValueError( + "No common columns to perform merge on." + f"Merge options: left_on={left_on}, " + f"right_on={right_on}, " + ) + if ( + not left_cols.join(common_cols, how="inner").is_unique + or not right_cols.join(common_cols, how="inner").is_unique + ): + raise ValueError(f"Data columns not unique: {repr(common_cols)}") + + return common_cols, common_cols diff --git a/bigframes/dataframe.py b/bigframes/dataframe.py index f016fddd83..df8c87416f 100644 --- a/bigframes/dataframe.py +++ b/bigframes/dataframe.py @@ -3653,92 +3653,18 @@ def merge( sort: bool = False, suffixes: tuple[str, str] = ("_x", "_y"), ) -> DataFrame: - if how == "cross": - if on is not None: - raise ValueError("'on' is not supported for cross join.") - result_block = self._block.merge( - right._block, - left_join_ids=[], - right_join_ids=[], - suffixes=suffixes, - how=how, - sort=True, - ) - return DataFrame(result_block) - - left_on, right_on = self._validate_left_right_on( - right, on, left_on=left_on, right_on=right_on - ) - - if utils.is_list_like(left_on): - left_on = list(left_on) # type: ignore - else: - left_on = [left_on] + from bigframes.core.reshape import merge - if utils.is_list_like(right_on): - right_on = list(right_on) # type: ignore - else: - right_on = [right_on] - - left_join_ids = [] - for label in left_on: # type: ignore - left_col_id = self._resolve_label_exact(label) - # 0 elements already throws an exception - if not left_col_id: - raise ValueError(f"No column {label} found in self.") - left_join_ids.append(left_col_id) - - right_join_ids = [] - for label in right_on: # type: ignore - right_col_id = right._resolve_label_exact(label) - if not right_col_id: - raise ValueError(f"No column {label} found in other.") - right_join_ids.append(right_col_id) - - block = self._block.merge( - right._block, + return merge.merge( + self, + right, how, - left_join_ids, - right_join_ids, + on, + left_on=left_on, + right_on=right_on, sort=sort, suffixes=suffixes, ) - return DataFrame(block) - - def _validate_left_right_on( - self, - right: DataFrame, - on: Union[blocks.Label, Sequence[blocks.Label], None] = None, - *, - left_on: Union[blocks.Label, Sequence[blocks.Label], None] = None, - right_on: Union[blocks.Label, Sequence[blocks.Label], None] = None, - ): - if on is not None: - if left_on is not None or right_on is not None: - raise ValueError( - "Can not pass both `on` and `left_on` + `right_on` params." - ) - return on, on - - if left_on is not None and right_on is not None: - return left_on, right_on - - left_cols = self.columns - right_cols = right.columns - common_cols = left_cols.intersection(right_cols) - if len(common_cols) == 0: - raise ValueError( - "No common columns to perform merge on." - f"Merge options: left_on={left_on}, " - f"right_on={right_on}, " - ) - if ( - not left_cols.join(common_cols, how="inner").is_unique - or not right_cols.join(common_cols, how="inner").is_unique - ): - raise ValueError(f"Data columns not unique: {repr(common_cols)}") - - return common_cols, common_cols def join( self, From 5e006e404b65c32e5b1d342ebfcfce59ee592c8c Mon Sep 17 00:00:00 2001 From: TrevorBergeron Date: Fri, 31 Oct 2025 13:01:24 -0700 Subject: [PATCH 18/21] feat: Add Series.dt.day_name (#2218) --- bigframes/operations/datetimes.py | 3 +++ .../system/small/operations/test_datetimes.py | 15 ++++++++++++ .../pandas/core/indexes/accessor.py | 24 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/bigframes/operations/datetimes.py b/bigframes/operations/datetimes.py index 608089ab41..3f2d16a896 100644 --- a/bigframes/operations/datetimes.py +++ b/bigframes/operations/datetimes.py @@ -148,6 +148,9 @@ def unit(self) -> str: # Assumption: pyarrow dtype return self._data._dtype.pyarrow_dtype.unit + def day_name(self) -> series.Series: + return self.strftime("%A") + def strftime(self, date_format: str) -> series.Series: return self._data._apply_unary_op(ops.StrftimeOp(date_format=date_format)) diff --git a/tests/system/small/operations/test_datetimes.py b/tests/system/small/operations/test_datetimes.py index 1462a68b49..d6a90597b4 100644 --- a/tests/system/small/operations/test_datetimes.py +++ b/tests/system/small/operations/test_datetimes.py @@ -123,6 +123,21 @@ def test_dt_dayofyear(scalars_dfs, col_name): assert_series_equal(pd_result, bf_result, check_dtype=False) +@pytest.mark.parametrize( + ("col_name",), + DATE_COLUMNS, +) +def test_dt_day_name(scalars_dfs, col_name): + pytest.importorskip("pandas", minversion="2.0.0") + scalars_df, scalars_pandas_df = scalars_dfs + bf_series: bigframes.series.Series = scalars_df[col_name] + + bf_result = bf_series.dt.day_name().to_pandas() + pd_result = scalars_pandas_df[col_name].dt.day_name() + + assert_series_equal(pd_result, bf_result, check_dtype=False) + + @pytest.mark.parametrize( ("col_name",), DATE_COLUMNS, diff --git a/third_party/bigframes_vendored/pandas/core/indexes/accessor.py b/third_party/bigframes_vendored/pandas/core/indexes/accessor.py index b9eb363b29..ee4de44b80 100644 --- a/third_party/bigframes_vendored/pandas/core/indexes/accessor.py +++ b/third_party/bigframes_vendored/pandas/core/indexes/accessor.py @@ -91,6 +91,30 @@ def day_of_week(self): raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + @property + def day_name(self): + """ + Return the day names in english. + + **Examples:** + >>> s = bpd.Series(pd.date_range(start="2018-01-01", freq="D", periods=3)) + >>> s + 0 2018-01-01 00:00:00 + 1 2018-01-02 00:00:00 + 2 2018-01-03 00:00:00 + dtype: timestamp[us][pyarrow] + >>> s.dt.day_name() + 0 Monday + 1 Tuesday + 2 Wednesday + dtype: string + + Returns: + Series: Series of day names. + + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + @property def dayofyear(self): """The ordinal day of the year. From ef5e83acedf005cbe1e6ad174bec523ac50517d7 Mon Sep 17 00:00:00 2001 From: TrevorBergeron Date: Fri, 31 Oct 2025 14:16:34 -0700 Subject: [PATCH 19/21] feat: Polars engine supports std, var (#2215) --- bigframes/core/compile/polars/compiler.py | 6 ++++-- bigframes/session/polars_executor.py | 3 +++ tests/system/small/engines/test_aggregation.py | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/bigframes/core/compile/polars/compiler.py b/bigframes/core/compile/polars/compiler.py index d48ddba0cc..3211d6ebf7 100644 --- a/bigframes/core/compile/polars/compiler.py +++ b/bigframes/core/compile/polars/compiler.py @@ -535,9 +535,11 @@ def compile_agg_op( if isinstance(op, agg_ops.StdOp): return pl.std(inputs[0]) if isinstance(op, agg_ops.VarOp): - return pl.var(inputs[0]) + # polars var doesnt' support decimal, so use std instead + return pl.std(inputs[0]).pow(2) if isinstance(op, agg_ops.PopVarOp): - return pl.var(inputs[0], ddof=0) + # polars var doesnt' support decimal, so use std instead + return pl.std(inputs[0], ddof=0).pow(2) if isinstance(op, agg_ops.FirstNonNullOp): return pl.col(*inputs).drop_nulls().first() if isinstance(op, agg_ops.LastNonNullOp): diff --git a/bigframes/session/polars_executor.py b/bigframes/session/polars_executor.py index 00f8f37934..575beff8fc 100644 --- a/bigframes/session/polars_executor.py +++ b/bigframes/session/polars_executor.py @@ -103,6 +103,9 @@ agg_ops.SumOp, agg_ops.MeanOp, agg_ops.CountOp, + agg_ops.VarOp, + agg_ops.PopVarOp, + agg_ops.StdOp, ) diff --git a/tests/system/small/engines/test_aggregation.py b/tests/system/small/engines/test_aggregation.py index d71013c648..3e6d4843de 100644 --- a/tests/system/small/engines/test_aggregation.py +++ b/tests/system/small/engines/test_aggregation.py @@ -111,6 +111,20 @@ def test_engines_unary_aggregates( assert_equivalence_execution(node, REFERENCE_ENGINE, engine) +@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +@pytest.mark.parametrize( + "op", + [agg_ops.std_op, agg_ops.var_op, agg_ops.PopVarOp()], +) +def test_engines_unary_variance_aggregates( + scalars_array_value: array_value.ArrayValue, + engine, + op, +): + node = apply_agg_to_all_valid(scalars_array_value, op).node + assert_equivalence_execution(node, REFERENCE_ENGINE, engine) + + def test_sql_engines_median_op_aggregates( scalars_array_value: array_value.ArrayValue, bigquery_client: bigquery.Client, From a0e1e50e47c758bdceb54d04180ed36b35cf2e35 Mon Sep 17 00:00:00 2001 From: Shuowei Li Date: Mon, 3 Nov 2025 15:56:15 -0800 Subject: [PATCH 20/21] fix: Correct connection normalization in blob system tests (#2222) * fix: Correct connection normalization in blob system tests * skip more tests * Skip failed e2e tests --- tests/system/conftest.py | 17 +++++++++ tests/system/large/blob/test_function.py | 15 ++++++++ tests/system/small/bigquery/test_ai.py | 2 ++ tests/system/small/blob/test_io.py | 38 ++++++++++++++++---- tests/system/small/blob/test_properties.py | 24 +++++++++++-- tests/system/small/ml/test_multimodal_llm.py | 1 + 6 files changed, 89 insertions(+), 8 deletions(-) diff --git a/tests/system/conftest.py b/tests/system/conftest.py index 70a379fe0e..2f08a695e9 100644 --- a/tests/system/conftest.py +++ b/tests/system/conftest.py @@ -70,6 +70,23 @@ def _hash_digest_file(hasher, filepath): hasher.update(chunk) +@pytest.fixture(scope="session") +def normalize_connection_id(): + """Normalizes the connection ID by casefolding only the LOCATION component. + + Connection format: PROJECT.LOCATION.CONNECTION_NAME + Only LOCATION is case-insensitive; PROJECT and CONNECTION_NAME must be lowercase. + """ + + def normalize(connection_id: str) -> str: + parts = connection_id.split(".") + if len(parts) == 3: + return f"{parts[0]}.{parts[1].casefold()}.{parts[2]}" + return connection_id # Return unchanged if invalid format + + return normalize + + @pytest.fixture(scope="session") def tokyo_location() -> str: return TOKYO_LOCATION diff --git a/tests/system/large/blob/test_function.py b/tests/system/large/blob/test_function.py index 7963fabd0b..9ba8126dc6 100644 --- a/tests/system/large/blob/test_function.py +++ b/tests/system/large/blob/test_function.py @@ -52,6 +52,7 @@ def images_output_uris(images_output_folder: str) -> list[str]: ] +@pytest.mark.skip(reason="b/457416070") def test_blob_exif( bq_connection: str, session: bigframes.Session, @@ -103,6 +104,7 @@ def test_blob_exif_verbose( assert content_series.dtype == dtypes.JSON_DTYPE +@pytest.mark.skip(reason="b/457416070") def test_blob_image_blur_to_series( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -136,6 +138,7 @@ def test_blob_image_blur_to_series( assert not actual.blob.size().isna().any() +@pytest.mark.skip(reason="b/457416070") def test_blob_image_blur_to_series_verbose( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -163,6 +166,7 @@ def test_blob_image_blur_to_series_verbose( assert not actual.blob.size().isna().any() +@pytest.mark.skip(reason="b/457416070") def test_blob_image_blur_to_folder( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -195,6 +199,7 @@ def test_blob_image_blur_to_folder( assert not actual.blob.size().isna().any() +@pytest.mark.skip(reason="b/457416070") def test_blob_image_blur_to_folder_verbose( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -254,6 +259,7 @@ def test_blob_image_blur_to_bq_verbose(images_mm_df: bpd.DataFrame, bq_connectio assert content_series.dtype == dtypes.BYTES_DTYPE +@pytest.mark.skip(reason="b/457416070") def test_blob_image_resize_to_series( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -291,6 +297,7 @@ def test_blob_image_resize_to_series( assert not actual.blob.size().isna().any() +@pytest.mark.skip(reason="b/457416070") def test_blob_image_resize_to_series_verbose( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -325,6 +332,7 @@ def test_blob_image_resize_to_series_verbose( assert not actual.blob.size().isna().any() +@pytest.mark.skip(reason="b/457416070") def test_blob_image_resize_to_folder( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -358,6 +366,7 @@ def test_blob_image_resize_to_folder( assert not actual.blob.size().isna().any() +@pytest.mark.skip(reason="b/457416070") def test_blob_image_resize_to_folder_verbose( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -420,6 +429,7 @@ def test_blob_image_resize_to_bq_verbose( assert content_series.dtype == dtypes.BYTES_DTYPE +@pytest.mark.skip(reason="b/457416070") def test_blob_image_normalize_to_series( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -492,6 +502,7 @@ def test_blob_image_normalize_to_series_verbose( assert hasattr(content_series, "blob") +@pytest.mark.skip(reason="b/457416070") def test_blob_image_normalize_to_folder( images_mm_df: bpd.DataFrame, bq_connection: str, @@ -598,6 +609,7 @@ def test_blob_image_normalize_to_bq_verbose( assert content_series.dtype == dtypes.BYTES_DTYPE +@pytest.mark.skip(reason="b/457416070") def test_blob_pdf_extract( pdf_mm_df: bpd.DataFrame, bq_connection: str, @@ -633,6 +645,7 @@ def test_blob_pdf_extract( ), f"Item (verbose=False): Expected keyword '{keyword}' not found in extracted text. " +@pytest.mark.skip(reason="b/457416070") def test_blob_pdf_extract_verbose( pdf_mm_df: bpd.DataFrame, bq_connection: str, @@ -670,6 +683,7 @@ def test_blob_pdf_extract_verbose( ), f"Item (verbose=True): Expected keyword '{keyword}' not found in extracted text. " +@pytest.mark.skip(reason="b/457416070") def test_blob_pdf_chunk(pdf_mm_df: bpd.DataFrame, bq_connection: str): actual = ( pdf_mm_df["pdf"] @@ -709,6 +723,7 @@ def test_blob_pdf_chunk(pdf_mm_df: bpd.DataFrame, bq_connection: str): ), f"Item (verbose=False): Expected keyword '{keyword}' not found in extracted text. " +@pytest.mark.skip(reason="b/457416070") def test_blob_pdf_chunk_verbose(pdf_mm_df: bpd.DataFrame, bq_connection: str): actual = ( pdf_mm_df["pdf"] diff --git a/tests/system/small/bigquery/test_ai.py b/tests/system/small/bigquery/test_ai.py index 0c7c40031b..6df4a7a528 100644 --- a/tests/system/small/bigquery/test_ai.py +++ b/tests/system/small/bigquery/test_ai.py @@ -273,6 +273,7 @@ def test_ai_if(session): assert result.dtype == dtypes.BOOL_DTYPE +@pytest.mark.skip(reason="b/457416070") def test_ai_if_multi_model(session): df = session.from_glob_path( "gs://bigframes-dev-testing/a_multimodel/images/*", name="image" @@ -293,6 +294,7 @@ def test_ai_classify(session): assert result.dtype == dtypes.STRING_DTYPE +@pytest.mark.skip(reason="b/457416070") def test_ai_classify_multi_model(session): df = session.from_glob_path( "gs://bigframes-dev-testing/a_multimodel/images/*", name="image" diff --git a/tests/system/small/blob/test_io.py b/tests/system/small/blob/test_io.py index 5ada4fabb0..5da113a5e1 100644 --- a/tests/system/small/blob/test_io.py +++ b/tests/system/small/blob/test_io.py @@ -12,27 +12,36 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Callable from unittest import mock import IPython.display import pandas as pd +import pytest import bigframes import bigframes.pandas as bpd def test_blob_create_from_uri_str( - bq_connection: str, session: bigframes.Session, images_uris + bq_connection: str, + session: bigframes.Session, + images_uris, + normalize_connection_id: Callable[[str], str], ): uri_series = bpd.Series(images_uris, session=session) blob_series = uri_series.str.to_blob(connection=bq_connection) pd_blob_df = blob_series.struct.explode().to_pandas() + pd_blob_df["authorizer"] = pd_blob_df["authorizer"].apply(normalize_connection_id) expected_pd_df = pd.DataFrame( { "uri": images_uris, "version": [None, None], - "authorizer": [bq_connection.casefold(), bq_connection.casefold()], + "authorizer": [ + normalize_connection_id(bq_connection), + normalize_connection_id(bq_connection), + ], "details": [None, None], } ) @@ -43,7 +52,11 @@ def test_blob_create_from_uri_str( def test_blob_create_from_glob_path( - bq_connection: str, session: bigframes.Session, images_gcs_path, images_uris + bq_connection: str, + session: bigframes.Session, + images_gcs_path, + images_uris, + normalize_connection_id: Callable[[str], str], ): blob_df = session.from_glob_path( images_gcs_path, connection=bq_connection, name="blob_col" @@ -55,12 +68,16 @@ def test_blob_create_from_glob_path( .sort_values("uri") .reset_index(drop=True) ) + pd_blob_df["authorizer"] = pd_blob_df["authorizer"].apply(normalize_connection_id) expected_df = pd.DataFrame( { "uri": images_uris, "version": [None, None], - "authorizer": [bq_connection.casefold(), bq_connection.casefold()], + "authorizer": [ + normalize_connection_id(bq_connection), + normalize_connection_id(bq_connection), + ], "details": [None, None], } ) @@ -71,7 +88,11 @@ def test_blob_create_from_glob_path( def test_blob_create_read_gbq_object_table( - bq_connection: str, session: bigframes.Session, images_gcs_path, images_uris + bq_connection: str, + session: bigframes.Session, + images_gcs_path, + images_uris, + normalize_connection_id: Callable[[str], str], ): obj_table = session._create_object_table(images_gcs_path, bq_connection) @@ -83,11 +104,15 @@ def test_blob_create_read_gbq_object_table( .sort_values("uri") .reset_index(drop=True) ) + pd_blob_df["authorizer"] = pd_blob_df["authorizer"].apply(normalize_connection_id) expected_df = pd.DataFrame( { "uri": images_uris, "version": [None, None], - "authorizer": [bq_connection.casefold(), bq_connection.casefold()], + "authorizer": [ + normalize_connection_id(bq_connection), + normalize_connection_id(bq_connection), + ], "details": [None, None], } ) @@ -97,6 +122,7 @@ def test_blob_create_read_gbq_object_table( ) +@pytest.mark.skip(reason="b/457416070") def test_display_images(monkeypatch, images_mm_df: bpd.DataFrame): mock_display = mock.Mock() monkeypatch.setattr(IPython.display, "display", mock_display) diff --git a/tests/system/small/blob/test_properties.py b/tests/system/small/blob/test_properties.py index 47d4d2aa04..c411c01f13 100644 --- a/tests/system/small/blob/test_properties.py +++ b/tests/system/small/blob/test_properties.py @@ -12,7 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + +from typing import Callable + import pandas as pd +import pytest import bigframes.dtypes as dtypes import bigframes.pandas as bpd @@ -27,10 +32,19 @@ def test_blob_uri(images_uris: list[str], images_mm_df: bpd.DataFrame): ) -def test_blob_authorizer(images_mm_df: bpd.DataFrame, bq_connection: str): +def test_blob_authorizer( + images_mm_df: bpd.DataFrame, + bq_connection: str, + normalize_connection_id: Callable[[str], str], +): actual = images_mm_df["blob_col"].blob.authorizer().to_pandas() + actual = actual.apply(normalize_connection_id) expected = pd.Series( - [bq_connection.casefold(), bq_connection.casefold()], name="authorizer" + [ + normalize_connection_id(bq_connection), + normalize_connection_id(bq_connection), + ], + name="authorizer", ) pd.testing.assert_series_equal( @@ -38,6 +52,7 @@ def test_blob_authorizer(images_mm_df: bpd.DataFrame, bq_connection: str): ) +@pytest.mark.skip(reason="b/457416070") def test_blob_version(images_mm_df: bpd.DataFrame): actual = images_mm_df["blob_col"].blob.version().to_pandas() expected = pd.Series(["1753907851152593", "1753907851111538"], name="version") @@ -47,6 +62,7 @@ def test_blob_version(images_mm_df: bpd.DataFrame): ) +@pytest.mark.skip(reason="b/457416070") def test_blob_metadata(images_mm_df: bpd.DataFrame): actual = images_mm_df["blob_col"].blob.metadata().to_pandas() expected = pd.Series( @@ -71,6 +87,7 @@ def test_blob_metadata(images_mm_df: bpd.DataFrame): pd.testing.assert_series_equal(actual, expected) +@pytest.mark.skip(reason="b/457416070") def test_blob_content_type(images_mm_df: bpd.DataFrame): actual = images_mm_df["blob_col"].blob.content_type().to_pandas() expected = pd.Series(["image/jpeg", "image/jpeg"], name="content_type") @@ -80,6 +97,7 @@ def test_blob_content_type(images_mm_df: bpd.DataFrame): ) +@pytest.mark.skip(reason="b/457416070") def test_blob_md5_hash(images_mm_df: bpd.DataFrame): actual = images_mm_df["blob_col"].blob.md5_hash().to_pandas() expected = pd.Series( @@ -92,6 +110,7 @@ def test_blob_md5_hash(images_mm_df: bpd.DataFrame): ) +@pytest.mark.skip(reason="b/457416070") def test_blob_size(images_mm_df: bpd.DataFrame): actual = images_mm_df["blob_col"].blob.size().to_pandas() expected = pd.Series([338390, 43333], name="size") @@ -101,6 +120,7 @@ def test_blob_size(images_mm_df: bpd.DataFrame): ) +@pytest.mark.skip(reason="b/457416070") def test_blob_updated(images_mm_df: bpd.DataFrame): actual = images_mm_df["blob_col"].blob.updated().to_pandas() expected = pd.Series( diff --git a/tests/system/small/ml/test_multimodal_llm.py b/tests/system/small/ml/test_multimodal_llm.py index 48a69f522c..fe34f9c02b 100644 --- a/tests/system/small/ml/test_multimodal_llm.py +++ b/tests/system/small/ml/test_multimodal_llm.py @@ -21,6 +21,7 @@ from bigframes.testing import utils +@pytest.mark.skip(reason="b/457416070") @pytest.mark.flaky(retries=2) def test_multimodal_embedding_generator_predict_default_params_success( images_mm_df, session, bq_connection From 94c8b3c19517ed42271b56de94bdf9cb04cde7df Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:25:31 -0800 Subject: [PATCH 21/21] chore(main): release 2.28.0 (#2199) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 23 +++++++++++++++++++++++ bigframes/version.py | 4 ++-- third_party/bigframes_vendored/version.py | 4 ++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60fe9ae5e3..1df3ad0f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ [1]: https://pypi.org/project/bigframes/#history +## [2.28.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.27.0...v2.28.0) (2025-11-03) + + +### Features + +* Add bigframes.bigquery.st_simplify ([#2210](https://github.com/googleapis/python-bigquery-dataframes/issues/2210)) ([ecee2bc](https://github.com/googleapis/python-bigquery-dataframes/commit/ecee2bc6ada0bc968fc56ed7194dc8c043547e93)) +* Add Series.dt.day_name ([#2218](https://github.com/googleapis/python-bigquery-dataframes/issues/2218)) ([5e006e4](https://github.com/googleapis/python-bigquery-dataframes/commit/5e006e404b65c32e5b1d342ebfcfce59ee592c8c)) +* Polars engine supports std, var ([#2215](https://github.com/googleapis/python-bigquery-dataframes/issues/2215)) ([ef5e83a](https://github.com/googleapis/python-bigquery-dataframes/commit/ef5e83acedf005cbe1e6ad174bec523ac50517d7)) +* Support INFORMATION_SCHEMA views in `read_gbq` ([#1895](https://github.com/googleapis/python-bigquery-dataframes/issues/1895)) ([d97cafc](https://github.com/googleapis/python-bigquery-dataframes/commit/d97cafcb5921fca2351b18011b0e54e2631cc53d)) +* Support some python standard lib callables in apply/combine ([#2187](https://github.com/googleapis/python-bigquery-dataframes/issues/2187)) ([86a2756](https://github.com/googleapis/python-bigquery-dataframes/commit/86a27564b48b854a32b3d11cd2105aa0fa496279)) + + +### Bug Fixes + +* Correct connection normalization in blob system tests ([#2222](https://github.com/googleapis/python-bigquery-dataframes/issues/2222)) ([a0e1e50](https://github.com/googleapis/python-bigquery-dataframes/commit/a0e1e50e47c758bdceb54d04180ed36b35cf2e35)) +* Improve error handling in blob operations ([#2194](https://github.com/googleapis/python-bigquery-dataframes/issues/2194)) ([d410046](https://github.com/googleapis/python-bigquery-dataframes/commit/d4100466612df0523d01ed01ca1e115dabd6ef45)) +* Resolve AttributeError in TableWidget and improve initialization ([#1937](https://github.com/googleapis/python-bigquery-dataframes/issues/1937)) ([4c4c9b1](https://github.com/googleapis/python-bigquery-dataframes/commit/4c4c9b14657b7cda1940ef39e7d4db20a9ff5308)) + + +### Documentation + +* Update bq_dataframes_llm_output_schema.ipynb ([#2004](https://github.com/googleapis/python-bigquery-dataframes/issues/2004)) ([316ba9f](https://github.com/googleapis/python-bigquery-dataframes/commit/316ba9f557d792117d5a7845d7567498f78dd513)) + ## [2.27.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.26.0...v2.27.0) (2025-10-24) diff --git a/bigframes/version.py b/bigframes/version.py index 4e319dd41d..cf7562a306 100644 --- a/bigframes/version.py +++ b/bigframes/version.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.27.0" +__version__ = "2.28.0" # {x-release-please-start-date} -__release_date__ = "2025-10-24" +__release_date__ = "2025-11-03" # {x-release-please-end} diff --git a/third_party/bigframes_vendored/version.py b/third_party/bigframes_vendored/version.py index 4e319dd41d..cf7562a306 100644 --- a/third_party/bigframes_vendored/version.py +++ b/third_party/bigframes_vendored/version.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.27.0" +__version__ = "2.28.0" # {x-release-please-start-date} -__release_date__ = "2025-10-24" +__release_date__ = "2025-11-03" # {x-release-please-end}