Skip to content

Commit 5d323fc

Browse files
feat: implement Connection.commit, DataError on empty data fetch (#76)
* add connection.commit method * raise DataError on empty result fetch * fix type annotations * improve integration tests
1 parent 730d091 commit 5d323fc

File tree

8 files changed

+87
-9
lines changed

8 files changed

+87
-9
lines changed

src/firebolt/async_db/connection.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ def _remove_cursor(self, cursor: Cursor) -> None:
191191
except ValueError:
192192
pass
193193

194+
def commit(self) -> None:
195+
"""Does nothing since Firebolt doesn't have transactions"""
196+
if self.closed:
197+
raise ConnectionClosedError("Unable to commit: connection closed")
198+
194199

195200
class Connection(BaseConnection):
196201
cleandoc(

src/firebolt/async_db/cursor.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,11 @@ def _get_next_range(self, size: int) -> Tuple[int, int]:
290290
and update _idx to point to the end of this range
291291
"""
292292
)
293+
293294
if self._rows is None:
294295
# No elements to take
295-
return (0, 0)
296+
raise DataError("no rows to fetch")
297+
296298
left = self._idx
297299
right = min(self._idx + size, len(self._rows))
298300
self._idx = right
@@ -328,8 +330,8 @@ def fetchmany(self, size: Optional[int] = None) -> List[List[ColType]]:
328330
@check_query_executed
329331
def fetchall(self) -> List[List[ColType]]:
330332
"""Fetch all remaining rows of a query result."""
333+
left, right = self._get_next_range(self.rowcount)
331334
assert self._rows is not None
332-
left, right = self._get_next_range(len(self._rows))
333335
rows = self._rows[left:right]
334336
return [self._parse_row(row) for row in rows]
335337

tests/integration/dbapi/async/test_queries_async.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from datetime import date, datetime
22
from typing import Any, List
33

4-
from pytest import mark
4+
from pytest import mark, raises
55

6-
from firebolt.async_db import Connection, Cursor
6+
from firebolt.async_db import Connection, Cursor, DataError
77
from firebolt.async_db._types import ColType, Column
88

99

@@ -137,7 +137,14 @@ async def test_empty_query(c: Cursor, query: str) -> None:
137137
assert await c.execute(query) == -1, "Invalid row count returned"
138138
assert c.rowcount == -1, "Invalid rowcount value"
139139
assert c.description is None, "Invalid description"
140-
assert await c.fetchone() is None, "Invalid data returned"
140+
with raises(DataError):
141+
await c.fetchone()
142+
143+
with raises(DataError):
144+
await c.fetchmany()
145+
146+
with raises(DataError):
147+
await c.fetchall()
141148

142149
with connection.cursor() as c:
143150
await c.execute("DROP TABLE IF EXISTS test_tb")

tests/integration/dbapi/sync/test_queries.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from datetime import date, datetime
22
from typing import Any, List
33

4+
from pytest import raises
5+
46
from firebolt.async_db._types import ColType
57
from firebolt.async_db.cursor import Column
6-
from firebolt.db import Connection, Cursor
8+
from firebolt.db import Connection, Cursor, DataError
79

810

911
def assert_deep_eq(got: Any, expected: Any, msg: str) -> bool:
@@ -128,7 +130,14 @@ def test_empty_query(c: Cursor, query: str) -> None:
128130
assert c.execute(query) == -1, "Invalid row count returned"
129131
assert c.rowcount == -1, "Invalid rowcount value"
130132
assert c.description is None, "Invalid description"
131-
assert c.fetchone() is None, "Invalid data returned"
133+
with raises(DataError):
134+
c.fetchone()
135+
136+
with raises(DataError):
137+
c.fetchmany()
138+
139+
with raises(DataError):
140+
c.fetchall()
132141

133142
with connection.cursor() as c:
134143
c.execute("DROP TABLE IF EXISTS test_tb")

tests/unit/async_db/test_connection.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,13 @@ async def test_connect_engine_name(
190190
api_endpoint=settings.server,
191191
) as connection:
192192
assert await connection.cursor().execute("select*") == len(python_query_data)
193+
194+
195+
@mark.asyncio
196+
async def test_connection_commit(connection: Connection):
197+
# nothing happens
198+
connection.commit()
199+
200+
await connection.aclose()
201+
with raises(ConnectionClosedError):
202+
connection.commit()

tests/unit/async_db/test_cursor.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from firebolt.async_db.cursor import ColType, CursorState
1111
from firebolt.common.exception import (
1212
CursorClosedError,
13+
DataError,
1314
EngineNotRunningError,
1415
FireboltDatabaseError,
1516
OperationalError,
@@ -175,7 +176,6 @@ async def test_cursor_execute(
175176
assert await query() == -1, "Invalid row count for insert query"
176177
assert cursor.rowcount == -1, "Invalid rowcount value for insert query"
177178
assert cursor.description is None, "Invalid description for insert query"
178-
assert await cursor.fetchone() is None, "Non-empty fetchone for insert query"
179179

180180

181181
@mark.asyncio
@@ -264,6 +264,7 @@ async def test_cursor_fetchone(
264264
auth_callback: Callable,
265265
auth_url: str,
266266
query_callback: Callable,
267+
insert_query_callback: Callable,
267268
query_url: str,
268269
cursor: Cursor,
269270
):
@@ -284,13 +285,19 @@ async def test_cursor_fetchone(
284285
await cursor.fetchone() is None
285286
), "fetchone should return None when no rows left to fetch"
286287

288+
httpx_mock.add_callback(insert_query_callback, url=query_url)
289+
await cursor.execute("sql")
290+
with raises(DataError):
291+
await cursor.fetchone()
292+
287293

288294
@mark.asyncio
289295
async def test_cursor_fetchmany(
290296
httpx_mock: HTTPXMock,
291297
auth_callback: Callable,
292298
auth_url: str,
293299
query_callback: Callable,
300+
insert_query_callback: Callable,
294301
query_url: str,
295302
cursor: Cursor,
296303
):
@@ -342,13 +349,19 @@ async def test_cursor_fetchmany(
342349
len(await cursor.fetchmany()) == 0
343350
), "fetchmany should return empty result set when no rows left to fetch"
344351

352+
httpx_mock.add_callback(insert_query_callback, url=query_url)
353+
await cursor.execute("sql")
354+
with raises(DataError):
355+
await cursor.fetchmany()
356+
345357

346358
@mark.asyncio
347359
async def test_cursor_fetchall(
348360
httpx_mock: HTTPXMock,
349361
auth_callback: Callable,
350362
auth_url: str,
351363
query_callback: Callable,
364+
insert_query_callback: Callable,
352365
query_url: str,
353366
cursor: Cursor,
354367
):
@@ -371,6 +384,11 @@ async def test_cursor_fetchall(
371384
len(await cursor.fetchall()) == 0
372385
), "fetchmany should return empty result set when no rows left to fetch"
373386

387+
httpx_mock.add_callback(insert_query_callback, url=query_url)
388+
await cursor.execute("sql")
389+
with raises(DataError):
390+
await cursor.fetchall()
391+
374392

375393
# This tests a temporary functionality, needs to be removed when the
376394
# functionality is removed

tests/unit/db/test_connection.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,12 @@ def test_connection_unclosed_warnings():
168168
assert "Unclosed" in str(
169169
winfo.list[0].message
170170
), "Invalid unclosed connection warning"
171+
172+
173+
def test_connection_commit(connection: Connection):
174+
# nothing happens
175+
connection.commit()
176+
177+
connection.close()
178+
with raises(ConnectionClosedError):
179+
connection.commit()

tests/unit/db/test_cursor.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from firebolt.async_db.cursor import ColType, Column, CursorState
99
from firebolt.common.exception import (
1010
CursorClosedError,
11+
DataError,
1112
OperationalError,
1213
QueryNotRunError,
1314
)
@@ -160,7 +161,6 @@ def test_cursor_execute(
160161
assert query() == -1, "Invalid row count for insert query"
161162
assert cursor.rowcount == -1, "Invalid rowcount value for insert query"
162163
assert cursor.description is None, "Invalid description for insert query"
163-
assert cursor.fetchone() is None, "Non-empty fetchone for insert query"
164164

165165

166166
def test_cursor_execute_error(
@@ -215,6 +215,7 @@ def test_cursor_fetchone(
215215
auth_callback: Callable,
216216
auth_url: str,
217217
query_callback: Callable,
218+
insert_query_callback: Callable,
218219
query_url: str,
219220
cursor: Cursor,
220221
):
@@ -235,12 +236,18 @@ def test_cursor_fetchone(
235236
cursor.fetchone() is None
236237
), "fetchone should return None when no rows left to fetch"
237238

239+
httpx_mock.add_callback(insert_query_callback, url=query_url)
240+
cursor.execute("sql")
241+
with raises(DataError):
242+
cursor.fetchone()
243+
238244

239245
def test_cursor_fetchmany(
240246
httpx_mock: HTTPXMock,
241247
auth_callback: Callable,
242248
auth_url: str,
243249
query_callback: Callable,
250+
insert_query_callback: Callable,
244251
query_url: str,
245252
cursor: Cursor,
246253
):
@@ -292,12 +299,18 @@ def test_cursor_fetchmany(
292299
len(cursor.fetchmany()) == 0
293300
), "fetchmany should return empty result set when no rows left to fetch"
294301

302+
httpx_mock.add_callback(insert_query_callback, url=query_url)
303+
cursor.execute("sql")
304+
with raises(DataError):
305+
cursor.fetchmany()
306+
295307

296308
def test_cursor_fetchall(
297309
httpx_mock: HTTPXMock,
298310
auth_callback: Callable,
299311
auth_url: str,
300312
query_callback: Callable,
313+
insert_query_callback: Callable,
301314
query_url: str,
302315
cursor: Cursor,
303316
):
@@ -320,6 +333,11 @@ def test_cursor_fetchall(
320333
len(cursor.fetchall()) == 0
321334
), "fetchmany should return empty result set when no rows left to fetch"
322335

336+
httpx_mock.add_callback(insert_query_callback, url=query_url)
337+
cursor.execute("sql")
338+
with raises(DataError):
339+
cursor.fetchall()
340+
323341

324342
# This tests a temporary functionality, needs to be removed when the
325343
# functionality is removed

0 commit comments

Comments
 (0)