Просмотр исходного кода

HUE-8580 [editor] Fix jdbc assist.

jdesjean 7 лет назад
Родитель
Сommit
a4d7b5500d

+ 2 - 2
apps/beeswax/src/beeswax/templates/execute.mako

@@ -1806,11 +1806,11 @@ $(document).one('fetched.design', editables);
 $(document).one('fetched.query', editables);
 
 function isNumericColumn(type) {
-  return $.inArray(type, ['TINYINT_TYPE', 'SMALLINT_TYPE', 'INT_TYPE', 'BIGINT_TYPE', 'FLOAT_TYPE', 'DOUBLE_TYPE', 'DECIMAL_TYPE', 'TIMESTAMP_TYPE', 'DATE_TYPE']) > -1;
+  return $.inArray(type, ['TINYINT_TYPE', 'SMALLINT_TYPE', 'INT_TYPE', 'BIGINT_TYPE', 'FLOAT_TYPE', 'DOUBLE_TYPE', 'DECIMAL_TYPE', 'TIMESTAMP_TYPE', 'DATE_TYPE', 'DATETIME_TYPE']) > -1;
 }
 
 function isDateTimeColumn(type) {
-  return $.inArray(type, ['TIMESTAMP_TYPE', 'DATE_TYPE']) > -1;
+  return $.inArray(type, ['TIMESTAMP_TYPE', 'DATE_TYPE', 'DATETIME_TYPE']) > -1;
 }
 
 function isStringColumn(type) {

+ 1 - 1
apps/beeswax/src/beeswax/templates/watch_results.mako

@@ -363,7 +363,7 @@ $(document).ready(function () {
           sType = "string"
           if col.type in ["TINYINT_TYPE", "SMALLINT_TYPE", "INT_TYPE", "BIGINT_TYPE", "FLOAT_TYPE", "DOUBLE_TYPE", "DECIMAL_TYPE"]:
             sType = "numeric"
-          elif col.type in ["TIMESTAMP_TYPE", "DATE_TYPE"]:
+          elif col.type in ["TIMESTAMP_TYPE", "DATE_TYPE", "DATETIME_TYPE"]:
             sType = "date"
           %>
         { "sSortDataType":"dom-text", "sType":"${ sType }"},

+ 9 - 34
desktop/libs/indexer/src/indexer/api3.py

@@ -43,7 +43,7 @@ from indexer.file_format import HiveFormat
 from indexer.fields import Field
 from indexer.indexers.envelope import EnvelopeIndexer
 from indexer.indexers.morphline import MorphlineIndexer
-from indexer.indexers.rdbms import RdbmsIndexer, run_sqoop,  _get_db
+from indexer.indexers.rdbms import run_sqoop,  _get_api
 from indexer.indexers.sql import SQLIndexer
 from indexer.solr_client import SolrClient, MAX_UPLOAD_SIZE
 
@@ -131,7 +131,7 @@ def guess_format(request):
   elif file_format['inputFormat'] == 'query':
     format_ = {"quoteChar": "\"", "recordSeparator": "\\n", "type": "csv", "hasHeader": False, "fieldSeparator": "\u0001"}
   elif file_format['inputFormat'] == 'rdbms':
-    format_ = RdbmsIndexer(request.user, file_format['rdbmsType']).guess_format()
+    format_ = {"type": "csv"}
   elif file_format['inputFormat'] == 'stream':
     if file_format['streamSelection'] == 'kafka':
       format_ = {"type": "csv", "fieldSeparator": ",", "hasHeader": True, "quoteChar": "\"", "recordSeparator": "\\n", 'topics': get_topics()}
@@ -213,40 +213,15 @@ def guess_field_types(request):
         "columns": columns,
     }
   elif file_format['inputFormat'] == 'rdbms':
-    if file_format.get('rdbmsUsername'):
-      db = _get_db(request)
-    else:
-      query_server = rdbms.get_query_server_config(server=file_format['rdbmsType'])
-      db = rdbms.get(request.user, query_server=query_server)
-
-    sample = RdbmsIndexer(request.user, file_format['rdbmsType'], db=db).get_sample_data(mode=file_format['rdbmsMode'], database=file_format['rdbmsDatabaseName'], table=file_format['tableName'])
-    table_metadata = db.get_columns(file_format['rdbmsDatabaseName'], file_format['tableName'], names_only=False)
-
-    # cf. https://github.com/apache/sqoop/blob/trunk/src/java/org/apache/sqoop/hive/HiveTypes.java#L39
-    for col in table_metadata:
-      col_type = col['type'].upper().split('(')[0]
-      print col_type
-      if col_type in ('INTEGER', 'SMALLINT', 'INT'):
-        col['type'] = 'int'
-      elif col_type in ('VARCHAR', 'CHAR', 'LONGVARCHAR', 'NVARCHAR', 'NCHAR', 'LONGNVARCHAR', 'DATE', 'TIME', 'TIMESTAMP', 'CLOB'):
-        col['type'] = 'string'
-      elif col_type in ('NUMERIC', 'DECIMAL', 'FLOAT', 'DOUBLE', 'REAL'):
-        col['type'] = 'double'
-      elif col_type in ('BIT', 'BOOLEAN'):
-        col['type'] = 'boolean'
-      elif col_type in ('TINYINT',):
-        col['type'] = 'tinyint'
-      elif col_type in ('BIGINT',):
-        col['type'] = 'bigint'
-      else:
-        col['type'] = 'string'
+    api = _get_api(request)
+    sample = api.get_sample_data(None, database=file_format['rdbmsDatabaseName'], table=file_format['tableName'])
 
     format_ = {
-        "sample": list(sample['rows'])[:4],
-        "columns": [
-            Field(col['name'], col['type']).to_dict()
-            for col in table_metadata
-        ]
+      "sample": list(sample['rows'])[:4],
+      "columns": [
+          Field(col['name'], col['type']).to_dict()
+          for col in sample['full_headers']
+      ]
     }
   elif file_format['inputFormat'] == 'stream':
     # Note: mocked here, should come from SFDC or Kafka API or sampling job

+ 1 - 0
desktop/libs/indexer/src/indexer/file_format.py

@@ -628,6 +628,7 @@ class HiveFormat(CSVFormat):
     "DOUBLE_TYPE": "double",
     "STRING_TYPE": "string",
     "TIMESTAMP_TYPE": "date",
+    "DATETIME_TYPE": "date",
     "BINARY_TYPE": "string",
     "DECIMAL_TYPE": "double",
     "DATE_TYPE": "date",

+ 34 - 77
desktop/libs/indexer/src/indexer/indexers/rdbms.py

@@ -29,8 +29,7 @@ from librdbms.conf import DATABASES, get_database_password, get_server_choices,
 from librdbms.jdbc import Jdbc
 from librdbms.server import dbms as rdbms
 from notebook.conf import get_ordered_interpreters
-from notebook.connectors.jdbc import Assist as JdbcAssist
-from notebook.connectors.rdbms import Assist
+from notebook.connectors.base import get_api
 from notebook.models import make_notebook
 
 
@@ -39,22 +38,17 @@ LOG = logging.getLogger(__name__)
 
 def get_db_component(request):
   format_ = {'data': [], 'status': 1, 'message': ''}
-  db = None
-
   try:
     source = json.loads(request.POST.get('source', '{}'))
 
-    db = _get_db(request)
-
-    if source['rdbmsType'] != 'jdbc':
-      assist = Assist(db)
-    else:
-      assist = JdbcAssist(db)
+    api = _get_api(request)
 
     if not source['rdbmsDatabaseName'] or (source['rdbmsMode'] == "customRdbms" and not source['rdbmsDbIsValid']):
-      data = assist.get_databases()
+      autocomplete = api.autocomplete(None)
+      data = autocomplete['databases']
     elif source['rdbmsDatabaseName']:
-      data = assist.get_tables(source['rdbmsDatabaseName'])
+      autocomplete = api.autocomplete(None, source['rdbmsDatabaseName'])
+      data = [table['name'] for table in autocomplete['tables_meta']]
 
     format_['data'] = [{'name': element, 'value': element} for element in data]
     format_['status'] = 0
@@ -65,40 +59,38 @@ def get_db_component(request):
 
   return JsonResponse(format_)
 
-def _get_db(request):
-  source = json.loads(request.POST.get('source', request.POST.get('fileFormat', '{}')))
-  user = User.objects.get(username=request.user)
-  name = None
-
-  if source['rdbmsMode'] == 'configRdbms':
-    if source['rdbmsType'] != 'jdbc':
-      query_server = rdbms.get_query_server_config(server=source['rdbmsType'])
-      db = rdbms.get(user, query_server=query_server)
+def _get_api(request):
+  file_format = json.loads(request.POST.get('source', request.POST.get('fileFormat', '{}')))
+  options = None
+  query_server = None
+  if file_format['rdbmsMode'] == 'customRdbms':
+    type = 'custom'
+    if file_format['rdbmsType'] == 'jdbc':
+      name = file_format['rdbmsHostname']
+      interface = file_format['rdbmsType']
+      options = {'driver': file_format['rdbmsJdbcDriver'],
+                 'url': file_format['rdbmsHostname'],
+                 'user': file_format['rdbmsUsername'],
+                  'password': file_format['rdbmsPassword']
+                }
     else:
-      interpreters = get_ordered_interpreters(request.user)
-      options = {}
-      key = [key for key in interpreters if key['name'] == source['rdbmsJdbcDriverName']]
-      if key:
-        options = key[0]['options']
-
-        db = Jdbc(driver_name=options['driver'], url=options['url'], username=options['user'], password=options['password'])
-  else:
-    name = source['rdbmsType']
-    if name != 'jdbc':
+      interface = 'rdbms'
       query_server = {
-        'server_name': name,
-        'server_host': source['rdbmsHostname'],
-        'server_port': int(source['rdbmsPort'] or 3306),
-        'username': source['rdbmsUsername'],
-        'password': source['rdbmsPassword'],
+        'server_name': file_format['rdbmsType'],
+        'server_host': file_format['rdbmsHostname'],
+        'server_port': int(file_format['rdbmsPort'] or 3306),
+        'username': file_format['rdbmsUsername'],
+        'password': file_format['rdbmsPassword'],
         'options': {},
-        'alias': name
+        'alias': file_format['rdbmsType']
       }
-      db = rdbms.get(user, query_server=query_server)
-    else:
-      db = Jdbc(driver_name=source['rdbmsJdbcDriver'], url=source['rdbmsHostname'], username=source['rdbmsUsername'], password=source['rdbmsPassword'])
+      name = query_server['server_host'] + ':' + query_server['port']
+  else:
+    type = file_format['rdbmsJdbcDriverName'] and file_format['rdbmsJdbcDriverName'].lower()
+    name = type
+    interface = file_format['inputFormat']
 
-  return db
+  return get_api(request, { 'type': type, 'interface': interface, 'options': options, 'query_server': query_server, 'name': name})
 
 def jdbc_db_list(request):
   format_ = {'data': [], 'status': 1}
@@ -267,39 +259,4 @@ def _splitby_column_check(statement, destination_splitby_column):
       'statement': statement,
       'destinationSplitbyColumn': destination_splitby_column
     }
-  return statement
-
-
-class RdbmsIndexer():
-
-  def __init__(self, user, db_conf_name, db=None):
-    self.user = user
-    self.db_conf_name = db_conf_name
-    self.db = db
-
-  def guess_format(self):
-    return {"type": "csv"}
-
-  def get_sample_data(self, mode=None, database=None, table=None, column=None):
-    if self.db:
-      db = self.db
-    else:
-      query_server = rdbms.get_query_server_config(server=self.db_conf_name)
-      db = rdbms.get(self.user, query_server=query_server)
-
-    if mode == 'configRdbms' or self.db_conf_name != 'jdbc':
-      assist = Assist(db)
-    else:
-      assist = JdbcAssist(db)
-
-    response = {'status': -1}
-    sample_data = assist.get_sample_data(database, table, column)
-
-    if sample_data:
-      response['status'] = 0
-      response['headers'] = sample_data.columns
-      response['rows'] = list(sample_data.rows())
-    else:
-      response['message'] = _('Failed to get sample data.')
-
-    return response
+  return statement

+ 9 - 7
desktop/libs/indexer/src/indexer/templates/importer.mako

@@ -264,11 +264,20 @@ ${ assist.assistPanel() }
             <!-- /ko -->
 
             <!-- ko if: createWizard.source.rdbmsMode() == 'customRdbms' -->
+              <!-- ko if: createWizard.source.rdbmsType() != 'jdbc' -->
               <div class="control-group">
                 <label for="rdbmsHostname" class="control-label"><div>${ _('Hostname') }</div>
                   <input type="text" class="input-xxlarge" data-bind="value: createWizard.source.rdbmsHostname" placeholder="${ _('Enter host/ip here e.g. mysql.domain.com or 123.123.123.123') }">
                 </label>
               </div>
+              <!-- /ko -->
+              <!-- ko if: createWizard.source.rdbmsType() == 'jdbc' -->
+              <div class="control-group">
+                <label for="rdbmsHostname" class="control-label"><div>${ _('Url') }</div>
+                  <input type="text" class="input-xxlarge" data-bind="value: createWizard.source.rdbmsHostname" placeholder="${ _('jdbc:mysql://localhost:3306') }">
+                </label>
+              </div>
+              <!-- /ko -->
 
               <!-- ko if: createWizard.source.rdbmsType() == 'jdbc' -->
               <div class="control-group">
@@ -1511,13 +1520,6 @@ ${ assist.assistPanel() }
           if (args) {
             loadFromObj(args);
           }
-
-          for (var i = 0; i < type.args.length; i++) {
-            self[type.args[i].name].subscribe(function() {
-              // Update the data preview when tweaking Format options on step 1
-              viewModel.createWizard.guessFieldTypes();
-            });
-          }
         }
       };
 

