Skip to content

Commit e8c7b0e

Browse files
committed
fix(tests): expected errors, escaping
1 parent eb043d9 commit e8c7b0e

File tree

13 files changed

+176
-308
lines changed

13 files changed

+176
-308
lines changed

src/sqlitecloud/datatypes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ class SQLiteCloudConnect:
123123
def __init__(self):
124124
self.socket: any = None
125125
self.config: SQLiteCloudConfig
126-
self.isblob: bool = False
127126

128127
self.pubsub_socket: any = None
129128
self.pubsub_callback: Callable[
@@ -239,6 +238,7 @@ def __init__(self) -> None:
239238
self.value: Optional[int] = None
240239
self.cstart: int = 0
241240
self.extcode: int = None
241+
self.offcode: int = None
242242

243243

244244
class SQLiteCloudValue:

src/sqlitecloud/dbapi2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ def _ensure_connection(self):
730730
SQLiteCloudException: If the cursor is closed.
731731
"""
732732
if not self._connection:
733-
raise SQLiteCloudOperationalError("The cursor is closed.")
733+
raise SQLiteCloudProgrammingError("The cursor is closed.")
734734

735735
def _adapt_parameters(self, parameters: Union[Dict, Tuple]) -> Union[Dict, Tuple]:
736736
if isinstance(parameters, dict):

src/sqlitecloud/driver.py

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
from sqlitecloud.exceptions import (
2525
SQLiteCloudException,
26-
raise_sqlitecloud_error_with_extended_code,
26+
get_sqlitecloud_error_with_extended_code,
2727
)
2828
from sqlitecloud.resultset import (
2929
SQLITECLOUD_RESULT_TYPE,
@@ -117,11 +117,7 @@ def send_blob(self, blob: bytes, conn: SQLiteCloudConnect) -> SQLiteCloudResult:
117117
"""
118118
Send a blob to the SQLite Cloud server.
119119
"""
120-
try:
121-
conn.isblob = True
122-
return self._internal_run_command(conn, blob)
123-
finally:
124-
conn.isblob = False
120+
return self._internal_run_command(conn, self._internal_serialize_command(blob))
125121

126122
def is_connected(
127123
self, connection: SQLiteCloudConnect, main_socket: bool = True
@@ -314,7 +310,9 @@ def upload_database(
314310
command = f"UPLOAD DATABASE '{dbname}' {keyarg}{keyvalue}"
315311

316312
# execute command on server side
317-
result = self._internal_run_command(connection, command)
313+
result = self._internal_run_command(
314+
connection, self._internal_serialize_command(command)
315+
)
318316
if not result.data[0]:
319317
raise SQLiteCloudException(
320318
"An error occurred while initializing the upload of the database."
@@ -350,7 +348,9 @@ def upload_database(
350348
# Upload completed
351349
break
352350
except Exception as e:
353-
self._internal_run_command(connection, "UPLOAD ABORT")
351+
self._internal_run_command(
352+
connection, self._internal_serialize_command("UPLOAD ABORT")
353+
)
354354
raise e
355355

356356
def download_database(
@@ -377,7 +377,10 @@ def download_database(
377377
"""
378378
exists_cmd = " IF EXISTS" if if_exists else ""
379379
result = self._internal_run_command(
380-
connection, f"DOWNLOAD DATABASE {dbname}{exists_cmd};"
380+
connection,
381+
self._internal_serialize_command(
382+
f"DOWNLOAD DATABASE {dbname}{exists_cmd};"
383+
),
381384
)
382385

383386
if result.nrows == 0:
@@ -394,7 +397,9 @@ def download_database(
394397

395398
try:
396399
while progress_size < db_size:
397-
result = self._internal_run_command(connection, "DOWNLOAD STEP")
400+
result = self._internal_run_command(
401+
connection, self._internal_serialize_command("DOWNLOAD STEP")
402+
)
398403

399404
# res is BLOB, decode it
400405
data = result.data[0]
@@ -408,7 +413,9 @@ def download_database(
408413
if data_len == 0:
409414
break
410415
except Exception as e:
411-
self._internal_run_command(connection, "DOWNLOAD ABORT")
416+
self._internal_run_command(
417+
connection, self._internal_serialize_command("DOWNLOAD ABORT")
418+
)
412419
raise e
413420

414421
def _internal_config_apply(
@@ -488,12 +495,6 @@ def _internal_socket_write(
488495
command (bytes): The command to send.
489496
main_socket (bool): If True, write to the main socket, otherwise write to the pubsub socket.
490497
"""
491-
# try:
492-
# if "ATTACH DATABASE" in command.decode() or '"test_schema".table_info' in command.decode():
493-
# pdb.set_trace()
494-
# except:
495-
# pass
496-
497498
# write buffer
498499
if len(command) == 0:
499500
return
@@ -594,30 +595,36 @@ def _internal_parse_number(
594595
sqlitecloud_number = SQLiteCloudNumber()
595596
sqlitecloud_number.value = 0
596597
extvalue = 0
597-
isext = False
598+
offcode = 0
599+
isext = 0
598600
blen = len(buffer)
599601

600602
# from 1 to skip the first command type character
601603
for i in range(index, blen):
602604
c = chr(buffer[i])
603605

604-
# check for optional extended error code (ERRCODE:EXTERRCODE)
606+
# check for optional extended error code (ERRCODE:EXTERRCODE:OFFCODE)
605607
if c == ":":
606-
isext = True
608+
isext += 1
607609
continue
608610

609611
# check for end of value
610612
if c == " ":
611613
sqlitecloud_number.cstart = i + 1
612614
sqlitecloud_number.extcode = extvalue
615+
sqlitecloud_number.offcode = offcode
613616
return sqlitecloud_number
614617

615618
val = int(c) if c.isdigit() else 0
616619

617-
# compute numeric value
618-
if isext:
620+
if isext == 1:
621+
# XERRCODE
619622
extvalue = (extvalue * 10) + val
623+
elif isext == 2:
624+
# OFFCODE
625+
offcode = (offcode * 10) + val
620626
else:
627+
# generic value or ERRCODE
621628
sqlitecloud_number.value = (sqlitecloud_number.value * 10) + val
622629

623630
sqlitecloud_number.value = 0
@@ -706,7 +713,7 @@ def _internal_parse_buffer(
706713
return SQLiteCloudResult(tag, clone)
707714

708715
elif cmd == SQLITECLOUD_CMD.ERROR.value:
709-
# -LEN ERRCODE:EXTCODE ERRMSG
716+
# -LEN ERRCODE:EXTCODE:OFFCODE ERRMSG
710717
sqlite_number = self._internal_parse_number(buffer)
711718
len_ = sqlite_number.value
712719
cstart = sqlite_number.cstart
@@ -721,9 +728,9 @@ def _internal_parse_buffer(
721728
len_ -= cstart2
722729
errmsg = clone[cstart2:]
723730

724-
raise raise_sqlitecloud_error_with_extended_code(
731+
raise get_sqlitecloud_error_with_extended_code(
725732
errmsg.decode(), errcode, xerrcode
726-
)
733+
)(errmsg.decode(), errcode, xerrcode)
727734

728735
elif cmd in [SQLITECLOUD_CMD.ROWSET.value, SQLITECLOUD_CMD.ROWSET_CHUNK.value]:
729736
# CMD_ROWSET: *LEN 0:VERSION ROWS COLS DATA

src/sqlitecloud/exceptions.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ def __init__(self, message: str, code: int = -1, xerrcode: int = 0) -> None:
7676
super().__init__(message, code, xerrcode)
7777

7878

79-
def raise_sqlitecloud_error_with_extended_code(
79+
def get_sqlitecloud_error_with_extended_code(
8080
message: str, code: int, xerrcode: int
8181
) -> None:
82-
# Define base error codes and their corresponding exceptions
82+
"""Mapping of sqlite error codes: https://www.sqlite.org/rescode.html"""
83+
84+
# define base error codes and their corresponding exceptions
8385
base_error_mapping = {
8486
1: SQLiteCloudOperationalError, # SQLITE_ERROR
8587
2: SQLiteCloudInternalError, # SQLITE_INTERNAL
@@ -113,7 +115,7 @@ def raise_sqlitecloud_error_with_extended_code(
113115
101: SQLiteCloudWarning, # SQLITE_DONE (not an error)
114116
}
115117

116-
# Define extended error codes and their corresponding exceptions
118+
# define extended error codes and their corresponding exceptions
117119
extended_error_mapping = {
118120
257: SQLiteCloudOperationalError, # SQLITE_ERROR_MISSING_COLLSEQ
119121
279: SQLiteCloudOperationalError, # SQLITE_AUTH_USER
@@ -176,11 +178,10 @@ def raise_sqlitecloud_error_with_extended_code(
176178
283: SQLiteCloudWarning, # SQLITE_NOTICE_RECOVER_ROLLBACK
177179
284: SQLiteCloudWarning, # SQLITE_WARNING_AUTOINDEX
178180
}
179-
# Combine base and extended mappings
181+
180182
error_mapping = {**base_error_mapping, **extended_error_mapping}
181183

182-
# Retrieve the corresponding exception based on the error code
184+
# retrieve the corresponding exception based on the error code
183185
exception = error_mapping.get(xerrcode, error_mapping.get(code, SQLiteCloudError))
184186

185-
# Raise the corresponding exception
186-
raise exception(message, code, xerrcode)
187+
return exception

src/tests/conftest.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,7 @@ def sqlitecloud_dbapi2_connection():
7272

7373
yield connection
7474

75-
try:
76-
next(connection_generator)
77-
except StopIteration:
78-
pass
75+
close_generator(connection_generator)
7976

8077

8178
def get_sqlitecloud_dbapi2_connection(detect_types: int = 0):
@@ -103,10 +100,7 @@ def sqlite3_connection():
103100

104101
yield connection
105102

106-
try:
107-
next(connection_generator)
108-
except StopIteration:
109-
pass
103+
close_generator(connection_generator)
110104

111105

112106
def get_sqlite3_connection(detect_types: int = 0):
@@ -120,3 +114,10 @@ def get_sqlite3_connection(detect_types: int = 0):
120114
yield connection
121115

122116
connection.close()
117+
118+
119+
def close_generator(generator):
120+
try:
121+
next(generator)
122+
except StopIteration:
123+
pass

src/tests/integration/test_client.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
SQLiteCloudAccount,
1212
SQLiteCloudConnect,
1313
)
14-
from sqlitecloud.exceptions import SQLiteCloudException
14+
from sqlitecloud.exceptions import (
15+
SQLiteCloudError,
16+
SQLiteCloudException,
17+
SQLiteCloudOperationalError,
18+
)
1519
from sqlitecloud.resultset import SQLITECLOUD_RESULT_TYPE
1620

1721

@@ -56,7 +60,7 @@ def test_connection_without_credentials_and_apikey(self):
5660

5761
client = SQLiteCloudClient(cloud_account=account)
5862

59-
with pytest.raises(SQLiteCloudException):
63+
with pytest.raises(SQLiteCloudError):
6064
client.open_connection()
6165

6266
def test_connect_with_string(self):
@@ -124,7 +128,7 @@ def test_select(self, sqlitecloud_connection):
124128

125129
def test_column_not_found(self, sqlitecloud_connection):
126130
connection, client = sqlitecloud_connection
127-
with pytest.raises(SQLiteCloudException) as e:
131+
with pytest.raises(SQLiteCloudOperationalError) as e:
128132
client.exec_query("SELECT not_a_column FROM albums", connection)
129133

130134
assert e.value.errcode == 1
@@ -267,7 +271,7 @@ def test_blob_zero_length(self, sqlitecloud_connection):
267271
def test_error(self, sqlitecloud_connection):
268272
connection, client = sqlitecloud_connection
269273

270-
with pytest.raises(SQLiteCloudException) as e:
274+
with pytest.raises(SQLiteCloudError) as e:
271275
client.exec_query("TEST ERROR", connection)
272276

273277
assert e.value.errcode == 66666
@@ -276,7 +280,7 @@ def test_error(self, sqlitecloud_connection):
276280
def test_ext_error(self, sqlitecloud_connection):
277281
connection, client = sqlitecloud_connection
278282

279-
with pytest.raises(SQLiteCloudException) as e:
283+
with pytest.raises(SQLiteCloudError) as e:
280284
client.exec_query("TEST EXTERROR", connection)
281285

282286
assert e.value.errcode == 66666
@@ -354,7 +358,7 @@ def test_max_rowset_option_to_fail_when_rowset_is_bigger(self):
354358

355359
connection = client.open_connection()
356360

357-
with pytest.raises(SQLiteCloudException) as e:
361+
with pytest.raises(SQLiteCloudError) as e:
358362
client.exec_query("SELECT * FROM albums", connection)
359363

360364
client.disconnect(connection)
@@ -737,18 +741,6 @@ def test_rowset_chunk_compressed(self, sqlitecloud_connection):
737741
assert 147 == len(rowset.data)
738742
assert "key" == rowset.get_name(0)
739743

740-
def test_exec_statement_with_named_placeholder(self, sqlitecloud_connection):
741-
connection, client = sqlitecloud_connection
742-
743-
result = client.exec_statement(
744-
"SELECT * FROM albums WHERE AlbumId = :id and Title = :title",
745-
{"id": 1, "title": "For Those About To Rock We Salute You"},
746-
connection,
747-
)
748-
749-
assert result.nrows == 1
750-
assert result.get_value(0, 0) == 1
751-
752744
def test_exec_statement_with_qmarks(self, sqlitecloud_connection):
753745
connection, client = sqlitecloud_connection
754746

src/tests/integration/test_dbapi2.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import sqlitecloud
77
from sqlitecloud.datatypes import SQLITECLOUD_INTERNAL_ERRCODE, SQLiteCloudAccount
8-
from sqlitecloud.exceptions import SQLiteCloudException
8+
from sqlitecloud.exceptions import SQLiteCloudError, SQLiteCloudException
99

1010

1111
class TestDBAPI2:
@@ -74,7 +74,7 @@ def test_connection_execute(self, sqlitecloud_dbapi2_connection):
7474
def test_column_not_found(self, sqlitecloud_dbapi2_connection):
7575
connection = sqlitecloud_dbapi2_connection
7676

77-
with pytest.raises(SQLiteCloudException) as e:
77+
with pytest.raises(SQLiteCloudError) as e:
7878
connection.execute("SELECT not_a_column FROM albums")
7979

8080
assert e.value.errcode == 1
@@ -123,7 +123,7 @@ def test_integer(self, sqlitecloud_dbapi2_connection):
123123
def test_error(self, sqlitecloud_dbapi2_connection):
124124
connection = sqlitecloud_dbapi2_connection
125125

126-
with pytest.raises(SQLiteCloudException) as e:
126+
with pytest.raises(SQLiteCloudError) as e:
127127
connection.execute("TEST ERROR")
128128

129129
assert e.value.errcode == 66666

src/tests/integration/test_download.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import pytest
55

66
from sqlitecloud import download
7-
from sqlitecloud.datatypes import SQLITECLOUD_ERRCODE
8-
from sqlitecloud.exceptions import SQLiteCloudException
7+
from sqlitecloud.exceptions import SQLiteCloudError
98

109

1110
class TestDownload:
@@ -26,8 +25,7 @@ def test_download_missing_database(self, sqlitecloud_connection):
2625

2726
temp_file = tempfile.mkstemp(prefix="missing")[1]
2827

29-
with pytest.raises(SQLiteCloudException) as e:
28+
with pytest.raises(SQLiteCloudError) as e:
3029
download.download_db(connection, "missing.sqlite", temp_file)
3130

32-
assert e.value.errcode == SQLITECLOUD_ERRCODE.COMMAND.value
3331
assert e.value.errmsg == "Database missing.sqlite does not exist."

src/tests/integration/test_pandas.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def test_insert_from_dataframe(self, sqlitecloud_dbapi2_connection):
2222
}
2323
)
2424

25-
conn.executemany("DROP TABLE IF EXISTS ?", [("PRICES",), ("TICKER_MAPPING",)])
25+
for table in ["PRICES", "TICKER_MAPPING"]:
26+
conn.execute(f"DROP TABLE IF EXISTS {table}")
2627

2728
# arg if_exists="replace" raises the error
2829
dfprices.to_sql("PRICES", conn, index=False)

0 commit comments

Comments
 (0)