Skip to content

Commit 000187a

Browse files
committed
feat: add bigquery.create_external_table method
1 parent 7261a4e commit 000187a

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

bigframes/bigquery/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
from bigframes.bigquery._operations.search import create_vector_index, vector_search
6161
from bigframes.bigquery._operations.sql import sql_scalar
6262
from bigframes.bigquery._operations.struct import struct
63+
from bigframes.bigquery.table import create_external_table
6364
from bigframes.core.logging import log_adapter
6465

6566
_functions = [
@@ -104,6 +105,8 @@
104105
sql_scalar,
105106
# struct ops
106107
struct,
108+
# table ops
109+
create_external_table,
107110
]
108111

109112
_module = sys.modules[__name__]
@@ -155,6 +158,8 @@
155158
"sql_scalar",
156159
# struct ops
157160
"struct",
161+
# table ops
162+
"create_external_table",
158163
# Modules / SQL namespaces
159164
"ai",
160165
"ml",

bigframes/bigquery/table.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright 2025 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+
from __future__ import annotations
16+
17+
from typing import Mapping, Optional, Union
18+
19+
import bigframes_vendored.constants
20+
import google.cloud.bigquery
21+
import pandas as pd
22+
23+
import bigframes.core.logging.log_adapter as log_adapter
24+
import bigframes.core.sql.table
25+
import bigframes.session
26+
27+
28+
def _get_table_metadata(
29+
*,
30+
bqclient: google.cloud.bigquery.Client,
31+
table_name: str,
32+
) -> pd.Series:
33+
table_metadata = bqclient.get_table(table_name)
34+
table_dict = table_metadata.to_api_repr()
35+
return pd.Series(table_dict)
36+
37+
38+
@log_adapter.method_logger(custom_base_name="bigquery_table")
39+
def create_external_table(
40+
table_name: str,
41+
*,
42+
replace: bool = False,
43+
if_not_exists: bool = False,
44+
columns: Optional[Mapping[str, str]] = None,
45+
partition_columns: Optional[Mapping[str, str]] = None,
46+
connection_name: Optional[str] = None,
47+
options: Mapping[str, Union[str, int, float, bool, list]],
48+
session: Optional[bigframes.session.Session] = None,
49+
) -> pd.Series:
50+
"""
51+
Creates a BigQuery external table.
52+
53+
See the `BigQuery CREATE EXTERNAL TABLE DDL syntax
54+
<https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_external_table_statement>`_
55+
for additional reference.
56+
57+
Args:
58+
table_name (str):
59+
The name of the table in BigQuery.
60+
replace (bool, default False):
61+
Whether to replace the table if it already exists.
62+
if_not_exists (bool, default False):
63+
Whether to ignore the error if the table already exists.
64+
columns (Mapping[str, str], optional):
65+
The table's schema.
66+
partition_columns (Mapping[str, str], optional):
67+
The table's partition columns.
68+
connection_name (str, optional):
69+
The connection to use for the table.
70+
options (Mapping[str, Union[str, int, float, bool, list]]):
71+
The OPTIONS clause, which specifies the table options.
72+
session (bigframes.session.Session, optional):
73+
The session to use. If not provided, the default session is used.
74+
75+
Returns:
76+
pandas.Series:
77+
A Series with object dtype containing the table metadata. Reference
78+
the `BigQuery Table REST API reference
79+
<https://cloud.google.com/bigquery/docs/reference/rest/v2/tables#Table>`_
80+
for available fields.
81+
"""
82+
import bigframes.pandas as bpd
83+
84+
sql = bigframes.core.sql.table.create_external_table_ddl(
85+
table_name=table_name,
86+
replace=replace,
87+
if_not_exists=if_not_exists,
88+
columns=columns,
89+
partition_columns=partition_columns,
90+
connection_name=connection_name,
91+
options=options,
92+
)
93+
94+
if session is None:
95+
bpd.read_gbq_query(sql)
96+
session = bpd.get_global_session()
97+
assert (
98+
session is not None
99+
), f"Missing connection to BigQuery. Please report how you encountered this error at {bigframes_vendored.constants.FEEDBACK_LINK}."
100+
else:
101+
session.read_gbq_query(sql)
102+
103+
return _get_table_metadata(bqclient=session.bqclient, table_name=table_name)

bigframes/core/sql/table.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2025 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+
from __future__ import annotations
16+
17+
from typing import Mapping, Optional, Union
18+
19+
20+
def create_external_table_ddl(
21+
table_name: str,
22+
*,
23+
replace: bool = False,
24+
if_not_exists: bool = False,
25+
columns: Optional[Mapping[str, str]] = None,
26+
partition_columns: Optional[Mapping[str, str]] = None,
27+
connection_name: Optional[str] = None,
28+
options: Mapping[str, Union[str, int, float, bool, list]],
29+
) -> str:
30+
"""Generates the CREATE EXTERNAL TABLE DDL statement."""
31+
statement = ["CREATE"]
32+
if replace:
33+
statement.append("OR REPLACE")
34+
statement.append("EXTERNAL TABLE")
35+
if if_not_exists:
36+
statement.append("IF NOT EXISTS")
37+
statement.append(table_name)
38+
39+
if columns:
40+
column_defs = ", ".join([f"{name} {typ}" for name, typ in columns.items()])
41+
statement.append(f"({column_defs})")
42+
43+
if connection_name:
44+
statement.append(f"WITH CONNECTION `{connection_name}`")
45+
46+
if partition_columns:
47+
part_defs = ", ".join(
48+
[f"{name} {typ}" for name, typ in partition_columns.items()]
49+
)
50+
statement.append(f"WITH PARTITION COLUMNS ({part_defs})")
51+
52+
if options:
53+
opts = []
54+
for key, value in options.items():
55+
if isinstance(value, str):
56+
value_sql = repr(value)
57+
opts.append(f"{key} = {value_sql}")
58+
elif isinstance(value, bool):
59+
opts.append(f"{key} = {str(value).upper()}")
60+
elif isinstance(value, list):
61+
list_str = ", ".join([repr(v) for v in value])
62+
opts.append(f"{key} = [{list_str}]")
63+
else:
64+
opts.append(f"{key} = {value}")
65+
options_str = ", ".join(opts)
66+
statement.append(f"OPTIONS ({options_str})")
67+
68+
return " ".join(statement)

0 commit comments

Comments
 (0)