+ 21 - 5
desktop/libs/notebook/src/notebook/connectors/base.py

@@ -299,6 +299,14 @@ def get_api(request, snippet):
         'options': {},
         'is_sql': False
       }]
+    elif snippet['type'] == 'custom':
+      interpreter = [{
+        'name': snippet['name'],
+        'type': snippet['type'],
+        'interface': snippet['interface'],
+        'options': snippet.get('options', {}),
+        'is_sql': False
+      }]
     else:
       raise PopupException(_('Snippet type %(type)s is not configured in hue.ini') % snippet)
 
@@ -334,16 +342,23 @@ def get_api(request, snippet):
     return TextApi(request.user)
   elif interface == 'rdbms':
     from notebook.connectors.rdbms import RdbmsApi
-    return RdbmsApi(request.user, interpreter=snippet['type'])
+    return RdbmsApi(request.user, interpreter=snippet['type'], query_server=snippet.get('query_server'))
   elif interface == 'altus-adb':
     from notebook.connectors.altus_adb import AltusAdbApi
     return AltusAdbApi(user=request.user, cluster_name=cluster, request=request)
   elif interface == 'dataeng':
     from notebook.connectors.dataeng import DataEngApi
     return DataEngApi(user=request.user, request=request, cluster_name=cluster)
