Repository navigation
fix(bigquery): account for range element type, rounding mode, and foreign type in SchemaField equality - #18540
Conversation
…eign type in SchemaField equality
There was a problem hiding this comment.
Code Review
This pull request updates the BigQuery schema classes to support new fields, including RANGE element types, rounding_mode, and foreign_type_definition. Specifically, it implements equality, hashing, and representation methods for FieldElementType, and updates SchemaField._key() and SchemaField.__repr__ to include these new attributes, along with adding comprehensive unit tests. Feedback on the changes suggests simplifying the SchemaField.__repr__ implementation by slicing the key tuple instead of unpacking all ten attributes to prevent fragility, and avoiding the local import of FieldElementType inside a test function since the schema module is already imported at the module level.
| ( | ||
| name, | ||
| field_type, | ||
| mode, | ||
| default_value_expression, | ||
| description, | ||
| fields, | ||
| policy_tags, | ||
| timestamp_precision_tag, | ||
| rounding_mode, | ||
| foreign_type_definition, | ||
| ) = self._key() | ||
| policy_tags_inst = None if policy_tags is None else PolicyTagList(policy_tags) | ||
| adjusted_key = (*initial_tags, policy_tags_inst, timestamp_precision_tag) | ||
| adjusted_key = ( | ||
| name, | ||
| field_type, | ||
| mode, | ||
| default_value_expression, | ||
| description, | ||
| fields, | ||
| policy_tags_inst, | ||
| timestamp_precision_tag, | ||
| rounding_mode, | ||
| foreign_type_definition, | ||
| ) | ||
| return f"{self.__class__.__name__}{adjusted_key}" |
There was a problem hiding this comment.
The current implementation of __repr__ explicitly unpacks and reconstructs all 10 attributes of SchemaField. This is highly verbose and fragile, as any future addition or reordering of fields in _key() will break this method. Since only policy_tags needs to be modified for the representation, we can slice the key tuple and replace the policy_tags element by index, making the code much more concise and maintainable.
key = self._key()
policy_tags = key[6]
policy_tags_inst = None if policy_tags is None else PolicyTagList(policy_tags)
adjusted_key = key[:6] + (policy_tags_inst,) + key[7:]
return f"{self.__class__.__name__}{adjusted_key}"There was a problem hiding this comment.
Addressed by introducing an internal _SchemaFieldKey NamedTuple and using key._replace(policy_tags=...) in __repr__. This avoids both verbose explicit unpacking and brittle magic index slicing (key[:6]), ensuring future field additions or reorderings remain safe.
| from google.cloud.bigquery.schema import FieldElementType | ||
|
|
||
| field = self._make_one("R", "RANGE", range_element_type="DATE") | ||
| other = self._make_one( | ||
| "R", "RANGE", range_element_type=FieldElementType("DATE") | ||
| ) |
There was a problem hiding this comment.
Avoid importing FieldElementType inside the test function since the schema module is already imported at the module level. You can reference it directly as schema.FieldElementType.
field = self._make_one("R", "RANGE", range_element_type="DATE")
other = self._make_one(
"R", "RANGE", range_element_type=schema.FieldElementType("DATE")
)References
- Do not import modules or classes inside functions if they are already imported at the module level.
There was a problem hiding this comment.
Done! Removed the local import and referenced schema.FieldElementType directly.
Problem
When comparing two
SchemaFieldobjects with==, fields with differentrange_element_typevalues (such asRANGEversusRANGE) were incorrectly evaluated as equal (True).Investigation revealed that
SchemaField._key()omittedrange_element_type, meaning two fields with different range element types produced identical comparison keys. Furthermore,rounding_modeandforeign_type_definitionwere also omitted from_key(), causing fields differing only in those attributes to compare as equal. In addition,FieldElementTypelacked custom equality (__eq__) and hashing (__hash__) methods, which prevented comparing element types directly.Solution
SchemaField._key:RANGEfields asRANGEin_key(), following the existing conventions used forSTRING(max_length)andNUMERIC(precision, scale).rounding_modeandforeign_type_definitionto_key().SchemaField.__repr__:FieldElementType:_key,__eq__,__ne__,__hash__, and__repr__forFieldElementType.range_element_type,rounding_mode, andforeign_type_definition.FieldElementType.SchemaField.from_api_reprgenerates expected_key()values forRANGEtypes.Notes to Reviewers
RANGEasRANGEaligns with GoogleSQL syntax and mirrors how parameterized string, bytes, and numeric fields are handled inSchemaField._key().FieldElementTypecomparisons are case-insensitive because element types are normalized to uppercase upon creation.