Skip to content

Commit

Permalink
feat: add support for Dataset.isCaseInsensitive (#1671)
Browse files Browse the repository at this point in the history
* feat: add support for Dataset.isCaseInsensitive

This commit creates a property named is_case_insensitive (in dataset.py)
that allows the usage of the isCaseSensitive field in the Dataset REST
API.

Fixes: #1670

* tests: add unit tests for dataset.is_case_insensitive

* docs: improve comments for dataset.is_case_sensitive (code and tests)

* docs: improve docstring of is_case_insensitive

Co-authored-by: Lingqing Gan <lingqing.gan@gmail.com>

* Update tests/system/test_client.py

---------

Co-authored-by: Lingqing Gan <lingqing.gan@gmail.com>
  • Loading branch information
joseignaciorc and Linchin committed Nov 1, 2023
1 parent f22eff2 commit 386fa86
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 2 deletions.
20 changes: 20 additions & 0 deletions google/cloud/bigquery/dataset.py
Expand Up @@ -524,6 +524,7 @@ class Dataset(object):
"default_table_expiration_ms": "defaultTableExpirationMs",
"friendly_name": "friendlyName",
"default_encryption_configuration": "defaultEncryptionConfiguration",
"is_case_insensitive": "isCaseInsensitive",
"storage_billing_model": "storageBillingModel",
"max_time_travel_hours": "maxTimeTravelHours",
"default_rounding_mode": "defaultRoundingMode",
Expand Down Expand Up @@ -822,6 +823,25 @@ def default_encryption_configuration(self, value):
api_repr = value.to_api_repr()
self._properties["defaultEncryptionConfiguration"] = api_repr

@property
def is_case_insensitive(self):
"""Optional[bool]: True if the dataset and its table names are case-insensitive, otherwise False.
By default, this is False, which means the dataset and its table names are case-sensitive.
This field does not affect routine references.
Raises:
ValueError: for invalid value types.
"""
return self._properties.get("isCaseInsensitive") or False

@is_case_insensitive.setter
def is_case_insensitive(self, value):
if not isinstance(value, bool) and value is not None:
raise ValueError("Pass a boolean value, or None")
if value is None:
value = False
self._properties["isCaseInsensitive"] = value

@property
def storage_billing_model(self):
"""Union[str, None]: StorageBillingModel of the dataset as set by the user
Expand Down
61 changes: 59 additions & 2 deletions tests/system/test_client.py
Expand Up @@ -237,6 +237,17 @@ def test_create_dataset(self):
self.assertTrue(_dataset_exists(dataset))
self.assertEqual(dataset.dataset_id, DATASET_ID)
self.assertEqual(dataset.project, Config.CLIENT.project)
self.assertIs(dataset.is_case_insensitive, False)

def test_create_dataset_case_sensitive(self):
DATASET_ID = _make_dataset_id("create_cs_dataset")
dataset = self.temp_dataset(DATASET_ID, is_case_insensitive=False)
self.assertIs(dataset.is_case_insensitive, False)

def test_create_dataset_case_insensitive(self):
DATASET_ID = _make_dataset_id("create_ci_dataset")
dataset = self.temp_dataset(DATASET_ID, is_case_insensitive=True)
self.assertIs(dataset.is_case_insensitive, True)

def test_create_dataset_max_time_travel_hours(self):
DATASET_ID = _make_dataset_id("create_ci_dataset")
Expand Down Expand Up @@ -283,16 +294,19 @@ def test_update_dataset(self):
self.assertIsNone(dataset.friendly_name)
self.assertIsNone(dataset.description)
self.assertEqual(dataset.labels, {})
self.assertIs(dataset.is_case_insensitive, False)

dataset.friendly_name = "Friendly"
dataset.description = "Description"
dataset.labels = {"priority": "high", "color": "blue"}
dataset.is_case_insensitive = True
ds2 = Config.CLIENT.update_dataset(
dataset, ("friendly_name", "description", "labels")
dataset, ("friendly_name", "description", "labels", "is_case_insensitive")
)
self.assertEqual(ds2.friendly_name, "Friendly")
self.assertEqual(ds2.description, "Description")
self.assertEqual(ds2.labels, {"priority": "high", "color": "blue"})
self.assertIs(ds2.is_case_insensitive, True)

ds2.labels = {
"color": "green", # change
Expand Down Expand Up @@ -347,6 +361,48 @@ def test_create_table(self):
self.assertTrue(_table_exists(table))
self.assertEqual(table.table_id, table_id)

def test_create_tables_in_case_insensitive_dataset(self):
ci_dataset = self.temp_dataset(
_make_dataset_id("create_table"), is_case_insensitive=True
)
table_arg = Table(ci_dataset.table("test_table2"), schema=SCHEMA)
tablemc_arg = Table(ci_dataset.table("Test_taBLe2")) # same name, in Mixed Case

table = helpers.retry_403(Config.CLIENT.create_table)(table_arg)
self.to_delete.insert(0, table)

self.assertTrue(_table_exists(table_arg))
self.assertTrue(_table_exists(tablemc_arg))
self.assertIs(ci_dataset.is_case_insensitive, True)

def test_create_tables_in_case_sensitive_dataset(self):
ci_dataset = self.temp_dataset(
_make_dataset_id("create_table"), is_case_insensitive=False
)
table_arg = Table(ci_dataset.table("test_table3"), schema=SCHEMA)
tablemc_arg = Table(ci_dataset.table("Test_taBLe3")) # same name, in Mixed Case

table = helpers.retry_403(Config.CLIENT.create_table)(table_arg)
self.to_delete.insert(0, table)

self.assertTrue(_table_exists(table_arg))
self.assertFalse(_table_exists(tablemc_arg))
self.assertIs(ci_dataset.is_case_insensitive, False)

def test_create_tables_in_default_sensitivity_dataset(self):
dataset = self.temp_dataset(_make_dataset_id("create_table"))
table_arg = Table(dataset.table("test_table4"), schema=SCHEMA)
tablemc_arg = Table(
dataset.table("Test_taBLe4")
) # same name, in MC (Mixed Case)

table = helpers.retry_403(Config.CLIENT.create_table)(table_arg)
self.to_delete.insert(0, table)

self.assertTrue(_table_exists(table_arg))
self.assertFalse(_table_exists(tablemc_arg))
self.assertIs(dataset.is_case_insensitive, False)

def test_create_table_with_real_custom_policy(self):
from google.cloud.bigquery.schema import PolicyTagList

Expand Down Expand Up @@ -2308,7 +2364,8 @@ def temp_dataset(self, dataset_id, *args, **kwargs):
dataset.max_time_travel_hours = kwargs.get("max_time_travel_hours")
if kwargs.get("default_rounding_mode"):
dataset.default_rounding_mode = kwargs.get("default_rounding_mode")

if kwargs.get("is_case_insensitive"):
dataset.is_case_insensitive = kwargs.get("is_case_insensitive")
dataset = helpers.retry_403(Config.CLIENT.create_dataset)(dataset)
self.to_delete.append(dataset)
return dataset
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/test_dataset.py
Expand Up @@ -744,6 +744,9 @@ def _verify_resource_properties(self, dataset, resource):
self.assertEqual(dataset.description, resource.get("description"))
self.assertEqual(dataset.friendly_name, resource.get("friendlyName"))
self.assertEqual(dataset.location, resource.get("location"))
self.assertEqual(
dataset.is_case_insensitive, resource.get("isCaseInsensitive") or False
)
if "defaultEncryptionConfiguration" in resource:
self.assertEqual(
dataset.default_encryption_configuration.kms_key_name,
Expand Down Expand Up @@ -781,6 +784,7 @@ def test_ctor_defaults(self):
self.assertIsNone(dataset.description)
self.assertIsNone(dataset.friendly_name)
self.assertIsNone(dataset.location)
self.assertEqual(dataset.is_case_insensitive, False)

def test_ctor_string(self):
dataset = self._make_one("some-project.some_dset")
Expand Down Expand Up @@ -818,6 +822,7 @@ def test_ctor_explicit(self):
self.assertIsNone(dataset.description)
self.assertIsNone(dataset.friendly_name)
self.assertIsNone(dataset.location)
self.assertEqual(dataset.is_case_insensitive, False)

def test_access_entries_setter_non_list(self):
dataset = self._make_one(self.DS_REF)
Expand Down Expand Up @@ -910,6 +915,26 @@ def test_labels_getter_missing_value(self):
dataset = self._make_one(self.DS_REF)
self.assertEqual(dataset.labels, {})

def test_is_case_insensitive_setter_bad_value(self):
dataset = self._make_one(self.DS_REF)
with self.assertRaises(ValueError):
dataset.is_case_insensitive = 0

def test_is_case_insensitive_setter_true(self):
dataset = self._make_one(self.DS_REF)
dataset.is_case_insensitive = True
self.assertEqual(dataset.is_case_insensitive, True)

def test_is_case_insensitive_setter_none(self):
dataset = self._make_one(self.DS_REF)
dataset.is_case_insensitive = None
self.assertEqual(dataset.is_case_insensitive, False)

def test_is_case_insensitive_setter_false(self):
dataset = self._make_one(self.DS_REF)
dataset.is_case_insensitive = False
self.assertEqual(dataset.is_case_insensitive, False)

def test_from_api_repr_missing_identity(self):
self._setUpConstants()
RESOURCE = {}
Expand Down

0 comments on commit 386fa86

Please sign in to comment.