-  elif interface == 'jdbc' or interface == 'teradata':
-    from notebook.connectors.jdbc import JdbcApi
-    return JdbcApi(request.user, interpreter=interpreter)
+  elif interface == 'jdbc':
+    if not interpreter['options'] or interpreter['options'].get('url', '').find('teradata') < 0:
+      from notebook.connectors.jdbc import JdbcApi
+      return JdbcApi(request.user, interpreter=interpreter)
+    else:
+      from notebook.connectors.jdbc_teradata import JdbcApiTeradata
+      return JdbcApiTeradata(request.user, interpreter=interpreter)
+  elif interface == 'teradata':
+    from notebook.connectors.jdbc import JdbcApiTeradata
+    return JdbcApiTeradata(request.user, interpreter=interpreter)
   elif interface == 'sqlalchemy':
     from notebook.connectors.sqlalchemyapi import SqlAlchemyApi
     return SqlAlchemyApi(request.user, interpreter=interpreter)
@@ -374,11 +389,12 @@ def _get_snippet_session(notebook, snippet):
 
 class Api(object):
 
-  def __init__(self, user, interpreter=None, request=None, cluster=None):
+  def __init__(self, user, interpreter=None, request=None, cluster=None, query_server=None):
     self.user = user
     self.interpreter = interpreter
     self.request = request
     self.cluster = cluster
