Skip to content

Commit a7e4d04

Browse files
authored
feat: add bigframes.bigquery.aead.* scalar functions (#17168)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/google-cloud-python/issues) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) Fixes #<issue_number_goes_here> 🦕
1 parent ed29e80 commit a7e4d04

20 files changed

Lines changed: 1354 additions & 291 deletions

File tree

.pre-commit-config.yaml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,10 @@ repos:
2020
hooks:
2121
- id: trailing-whitespace
2222
- id: end-of-file-fixer
23-
- id: check-yaml
24-
- repo: https://github.com/psf/black
25-
rev: 23.7.0
26-
hooks:
27-
- id: black
28-
- repo: https://github.com/pycqa/flake8
29-
rev: 6.1.0 # version-scanner: ignore
30-
hooks:
31-
- id: flake8
32-
args: [--config, packages/google-cloud-alloydb/.flake8]
23+
- repo: https://github.com/astral-sh/ruff-pre-commit
24+
# Ruff version.
25+
rev: v0.14.14
26+
hooks:
27+
# Run the linter.
28+
- id: ruff-check
29+
args: [ --select, I, --fix, --target-version=py310, --line-length=88 ]

packages/bigframes/bigframes/bigquery/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
import sys
4949

50-
from bigframes.bigquery import ai, ml, obj
50+
from bigframes.bigquery import aead, ai, ml, obj
5151
from bigframes.bigquery._operations.approx_agg import approx_top_count
5252
from bigframes.bigquery._operations.array import (
5353
array_agg,
@@ -208,6 +208,7 @@
208208
# io ops
209209
"load_data",
210210
# Modules / SQL namespaces
211+
"aead",
211212
"ai",
212213
"ml",
213214
"obj",
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Utilities for working with GoogleSqlScalarOps."""
16+
17+
from __future__ import annotations
18+
19+
from typing import Any, Union
20+
21+
import bigframes.core.col
22+
import bigframes.core.expression as ex
23+
import bigframes.core.sentinels as sentinels
24+
import bigframes.series as series
25+
from bigframes.operations import googlesql
26+
27+
28+
def apply_googlesql_scalar_op(
29+
op: googlesql.GoogleSqlScalarOp,
30+
*args: Any,
31+
) -> Union[series.Series, bigframes.core.col.Expression]:
32+
"""Applies a GoogleSQL scalar operator to the given arguments.
33+
34+
Handles a mix of Series, Expression, and literal inputs.
35+
36+
Args:
37+
op (googlesql.GoogleSqlScalarOp):
38+
The operator to apply.
39+
*args (Any):
40+
The arguments to apply the operator to.
41+
42+
Returns:
43+
bigframes.pandas.Series | bigframes.core.col.Expression:
44+
The result of the operation. If any of ``args`` is a Series, returns
45+
a Series. Otherwise, returns an Expression.
46+
"""
47+
# Find the first Series to use for alignment
48+
first_series = None
49+
for arg in args:
50+
if isinstance(arg, series.Series):
51+
first_series = arg
52+
break
53+
54+
if first_series is not None:
55+
processed_args: list[Union[bigframes.core.col.Expression, series.Series]] = []
56+
block = first_series._block
57+
for arg in args:
58+
if isinstance(arg, bigframes.core.col.Expression):
59+
block, col_id = block.project_expr(bigframes.core.col._as_bf_expr(arg))
60+
processed_args.append(series.Series(block.select_column(col_id)))
61+
elif arg is sentinels.Sentinel.ARGUMENT_DEFAULT:
62+
processed_args.append(bigframes.core.col.Expression(ex.OmittedArg()))
63+
else:
64+
processed_args.append(arg)
65+
66+
# Apply the n-ary op. _apply_nary_op handles alignment of Series and literals.
67+
result = first_series._apply_nary_op(op, processed_args, ignore_self=True)
68+
result.name = None
69+
return result
70+
71+
# No Series, return an Expression
72+
expr_args = []
73+
for arg in args:
74+
if isinstance(arg, bigframes.core.col.Expression):
75+
expr_args.append(bigframes.core.col._as_bf_expr(arg))
76+
elif arg is sentinels.Sentinel.ARGUMENT_DEFAULT:
77+
expr_args.append(ex.OmittedArg())
78+
else:
79+
expr_args.append(ex.const(arg))
80+
81+
return bigframes.core.col.Expression(ex.OpExpression(op, tuple(expr_args)))
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# DO NOT MODIFY THIS FILE DIRECTLY.
16+
# This file was generated from: scripts/data/sql-functions/aead.yaml
17+
# by the script: scripts/generate_bigframes_bigquery.py
18+
19+
from __future__ import annotations
20+
21+
import datetime
22+
from typing import Any, Literal, Optional, TypeVar, Union
23+
24+
import bigframes.bigquery._googlesql
25+
import bigframes.core.col
26+
import bigframes.core.expression as ex
27+
import bigframes.core.sentinels as sentinels
28+
import bigframes.operations as ops
29+
import bigframes.series as series
30+
from bigframes import dtypes
31+
from bigframes.operations import googlesql
32+
33+
T = TypeVar("T", series.Series, bigframes.core.col.Expression)
34+
35+
_DECRYPT_BYTES_OP = googlesql.GoogleSqlScalarOp(
36+
"AEAD.DECRYPT_BYTES",
37+
args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()),
38+
signature=lambda *args: dtypes.BYTES_DTYPE,
39+
)
40+
_DECRYPT_STRING_OP = googlesql.GoogleSqlScalarOp(
41+
"AEAD.DECRYPT_STRING",
42+
args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()),
43+
signature=lambda *args: dtypes.STRING_DTYPE,
44+
)
45+
_ENCRYPT_OP = googlesql.GoogleSqlScalarOp(
46+
"AEAD.ENCRYPT",
47+
args=(googlesql.ArgSpec(), googlesql.ArgSpec(), googlesql.ArgSpec()),
48+
signature=lambda *args: dtypes.BYTES_DTYPE,
49+
)
50+
51+
52+
def decrypt_bytes(
53+
keyset: Union[
54+
T,
55+
bigframes.core.col.Expression,
56+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict],
57+
],
58+
ciphertext: Union[
59+
T,
60+
bigframes.core.col.Expression,
61+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes],
62+
],
63+
additional_data: Union[
64+
T,
65+
bigframes.core.col.Expression,
66+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes],
67+
],
68+
) -> T:
69+
"""Uses the matching key from keyset to decrypt ciphertext and verifies the integrity of the data using additional_data. Returns an error if decryption or verification fails."""
70+
return bigframes.bigquery._googlesql.apply_googlesql_scalar_op(
71+
_DECRYPT_BYTES_OP,
72+
keyset,
73+
ciphertext,
74+
additional_data,
75+
) # type: ignore
76+
77+
78+
def decrypt_string(
79+
keyset: Union[
80+
T,
81+
bigframes.core.col.Expression,
82+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict],
83+
],
84+
ciphertext: Union[
85+
T,
86+
bigframes.core.col.Expression,
87+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes],
88+
],
89+
additional_data: Union[
90+
T,
91+
bigframes.core.col.Expression,
92+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], str],
93+
],
94+
) -> T:
95+
"""Like AEAD.DECRYPT_BYTES, but where additional_data is of type STRING."""
96+
return bigframes.bigquery._googlesql.apply_googlesql_scalar_op(
97+
_DECRYPT_STRING_OP,
98+
keyset,
99+
ciphertext,
100+
additional_data,
101+
) # type: ignore
102+
103+
104+
def encrypt(
105+
keyset: Union[
106+
T,
107+
bigframes.core.col.Expression,
108+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, dict],
109+
],
110+
plaintext: Union[
111+
T,
112+
bigframes.core.col.Expression,
113+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str],
114+
],
115+
additional_data: Union[
116+
T,
117+
bigframes.core.col.Expression,
118+
Union[Literal[sentinels.Sentinel.ARGUMENT_DEFAULT], bytes, str],
119+
],
120+
) -> T:
121+
"""Encrypts plaintext using the primary cryptographic key in keyset. The algorithm of the primary key must be AEAD_AES_GCM_256. Binds the ciphertext to the context defined by additional_data. Returns NULL if any input is NULL."""
122+
return bigframes.bigquery._googlesql.apply_googlesql_scalar_op(
123+
_ENCRYPT_OP,
124+
keyset,
125+
plaintext,
126+
additional_data,
127+
) # type: ignore
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""AEAD encryption functions"""
16+
17+
from __future__ import annotations
18+
19+
from bigframes.bigquery._operations.aead import decrypt_bytes, decrypt_string, encrypt
20+
21+
__all__ = [
22+
"decrypt_bytes",
23+
"decrypt_string",
24+
"encrypt",
25+
]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Sentinel values used throughout BigFrames."""
16+
17+
from __future__ import annotations
18+
19+
import enum
20+
21+
22+
class Sentinel(enum.Enum):
23+
"""Default values used throughout BigFrames."""
24+
25+
"""Default value for an optional argument.
26+
27+
When a parameter is set to this, that parameter is explicitly omitted
28+
from the SQL text. This allows for NULL (None in Python) to be explicitly
29+
passed in to optional parameters.
30+
"""
31+
ARGUMENT_DEFAULT = enum.auto()

packages/bigframes/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[build-system]
22
requires = ["setuptools"]
33
build-backend = "setuptools.build_meta"
4+
5+
[tool.ruff.lint.isort]
6+
known-first-party = ["bigframes"]

0 commit comments

Comments
 (0)