Skip to content

Commit 06eb15b

Browse files
authored
feat(bigquery): add bigquery_call_procedure sample to call a SQL stored procedure (#14730)
Problem: The BigQuery documentation (https://cloud.google.com/bigquery/docs/procedures) needs a standalone, tested Python code sample demonstrating how to call a SQL stored procedure (region tag `bigquery_call_procedure`). Solution: - Add `call_procedure(project_id: str, dataset_id: str, procedure_id: str) -> None` in `bigquery/stored_procedures/bigquery_call_procedure.py` (Invoke a BigQuery SQL stored procedure with input parameters and capture its output variable using CALL.) - Add unit tests (`bigquery/stored_procedures/bigquery_call_procedure_test.py`) covering both the success path and API error handling (`GoogleAPICallError`)
1 parent 7c901dd commit 06eb15b

5 files changed

Lines changed: 173 additions & 0 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
# https://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+
# [START bigquery_call_procedure]
16+
import sys
17+
18+
from google.api_core import exceptions
19+
from google.cloud import bigquery
20+
21+
22+
def call_procedure(
23+
project_id: str,
24+
dataset_id: str,
25+
procedure_id: str,
26+
) -> None:
27+
"""Calls a GoogleSQL stored procedure with input and output parameters.
28+
29+
Args:
30+
project_id: Google Cloud project ID (for example, 'example-project').
31+
dataset_id: BigQuery dataset ID containing the stored procedure
32+
(for example, 'example_dataset').
33+
procedure_id: Stored procedure ID to invoke
34+
(for example, 'example_procedure').
35+
"""
36+
client = bigquery.Client(project=project_id)
37+
routine_ref = f"{project_id}.{dataset_id}.{procedure_id}"
38+
# Bind the input filter value via a query parameter to prevent SQL injection.
39+
target_corpus = "hamlet"
40+
job_config = bigquery.QueryJobConfig(
41+
query_parameters=[
42+
bigquery.ScalarQueryParameter("target_corpus", "STRING", target_corpus),
43+
]
44+
)
45+
sql = f"""
46+
DECLARE out_total INT64;
47+
CALL `{routine_ref}`(@target_corpus, out_total);
48+
SELECT out_total AS total_words;
49+
"""
50+
51+
try:
52+
print(f"Calling stored procedure: {routine_ref}")
53+
query_job = client.query(sql, job_config=job_config)
54+
rows = list(query_job.result())
55+
except exceptions.NotFound as err:
56+
print(
57+
f"error: stored procedure '{procedure_id}' not found in dataset "
58+
f"'{dataset_id}' for project '{project_id}': {err.message}",
59+
file=sys.stderr,
60+
)
61+
return
62+
except exceptions.GoogleAPICallError as err:
63+
print(
64+
f"error: failed to call stored procedure '{procedure_id}' in dataset "
65+
f"'{dataset_id}' for project '{project_id}': {err.message}",
66+
file=sys.stderr,
67+
)
68+
return
69+
70+
total_words = rows[0].total_words if rows else 0
71+
print(f"Resource ID: {routine_ref}")
72+
print(f"Corpus: {target_corpus}")
73+
print(f"Job ID: {query_job.job_id}")
74+
print(f"Total Words: {total_words}")
75+
76+
77+
# [END bigquery_call_procedure]
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
# https://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+
from unittest import mock
16+
17+
import bigquery_call_procedure
18+
from google.api_core import exceptions
19+
import pytest
20+
21+
22+
def test_call_procedure_success(
23+
monkeypatch: pytest.MonkeyPatch,
24+
capsys: pytest.CaptureFixture[str],
25+
) -> None:
26+
mock_client = mock.MagicMock()
27+
mock_job = mock.MagicMock()
28+
mock_job.job_id = "example-job-123"
29+
mock_row = mock.MagicMock()
30+
mock_row.total_words = 42
31+
mock_job.result.return_value = [mock_row]
32+
mock_client.query.return_value = mock_job
33+
monkeypatch.setattr(
34+
bigquery_call_procedure.bigquery,
35+
"Client",
36+
lambda project=None: mock_client,
37+
)
38+
39+
bigquery_call_procedure.call_procedure(
40+
project_id="example-project",
41+
dataset_id="example_dataset",
42+
procedure_id="example_procedure",
43+
)
44+
45+
captured = capsys.readouterr()
46+
assert "Resource ID:" in captured.out
47+
assert "example-project" in captured.out
48+
assert mock_client.query.called
49+
50+
51+
def test_call_procedure_error(
52+
monkeypatch: pytest.MonkeyPatch,
53+
capsys: pytest.CaptureFixture[str],
54+
) -> None:
55+
mock_client = mock.MagicMock()
56+
mock_client.query.side_effect = exceptions.NotFound("resource error")
57+
monkeypatch.setattr(
58+
bigquery_call_procedure.bigquery,
59+
"Client",
60+
lambda project=None: mock_client,
61+
)
62+
63+
bigquery_call_procedure.call_procedure(
64+
project_id="example-project",
65+
dataset_id="example_dataset",
66+
procedure_id="example_procedure",
67+
)
68+
69+
captured = capsys.readouterr()
70+
assert "error:" in captured.err
71+
assert "example-project" in captured.err
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
# https://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+
TEST_CONFIG_OVERRIDE = {
16+
# You can opt out from the test for specific Python versions.
17+
"ignored_versions": ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11"],
18+
# Enforce type hints on new samples.
19+
"enforce_type_hints": True,
20+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
21+
"pip_version_override": None,
22+
"envs": {},
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest==9.0.3; python_version >= "3.10"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
google-cloud-bigquery==3.47.0

0 commit comments

Comments
 (0)