Skip to content

Commit c6a20cd

Browse files
committed
SQLAlchemy Tests
1 parent acb5924 commit c6a20cd

File tree

11 files changed

+810
-202
lines changed

11 files changed

+810
-202
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: CI
1+
name: CI TEST
22

33
on:
44
push:
@@ -39,14 +39,8 @@ jobs:
3939
docker logs ${cid}
4040
curl -v http://localhost:8000/v1/health
4141
42-
- name: Unit Test
42+
- name: SQLAlchemy Test Suite
4343
env:
4444
TEST_DATABEND_DSN: "databend://databend:databend@localhost:8000/default?sslmode=disable"
4545
run: |
46-
pipenv run pytest -s tests/unit
47-
48-
- name: Integration Test
49-
env:
50-
TEST_DATABEND_DSN: "databend://databend:databend@localhost:8000/default?sslmode=disable"
51-
run: |
52-
pipenv run pytest -s tests/integration
46+
pipenv run pytest -n4

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ unittest:
44
integration:
55
python -m pytest -s tests/integration
66

7-
sampletest:
8-
python databend_sqlalchemy/test.py
7+
testsuite:
8+
python -m pytest -n4
99

1010
install:
1111
pip install -e ".[dev]"

databend_sqlalchemy/connector.py

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
# See http://www.python.org/dev/peps/pep-0249/
44
#
55
# Many docstrings in this file are based on the PEP, which is in the public domain.
6-
6+
import decimal
77
import re
88
import uuid
9-
from datetime import datetime
10-
from databend_sqlalchemy.errors import ServerException, NotSupportedError
9+
from datetime import datetime, date
10+
from databend_sqlalchemy.errors import Error, ServerException, NotSupportedError
1111

1212
from databend_driver import BlockingDatabendClient
1313

@@ -17,15 +17,7 @@
1717
paramstyle = "pyformat" # Python extended format codes, e.g. ...WHERE name=%(name)s
1818

1919

20-
class Error(Exception):
21-
"""Exception that is the base class of all other error exceptions.
22-
You can use this to catch all errors with one single except statement.
23-
"""
24-
25-
pass
26-
27-
28-
class ParamEscaper(object):
20+
class ParamEscaper:
2921
def escape_args(self, parameters):
3022
if isinstance(parameters, dict):
3123
return {k: self.escape_item(v) for k, v in parameters.items()}
@@ -45,15 +37,17 @@ def escape_string(self, item):
4537
if isinstance(item, bytes):
4638
item = item.decode("utf-8")
4739
return "'{}'".format(
48-
item.replace("\\", "\\\\").replace("'", "\\'").replace("$", "$$")
40+
item.replace("\\", "\\\\").replace("'", "\\'").replace("$", "$$").replace("%", "%%")
4941
)
5042

5143
def escape_item(self, item):
5244
if item is None:
5345
return "NULL"
5446
elif isinstance(item, (int, float)):
5547
return self.escape_number(item)
56-
elif isinstance(item, datetime):
48+
elif isinstance(item, decimal.Decimal):
49+
return self.escape_number(item)
50+
elif isinstance(item, (datetime, date)):
5751
return self.escape_string(item.strftime("%Y-%m-%d %H:%M:%S"))
5852
else:
5953
return self.escape_string(item)
@@ -115,7 +109,7 @@ def rollback(self):
115109
raise NotSupportedError("Transactions are not supported") # pragma: no cover
116110

117111

