Skip to content

Commit

Permalink
chore(bigquery): fix undelete table system test to use milliseconds i…
Browse files Browse the repository at this point in the history
…n snapshot decorator (#9649)

This fixes a flakey system test, where sometimes we sent an invalid
timestamp as a snapshot to copy from.
  • Loading branch information
tswast committed Nov 9, 2019
1 parent 0c5405f commit 7f7d1b5
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 64 deletions.
64 changes: 0 additions & 64 deletions bigquery/docs/snippets.py
Expand Up @@ -40,7 +40,6 @@
except (ImportError, AttributeError):
pyarrow = None

from google.api_core import datetime_helpers
from google.api_core.exceptions import InternalServerError
from google.api_core.exceptions import ServiceUnavailable
from google.api_core.exceptions import TooManyRequests
Expand Down Expand Up @@ -1428,69 +1427,6 @@ def test_extract_table_compressed(client, to_delete):
to_delete.insert(0, blob)


def test_undelete_table(client, to_delete):
dataset_id = "undelete_table_dataset_{}".format(_millis())
table_id = "undelete_table_table_{}".format(_millis())
dataset = bigquery.Dataset(client.dataset(dataset_id))
dataset.location = "US"
dataset = client.create_dataset(dataset)
to_delete.append(dataset)

table = bigquery.Table(dataset.table(table_id), schema=SCHEMA)
client.create_table(table)

# [START bigquery_undelete_table]
# TODO(developer): Uncomment the lines below and replace with your values.
# import time
# from google.cloud import bigquery
# client = bigquery.Client()
# dataset_id = 'my_dataset' # Replace with your dataset ID.
# table_id = 'my_table' # Replace with your table ID.

table_ref = client.dataset(dataset_id).table(table_id)

# TODO(developer): Choose an appropriate snapshot point as epoch
# milliseconds. For this example, we choose the current time as we're about
# to delete the table immediately afterwards.
snapshot_epoch = int(time.time() * 1000)
# [END bigquery_undelete_table]

# Due to very short lifecycle of the table, ensure we're not picking a time
# prior to the table creation due to time drift between backend and client.
table = client.get_table(table_ref)
created_epoch = datetime_helpers.to_microseconds(table.created)
if created_epoch > snapshot_epoch:
snapshot_epoch = created_epoch

# [START bigquery_undelete_table]

# "Accidentally" delete the table.
client.delete_table(table_ref) # API request

# Construct the restore-from table ID using a snapshot decorator.
snapshot_table_id = "{}@{}".format(table_id, snapshot_epoch)
source_table_ref = client.dataset(dataset_id).table(snapshot_table_id)

# Choose a new table ID for the recovered table data.
recovered_table_id = "{}_recovered".format(table_id)
dest_table_ref = client.dataset(dataset_id).table(recovered_table_id)

# Construct and run a copy job.
job = client.copy_table(
source_table_ref,
dest_table_ref,
# Location must match that of the source and destination tables.
location="US",
) # API request

job.result() # Waits for job to complete.

print(
"Copied data from deleted table {} to {}".format(table_id, recovered_table_id)
)
# [END bigquery_undelete_table]


def test_client_query_legacy_sql(client):
"""Run a query with Legacy SQL explicitly set"""
# [START bigquery_query_legacy]
Expand Down
12 changes: 12 additions & 0 deletions bigquery/docs/usage/tables.rst
Expand Up @@ -186,3 +186,15 @@ Delete a table with the
:dedent: 4
:start-after: [START bigquery_delete_table]
:end-before: [END bigquery_delete_table]

Restoring a Deleted Table
^^^^^^^^^^^^^^^^^^^^^^^^^

Restore a deleted table from a snapshot by using the
:func:`~google.cloud.bigquery.client.Client.copy_table` method:

.. literalinclude:: ../samples/undelete_table.py
:language: python
:dedent: 4
:start-after: [START bigquery_undelete_table]
:end-before: [END bigquery_undelete_table]
16 changes: 16 additions & 0 deletions bigquery/samples/tests/conftest.py
Expand Up @@ -78,6 +78,22 @@ def table_id(client, dataset_id):
client.delete_table(table, not_found_ok=True)


@pytest.fixture
def table_with_schema_id(client, dataset_id):
now = datetime.datetime.now()
table_id = "python_table_with_schema_{}_{}".format(
now.strftime("%Y%m%d%H%M%S"), uuid.uuid4().hex[:8]
)
schema = [
bigquery.SchemaField("full_name", "STRING"),
bigquery.SchemaField("age", "INTEGER"),
]
table = bigquery.Table("{}.{}".format(dataset_id, table_id), schema=schema)
table = client.create_table(table)
yield "{}.{}.{}".format(table.project, table.dataset_id, table.table_id)
client.delete_table(table, not_found_ok=True)


@pytest.fixture
def table_with_data_id(client):
return "bigquery-public-data.samples.shakespeare"
Expand Down
26 changes: 26 additions & 0 deletions bigquery/samples/tests/test_undelete_table.py
@@ -0,0 +1,26 @@
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from .. import undelete_table


def test_undelete_table(capsys, client, table_with_schema_id, random_table_id):
undelete_table.undelete_table(client, table_with_schema_id, random_table_id)
out, _ = capsys.readouterr()
assert (
"Copied data from deleted table {} to {}".format(
table_with_schema_id, random_table_id
)
in out
)
67 changes: 67 additions & 0 deletions bigquery/samples/undelete_table.py
@@ -0,0 +1,67 @@
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from google.api_core import datetime_helpers


def undelete_table(client, table_id, recovered_table_id):
# [START bigquery_undelete_table]
import time

# TODO(developer): Import the client library.
# from google.cloud import bigquery

# TODO(developer): Construct a BigQuery client object.
# client = bigquery.Client()

# TODO(developer): Choose a table to recover.
# table_id = "your-project.your_dataset.your_table"

# TODO(developer): Choose a new table ID for the recovered table data.
# recovery_table_id = "your-project.your_dataset.your_table_recovered"

# TODO(developer): Choose an appropriate snapshot point as epoch
# milliseconds. For this example, we choose the current time as we're about
# to delete the table immediately afterwards.
snapshot_epoch = int(time.time() * 1000)

# [START_EXCLUDE]
# Due to very short lifecycle of the table, ensure we're not picking a time
# prior to the table creation due to time drift between backend and client.
table = client.get_table(table_id)
created_epoch = datetime_helpers.to_milliseconds(table.created)
if created_epoch > snapshot_epoch:
snapshot_epoch = created_epoch
# [END_EXCLUDE]

# "Accidentally" delete the table.
client.delete_table(table_id) # API request

# Construct the restore-from table ID using a snapshot decorator.
snapshot_table_id = "{}@{}".format(table_id, snapshot_epoch)

# Construct and run a copy job.
job = client.copy_table(
snapshot_table_id,
recovered_table_id,
# Location must match that of the source and destination tables.
location="US",
) # API request

job.result() # Wait for job to complete.

print(
"Copied data from deleted table {} to {}".format(table_id, recovered_table_id)
)
# [END bigquery_undelete_table]

0 comments on commit 7f7d1b5

Please sign in to comment.