mirror of
https://github.com/valitydev/redash.git
synced 2024-11-07 17:38:54 +00:00
Merge pull request #625 from essence-tech/oracle-support
Feature: Oracle query runner
This commit is contained in:
commit
b548cb1d8f
@ -201,3 +201,18 @@ Vertica
|
||||
- **Additional requirements**:
|
||||
|
||||
- ``vertica-python`` python package
|
||||
|
||||
Oracle
|
||||
------
|
||||
|
||||
- **Options**
|
||||
|
||||
- DSN Service name
|
||||
- User
|
||||
- Password
|
||||
- Host
|
||||
- Port
|
||||
|
||||
- **Additional requirements**
|
||||
|
||||
- ``cx_Oracle`` python package. This requires the installation of the Oracle `instant client <http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html>`__.
|
||||
|
175
redash/query_runner/oracle.py
Normal file
175
redash/query_runner/oracle.py
Normal file
@ -0,0 +1,175 @@
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from redash.query_runner import *
|
||||
from redash.utils import JSONEncoder
|
||||
|
||||
try:
|
||||
import cx_Oracle
|
||||
|
||||
TYPES_MAP = {
|
||||
cx_Oracle.DATETIME: TYPE_DATETIME,
|
||||
cx_Oracle.CLOB: TYPE_STRING,
|
||||
cx_Oracle.LOB: TYPE_STRING,
|
||||
cx_Oracle.FIXED_CHAR: TYPE_STRING,
|
||||
cx_Oracle.FIXED_NCHAR: TYPE_STRING,
|
||||
cx_Oracle.FIXED_UNICODE: TYPE_STRING,
|
||||
cx_Oracle.INTERVAL: TYPE_DATETIME,
|
||||
cx_Oracle.LONG_NCHAR: TYPE_STRING,
|
||||
cx_Oracle.LONG_STRING: TYPE_STRING,
|
||||
cx_Oracle.LONG_UNICODE: TYPE_STRING,
|
||||
cx_Oracle.NATIVE_FLOAT: TYPE_FLOAT,
|
||||
cx_Oracle.NCHAR: TYPE_STRING,
|
||||
cx_Oracle.NUMBER: TYPE_FLOAT,
|
||||
cx_Oracle.ROWID: TYPE_INTEGER,
|
||||
cx_Oracle.STRING: TYPE_STRING,
|
||||
cx_Oracle.TIMESTAMP: TYPE_DATETIME,
|
||||
cx_Oracle.UNICODE: TYPE_STRING,
|
||||
}
|
||||
|
||||
|
||||
ENABLED = True
|
||||
except ImportError:
|
||||
ENABLED = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Oracle(BaseQueryRunner):
|
||||
|
||||
@classmethod
|
||||
def get_col_type(cls, col_type, scale):
|
||||
if col_type == cx_Oracle.NUMBER:
|
||||
return TYPE_FLOAT if scale > 0 else TYPE_INTEGER
|
||||
else:
|
||||
return TYPES_MAP.get(col_type, None)
|
||||
|
||||
@classmethod
|
||||
def enabled(cls):
|
||||
return ENABLED
|
||||
|
||||
@classmethod
|
||||
def configuration_schema(cls):
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"type": "number"
|
||||
},
|
||||
"servicename": {
|
||||
"type": "string",
|
||||
"title": "DSN Service Name"
|
||||
}
|
||||
},
|
||||
"required": ["servicename"],
|
||||
"secret": ["password"]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def type(cls):
|
||||
return "oracle"
|
||||
|
||||
def __init__(self, configuration_json):
|
||||
super(Oracle, self).__init__(configuration_json)
|
||||
|
||||
dsn = cx_Oracle.makedsn(
|
||||
self.configuration["host"],
|
||||
self.configuration["port"],
|
||||
service_name=self.configuration["servicename"])
|
||||
|
||||
self.connection_string = "{}/{}@{}".format(self.configuration["user"], self.configuration["password"], dsn)
|
||||
|
||||
def get_schema(self):
|
||||
query = """
|
||||
SELECT
|
||||
user_tables.TABLESPACE_NAME,
|
||||
all_tab_cols.TABLE_NAME,
|
||||
all_tab_cols.COLUMN_NAME
|
||||
FROM all_tab_cols
|
||||
JOIN user_tables ON (all_tab_cols.TABLE_NAME = user_tables.TABLE_NAME)
|
||||
"""
|
||||
|
||||
results, error = self.run_query(query)
|
||||
|
||||
if error is not None:
|
||||
raise Exception("Failed getting schema.")
|
||||
|
||||
results = json.loads(results)
|
||||
|
||||
schema = {}
|
||||
for row in results['rows']:
|
||||
if row['TABLESPACE_NAME'] != None:
|
||||
table_name = '{}.{}'.format(row['TABLESPACE_NAME'], row['TABLE_NAME'])
|
||||
else:
|
||||
table_name = row['TABLE_NAME']
|
||||
|
||||
if table_name not in schema:
|
||||
schema[table_name] = {'name': table_name, 'columns': []}
|
||||
|
||||
schema[table_name]['columns'].append(row['COLUMN_NAME'])
|
||||
|
||||
return schema.values()
|
||||
|
||||
@classmethod
|
||||
def _convert_number(cls, value):
|
||||
try:
|
||||
return int(value)
|
||||
except:
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def output_handler(cls, cursor, name, default_type, length, precision, scale):
|
||||
if default_type in (cx_Oracle.CLOB, cx_Oracle.LOB):
|
||||
return cursor.var(cx_Oracle.LONG_STRING, 80000, cursor.arraysize)
|
||||
|
||||
if default_type in (cx_Oracle.STRING, cx_Oracle.FIXED_CHAR):
|
||||
return cursor.var(unicode, length, cursor.arraysize)
|
||||
|
||||
if default_type == cx_Oracle.NUMBER:
|
||||
if scale <= 0:
|
||||
return cursor.var(cx_Oracle.STRING, 255, outconverter=Oracle._convert_number, arraysize=cursor.arraysize)
|
||||
|
||||
def run_query(self, query):
|
||||
connection = cx_Oracle.connect(self.connection_string)
|
||||
connection.outputtypehandler = Oracle.output_handler
|
||||
|
||||
cursor = connection.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute(query)
|
||||
|
||||
if cursor.description is not None:
|
||||
columns = self.fetch_columns([(i[0], Oracle.get_col_type(i[1], i[5])) for i in cursor.description])
|
||||
rows = [dict(zip((c['name'] for c in columns), row)) for row in cursor]
|
||||
|
||||
data = {'columns': columns, 'rows': rows}
|
||||
error = None
|
||||
json_data = json.dumps(data, cls=JSONEncoder)
|
||||
else:
|
||||
error = 'Query completed but it returned no data.'
|
||||
json_data = None
|
||||
except cx_Oracle.DatabaseError as err:
|
||||
logging.exception(err.message)
|
||||
error = "Query failed. {}.".format(err.message)
|
||||
json_data = None
|
||||
except KeyboardInterrupt:
|
||||
connection.cancel()
|
||||
error = "Query cancelled by user."
|
||||
json_data = None
|
||||
except Exception as err:
|
||||
raise sys.exc_info()[1], None, sys.exc_info()[2]
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
return json_data, error
|
||||
|
||||
register(Oracle)
|
@ -127,7 +127,8 @@ default_query_runners = [
|
||||
'redash.query_runner.hive_ds',
|
||||
'redash.query_runner.impala_ds',
|
||||
'redash.query_runner.vertica',
|
||||
'redash.query_runner.treasuredata'
|
||||
'redash.query_runner.treasuredata',
|
||||
'redash.query_runner.oracle',
|
||||
]
|
||||
|
||||
enabled_query_runners = array_from_string(os.environ.get("REDASH_ENABLED_QUERY_RUNNERS", ",".join(default_query_runners)))
|
||||
|
4
requirements_oracle_ds.txt
Normal file
4
requirements_oracle_ds.txt
Normal file
@ -0,0 +1,4 @@
|
||||
# Requires installation of, or similar versions of:
|
||||
# oracle-instantclient12.1-basic_12.1.0.2.0-2_amd64.deb
|
||||
# oracle-instantclient12.1-devel_12.1.0.2.0-2_amd64.deb
|
||||
cx_Oracle==5.2
|
Loading…
Reference in New Issue
Block a user