Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit 9d964d7

Browse files
authored
chore(bigquery): fix undelete table system test to use milliseconds in snapshot decorator (#9649)
This fixes a flakey system test, where sometimes we sent an invalid timestamp as a snapshot to copy from.
1 parent e609f34 commit 9d964d7

5 files changed

Lines changed: 121 additions & 64 deletions

File tree

docs/snippets.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
except (ImportError, AttributeError):
4141
pyarrow = None
4242

43-
from google.api_core import datetime_helpers
4443
from google.api_core.exceptions import InternalServerError
4544
from google.api_core.exceptions import ServiceUnavailable
4645
from google.api_core.exceptions import TooManyRequests
@@ -1428,69 +1427,6 @@ def test_extract_table_compressed(client, to_delete):
14281427
to_delete.insert(0, blob)
14291428

14301429

1431-
def test_undelete_table(client, to_delete):
1432-
dataset_id = "undelete_table_dataset_{}".format(_millis())
1433-
table_id = "undelete_table_table_{}".format(_millis())
1434-
dataset = bigquery.Dataset(client.dataset(dataset_id))
1435-
dataset.location = "US"
1436-
dataset = client.create_dataset(dataset)
1437-
to_delete.append(dataset)
1438-
1439-
table = bigquery.Table(dataset.table(table_id), schema=SCHEMA)
1440-
client.create_table(table)
1441-
1442-
# [START bigquery_undelete_table]
1443-
# TODO(developer): Uncomment the lines below and replace with your values.
1444-
# import time
1445-
# from google.cloud import bigquery
1446-
# client = bigquery.Client()
1447-
# dataset_id = 'my_dataset' # Replace with your dataset ID.
1448-
# table_id = 'my_table' # Replace with your table ID.
1449-
1450-
table_ref = client.dataset(dataset_id).table(table_id)
1451-
1452-
# TODO(developer): Choose an appropriate snapshot point as epoch
1453-
# milliseconds. For this example, we choose the current time as we're about
1454-
# to delete the table immediately afterwards.
1455-
snapshot_epoch = int(time.time() * 1000)
1456-
# [END bigquery_undelete_table]
1457-
1458-
# Due to very short lifecycle of the table, ensure we're not picking a time
1459-
# prior to the table creation due to time drift between backend and client.
1460-
table = client.get_table(table_ref)
1461-
created_epoch = datetime_helpers.to_microseconds(table.created)
1462-
if created_epoch > snapshot_epoch:
1463-
snapshot_epoch = created_epoch
1464-
1465-
# [START bigquery_undelete_table]
1466-
1467-
# "Accidentally" delete the table.
1468-
client.delete_table(table_ref) # API request
1469-
1470-
# Construct the restore-from table ID using a snapshot decorator.
1471-
snapshot_table_id = "{}@{}".format(table_id, snapshot_epoch)
1472-
source_table_ref = client.dataset(dataset_id).table(snapshot_table_id)
1473-
1474-
# Choose a new table ID for the recovered table data.
1475-
recovered_table_id = "{}_recovered".format(table_id)
1476-
dest_table_ref = client.dataset(dataset_id).table(recovered_table_id)
1477-
1478-
# Construct and run a copy job.
1479-
job = client.copy_table(
1480-
source_table_ref,
1481-
dest_table_ref,
1482-
# Location must match that of the source and destination tables.
1483-
location="US",
1484-
) # API request
1485-
1486-
job.result() # Waits for job to complete.
1487-
1488-
print(
1489-
"Copied data from deleted table {} to {}".format(table_id, recovered_table_id)
1490-
)
1491-
# [END bigquery_undelete_table]
1492-
1493-
14941430
def test_client_query_legacy_sql(client):
14951431
"""Run a query with Legacy SQL explicitly set"""
14961432
# [START bigquery_query_legacy]

docs/usage/tables.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,15 @@ Delete a table with the
186186
:dedent: 4
187187
:start-after: [START bigquery_delete_table]
188188
:end-before: [END bigquery_delete_table]
189+
190+
Restoring a Deleted Table
191+
^^^^^^^^^^^^^^^^^^^^^^^^^
192+
193+
Restore a deleted table from a snapshot by using the
194+
:func:`~google.cloud.bigquery.client.Client.copy_table` method:
195+
196+
.. literalinclude:: ../samples/undelete_table.py
197+
:language: python
198+
:dedent: 4
199+
:start-after: [START bigquery_undelete_table]
200+
:end-before: [END bigquery_undelete_table]

samples/tests/conftest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,22 @@ def table_id(client, dataset_id):
7878
client.delete_table(table, not_found_ok=True)
7979

8080

81+
@pytest.fixture
82+
def table_with_schema_id(client, dataset_id):
83+
now = datetime.datetime.now()
84+
table_id = "python_table_with_schema_{}_{}".format(
85+
now.strftime("%Y%m%d%H%M%S"), uuid.uuid4().hex[:8]
86+
)
87+
schema = [
88+
bigquery.SchemaField("full_name", "STRING"),
89+
bigquery.SchemaField("age", "INTEGER"),
90+
]
91+
table = bigquery.Table("{}.{}".format(dataset_id, table_id), schema=schema)
92+
table = client.create_table(table)
93+
yield "{}.{}.{}".format(table.project, table.dataset_id, table.table_id)
94+
client.delete_table(table, not_found_ok=True)
95+
96+
8197
@pytest.fixture
8298
def table_with_data_id(client):
8399
return "bigquery-public-data.samples.shakespeare"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2019 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 .. import undelete_table
16+
17+
18+
def test_undelete_table(capsys, client, table_with_schema_id, random_table_id):
19+
undelete_table.undelete_table(client, table_with_schema_id, random_table_id)
20+
out, _ = capsys.readouterr()
21+
assert (
22+
"Copied data from deleted table {} to {}".format(
23+
table_with_schema_id, random_table_id
24+
)
25+
in out
26+
)

samples/undelete_table.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2019 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 google.api_core import datetime_helpers
16+
17+
18+
def undelete_table(client, table_id, recovered_table_id):
19+
# [START bigquery_undelete_table]
20+
import time
21+
22+
# TODO(developer): Import the client library.
23+
# from google.cloud import bigquery
24+
25+
# TODO(developer): Construct a BigQuery client object.
26+
# client = bigquery.Client()
27+
28+
# TODO(developer): Choose a table to recover.
29+
# table_id = "your-project.your_dataset.your_table"
30+
31+
# TODO(developer): Choose a new table ID for the recovered table data.
32+
# recovery_table_id = "your-project.your_dataset.your_table_recovered"
33+
34+
# TODO(developer): Choose an appropriate snapshot point as epoch
35+
# milliseconds. For this example, we choose the current time as we're about
36+
# to delete the table immediately afterwards.
37+
snapshot_epoch = int(time.time() * 1000)
38+
39+
# [START_EXCLUDE]
40+
# Due to very short lifecycle of the table, ensure we're not picking a time
41+
# prior to the table creation due to time drift between backend and client.
42+
table = client.get_table(table_id)
43+
created_epoch = datetime_helpers.to_milliseconds(table.created)
44+
if created_epoch > snapshot_epoch:
45+
snapshot_epoch = created_epoch
46+
# [END_EXCLUDE]
47+
48+
# "Accidentally" delete the table.
49+
client.delete_table(table_id) # API request
50+
51+
# Construct the restore-from table ID using a snapshot decorator.
52+
snapshot_table_id = "{}@{}".format(table_id, snapshot_epoch)
53+
54+
# Construct and run a copy job.
55+
job = client.copy_table(
56+
snapshot_table_id,
57+
recovered_table_id,
58+
# Location must match that of the source and destination tables.
59+
location="US",
60+
) # API request
61+
62+
job.result() # Wait for job to complete.
63+
64+
print(
65+
"Copied data from deleted table {} to {}".format(table_id, recovered_table_id)
66+
)
67+
# [END bigquery_undelete_table]

0 commit comments

Comments
 (0)