+    self.query_server = query_server
 
   def create_session(self, lang, properties=None):
     return {

+ 38 - 30
desktop/libs/notebook/src/notebook/connectors/jdbc.py

@@ -16,6 +16,7 @@
 # limitations under the License.
 
 import logging
+import sys
 
 from django.utils.translation import ugettext as _
 
@@ -42,11 +43,11 @@ def query_error_handler(func):
     except Exception, e:
       message = force_unicode(smart_str(e))
       if 'error occurred while trying to connect to the Java server' in message:
-        raise QueryError(_('%s: is the DB Proxy server running?') % message)
+        raise QueryError, _('%s: is the DB Proxy server running?') % message, sys.exc_info()[2]
       elif 'Access denied' in message:
-        raise AuthenticationRequired()
+        raise AuthenticationRequired, '', sys.exc_info()[2]
       else:
-        raise QueryError(message)
+        raise QueryError, message, sys.exc_info()[2]
   return decorator
 
 
@@ -138,22 +139,19 @@ class JdbcApi(Api):
     if self.db is None:
       raise AuthenticationRequired()
 
-    assist = Assist(self.db)
+    assist = self._createAssist(self.db)
     response = {'status': -1}
 
     if database is None:
       response['databases'] = assist.get_databases()
     elif table is None:
-      response['tables'] = assist.get_tables(database)
-      response['tables_meta'] = response['tables']
+      tables = assist.get_tables_full(database)
+      response['tables'] = [table['name'] for table in tables]
+      response['tables_meta'] = tables
     else:
-      columns = assist.get_columns(database, table)
-      response['columns'] = [col[0] for col in columns]
-      response['extended_columns'] = [{
-        'name': col[0],
-        'type': col[1],
-        'comment': col[5]
-      } for col in columns]
+      columns = assist.get_columns_full(database, table)
+      response['columns'] = [col['name'] for col in columns]
+      response['extended_columns'] = columns
 
     response['status'] = 0
     return response
@@ -163,14 +161,19 @@ class JdbcApi(Api):
     if self.db is None:
       raise AuthenticationRequired()
 
-    assist = Assist(self.db)
-    response = {'status': -1}
+    assist = self._createAssist(self.db)
+    response = {'status': -1, 'result': {}}
 
     sample_data, description = assist.get_sample_data(database, table, column)
 
     if sample_data:
       response['status'] = 0
       response['headers'] = [col[0] for col in description] if description else []
+      response['full_headers'] = [{
+        'name': col[0],
+        'type': col[1],
+        'comment': ''
+      } for col in description]
       response['rows'] = sample_data
     else:
       response['message'] = _('Failed to get sample data.')
@@ -181,6 +184,9 @@ class JdbcApi(Api):
   def cache_key(self):
     return '%s-%s' % (self.interpreter['name'], self.user.username)
 
+  def _createAssist(self, db):
+    return Assist(db)
+
 
 class Assist():
 
@@ -188,26 +194,28 @@ class Assist():
     self.db = db
 
   def get_databases(self):
-    dbs, description = query_and_fetch(self.db, 'SELECT DatabaseName FROM DBC.Databases')
+    dbs, description = query_and_fetch(self.db, 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA')
     return [db[0] and db[0].strip() for db in dbs]
 
   def get_tables(self, database, table_names=[]):
-    tables, description = query_and_fetch(self.db, "SELECT * FROM dbc.tables WHERE tablekind = 'T' and databasename='%s'" % database)
-    return [{"comment": table[7] and table[7].strip(), "type": "Table", "name": table[1] and table[1].strip()} for table in tables]
+    tables = self.get_tables_full(database, table_names)
+    return [table['name'] for table in tables]
+
+  def get_tables_full(self, database, table_names=[]):
+    tables, description = query_and_fetch(self.db, "SELECT TABLE_NAME, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='%s'" % database)
+    return [{"comment": table[1] and table[1].strip(), "type": "Table", "name": table[0] and table[0].strip()} for table in tables]
 
   def get_columns(self, database, table):
-    columns, description = query_and_fetch(self.db, "SELECT ColumnName, ColumnType, CommentString FROM DBC.Columns WHERE DatabaseName='%s' AND TableName='%s'" % (database, table))
-    return [[col[0] and col[0].strip(), self._type_converter(col[1]), '', '', col[2], ''] for col in columns]
+    columns = self.get_columns_full(database, table)
+    return [col['name'] for col in columns]
+
+  def get_columns_full(self, database, table):
+    columns, description = query_and_fetch(self.db, "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'" % (database, table))
+    return [{"comment": col[2] and col[2].strip(), "type": col[1], "name": col[0] and col[0].strip()} for col in columns]
 
   def get_sample_data(self, database, table, column=None):
     column = column or '*'
-    return query_and_fetch(self.db, 'SELECT %s FROM %s.%s' % (column, database, table))
-
-  def _type_converter(self, name):
-    return {
-        "I": "INT_TYPE",
-        "I2": "SMALLINT_TYPE",
-        "CF": "STRING_TYPE",
-        "CV": "CHAR_TYPE",
-        "DA": "DATE_TYPE",
-      }.get(name, 'STRING_TYPE')
+    #data, description =  query_and_fetch(self.db, 'SELECT %s FROM %s.%s limit 100' % (column, database, table))
+    #response['rows'] = data
+    #response['columns'] = []
+    return query_and_fetch(self.db, 'SELECT %s FROM %s.%s limit 100' % (column, database, table))

+ 53 - 0
desktop/libs/notebook/src/notebook/connectors/jdbc_teradata.py

@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from librdbms.jdbc import query_and_fetch
+
+from notebook.connectors.jdbc import JdbcApi
+from notebook.connectors.jdbc import Assist
+
+class JdbcApiTeradata(JdbcApi):
+
+  def _createAssist(self, db):
+    return TeradataAssist(db)
+
+class TeradataAssist(Assist):
+
+  def get_databases(self):
+    dbs, description = query_and_fetch(self.db, 'SELECT DatabaseName FROM DBC.Databases ORDER BY DatabaseName')
+    return [db[0] and db[0].strip() for db in dbs]
+
+  def get_tables_full(self, database, table_names=[]):
+    tables, description = query_and_fetch(self.db, "SELECT TableName, CommentString FROM dbc.tables WHERE tablekind = 'T' and databasename='%s' ORDER BY TableName" % database)
+    return [{"comment": table[1] and table[1].strip(), "type": "Table", "name": table[0] and table[0].strip()} for table in tables]
+
+  def get_columns_full(self, database, table):
+    columns, description = query_and_fetch(self.db, "SELECT ColumnName, ColumnType, CommentString FROM DBC.Columns WHERE DatabaseName='%s' AND TableName='%s' ORDER BY ColumnName" % (database, table))
+    return [{"comment": col[1] and col[1].strip(), "type": self._type_converter(col[1]), "name": col[0] and col[0].strip()} for col in columns]
+
+  def get_sample_data(self, database, table, column=None):
+    column = column or '*'
+    return query_and_fetch(self.db, 'SELECT %s FROM %s.%s sample 100' % (column, database, table))
+
+  def _type_converter(self, name):
+    return {
+        "I": "INT_TYPE",
+        "I2": "SMALLINT_TYPE",
+        "CF": "STRING_TYPE",
+        "CV": "CHAR_TYPE",
+        "DA": "DATE_TYPE",
+      }.get(name, 'STRING_TYPE')

+ 13 - 6
desktop/libs/notebook/src/notebook/connectors/rdbms.py

@@ -16,6 +16,7 @@
 # limitations under the License.
 
 import logging
+import sys
 
 from desktop.lib.i18n import force_unicode
 
@@ -37,14 +38,14 @@ def query_error_handler(func):
       if 'Invalid query handle' in message or 'Invalid OperationHandle' in message:
         raise QueryExpired(e)
       else:
-        raise QueryError(message)
+        raise QueryError, message, sys.exc_info()[2]
   return decorator
 
 
 class RdbmsApi(Api):
 
   def _execute(self, notebook, snippet):
-    query_server = dbms.get_query_server_config(server=self.interpreter)
+    query_server = self._get_query_server()
     db = dbms.get(self.user, query_server)
 
     db.use(snippet['database'])  # TODO: only do the use on the first statement in a multi query
@@ -122,7 +123,7 @@ class RdbmsApi(Api):
 
   @query_error_handler
   def autocomplete(self, snippet, database=None, table=None, column=None, nested=None):
-    query_server = dbms.get_query_server_config(server=self.interpreter)
+    query_server = self._get_query_server()
     db = dbms.get(self.user, query_server)
 
     assist = Assist(db)
@@ -150,11 +151,11 @@ class RdbmsApi(Api):
 
   @query_error_handler
   def get_sample_data(self, snippet, database=None, table=None, column=None, async=False, operation=None):
-    query_server = dbms.get_query_server_config(server=self.interpreter)
+    query_server = self._get_query_server()
     db = dbms.get(self.user, query_server)
 
     assist = Assist(db)
-    response = {'status': -1}
+    response = {'status': -1, 'result': {}}
 
     sample_data = assist.get_sample_data(database, table, column)
 
@@ -174,7 +175,7 @@ class RdbmsApi(Api):
 
   @query_error_handler
   def explain(self, notebook, snippet):
-    query_server = dbms.get_query_server_config(server=self.interpreter)
+    query_server = self._get_query_server()
     db = dbms.get(self.user, query_server)
 
     db.use(snippet['database'])
@@ -201,6 +202,12 @@ class RdbmsApi(Api):
       'statement': snippet['statement'],
     }
 
+  def _get_query_server(self):
+    if self.query_server:
+      return self.query_server
+    else:
+      return dbms.get_query_server_config(server=self.interpreter)
+
 
 class Assist():
 

+ 2 - 2
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -139,7 +139,7 @@ var EditorViewModel = (function() {
     }
 
     function isDateTimeColumn(type) {
-      return $.inArray(type, ['timestamp', 'date']) > -1;
+      return $.inArray(type, ['timestamp', 'date', 'datetime']) > -1;
     }
 
     function isComplexColumn(type) {
@@ -1768,7 +1768,7 @@ var EditorViewModel = (function() {
       self.result.meta().forEach(function (meta) {
         if ($.inArray(meta.type, ['TINYINT_TYPE', 'SMALLINT_TYPE', 'INT_TYPE', 'BIGINT_TYPE', 'FLOAT_TYPE', 'DOUBLE_TYPE', 'DECIMAL_TYPE']) > -1) {
           meta.cssClass = 'sort-numeric';
-        } else if ($.inArray(meta.type, ['TIMESTAMP_TYPE', 'DATE_TYPE']) > -1) {
+        } else if ($.inArray(meta.type, ['TIMESTAMP_TYPE', 'DATE_TYPE', 'DATETIME_TYPE']) > -1) {
           meta.cssClass = 'sort-date';
         } else {
           meta.cssClass = 'sort-string';