Skip to content

Commit a23bb95

Browse files
authored
Merge pull request #51 from ClickHouse/update_clickhouse_driver
Update clickhouse driver
2 parents 454b7a1 + 30e112e commit a23bb95

File tree

11 files changed

+90
-73
lines changed

11 files changed

+90
-73
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ jobs:
2525

2626
services:
2727
clickhouse:
28-
image: yandex/clickhouse-server:${{ matrix.clickhouse-version}}
28+
image: clickhouse/clickhouse-server:${{ matrix.clickhouse-version}}
2929
ports:
3030
- 9000:9000
31+
- 8123:8123
3132

3233
steps:
3334
- name: Checkout

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ your_profile_name:
6767
connect_timeout: [10] # default 10
6868
send_receive_timeout: [300] # default 300
6969
sync_request_timeout: [5] # default 5
70-
compress_block_size: [1048576] # default 1048576
7170
compression: ['lz4'] # default '' (disable)
7271
```
7372

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = '1.0.5'
1+
version = '1.1.0'

dbt/adapters/clickhouse/connections.py

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,25 @@
44
from typing import Any, Optional, Tuple
55

66
import agate
7+
import clickhouse_connect
78
import dbt.exceptions
8-
from clickhouse_driver import Client, errors
9+
from clickhouse_connect.driver.client import Client as ChClient
10+
from clickhouse_connect.driver.exceptions import DatabaseError, Error
911
from dbt.adapters.base import Credentials
1012
from dbt.adapters.sql import SQLConnectionManager
1113
from dbt.contracts.connection import Connection
1214
from dbt.events import AdapterLogger
13-
from dbt.version import __version__ as dbt_version
1415

1516
logger = AdapterLogger('clickhouse')
1617

1718

1819
@dataclass
1920
class ClickhouseCredentials(Credentials):
21+
"""
22+
ClickHouse connectio credentials data class.
23+
"""
24+
25+
# pylint: disable=too-many-instance-attributes
2026
host: str = 'localhost'
2127
port: Optional[int] = None
2228
user: Optional[str] = 'default'
@@ -52,37 +58,38 @@ def __post_init__(self):
5258
self.database = None
5359

5460
def _connection_keys(self):
55-
return ('host', 'port', 'user', 'schema', 'secure', 'verify')
61+
return 'host', 'port', 'user', 'schema', 'secure', 'verify'
5662

5763

5864
class ClickhouseConnectionManager(SQLConnectionManager):
65+
"""
66+
ClickHouse Connector connection manager.
67+
"""
68+
5969
TYPE = 'clickhouse'
6070

6171
@contextmanager
6272
def exception_handler(self, sql):
6373
try:
6474
yield
65-
66-
except errors.ServerException as e:
67-
logger.debug('Clickhouse error: {}', str(e))
68-
75+
except DatabaseError as err:
76+
logger.debug('Clickhouse error: {}', str(err))
6977
try:
7078
# attempt to release the connection
7179
self.release()
72-
except errors.Error:
80+
except Error:
7381
logger.debug('Failed to release connection!')
74-
pass
7582

76-
raise dbt.exceptions.DatabaseException(str(e).strip()) from e
83+
raise dbt.exceptions.DatabaseException(str(err).strip()) from err
7784

78-
except Exception as e:
85+
except Exception as exp:
7986
logger.debug('Error running SQL: {}', sql)
8087
logger.debug('Rolling back transaction.')
8188
self.release()
82-
if isinstance(e, dbt.exceptions.RuntimeException):
89+
if isinstance(exp, dbt.exceptions.RuntimeException):
8390
raise
8491

85-
raise dbt.exceptions.RuntimeException(e) from e
92+
raise dbt.exceptions.RuntimeException(exp) from exp
8693

8794
@classmethod
8895
def open(cls, connection):
@@ -94,34 +101,32 @@ def open(cls, connection):
94101
kwargs = {}
95102

96103
try:
97-
handle = Client(
104+
handle = clickhouse_connect.get_client(
98105
host=credentials.host,
99106
port=credentials.port,
100107
database='default',
101-
user=credentials.user,
108+
username=credentials.user,
102109
password=credentials.password,
103-
client_name=f'dbt-{dbt_version}',
104-
secure=credentials.secure,
105-
verify=credentials.verify,
110+
interface='https' if credentials.secure else 'http',
111+
compress=False if credentials.compression == '' else bool(credentials.compression),
106112
connect_timeout=credentials.connect_timeout,
107113
send_receive_timeout=credentials.send_receive_timeout,
108-
sync_request_timeout=credentials.sync_request_timeout,
109-
compress_block_size=credentials.compress_block_size,
110-
compression=False if credentials.compression == '' else credentials.compression,
114+
verify=credentials.verify,
115+
query_limit=0,
111116
**kwargs,
112117
)
113118
connection.handle = handle
114119
connection.state = 'open'
115-
except errors.ServerException as e:
120+
except DatabaseError as err:
116121
logger.debug(
117122
'Got an error when attempting to open a clickhouse connection: \'{}\'',
118-
str(e),
123+
str(err),
119124
)
120125

121126
connection.handle = None
122127
connection.state = 'fail'
123128

124-
raise dbt.exceptions.FailedToConnectException(str(e))
129+
raise dbt.exceptions.FailedToConnectException(str(err))
125130

126131
return connection
127132

@@ -135,9 +140,12 @@ def cancel(self, connection):
135140
logger.debug('Cancel query \'{}\'', connection_name)
136141

137142
@classmethod
138-
def get_table_from_response(cls, response, columns) -> agate.Table:
139-
column_names = [x[0] for x in columns]
140-
143+
def get_table_from_response(cls, response, column_names) -> agate.Table:
144+
"""
145+
Build agate tabel from response.
146+
:param response: ClickHouse query result
147+
:param column_names: Table column names
148+
"""
141149
data = []
142150
for row in response:
143151
data.append(dict(zip(column_names, row)))
@@ -152,28 +160,37 @@ def execute(
152160
client = conn.handle
153161

154162
with self.exception_handler(sql):
155-
logger.debug(
156-
'On {connection_name}: {sql}'.format(connection_name=conn.name, sql=f'{sql}...'),
157-
)
163+
logger.debug(f'On {conn.name}: {sql}...')
158164

159165
pre = time.time()
160166

161-
response, columns = client.execute(sql, with_column_types=True)
167+
if fetch:
168+
query_result = client.query(sql)
169+
else:
170+
query_result = client.command(sql)
162171

163172
status = self.get_status(client)
164173

165-
logger.debug(
166-
'SQL status: {status} in {elapsed:0.2f} seconds'.format(
167-
status=status, elapsed=(time.time() - pre)
168-
),
169-
)
174+
logger.debug(f'SQL status: {status} in {(time.time() - pre):.2f} seconds')
170175

171176
if fetch:
172-
table = self.get_table_from_response(response, columns)
177+
table = self.get_table_from_response(
178+
query_result.result_set, query_result.column_names
179+
)
173180
else:
174181
table = dbt.clients.agate_helper.empty_table()
175182
return status, table
176183

184+
def insert_table_data(self, table_name, table: agate.Table):
185+
"""
186+
Insert data into ClickHouse table
187+
:param table_name: Target table name
188+
:param table: Data to be inserted
189+
"""
190+
client: ChClient = self.get_thread_connection().handle
191+
with self.exception_handler(f'INSERT INTO {table_name}'):
192+
client.insert(table_name, table.rows, table.column_names)
193+
177194
def add_query(
178195
self,
179196
sql: str,
@@ -186,33 +203,33 @@ def add_query(
186203
client = conn.handle
187204

188205
with self.exception_handler(sql):
189-
logger.debug(
190-
'On {connection_name}: {sql}'.format(connection_name=conn.name, sql=f'{sql}...')
191-
)
206+
logger.debug(f'On {conn.name}: {sql}...')
192207

193208
pre = time.time()
194-
client.execute(sql)
209+
client.query(sql)
195210

196211
status = self.get_status(client)
197212

198-
logger.debug(
199-
'SQL status: {status} in {elapsed:0.2f} seconds'.format(
200-
status=status, elapsed=(time.time() - pre)
201-
)
202-
)
213+
logger.debug(f'SQL status: {status} in {(time.time() - pre):0.2f} seconds')
203214

204215
return conn, None
205216

206217
@classmethod
207218
def get_credentials(cls, credentials):
219+
"""
220+
Returns ClickHouse credentials
221+
"""
208222
return credentials
209223

210224
@classmethod
211-
def get_status(cls, cursor):
225+
def get_status(cls, _):
226+
"""
227+
Returns connection status
228+
"""
212229
return 'OK'
213230

214231
@classmethod
215-
def get_response(cls, cursor):
232+
def get_response(cls, _):
216233
return 'OK'
217234

218235
def begin(self):

dbt/adapters/clickhouse/impl.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,21 @@ def get_csv_data(self, table):
235235

236236
return buf.getvalue()
237237

238+
@available
239+
def insert_table_data(self, table_name, table):
240+
self.connections.insert_table_data(table_name, table)
241+
238242
def run_sql_for_tests(self, sql, fetch, conn):
239-
cursor = conn.handle
243+
client = conn.handle
240244
try:
241-
result = cursor.execute(sql)
245+
if fetch:
246+
result = client.query(sql).result_set
247+
else:
248+
result = client.command(sql)
242249
if fetch == "one" and len(result) > 0:
243250
return result[0]
244-
elif fetch == "all":
251+
if fetch == "all":
245252
return result
246-
else:
247-
return
248253
except BaseException as e:
249254
self.logger.error(sql)
250255
self.logger.error(e)

dbt/include/clickhouse/macros/catalog.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
{%- if not loop.last %} or {% endif -%}
2121
{%- endfor -%}
2222
)
23-
order by columns.database, columns.table, columns.position;
23+
order by columns.database, columns.table, columns.position
2424
{%- endcall -%}
2525
{{ return(load_result('catalog').table) }}
2626
{%- endmacro %}

dbt/include/clickhouse/macros/materializations/seed.sql

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
{% macro clickhouse__load_csv_rows(model, agate_table) %}
2-
{% set cols_sql = get_seed_column_quoted_csv(model, agate_table.column_names) %}
3-
{% set data_sql = adapter.get_csv_data(agate_table) %}
4-
5-
{% set sql -%}
6-
insert into {{ this.render() }} ({{ cols_sql }}) format CSV
7-
{{ data_sql }}
8-
{%- endset %}
9-
10-
{% do adapter.add_query(sql, bindings=agate_table, abridge_sql_log=True) %}
2+
{% do adapter.insert_table_data(this.render(), agate_table) %}
113
{% endmacro %}
124

135
{% macro clickhouse__create_csv_table(model, agate_table) %}

dev_requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
dbt-core==1.1.0
2-
clickhouse-driver==0.2.2
2+
clickhouse-connect>=0.0.14
33
pytest==7.0.0
4-
pytest-dotenv
4+
pytest-dotenv==0.5.2
55
dbt-tests-adapter==1.1
66
black==22.3.0
77
isort==5.10.1

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ line_length = 100
99
profile = "black"
1010
use_parentheses = true
1111
skip = '.eggs/,.mypy_cache/,.venv/,venv/,env/'
12+
13+
[tool.pytest.ini_options]
14+
log_cli = true
15+
log_cli_level = "WARNING"

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,16 @@ def _dbt_clickhouse_version():
5454
},
5555
install_requires=[
5656
f'dbt-core=={dbt_version}',
57-
'clickhouse-driver>=0.2.2',
57+
'clickhouse-connect>=0.0.14',
5858
],
59-
python_requires=">=3.6",
59+
python_requires=">=3.7",
6060
platforms='any',
6161
classifiers=[
6262
'Development Status :: 5 - Production/Stable',
6363
'License :: OSI Approved :: Apache Software License',
6464
'Operating System :: Microsoft :: Windows',
6565
'Operating System :: MacOS :: MacOS X',
6666
'Operating System :: POSIX :: Linux',
67-
'Programming Language :: Python :: 3.6',
6867
'Programming Language :: Python :: 3.7',
6968
'Programming Language :: Python :: 3.8',
7069
],

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def test_config():
2727
up_result = run_cmd(['docker-compose', '-f', compose_file, 'up', '-d'])
2828
if up_result[0]:
2929
raise Exception(f'Failed to start docker: {up_result[2]}')
30-
url = "http://{}:{}".format(os.environ.get('HOST_ENV_VAR_NAME'), 10723)
30+
url = "http://{}:{}".format(os.environ.get('HOST_ENV_VAR_NAME', 'localhost'), 10723)
3131
wait_until_responsive(timeout=30.0, pause=0.5, check=lambda: is_responsive(url))
3232
except Exception as e:
3333
raise Exception('Failed to run docker-compose: {}', str(e))
@@ -50,7 +50,7 @@ def dbt_profile_target():
5050
'host': os.environ.get('HOST_ENV_VAR_NAME', 'localhost'),
5151
'user': os.environ.get('USER_ENV_VAR_NAME', 'default'),
5252
'password': os.environ.get('PASSWORD_ENV_VAR_NAME', ''),
53-
'port': int(os.environ.get('PORT_ENV_VAR_NAME', 9000)), # docker client port
53+
'port': int(os.environ.get('PORT_ENV_VAR_NAME', 8123)), # docker client port
5454
'secure': False,
5555
}
5656

0 commit comments

Comments
 (0)