Skip to content

Commit 233c3b1

Browse files
committed
Use sqlitelike API in DBAPI
1 parent ab080f7 commit 233c3b1

File tree

3 files changed

+80
-248
lines changed

3 files changed

+80
-248
lines changed

chdb/dbapi/connections.py

+36-153
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import json
21
from . import err
32
from .cursors import Cursor
43
from . import converters
4+
from ..state import sqlitelike as chdb_stateful
55

66
DEBUG = False
77
VERBOSE = False
@@ -10,152 +10,78 @@
1010
class Connection(object):
1111
"""
1212
Representation of a connection with chdb.
13-
14-
The proper way to get an instance of this class is to call
15-
connect().
16-
17-
Accepts several arguments:
18-
19-
:param cursorclass: Custom cursor class to use.
20-
:param path: Optional folder path to store database files on disk.
21-
22-
See `Connection <https://www.python.org/dev/peps/pep-0249/#connection-objects>`_ in the
23-
specification.
2413
"""
2514

26-
_closed = False
27-
_session = None
28-
29-
def __init__(self, cursorclass=Cursor, path=None):
30-
31-
self._resp = None
32-
33-
# 1. pre-process params in init
34-
self.encoding = 'utf8'
35-
36-
self.cursorclass = cursorclass
37-
38-
self._result = None
15+
def __init__(self, path=None):
16+
self._closed = False
17+
self.encoding = "utf8"
3918
self._affected_rows = 0
19+
self._resp = None
4020

41-
self.connect(path)
21+
# Initialize sqlitelike connection
22+
connection_string = ":memory:" if path is None else f"file:{path}"
23+
self._conn = chdb_stateful.Connection(connection_string)
4224

43-
def connect(self, path=None):
44-
from chdb import session as chs
45-
self._session = chs.Session(path)
46-
self._closed = False
47-
self._execute_command("select 1;")
48-
self._read_query_result()
25+
# Test connection with a simple query
26+
cursor = self._conn.cursor()
27+
cursor.execute("SELECT 1")
28+
cursor.close()
4929

5030
def close(self):
51-
"""
52-
Send the quit message and close the socket.
53-
54-
See `Connection.close() <https://www.python.org/dev/peps/pep-0249/#Connection.close>`_
55-
in the specification.
56-
57-
:raise Error: If the connection is already closed.
58-
"""
31+
"""Send the quit message and close the socket."""
5932
if self._closed:
6033
raise err.Error("Already closed")
6134
self._closed = True
62-
self._session = None
35+
self._conn.close()
6336

6437
@property
6538
def open(self):
6639
"""Return True if the connection is open"""
6740
return not self._closed
6841

6942
def commit(self):
70-
"""
71-
Commit changes to stable storage.
72-
73-
See `Connection.commit() <https://www.python.org/dev/peps/pep-0249/#commit>`_
74-
in the specification.
75-
"""
76-
return
43+
"""Commit changes to stable storage."""
44+
# No-op for ClickHouse
45+
pass
7746

7847
def rollback(self):
79-
"""
80-
Roll back the current transaction.
81-
82-
See `Connection.rollback() <https://www.python.org/dev/peps/pep-0249/#rollback>`_
83-
in the specification.
84-
"""
85-
return
48+
"""Roll back the current transaction."""
49+
# No-op for ClickHouse
50+
pass
8651

8752
def cursor(self, cursor=None):
88-
"""
89-
Create a new cursor to execute queries with.
90-
91-
:param cursor: The type of cursor to create; current only :py:class:`Cursor`
92-
None means use Cursor.
93-
"""
53+
"""Create a new cursor to execute queries with."""
54+
if self._closed:
55+
raise err.Error("Connection closed")
9456
if cursor:
95-
return cursor(self)
96-
return self.cursorclass(self)
57+
return Cursor(self)
58+
return Cursor(self)
9759

