Skip to content

Commit a7699aa

Browse files
committed
chore: Add support for accessing BI Engine statistics
The REST API returns BiEngineStatistics for a query which denotes if the query was accelerated by BI Engine or not. This commit adds the necessary function to access this information for executed queries.
1 parent 39ade39 commit a7699aa

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

google/cloud/bigquery/enums.py

+40
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,46 @@ class AutoRowIDs(enum.Enum):
2828
GENERATE_UUID = enum.auto()
2929

3030

31+
class BiEngineReasonCode(enum.Enum):
32+
"""Specifies reason why BI Engine did not accelerate query"""
33+
34+
CODE_UNSPECIFIED = enum.auto()
35+
"""BiEngineReason not specified."""
36+
37+
NO_RESERVATION = enum.auto()
38+
"""No reservation available for BI Engine acceleration."""
39+
40+
INSUFFICIENT_RESERVATION = enum.auto()
41+
"""Not enough memory available for BI Engine acceleration."""
42+
43+
UNCACHED = enum.auto()
44+
"""Data is not-cached and could not be accelerated by BI Engine."""
45+
46+
UNSUPPORTED_SQL_TEXT = enum.auto()
47+
"""This particular SQL text is not supported for acceleration by BI Engine."""
48+
49+
INPUT_TOO_LARGE = enum.auto()
50+
"""Input too large for acceleration by BI Engine."""
51+
52+
OTHER_REASON = enum.auto()
53+
"""Catch-all code for all other cases for partial or disabled acceleration."""
54+
55+
TABLE_EXCLUDED = enum.auto()
56+
"""One or more tables were not eligible for BI Engine acceleration."""
57+
58+
59+
class BiEngineMode(enum.Enum):
60+
"""Specifies which mode of BI Engine acceleration was performed"""
61+
62+
ACCELERATION_MODE_UNSPECIFIED = enum.auto()
63+
64+
DISABLED = enum.auto()
65+
66+
PARTIAL = enum.auto()
67+
68+
FULL = enum.auto()
69+
70+
3171
class Compression(object):
3272
"""The compression type to use for exported files. The default value is
3373
:attr:`NONE`.

google/cloud/bigquery/job/query.py

+54
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from google.cloud.bigquery.dataset import DatasetReference
3030
from google.cloud.bigquery.encryption_configuration import EncryptionConfiguration
3131
from google.cloud.bigquery.enums import KeyResultStatementKind
32+
from google.cloud.bigquery.enums import BiEngineMode, BiEngineReasonCode
3233
from google.cloud.bigquery.external_config import ExternalConfig
3334
from google.cloud.bigquery import _helpers
3435
from google.cloud.bigquery.query import (
@@ -121,6 +122,50 @@ def _to_api_repr_table_defs(value):
121122
return {k: ExternalConfig.to_api_repr(v) for k, v in value.items()}
122123

123124

125+
class BiEngineReason(typing.NamedTuple):
126+
"""Reason for BI Engine acceleration failure
127+
128+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#bienginereason
129+
"""
130+
131+
code: BiEngineReasonCode = BiEngineReasonCode.CODE_UNSPECIFIED
132+
133+
reason: str = ""
134+
135+
@classmethod
136+
def from_api_repr(cls, reason: Dict[str, str]) -> "BiEngineReason":
137+
return cls(BiEngineReasonCode[reason.get("code")], reason.get("message"))
138+
139+
140+
class BiEngineStats(typing.NamedTuple):
141+
"""Statistics for a BI Engine query
142+
143+
https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#bienginestatistics
144+
"""
145+
146+
mode: BiEngineMode = BiEngineMode.ACCELERATION_MODE_UNSPECIFIED
147+
""" Specifies which mode of BI Engine acceleration was performed (if any)
148+
"""
149+
150+
reasons: List[BiEngineReason] = []
151+
""" Contains explanatory messages in case of DISABLED / PARTIAL acceleration
152+
"""
153+
154+
@classmethod
155+
def from_api_repr(cls, stats: Dict[str, str]) -> "BiEngineStats":
156+
biEngineMode = stats.get("biEngineMode")
157+
biEngineReasons = stats.get("biEngineReasons")
158+
159+
mode = BiEngineMode[biEngineMode]
160+
161+
if biEngineReasons is None:
162+
reasons = []
163+
else:
164+
reasons = [BiEngineReason.from_api_repr(r) for r in biEngineReasons]
165+
166+
return cls(mode, reasons)
167+
168+
124169
class DmlStats(typing.NamedTuple):
125170
"""Detailed statistics for DML statements.
126171
@@ -1191,6 +1236,15 @@ def dml_stats(self) -> Optional[DmlStats]:
11911236
else:
11921237
return DmlStats.from_api_repr(stats)
11931238

1239+
@property
1240+
def bi_engine_stats(self) -> Optional[BiEngineStats]:
1241+
stats = self._job_statistics().get("biEngineStatistics")
1242+
1243+
if stats is not None:
1244+
return None
1245+
else:
1246+
return BiEngineStats.from_api_repr(stats)
1247+
11941248
def _blocking_poll(self, timeout=None, **kwargs):
11951249
self._done_timeout = timeout
11961250
self._transport_timeout = timeout

tests/unit/job/test_query_stats.py

+57
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,63 @@
1313
# limitations under the License.
1414

1515
from .helpers import _Base
16+
from google.cloud.bigquery.enums import BiEngineMode, BiEngineReasonCode
17+
18+
19+
class TestBiEngineStats:
20+
@staticmethod
21+
def _get_target_class():
22+
from google.cloud.bigquery.job.query import BiEngineStats
23+
24+
return BiEngineStats
25+
26+
def _make_one(self, *args, **kw):
27+
return self._get_target_class()(*args, **kw)
28+
29+
def test_ctor_defaults(self):
30+
bi_engine_stats = self._make_one()
31+
assert bi_engine_stats.mode == BiEngineMode.ACCELERATION_MODE_UNSPECIFIED
32+
assert bi_engine_stats.reasons == []
33+
34+
def test_from_api_repr_unspecified(self):
35+
klass = self._get_target_class()
36+
result = klass.from_api_repr({"biEngineMode": "ACCELERATION_MODE_UNSPECIFIED"})
37+
38+
assert isinstance(result, klass)
39+
assert result.mode == BiEngineMode.ACCELERATION_MODE_UNSPECIFIED
40+
assert result.reasons == []
41+
42+
def test_from_api_repr_full(self):
43+
klass = self._get_target_class()
44+
result = klass.from_api_repr({"biEngineMode": "FULL"})
45+
46+
assert isinstance(result, klass)
47+
assert result.mode == BiEngineMode.FULL
48+
assert result.reasons == []
49+
50+
def test_from_api_repr_disabled(self):
51+
klass = self._get_target_class()
52+
result = klass.from_api_repr(
53+
{
54+
"biEngineMode": "DISABLED",
55+
"biEngineReasons": [
56+
{
57+
"code": "OTHER_REASON",
58+
"message": "Unable to support input table xyz due to an internal error.",
59+
}
60+
],
61+
}
62+
)
63+
64+
assert isinstance(result, klass)
65+
assert result.mode == BiEngineMode.DISABLED
66+
67+
reason = result.reasons[0]
68+
assert reason.code == BiEngineReasonCode.OTHER_REASON
69+
assert (
70+
reason.reason
71+
== "Unable to support input table xyz due to an internal error."
72+
)
1673

1774

1875
class TestDmlStats:

0 commit comments

Comments
 (0)