clickhouse-driver 0.2.9__cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clickhouse_driver/__init__.py +9 -0
- clickhouse_driver/block.py +227 -0
- clickhouse_driver/blockstreamprofileinfo.py +22 -0
- clickhouse_driver/bufferedreader.cpython-311-powerpc64le-linux-gnu.so +0 -0
- clickhouse_driver/bufferedwriter.cpython-311-powerpc64le-linux-gnu.so +0 -0
- clickhouse_driver/client.py +812 -0
- clickhouse_driver/clientinfo.py +119 -0
- clickhouse_driver/columns/__init__.py +0 -0
- clickhouse_driver/columns/arraycolumn.py +161 -0
- clickhouse_driver/columns/base.py +221 -0
- clickhouse_driver/columns/boolcolumn.py +7 -0
- clickhouse_driver/columns/datecolumn.py +108 -0
- clickhouse_driver/columns/datetimecolumn.py +203 -0
- clickhouse_driver/columns/decimalcolumn.py +116 -0
- clickhouse_driver/columns/enumcolumn.py +129 -0
- clickhouse_driver/columns/exceptions.py +12 -0
- clickhouse_driver/columns/floatcolumn.py +34 -0
- clickhouse_driver/columns/intcolumn.py +157 -0
- clickhouse_driver/columns/intervalcolumn.py +33 -0
- clickhouse_driver/columns/ipcolumn.py +118 -0
- clickhouse_driver/columns/jsoncolumn.py +37 -0
- clickhouse_driver/columns/largeint.cpython-311-powerpc64le-linux-gnu.so +0 -0
- clickhouse_driver/columns/lowcardinalitycolumn.py +142 -0
- clickhouse_driver/columns/mapcolumn.py +73 -0
- clickhouse_driver/columns/nestedcolumn.py +10 -0
- clickhouse_driver/columns/nothingcolumn.py +13 -0
- clickhouse_driver/columns/nullablecolumn.py +7 -0
- clickhouse_driver/columns/nullcolumn.py +15 -0
- clickhouse_driver/columns/numpy/__init__.py +0 -0
- clickhouse_driver/columns/numpy/base.py +47 -0
- clickhouse_driver/columns/numpy/boolcolumn.py +8 -0
- clickhouse_driver/columns/numpy/datecolumn.py +19 -0
- clickhouse_driver/columns/numpy/datetimecolumn.py +146 -0
- clickhouse_driver/columns/numpy/floatcolumn.py +24 -0
- clickhouse_driver/columns/numpy/intcolumn.py +43 -0
- clickhouse_driver/columns/numpy/lowcardinalitycolumn.py +96 -0
- clickhouse_driver/columns/numpy/service.py +58 -0
- clickhouse_driver/columns/numpy/stringcolumn.py +78 -0
- clickhouse_driver/columns/numpy/tuplecolumn.py +37 -0
- clickhouse_driver/columns/service.py +185 -0
- clickhouse_driver/columns/simpleaggregatefunctioncolumn.py +7 -0
- clickhouse_driver/columns/stringcolumn.py +73 -0
- clickhouse_driver/columns/tuplecolumn.py +63 -0
- clickhouse_driver/columns/util.py +61 -0
- clickhouse_driver/columns/uuidcolumn.py +64 -0
- clickhouse_driver/compression/__init__.py +28 -0
- clickhouse_driver/compression/base.py +87 -0
- clickhouse_driver/compression/lz4.py +21 -0
- clickhouse_driver/compression/lz4hc.py +9 -0
- clickhouse_driver/compression/zstd.py +20 -0
- clickhouse_driver/connection.py +793 -0
- clickhouse_driver/context.py +36 -0
- clickhouse_driver/dbapi/__init__.py +62 -0
- clickhouse_driver/dbapi/connection.py +99 -0
- clickhouse_driver/dbapi/cursor.py +370 -0
- clickhouse_driver/dbapi/errors.py +40 -0
- clickhouse_driver/dbapi/extras.py +73 -0
- clickhouse_driver/defines.py +58 -0
- clickhouse_driver/errors.py +453 -0
- clickhouse_driver/log.py +48 -0
- clickhouse_driver/numpy/__init__.py +0 -0
- clickhouse_driver/numpy/block.py +8 -0
- clickhouse_driver/numpy/helpers.py +28 -0
- clickhouse_driver/numpy/result.py +123 -0
- clickhouse_driver/opentelemetry.py +43 -0
- clickhouse_driver/progress.py +44 -0
- clickhouse_driver/protocol.py +130 -0
- clickhouse_driver/queryprocessingstage.py +8 -0
- clickhouse_driver/reader.py +69 -0
- clickhouse_driver/readhelpers.py +26 -0
- clickhouse_driver/result.py +144 -0
- clickhouse_driver/settings/__init__.py +0 -0
- clickhouse_driver/settings/available.py +405 -0
- clickhouse_driver/settings/types.py +50 -0
- clickhouse_driver/settings/writer.py +34 -0
- clickhouse_driver/streams/__init__.py +0 -0
- clickhouse_driver/streams/compressed.py +88 -0
- clickhouse_driver/streams/native.py +108 -0
- clickhouse_driver/util/__init__.py +0 -0
- clickhouse_driver/util/compat.py +39 -0
- clickhouse_driver/util/escape.py +94 -0
- clickhouse_driver/util/helpers.py +171 -0
- clickhouse_driver/varint.cpython-311-powerpc64le-linux-gnu.so +0 -0
- clickhouse_driver/writer.py +67 -0
- clickhouse_driver-0.2.9.dist-info/LICENSE +21 -0
- clickhouse_driver-0.2.9.dist-info/METADATA +202 -0
- clickhouse_driver-0.2.9.dist-info/RECORD +89 -0
- clickhouse_driver-0.2.9.dist-info/WHEEL +6 -0
- clickhouse_driver-0.2.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
class Context(object):
|
|
3
|
+
def __init__(self):
|
|
4
|
+
self._server_info = None
|
|
5
|
+
self._settings = None
|
|
6
|
+
self._client_settings = None
|
|
7
|
+
super(Context, self).__init__()
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def server_info(self):
|
|
11
|
+
return self._server_info
|
|
12
|
+
|
|
13
|
+
@server_info.setter
|
|
14
|
+
def server_info(self, value):
|
|
15
|
+
self._server_info = value
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def settings(self):
|
|
19
|
+
return self._settings.copy()
|
|
20
|
+
|
|
21
|
+
@settings.setter
|
|
22
|
+
def settings(self, value):
|
|
23
|
+
self._settings = value.copy()
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def client_settings(self):
|
|
27
|
+
return self._client_settings.copy()
|
|
28
|
+
|
|
29
|
+
@client_settings.setter
|
|
30
|
+
def client_settings(self, value):
|
|
31
|
+
self._client_settings = value.copy()
|
|
32
|
+
|
|
33
|
+
def __repr__(self):
|
|
34
|
+
return '<Context(server_info=%s, client_settings=%s, settings=%s)>' % (
|
|
35
|
+
self._server_info, self._client_settings, self._settings
|
|
36
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from .connection import Connection
|
|
2
|
+
from .errors import (
|
|
3
|
+
Warning, Error, DataError, DatabaseError, ProgrammingError, IntegrityError,
|
|
4
|
+
InterfaceError, InternalError, NotSupportedError, OperationalError
|
|
5
|
+
)
|
|
6
|
+
from .. import defines
|
|
7
|
+
|
|
8
|
+
apilevel = '2.0'
|
|
9
|
+
|
|
10
|
+
threadsafety = 2
|
|
11
|
+
|
|
12
|
+
paramstyle = 'pyformat'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def connect(dsn=None, host=None,
|
|
16
|
+
user=defines.DEFAULT_USER, password=defines.DEFAULT_PASSWORD,
|
|
17
|
+
port=defines.DEFAULT_PORT, database=defines.DEFAULT_DATABASE,
|
|
18
|
+
**kwargs):
|
|
19
|
+
"""
|
|
20
|
+
Create a new database connection.
|
|
21
|
+
|
|
22
|
+
The connection can be specified via DSN:
|
|
23
|
+
|
|
24
|
+
``conn = connect("clickhouse://localhost/test?param1=value1&...")``
|
|
25
|
+
|
|
26
|
+
or using database and credentials arguments:
|
|
27
|
+
|
|
28
|
+
``conn = connect(database="test", user="default", password="default",
|
|
29
|
+
host="localhost", **kwargs)``
|
|
30
|
+
|
|
31
|
+
The basic connection parameters are:
|
|
32
|
+
|
|
33
|
+
- *host*: host with running ClickHouse server.
|
|
34
|
+
- *port*: port ClickHouse server is bound to.
|
|
35
|
+
- *database*: database connect to.
|
|
36
|
+
- *user*: database user.
|
|
37
|
+
- *password*: user's password.
|
|
38
|
+
|
|
39
|
+
See defaults in :data:`~clickhouse_driver.connection.Connection`
|
|
40
|
+
constructor.
|
|
41
|
+
|
|
42
|
+
DSN or host is required.
|
|
43
|
+
|
|
44
|
+
Any other keyword parameter will be passed to the underlying Connection
|
|
45
|
+
class.
|
|
46
|
+
|
|
47
|
+
:return: a new connection.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if dsn is None and host is None:
|
|
51
|
+
raise ValueError('host or dsn is required')
|
|
52
|
+
|
|
53
|
+
return Connection(dsn=dsn, user=user, password=password, host=host,
|
|
54
|
+
port=port, database=database, **kwargs)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
'connect',
|
|
59
|
+
'Warning', 'Error', 'DataError', 'DatabaseError', 'ProgrammingError',
|
|
60
|
+
'IntegrityError', 'InterfaceError', 'InternalError', 'NotSupportedError',
|
|
61
|
+
'OperationalError'
|
|
62
|
+
]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from ..client import Client
|
|
2
|
+
from .. import defines
|
|
3
|
+
from .cursor import Cursor
|
|
4
|
+
from .errors import InterfaceError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Connection(object):
|
|
8
|
+
"""
|
|
9
|
+
Creates new Connection for accessing ClickHouse database.
|
|
10
|
+
|
|
11
|
+
Connection is just wrapper for handling multiple cursors (clients) and
|
|
12
|
+
do not initiate actual connections to the ClickHouse server.
|
|
13
|
+
|
|
14
|
+
See parameters description in
|
|
15
|
+
:data:`~clickhouse_driver.connection.Connection`.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, dsn=None, host=None,
|
|
18
|
+
user=defines.DEFAULT_USER, password=defines.DEFAULT_PASSWORD,
|
|
19
|
+
port=defines.DEFAULT_PORT, database=defines.DEFAULT_DATABASE,
|
|
20
|
+
**kwargs):
|
|
21
|
+
self.cursors = []
|
|
22
|
+
|
|
23
|
+
self.dsn = dsn
|
|
24
|
+
self.user = user
|
|
25
|
+
self.password = password
|
|
26
|
+
self.host = host
|
|
27
|
+
self.port = port
|
|
28
|
+
self.database = database
|
|
29
|
+
self.connection_kwargs = kwargs
|
|
30
|
+
self.is_closed = False
|
|
31
|
+
self._hosts = None
|
|
32
|
+
super(Connection, self).__init__()
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
return '<connection object at 0x{0:x}; closed: {1:}>'.format(
|
|
36
|
+
id(self), self.is_closed
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Context manager integrations.
|
|
40
|
+
def __enter__(self):
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
44
|
+
self.close()
|
|
45
|
+
|
|
46
|
+
def _make_client(self):
|
|
47
|
+
"""
|
|
48
|
+
:return: a new Client instance.
|
|
49
|
+
"""
|
|
50
|
+
if self.dsn is not None:
|
|
51
|
+
return Client.from_url(self.dsn)
|
|
52
|
+
|
|
53
|
+
return Client(self.host, port=self.port,
|
|
54
|
+
user=self.user, password=self.password,
|
|
55
|
+
database=self.database, **self.connection_kwargs)
|
|
56
|
+
|
|
57
|
+
def close(self):
|
|
58
|
+
"""
|
|
59
|
+
Close the connection now. The connection will be unusable from this
|
|
60
|
+
point forward; an :data:`~clickhouse_driver.dbapi.Error` (or subclass)
|
|
61
|
+
exception will be raised if any operation is attempted with the
|
|
62
|
+
connection. The same applies to all cursor objects trying to use the
|
|
63
|
+
connection.
|
|
64
|
+
"""
|
|
65
|
+
for cursor in self.cursors:
|
|
66
|
+
cursor.close()
|
|
67
|
+
|
|
68
|
+
self.is_closed = True
|
|
69
|
+
|
|
70
|
+
def commit(self):
|
|
71
|
+
"""
|
|
72
|
+
Do nothing since ClickHouse has no transactions.
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def rollback(self):
|
|
77
|
+
"""
|
|
78
|
+
Do nothing since ClickHouse has no transactions.
|
|
79
|
+
"""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def cursor(self, cursor_factory=None):
|
|
83
|
+
"""
|
|
84
|
+
:param cursor_factory: Argument can be used to create non-standard
|
|
85
|
+
cursors.
|
|
86
|
+
:return: a new cursor object using the connection.
|
|
87
|
+
"""
|
|
88
|
+
if self.is_closed:
|
|
89
|
+
raise InterfaceError('connection already closed')
|
|
90
|
+
|
|
91
|
+
client = self._make_client()
|
|
92
|
+
if self._hosts is None:
|
|
93
|
+
self._hosts = client.connection.hosts
|
|
94
|
+
else:
|
|
95
|
+
client.connection.hosts = self._hosts
|
|
96
|
+
cursor_factory = cursor_factory or Cursor
|
|
97
|
+
cursor = cursor_factory(client, self)
|
|
98
|
+
self.cursors.append(cursor)
|
|
99
|
+
return cursor
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
from itertools import islice
|
|
3
|
+
|
|
4
|
+
from ..errors import Error as DriverError
|
|
5
|
+
from .errors import InterfaceError, OperationalError, ProgrammingError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Column = namedtuple(
|
|
9
|
+
'Column',
|
|
10
|
+
'name type_code display_size internal_size precision scale null_ok'
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Cursor(object):
|
|
15
|
+
|
|
16
|
+
class States(object):
|
|
17
|
+
(
|
|
18
|
+
NONE,
|
|
19
|
+
RUNNING,
|
|
20
|
+
FINISHED,
|
|
21
|
+
CURSOR_CLOSED
|
|
22
|
+
) = range(4)
|
|
23
|
+
|
|
24
|
+
_states = States()
|
|
25
|
+
|
|
26
|
+
def __init__(self, client, connection):
|
|
27
|
+
self._client = client
|
|
28
|
+
self._connection = connection
|
|
29
|
+
self._reset_state()
|
|
30
|
+
|
|
31
|
+
self.arraysize = 1
|
|
32
|
+
|
|
33
|
+
# Begin non-PEP attributes
|
|
34
|
+
self._columns_with_types = None
|
|
35
|
+
# End non-PEP attributes
|
|
36
|
+
|
|
37
|
+
super(Cursor, self).__init__()
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
is_closed = self._state == self._states.CURSOR_CLOSED
|
|
41
|
+
return '<cursor object at 0x{0:x}; closed: {1:}>'.format(
|
|
42
|
+
id(self), is_closed
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Iteration support.
|
|
46
|
+
def __iter__(self):
|
|
47
|
+
while True:
|
|
48
|
+
one = self.fetchone()
|
|
49
|
+
if one is None:
|
|
50
|
+
return
|
|
51
|
+
yield one
|
|
52
|
+
|
|
53
|
+
# Context manager integrations.
|
|
54
|
+
def __enter__(self):
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
58
|
+
self.close()
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def description(self):
|
|
62
|
+
if self._state == self._states.NONE:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
columns = self._columns or []
|
|
66
|
+
types = self._types or []
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
Column(name, type_code, None, None, None, None, True)
|
|
70
|
+
for name, type_code in zip(columns, types)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def rowcount(self):
|
|
75
|
+
"""
|
|
76
|
+
:return: the number of rows that the last .execute*() produced.
|
|
77
|
+
"""
|
|
78
|
+
return self._rowcount
|
|
79
|
+
|
|
80
|
+
def close(self):
|
|
81
|
+
"""
|
|
82
|
+
Close the cursor now. The cursor will be unusable from this point
|
|
83
|
+
forward; an :data:`~clickhouse_driver.dbapi.Error` (or subclass)
|
|
84
|
+
exception will be raised if any operation is attempted with the
|
|
85
|
+
cursor.
|
|
86
|
+
"""
|
|
87
|
+
self._client.disconnect()
|
|
88
|
+
self._state = self._states.CURSOR_CLOSED
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# cursor can be already closed
|
|
92
|
+
self._connection.cursors.remove(self)
|
|
93
|
+
except ValueError:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
def execute(self, operation, parameters=None):
|
|
97
|
+
"""
|
|
98
|
+
Prepare and execute a database operation (query or command).
|
|
99
|
+
|
|
100
|
+
:param operation: query or command to execute.
|
|
101
|
+
:param parameters: sequence or mapping that will be bound to
|
|
102
|
+
variables in the operation.
|
|
103
|
+
:return: None
|
|
104
|
+
"""
|
|
105
|
+
self._check_cursor_closed()
|
|
106
|
+
self._begin_query()
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
execute, execute_kwargs = self._prepare()
|
|
110
|
+
|
|
111
|
+
response = execute(
|
|
112
|
+
operation, params=parameters, with_column_types=True,
|
|
113
|
+
**execute_kwargs
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
except DriverError as orig:
|
|
117
|
+
raise OperationalError(orig)
|
|
118
|
+
|
|
119
|
+
self._process_response(response)
|
|
120
|
+
self._end_query()
|
|
121
|
+
|
|
122
|
+
def executemany(self, operation, seq_of_parameters):
|
|
123
|
+
"""
|
|
124
|
+
Prepare a database operation (query or command) and then execute it
|
|
125
|
+
against all parameter sequences found in the sequence
|
|
126
|
+
`seq_of_parameters`.
|
|
127
|
+
|
|
128
|
+
:param operation: query or command to execute.
|
|
129
|
+
:param seq_of_parameters: sequences or mappings for execution.
|
|
130
|
+
:return: None
|
|
131
|
+
"""
|
|
132
|
+
self._check_cursor_closed()
|
|
133
|
+
self._begin_query()
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
execute, execute_kwargs = self._prepare()
|
|
137
|
+
|
|
138
|
+
response = execute(
|
|
139
|
+
operation, params=seq_of_parameters, **execute_kwargs
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
except DriverError as orig:
|
|
143
|
+
raise OperationalError(orig)
|
|
144
|
+
|
|
145
|
+
self._process_response(response, executemany=True)
|
|
146
|
+
self._end_query()
|
|
147
|
+
|
|
148
|
+
def fetchone(self):
|
|
149
|
+
"""
|
|
150
|
+
Fetch the next row of a query result set, returning a single sequence,
|
|
151
|
+
or None when no more data is available.
|
|
152
|
+
|
|
153
|
+
:return: the next row of a query result set or None.
|
|
154
|
+
"""
|
|
155
|
+
self._check_query_started()
|
|
156
|
+
|
|
157
|
+
if self._stream_results:
|
|
158
|
+
return next(self._rows, None)
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
if not self._rows:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
return self._rows.pop(0)
|
|
165
|
+
|
|
166
|
+
def fetchmany(self, size=None):
|
|
167
|
+
"""
|
|
168
|
+
Fetch the next set of rows of a query result, returning a sequence of
|
|
169
|
+
sequences (e.g. a list of tuples). An empty sequence is returned when
|
|
170
|
+
no more rows are available.
|
|
171
|
+
|
|
172
|
+
:param size: amount of rows to return.
|
|
173
|
+
:return: list of fetched rows or empty list.
|
|
174
|
+
"""
|
|
175
|
+
self._check_query_started()
|
|
176
|
+
|
|
177
|
+
if size is None:
|
|
178
|
+
size = self.arraysize
|
|
179
|
+
|
|
180
|
+
if self._stream_results:
|
|
181
|
+
if size == -1:
|
|
182
|
+
return list(self._rows)
|
|
183
|
+
else:
|
|
184
|
+
return list(islice(self._rows, size))
|
|
185
|
+
|
|
186
|
+
if size < 0:
|
|
187
|
+
rv = self._rows
|
|
188
|
+
self._rows = []
|
|
189
|
+
else:
|
|
190
|
+
rv = self._rows[:size]
|
|
191
|
+
self._rows = self._rows[size:]
|
|
192
|
+
|
|
193
|
+
return rv
|
|
194
|
+
|
|
195
|
+
def fetchall(self):
|
|
196
|
+
"""
|
|
197
|
+
Fetch all (remaining) rows of a query result, returning them as a
|
|
198
|
+
sequence of sequences (e.g. a list of tuples).
|
|
199
|
+
|
|
200
|
+
:return: list of fetched rows.
|
|
201
|
+
"""
|
|
202
|
+
self._check_query_started()
|
|
203
|
+
|
|
204
|
+
if self._stream_results:
|
|
205
|
+
return list(self._rows)
|
|
206
|
+
|
|
207
|
+
rv = self._rows
|
|
208
|
+
self._rows = []
|
|
209
|
+
return rv
|
|
210
|
+
|
|
211
|
+
def setinputsizes(self, sizes):
|
|
212
|
+
# Do nothing.
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
def setoutputsize(self, size, column=None):
|
|
216
|
+
# Do nothing.
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
# Begin non-PEP methods
|
|
220
|
+
@property
|
|
221
|
+
def columns_with_types(self):
|
|
222
|
+
"""
|
|
223
|
+
:return: list of column names with corresponding types of the last
|
|
224
|
+
.execute*(). E.g. [('x', 'UInt64')].
|
|
225
|
+
"""
|
|
226
|
+
return self._columns_with_types
|
|
227
|
+
|
|
228
|
+
def set_stream_results(self, stream_results, max_row_buffer):
|
|
229
|
+
"""
|
|
230
|
+
Toggles results streaming from server. Driver will consume
|
|
231
|
+
block-by-block of `max_row_buffer` size and yield row-by-row from each
|
|
232
|
+
block.
|
|
233
|
+
|
|
234
|
+
:param stream_results: enable or disable results streaming.
|
|
235
|
+
:param max_row_buffer: specifies the maximum number of rows to buffer
|
|
236
|
+
at a time.
|
|
237
|
+
:return: None
|
|
238
|
+
"""
|
|
239
|
+
self._stream_results = stream_results
|
|
240
|
+
self._max_row_buffer = max_row_buffer
|
|
241
|
+
|
|
242
|
+
def set_settings(self, settings):
|
|
243
|
+
"""
|
|
244
|
+
Specifies settings for cursor.
|
|
245
|
+
|
|
246
|
+
:param settings: dictionary of query settings
|
|
247
|
+
:return: None
|
|
248
|
+
"""
|
|
249
|
+
self._settings = settings
|
|
250
|
+
|
|
251
|
+
def set_types_check(self, types_check):
|
|
252
|
+
"""
|
|
253
|
+
Toggles type checking for sequence of INSERT parameters.
|
|
254
|
+
Disabled by default.
|
|
255
|
+
|
|
256
|
+
:param types_check: new types check value.
|
|
257
|
+
:return: None
|
|
258
|
+
"""
|
|
259
|
+
self._types_check = types_check
|
|
260
|
+
|
|
261
|
+
def set_external_table(self, name, structure, data):
|
|
262
|
+
"""
|
|
263
|
+
Adds external table to cursor context.
|
|
264
|
+
|
|
265
|
+
If the same table is specified more than once the last one is used.
|
|
266
|
+
|
|
267
|
+
:param name: name of external table
|
|
268
|
+
:param structure: list of tuples (name, type) that defines table
|
|
269
|
+
structure. Example [(x, 'Int32')].
|
|
270
|
+
:param data: sequence of rows of tuples or dicts for transmission.
|
|
271
|
+
:return: None
|
|
272
|
+
"""
|
|
273
|
+
self._external_tables[name] = (structure, data)
|
|
274
|
+
|
|
275
|
+
def set_query_id(self, query_id):
|
|
276
|
+
"""
|
|
277
|
+
Specifies the query identifier for cursor.
|
|
278
|
+
|
|
279
|
+
:param query_id: the query identifier.
|
|
280
|
+
:return: None
|
|
281
|
+
"""
|
|
282
|
+
self._query_id = query_id
|
|
283
|
+
# End non-PEP methods
|
|
284
|
+
|
|
285
|
+
# Private methods.
|
|
286
|
+
def _prepare(self):
|
|
287
|
+
external_tables = [
|
|
288
|
+
{'name': name, 'structure': structure, 'data': data}
|
|
289
|
+
for name, (structure, data) in self._external_tables.items()
|
|
290
|
+
] or None
|
|
291
|
+
|
|
292
|
+
execute = self._client.execute
|
|
293
|
+
|
|
294
|
+
if self._stream_results:
|
|
295
|
+
execute = self._client.execute_iter
|
|
296
|
+
self._settings = self._settings or {}
|
|
297
|
+
self._settings['max_block_size'] = self._max_row_buffer
|
|
298
|
+
|
|
299
|
+
execute_kwargs = {
|
|
300
|
+
'settings': self._settings,
|
|
301
|
+
'external_tables': external_tables,
|
|
302
|
+
'types_check': self._types_check,
|
|
303
|
+
'query_id': self._query_id
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return execute, execute_kwargs
|
|
307
|
+
|
|
308
|
+
def _process_response(self, response, executemany=False):
|
|
309
|
+
if executemany:
|
|
310
|
+
self._rowcount = response
|
|
311
|
+
response = None
|
|
312
|
+
|
|
313
|
+
if not response or isinstance(response, int):
|
|
314
|
+
self._columns = self._types = self._rows = []
|
|
315
|
+
if isinstance(response, int):
|
|
316
|
+
self._rowcount = response
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
if self._stream_results:
|
|
320
|
+
columns_with_types = next(response)
|
|
321
|
+
rows = response
|
|
322
|
+
|
|
323
|
+
else:
|
|
324
|
+
rows, columns_with_types = response
|
|
325
|
+
|
|
326
|
+
self._columns_with_types = columns_with_types
|
|
327
|
+
|
|
328
|
+
# Only SELECT queries have columns_with_types.
|
|
329
|
+
# DDL and INSERT INTO ... SELECT queries have empty columns header.
|
|
330
|
+
# We need to obtain rows count only during non-streaming SELECTs.
|
|
331
|
+
if columns_with_types:
|
|
332
|
+
self._columns, self._types = zip(*columns_with_types)
|
|
333
|
+
if not self._stream_results:
|
|
334
|
+
self._rowcount = len(rows)
|
|
335
|
+
else:
|
|
336
|
+
self._columns = self._types = []
|
|
337
|
+
|
|
338
|
+
self._rows = rows
|
|
339
|
+
|
|
340
|
+
def _reset_state(self):
|
|
341
|
+
"""
|
|
342
|
+
Resets query state and get ready for another query.
|
|
343
|
+
"""
|
|
344
|
+
self._state = self._states.NONE
|
|
345
|
+
|
|
346
|
+
self._columns = None
|
|
347
|
+
self._types = None
|
|
348
|
+
self._rows = None
|
|
349
|
+
self._rowcount = -1
|
|
350
|
+
|
|
351
|
+
self._stream_results = False
|
|
352
|
+
self._max_row_buffer = 0
|
|
353
|
+
self._settings = None
|
|
354
|
+
self._query_id = None
|
|
355
|
+
self._external_tables = {}
|
|
356
|
+
self._types_check = False
|
|
357
|
+
|
|
358
|
+
def _begin_query(self):
|
|
359
|
+
self._state = self._states.RUNNING
|
|
360
|
+
|
|
361
|
+
def _end_query(self):
|
|
362
|
+
self._state = self._states.FINISHED
|
|
363
|
+
|
|
364
|
+
def _check_cursor_closed(self):
|
|
365
|
+
if self._state == self._states.CURSOR_CLOSED:
|
|
366
|
+
raise InterfaceError('cursor already closed')
|
|
367
|
+
|
|
368
|
+
def _check_query_started(self):
|
|
369
|
+
if self._state == self._states.NONE:
|
|
370
|
+
raise ProgrammingError('no results to fetch')
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class Warning(Exception):
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Error(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InterfaceError(Error):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatabaseError(Error):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InternalError(DatabaseError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OperationalError(DatabaseError):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProgrammingError(DatabaseError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class IntegrityError(DatabaseError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DataError(DatabaseError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NotSupportedError(DatabaseError):
|
|
40
|
+
pass
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
|
|
5
|
+
from .cursor import Cursor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DictCursor(Cursor):
|
|
9
|
+
"""
|
|
10
|
+
A cursor that generates results as :class:`dict`.
|
|
11
|
+
|
|
12
|
+
``fetch*()`` methods will return dicts instead of tuples.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def fetchone(self):
|
|
16
|
+
rv = super(DictCursor, self).fetchone()
|
|
17
|
+
if rv is not None:
|
|
18
|
+
rv = dict(zip(self._columns, rv))
|
|
19
|
+
return rv
|
|
20
|
+
|
|
21
|
+
def fetchmany(self, size=None):
|
|
22
|
+
rv = super(DictCursor, self).fetchmany(size=size)
|
|
23
|
+
return [dict(zip(self._columns, x)) for x in rv]
|
|
24
|
+
|
|
25
|
+
def fetchall(self):
|
|
26
|
+
rv = super(DictCursor, self).fetchall()
|
|
27
|
+
return [dict(zip(self._columns, x)) for x in rv]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NamedTupleCursor(Cursor):
|
|
31
|
+
"""
|
|
32
|
+
A cursor that generates results as named tuples created by
|
|
33
|
+
:func:`~collections.namedtuple`.
|
|
34
|
+
|
|
35
|
+
``fetch*()`` methods will return named tuples instead of regular tuples, so
|
|
36
|
+
their elements can be accessed both as regular numeric items as well as
|
|
37
|
+
attributes.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# ascii except alnum and underscore
|
|
41
|
+
_re_clean = re.compile(
|
|
42
|
+
'[' + re.escape(' !"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~') + ']')
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
@lru_cache(512)
|
|
46
|
+
def _make_nt(self, key):
|
|
47
|
+
fields = []
|
|
48
|
+
for s in key:
|
|
49
|
+
s = self._re_clean.sub('_', s)
|
|
50
|
+
# Python identifier cannot start with numbers, namedtuple fields
|
|
51
|
+
# cannot start with underscore.
|
|
52
|
+
if s[0] == '_' or '0' <= s[0] <= '9':
|
|
53
|
+
s = 'f' + s
|
|
54
|
+
fields.append(s)
|
|
55
|
+
|
|
56
|
+
return namedtuple('Record', fields)
|
|
57
|
+
|
|
58
|
+
def fetchone(self):
|
|
59
|
+
rv = super(NamedTupleCursor, self).fetchone()
|
|
60
|
+
if rv is not None:
|
|
61
|
+
nt = self._make_nt(self._columns)
|
|
62
|
+
rv = nt(*rv)
|
|
63
|
+
return rv
|
|
64
|
+
|
|
65
|
+
def fetchmany(self, size=None):
|
|
66
|
+
rv = super(NamedTupleCursor, self).fetchmany(size=size)
|
|
67
|
+
nt = self._make_nt(self._columns)
|
|
68
|
+
return [nt(*x) for x in rv]
|
|
69
|
+
|
|
70
|
+
def fetchall(self):
|
|
71
|
+
rv = super(NamedTupleCursor, self).fetchall()
|
|
72
|
+
nt = self._make_nt(self._columns)
|
|
73
|
+
return [nt(*x) for x in rv]
|