Skip to content

Commit 02f2236

Browse files
authored
fix(NoTicket): Hashability of extended types (#427)
1 parent 4ce3ce9 commit 02f2236

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

src/firebolt/common/_types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ class ExtendedType:
8787
def is_valid_type(type_: Any) -> bool:
8888
return type_ in _col_types or isinstance(type_, ExtendedType)
8989

90+
# Remember to override this method in subclasses
91+
# if __eq__ is overridden
92+
# https://docs.python.org/3/reference/datamodel.html#object.__hash__
9093
def __hash__(self) -> int:
9194
return hash(str(self))
9295

@@ -110,6 +113,8 @@ def __eq__(self, other: object) -> bool:
110113
return NotImplemented
111114
return other.subtype == self.subtype
112115

116+
__hash__ = ExtendedType.__hash__
117+
113118

114119
class DECIMAL(ExtendedType):
115120
"""Class for holding `decimal` value information in Firebolt DB."""
@@ -129,6 +134,8 @@ def __eq__(self, other: object) -> bool:
129134
return NotImplemented
130135
return other.precision == self.precision and other.scale == self.scale
131136

137+
__hash__ = ExtendedType.__hash__
138+
132139

133140
class STRUCT(ExtendedType):
134141
__name__ = "Struct"
@@ -146,6 +153,8 @@ def __str__(self) -> str:
146153
def __eq__(self, other: Any) -> bool:
147154
return isinstance(other, STRUCT) and other.fields == self.fields
148155

156+
__hash__ = ExtendedType.__hash__
157+
149158

150159
NULLABLE_SUFFIX = "null"
151160

tests/unit/common/test_types.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from dataclasses import fields
22

3+
from firebolt.common._types import ARRAY, DECIMAL, STRUCT
34
from firebolt.common.row_set.types import Column
45

56

@@ -15,3 +16,81 @@ def test_columns_supports_indexing():
1516
)
1617
for i, field in enumerate(fields(column)):
1718
assert getattr(column, field.name) == column[i]
19+
20+
21+
def test_array_is_hashable():
22+
"""Test that ARRAY type is hashable and can be used in dictionaries and sets."""
23+
# Create ARRAY types
24+
array_of_int = ARRAY(int)
25+
array_of_str = ARRAY(str)
26+
array_of_array = ARRAY(ARRAY(int))
27+
28+
# Test hash function works
29+
assert isinstance(hash(array_of_int), int)
30+
assert isinstance(hash(array_of_str), int)
31+
assert isinstance(hash(array_of_array), int)
32+
33+
# Test equality with same hash values
34+
assert hash(array_of_int) == hash(ARRAY(int))
35+
assert hash(array_of_str) == hash(ARRAY(str))
36+
37+
# Test usage in dictionary
38+
d = {array_of_int: "array_of_int", array_of_str: "array_of_str"}
39+
assert d[array_of_int] == "array_of_int"
40+
assert d[ARRAY(int)] == "array_of_int"
41+
42+
# Test usage in set
43+
s = {array_of_int, array_of_str, array_of_array, ARRAY(int)}
44+
assert len(s) == 3 # array_of_int and ARRAY(int) are equal
45+
46+
47+
def test_decimal_is_hashable():
48+
"""Test that DECIMAL type is hashable and can be used in dictionaries and sets."""
49+
# Create DECIMAL types
50+
dec1 = DECIMAL(10, 2)
51+
dec2 = DECIMAL(5, 0)
52+
dec3 = DECIMAL(10, 2) # Same as dec1
53+
54+
# Test hash function works
55+
assert isinstance(hash(dec1), int)
56+
assert isinstance(hash(dec2), int)
57+
58+
# Test equality with same hash values
59+
assert hash(dec1) == hash(dec3)
60+
assert dec1 == dec3
61+
62+
# Test usage in dictionary
63+
d = {dec1: "dec1", dec2: "dec2"}
64+
assert d[dec1] == "dec1"
65+
assert d[DECIMAL(10, 2)] == "dec1"
66+
67+
# Test usage in set
68+
s = {dec1, dec2, dec3}
69+
assert len(s) == 2 # dec1 and dec3 are the same
70+
71+
72+
def test_struct_is_hashable():
73+
"""Test that STRUCT type is hashable and can be used in dictionaries and sets."""
74+
# Create STRUCT types
75+
struct1 = STRUCT({"name": str, "age": int})
76+
struct2 = STRUCT({"value": DECIMAL(10, 2)})
77+
struct3 = STRUCT({"name": str, "age": int}) # Same as struct1
78+
nested_struct = STRUCT({"person": struct1, "balance": float})
79+
80+
# Test hash function works
81+
assert isinstance(hash(struct1), int)
82+
assert isinstance(hash(struct2), int)
83+
assert isinstance(hash(nested_struct), int)
84+
85+
# Test equality with same hash values
86+
assert hash(struct1) == hash(struct3)
87+
assert struct1 == struct3
88+
89+
# Test usage in dictionary
90+
d = {struct1: "struct1", struct2: "struct2"}
91+
assert d[struct1] == "struct1"
92+
assert d[STRUCT({"name": str, "age": int})] == "struct1"
93+
94+
# Test usage in set
95+
s = {struct1, struct2, struct3, nested_struct}
96+
assert len(s) == 3 # struct1 and struct3 are the same

0 commit comments

Comments
 (0)