Skip to content

Commit ef07053

Browse files
committed
Add support for accessing BI Engine statistics of query
The REST API return BiEngineStatistics 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 ef07053

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)