Files
redash/redash/query_runner/clickhouse.py
Jannis Leidel c2429e92d2 Consistently use simplejson to loading and dumping JSON. (#2817)
* Consistently use simplejson to loading and dumping JSON.

This introduces the new functions redash.utils.json_dumps and redash.utils.json_loads and simplifies the custom encoder setup.

UUIDs are now handled by the default encoder, too.

Fixes #2807.

Use string comparison in parse_boolean instead of the (simple)json module.
2018-10-09 15:38:06 +02:00

121 lines
3.6 KiB
Python

import logging
import re
import requests
from redash.query_runner import *
from redash.utils import json_dumps, json_loads
logger = logging.getLogger(__name__)
class ClickHouse(BaseSQLQueryRunner):
noop_query = "SELECT 1"
@classmethod
def configuration_schema(cls):
return {
"type": "object",
"properties": {
"url": {
"type": "string",
"default": "http://127.0.0.1:8123"
},
"user": {
"type": "string",
"default": "default"
},
"password": {
"type": "string"
},
"dbname": {
"type": "string",
"title": "Database Name"
}
},
"required": ["dbname"],
"secret": ["password"]
}
@classmethod
def type(cls):
return "clickhouse"
def _get_tables(self, schema):
query = "SELECT database, table, name FROM system.columns WHERE database NOT IN ('system')"
results, error = self.run_query(query, None)
if error is not None:
raise Exception("Failed getting schema.")
results = json_loads(results)
for row in results['rows']:
table_name = '{}.{}'.format(row['database'], row['table'])
if table_name not in schema:
schema[table_name] = {'name': table_name, 'columns': []}
schema[table_name]['columns'].append(row['name'])
return schema.values()
def _send_query(self, data, stream=False):
r = requests.post(self.configuration['url'], data=data.encode("utf-8"), stream=stream, params={
'user': self.configuration['user'], 'password': self.configuration['password'],
'database': self.configuration['dbname']
})
if r.status_code != 200:
raise Exception(r.text)
# logging.warning(r.json())
return r.json()
@staticmethod
def _define_column_type(column):
c = column.lower()
f = re.search(r'^nullable\((.*)\)$', c)
if f is not None:
c = f.group(1)
if c.startswith('int') or c.startswith('uint'):
return TYPE_INTEGER
elif c.startswith('float'):
return TYPE_FLOAT
elif c == 'datetime':
return TYPE_DATETIME
elif c == 'date':
return TYPE_DATE
else:
return TYPE_STRING
def _clickhouse_query(self, query):
query += ' FORMAT JSON'
result = self._send_query(query)
columns = [{'name': r['name'], 'friendly_name': r['name'],
'type': self._define_column_type(r['type'])} for r in result['meta']]
# db converts value to string if its type equals UInt64
columns_uint64 = [r['name'] for r in result['meta'] if r['type'] == 'UInt64']
rows = result['data']
for row in rows:
for column in columns_uint64:
row[column] = int(row[column])
return {'columns': columns, 'rows': rows}
def run_query(self, query, user):
logger.debug("Clickhouse is about to execute query: %s", query)
if query == "":
json_data = None
error = "Query is empty"
return json_data, error
try:
q = self._clickhouse_query(query)
data = json_dumps(q)
error = None
except Exception as e:
data = None
logging.exception(e)
error = unicode(e)
return data, error
register(ClickHouse)