Skip to content

Commit

Permalink
Better number handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Fyne committed Oct 21, 2015
1 parent 9a0ccd1 commit 664a180
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 34 deletions.
104 changes: 72 additions & 32 deletions redash/query_runner/oracle.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,53 @@
import cx_Oracle
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__)

types_map = {
cx_Oracle.DATETIME: TYPE_DATETIME,
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,
}
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

class Oracle(BaseQueryRunner):
@classmethod
def configuration_schema(cls):
return {
Expand Down Expand Up @@ -71,11 +91,11 @@ def __init__(self, configuration_json):
def get_schema(self):
query = """
SELECT
user_tables.tablespace_name,
all_tab_cols.table_name,
all_tab_cols.column_name
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)
JOIN user_tables ON (all_tab_cols.TABLE_NAME = user_tables.TABLE_NAME)
"""

results, error = self.run_query(query)
Expand All @@ -87,28 +107,48 @@ def get_schema(self):

schema = {}
for row in results['rows']:
if row['tablespace_name'] != None:
table_name = '{}.{}'.format(row['tablespace_name'], row['table_name'])
if row['TABLESPACE_NAME'] != None:
table_name = '{}.{}'.format(row['TABLESPACE_NAME'], row['TABLE_NAME'])
else:
table_name = row['table_name']
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'])
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], types_map.get(i[1], None)) for i in cursor.description])
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}
Expand All @@ -117,15 +157,15 @@ def run_query(self, query):
else:
error = 'Query completed but it returned no data.'
json_data = None
except (select.error, OSError) as e:
logging.exception(e)
error = "Query interrupted. Please retry."
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 e:
except Exception as err:
raise sys.exc_info()[1], None, sys.exc_info()[2]
finally:
connection.close()
Expand Down
3 changes: 2 additions & 1 deletion redash/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ def all_settings():
'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)))
Expand Down
1 change: 0 additions & 1 deletion requirements_all_ds.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ pymongo==2.7.2
pyOpenSSL==0.14
vertica-python==0.5.1
td-client==0.3.2
cx_Oracle==5.2
4 changes: 4 additions & 0 deletions requirements_oracle_ds.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 664a180

Please sign in to comment.