Skip to content

Commit 7a34135

Browse files
feat: Fir 16854 add boolean support (#231)
1 parent 19f7778 commit 7a34135

File tree

7 files changed

+83
-6
lines changed

7 files changed

+83
-6
lines changed

src/firebolt/async_db/_types.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ class _InternalType(Enum):
200200
TimestampNtz = "TimestampNtz"
201201
TimestampTz = "TimestampTz"
202202

203+
# BOOLEAN
204+
Boolean = "boolean"
205+
203206
# Nullable(Nothing)
204207
Nothing = "Nothing"
205208

@@ -224,6 +227,7 @@ def python_type(self) -> type:
224227
_InternalType.DateTime: datetime,
225228
_InternalType.TimestampNtz: datetime,
226229
_InternalType.TimestampTz: datetime,
230+
_InternalType.Boolean: bool,
227231
# For simplicity, this could happen only during 'select null' query
228232
_InternalType.Nothing: str,
229233
}
@@ -285,6 +289,10 @@ def parse_value(
285289
if not isinstance(value, str):
286290
raise DataError(f"Invalid datetime value {value}: str expected")
287291
return parse_datetime(value)
292+
if ctype is bool:
293+
if not isinstance(value, (bool, int)):
294+
raise DataError(f"Invalid boolean value {value}: bool or int expected")
295+
return bool(value)
288296
if isinstance(ctype, DECIMAL):
289297
assert isinstance(value, (str, int))
290298
return Decimal(value)
@@ -304,7 +312,7 @@ def parse_value(
304312
def format_value(value: ParameterType) -> str:
305313
"""For Python value to be used in a SQL query."""
306314
if isinstance(value, bool):
307-
return str(int(value))
315+
return "true" if value else "false"
308316
if isinstance(value, (int, float, Decimal)):
309317
return str(value)
310318
elif isinstance(value, str):

tests/integration/dbapi/async/test_queries_async.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,11 @@ async def test_select(
138138
assert (
139139
await c.execute(f"SET advanced_mode=1") == -1
140140
), "Invalid set statment row count"
141+
# For TimestampTz test
141142
assert (
142143
await c.execute(f"SET time_zone={timezone_name}") == -1
143144
), "Invalid set statment row count"
145+
144146
assert await c.execute(all_types_query) == 1, "Invalid row count returned"
145147
assert c.rowcount == 1, "Invalid rowcount value"
146148
data = await c.fetchall()
@@ -166,6 +168,37 @@ async def test_select(
166168
)
167169

168170

171+
async def test_boolean(
172+
connection: Connection,
173+
) -> None:
174+
"""Select handles boolean properly."""
175+
with connection.cursor() as c:
176+
assert (
177+
await c.execute(f"SET advanced_mode=1") == -1
178+
), "Invalid set statment row count"
179+
# Unfortunately our parser doesn't support string set parameters
180+
c._set_parameters.update(
181+
{
182+
"bool_output_format": "postgres",
183+
"output_format_firebolt_type_names": "true",
184+
}
185+
)
186+
187+
assert (
188+
await c.execute('select true as "bool"') == 1
189+
), "Invalid row count returned"
190+
assert c.rowcount == 1, "Invalid rowcount value"
191+
data = await c.fetchall()
192+
assert len(data) == c.rowcount, "Invalid data length"
193+
assert_deep_eq(data, [[True]], "Invalid data")
194+
assert (
195+
c.description == [Column("bool", bool, None, None, None, None, None)],
196+
"Invalid description value",
197+
)
198+
assert len(data[0]) == len(c.description), "Invalid description length"
199+
assert len(await c.fetchall()) == 0, "Redundant data returned by fetchall"
200+
201+
169202
@mark.skip("Don't have a good way to test this anymore. FIR-16038")
170203
@mark.timeout(timeout=400)
171204
async def test_long_query(

tests/integration/dbapi/conftest.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def all_types_query() -> str:
7272
"CAST('2019-07-31 01:01:01.1234' AS TIMESTAMP_EXT(4)) as \"datetime64\", "
7373
"CAST('1111-01-05 17:04:42.123456' as timestampntz) as timestampntz, "
7474
"'1111-01-05 17:04:42.123456'::timestamptz as timestamptz,"
75-
'true as "bool",'
7675
"[1,2,3,4] as \"array\", cast('1231232.123459999990457054844258706536' as "
7776
'decimal(38,30)) as "decimal", '
7877
"cast(null as int) as nullable"
@@ -100,7 +99,6 @@ def all_types_query_description() -> List[Column]:
10099
Column("datetime64", DATETIME64(4), None, None, None, None, None),
101100
Column("timestampntz", datetime, None, None, None, None, None),
102101
Column("timestamptz", datetime, None, None, None, None, None),
103-
Column("bool", int, None, None, None, None, None),
104102
Column("array", ARRAY(int), None, None, None, None, None),
105103
Column("decimal", DECIMAL(38, 30), None, None, None, None, None),
106104
Column("nullable", int, None, None, None, None, None),
@@ -138,7 +136,6 @@ def all_types_query_response(timezone_offset_seconds: int) -> List[ColType]:
138136
123456,
139137
tzinfo=timezone(timedelta(seconds=timezone_offset_seconds)),
140138
),
141-
1,
142139
[1, 2, 3, 4],
143140
Decimal("1231232.123459999990457054844258706536"),
144141
None,

tests/integration/dbapi/sync/test_queries.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,32 @@ def test_select(
122122
)
123123

124124

125+
def test_boolean(
126+
connection: Connection,
127+
) -> None:
128+
"""Select handles boolean properly."""
129+
with connection.cursor() as c:
130+
assert c.execute(f"SET advanced_mode=1") == -1, "Invalid set statment row count"
131+
# Unfortunately our parser doesn't support string set parameters
132+
c._set_parameters.update(
133+
{
134+
"bool_output_format": "postgres",
135+
"output_format_firebolt_type_names": "true",
136+
}
137+
)
138+
139+
assert c.execute('select true as "bool"') == 1, "Invalid row count returned"
140+
assert c.rowcount == 1, "Invalid rowcount value"
141+
data = c.fetchall()
142+
assert len(data) == c.rowcount, "Invalid data length"
143+
assert_deep_eq(data, [[True]], "Invalid data")
144+
assert c.description == [
145+
Column("bool", bool, None, None, None, None, None)
146+
], "Invalid description value"
147+
assert len(data[0]) == len(c.description), "Invalid description length"
148+
assert len(c.fetchall()) == 0, "Redundant data returned by fetchall"
149+
150+
125151
@mark.skip("Don't have a good way to test this anymore. FIR-16038")
126152
@mark.timeout(timeout=400)
127153
def test_long_query(

tests/unit/async_db/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def types_map() -> Dict[str, type]:
6060
"Decimal(38,0)": DECIMAL(38, 0),
6161
# Invalid decimal format
6262
"Decimal(38)": str,
63+
"boolean": bool,
6364
"SomeRandomNotExistingType": str,
6465
}
6566
array_types = {f"Array({k})": ARRAY(v) for k, v in base_types.items()}

tests/unit/async_db/test_typing_format.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
(1.123, "1.123"),
3030
(Decimal("1.123"), "1.123"),
3131
(Decimal(1.123), "1.1229999999999999982236431605997495353221893310546875"),
32-
(True, "1"),
33-
(False, "0"),
32+
(True, "true"),
33+
(False, "false"),
3434
# Date, datetime
3535
(date(2022, 1, 10), "'2022-01-10'"),
3636
(datetime(2022, 1, 10, 1, 1, 1), "'2022-01-10 01:01:01'"),

tests/unit/async_db/test_typing_parse.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,15 @@ def test_helpers() -> None:
236236

237237
with raises(NotSupportedError):
238238
TimeFromTicks(0)
239+
240+
241+
def test_parse_value_bool() -> None:
242+
"""parse_value parses all int values correctly."""
243+
assert parse_value(True, bool) == True, "Error parsing boolean: provided true"
244+
assert parse_value(False, bool) == False, "Error parsing boolean: provided false"
245+
assert parse_value(2, bool) == True, "Error parsing boolean: provided 2"
246+
assert parse_value(0, bool) == False, "Error parsing boolean: provided 0"
247+
assert parse_value(None, bool) is None, "Error parsing boolean: provided None"
248+
249+
with raises(DataError):
250+
parse_value("true", bool)

0 commit comments

Comments
 (0)