98-
# The following methods are INTERNAL USE ONLY (called from Cursor)
99-
def query(self, sql):
100-
if isinstance(sql, str):
101-
sql = sql.encode(self.encoding, 'surrogateescape')
102-
self._execute_command(sql)
103-
self._affected_rows = self._read_query_result()
104-
return self._affected_rows
105-
106-
def _execute_command(self, sql):
107-
"""
108-
:raise InterfaceError: If the connection is closed.
109-
:raise ValueError: If no username was specified.
110-
"""
60+
def query(self, sql, fmt="ArrowStream"):
61+
"""Execute a query and return the raw result."""
11162
if self._closed:
11263
raise err.InterfaceError("Connection closed")
11364

11465
if isinstance(sql, str):
115-
sql = sql.encode(self.encoding)
66+
sql = sql.encode(self.encoding, "surrogateescape")
11667

117-
if isinstance(sql, bytearray):
118-
sql = bytes(sql)
119-
120-
# drop last command return
121-
if self._resp is not None:
122-
self._resp = None
123-
124-
if DEBUG:
125-
print("DEBUG: query:", sql)
12668
try:
127-
res = self._session.query(sql, fmt="JSON")
128-
if res.has_error():
129-
raise err.DatabaseError(res.error_message())
130-
self._resp = res.data()
69+
result = self._conn.query(sql.decode(), fmt)
70+
self._resp = result
71+
return result
13172
except Exception as error:
132-
raise err.InterfaceError("query err: %s" % error)
73+
raise err.InterfaceError(f"Query error: {error}")
13374

13475
def escape(self, obj, mapping=None):
135-
"""Escape whatever value you pass to it.
136-
137-
Non-standard, for internal use; do not use this in your applications.
138-
"""
139-
if isinstance(obj, str):
140-
return "'" + self.escape_string(obj) + "'"
141-
if isinstance(obj, (bytes, bytearray)):
142-
ret = self._quote_bytes(obj)
143-
return ret
144-
return converters.escape_item(obj, mapping=mapping)
76+
"""Escape whatever value you pass to it."""
77+
return converters.escape_item(obj, mapping)
14578

14679
def escape_string(self, s):
14780
return converters.escape_string(s)
14881

14982
def _quote_bytes(self, s):
15083
return converters.escape_bytes(s)
15184

152-
def _read_query_result(self):
153-
self._result = None
154-
result = CHDBResult(self)
155-
result.read()
156-
self._result = result
157-
return result.affected_rows
158-
15985
def __enter__(self):
16086
"""Context manager that returns a Cursor"""
16187
return self.cursor()
@@ -166,52 +92,9 @@ def __exit__(self, exc, value, traceback):
16692
self.rollback()
16793
else:
16894
self.commit()
95+
self.close()
16996

17097
@property
17198
def resp(self):
99+
"""Return the last query response"""
172100
return self._resp
173-
174-
175-
class CHDBResult(object):
176-
def __init__(self, connection):
177-
"""
178-
:type connection: Connection
179-
"""
180-
self.connection = connection
181-
self.affected_rows = 0
182-
self.insert_id = None
183-
self.warning_count = 0
184-
self.message = None
185-
self.field_count = 0
186-
self.description = None
187-
self.rows = None
188-
self.has_next = None
189-
190-
def read(self):
191-
# Handle empty responses (for instance from CREATE TABLE)
192-
if self.connection.resp is None:
193-
return
194-
195-
try:
196-
data = json.loads(self.connection.resp)
197-
except Exception as error:
198-
raise err.InterfaceError("Unsupported response format:" % error)
199-
200-
try:
201-
self.field_count = len(data["meta"])
202-
description = []
203-
for meta in data["meta"]:
204-
fields = [meta["name"], meta["type"]]
205-
description.append(tuple(fields))
206-
self.description = tuple(description)
207-
208-
rows = []
209-
for line in data["data"]:
210-
row = []
211-
for i in range(self.field_count):
212-
column_data = converters.convert_column_data(self.description[i][1], line[self.description[i][0]])
213-
row.append(column_data)
214-
rows.append(tuple(row))
215-
self.rows = tuple(rows)
216-
except Exception as error:
217-
raise err.InterfaceError("Read return data err:" % error)

0 commit comments

Comments
 (0)