118-
class Cursor(object):
112+
class Cursor:
119113
"""These objects represent a database cursor, which is used to manage the context of a fetch
120114
operation.
121115
@@ -173,22 +167,39 @@ def close(self):
173167
def execute(self, operation, parameters=None, is_response=True):
174168
"""Prepare and execute a database operation (query or command)."""
175169

170+
#ToDo - Fix this, which is preventing the execution of blank DDL sunch as CREATE INDEX statements which aren't suppoorted
171+
# Seems hard to fix when statements are coming from metadata.create_all()
172+
if operation == "":
173+
return
174+
176175
self._reset_state()
177176

178177
self._state = self._STATE_RUNNING
179178
self._uuid = uuid.uuid1()
180179

181-
if is_response:
182-
rows = self._db.query_iter(operation)
183-
schema = rows.schema()
184-
columns = []
185-
for field in schema.fields():
186-
columns.append((field.name, field.data_type))
187-
if self._state != self._STATE_RUNNING:
188-
raise Exception("Should be running if processing response")
189-
self._rows = rows
190-
self._columns = columns
191-
self._state = self._STATE_SUCCEEDED
180+
try:
181+
# operation = operation.replace('%%', '%')
182+
if parameters:
183+
query = operation % _escaper.escape_args(parameters)
184+
else:
185+
query = operation
186+
query = query.replace('%%', '%')
187+
if is_response:
188+
rows = self._db.query_iter(query)
189+
schema = rows.schema()
190+
columns = []
191+
for field in schema.fields():
192+
columns.append((field.name, field.data_type))
193+
if self._state != self._STATE_RUNNING:
194+
raise Exception("Should be running if processing response")
195+
self._rows = rows
196+
self._columns = columns
197+
self._state = self._STATE_SUCCEEDED
198+
else:
199+
self._db.exec(query)
200+
except Exception as e:
201+
# We have to raise dbAPI error
202+
raise Error(str(e)) from e
192203

193204
def executemany(self, operation, seq_of_parameters):
194205
"""Prepare a database operation (query or command) and then execute it against all parameter
@@ -208,23 +219,27 @@ def executemany(self, operation, seq_of_parameters):
208219

209220
m = RE_INSERT_VALUES.match(operation)
210221
if m:
211-
q_prefix = m.group(1) % ()
212-
q_values = m.group(2).rstrip()
213-
214-
for parameters in seq_of_parameters[:-1]:
215-
values_list.append(q_values % _escaper.escape_args(parameters))
216-
query = "{} {};".format(q_prefix, ",".join(values_list))
217-
return self._db.raw(query)
218-
for parameters in seq_of_parameters[:-1]:
222+
try:
223+
q_prefix = m.group(1) #.replace('%%', '%') % ()
224+
q_values = m.group(2).rstrip()
225+
226+
for parameters in seq_of_parameters:
227+
values_list.append(q_values % _escaper.escape_args(parameters))
228+
query = "{} {};".format(q_prefix, ",".join(values_list))
229+
return self._db.exec(query)
230+
except Exception as e:
231+
# We have to raise dbAPI error
232+
raise Error(str(e)) from e
233+
for parameters in seq_of_parameters:
219234
self.execute(operation, parameters, is_response=False)
220235

221236
def fetchone(self):
222237
"""Fetch the next row of a query result set, returning a single sequence, or ``None`` when
223238
no more data is available."""
224239
if self._state == self._STATE_NONE:
225-
raise Exception("No query yet")
240+
raise Error("No query yet")
226241
if not self._rows:
227-
raise Exception("No rows yet")
242+
raise Error("No rows yet")
228243
else:
229244
self._rownumber += 1
230245
try:
@@ -243,7 +258,7 @@ def fetchmany(self, size=None):
243258
specified number of rows not being available, fewer rows may be returned.
244259
"""
245260
if self._state == self._STATE_NONE:
246-
raise Exception("No query yet")
261+
raise Error("No query yet")
247262

248263
if size is None:
249264
size = 1
@@ -262,7 +277,7 @@ def fetchall(self):
262277
(e.g. a list of tuples).
263278
"""
264279
if self._state == self._STATE_NONE:
265-
raise Exception("No query yet")
280+
raise Error("No query yet")
266281

267282
data = []
268283
if self._rows:

0 commit comments

Comments
 (0)