Forráskód Böngészése

HUE-7983 [assist] Use the data catalog for the context popover

This also contains the following changes:

- Add partitions to the data catalog
- Move the old autocompleter2.js and the jquery.hiveautocomplete plugin to the data catalog
- Remove the very old autocompleter.js as it's not used anymore
- Remove unused ApiHelper functions that uses the old cache
Johan Ahlen 7 éve
szülő
commit
aaef3f67d6

+ 0 - 1
apps/beeswax/src/beeswax/templates/execute.mako

@@ -796,7 +796,6 @@ ${ commonshare() | n,unicode }
 <script src="${ static('desktop/js/hue.json.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery-ui-1.10.4.custom.min.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/hue.routie.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/js/sqlAutocompleter.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/sqlAutocompleter2.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/sqlFunctions.js') }" type="text/javascript" charset="utf-8"></script>

+ 11 - 18
apps/metastore/src/metastore/static/metastore/js/metastore.model.js

@@ -269,24 +269,17 @@ var MetastoreTable = (function () {
     if (self.loaded()) {
       return;
     }
-    // TODO: Add to DataCatalogEntry
-    self.apiHelper.fetchPartitions({
-      databaseName: self.metastoreTable.catalogEntry.path[0],
-      tableName: self.metastoreTable.catalogEntry.name,
-      successCallback: function (data) {
-        self.keys(data.partition_keys_json);
-        self.values(data.partition_values_json);
-        self.preview.values(self.values().slice(0, 5));
-        self.preview.keys(self.keys());
-        self.loading(false);
-        self.loaded(true);
-        huePubSub.publish('metastore.loaded.partitions');
-      },
-      errorCallback: function () {
-        self.loading(false);
-        self.loaded(true);
-      }
-    })
+
+    self.metastoreTable.catalogEntry.getPartitions().done(function (partitions) {
+      self.keys(partitions.partition_keys_json);
+      self.values(partitions.partition_values_json);
+      self.preview.values(self.values().slice(0, 5));
+      self.preview.keys(self.keys());
+      huePubSub.publish('metastore.loaded.partitions');
+    }).always(function () {
+      self.loading(false);
+      self.loaded(true);
+    });
   };
 
   /**

+ 3 - 10
apps/pig/src/pig/templates/app.mako

@@ -1020,16 +1020,9 @@ ${ commonshare() | n,unicode }
       var apiHelper = ApiHelper.getInstance({
         user: '${ user }'
       });
-      apiHelper.fetchTables({
-        successCallback: function (data) {
-          if (data && data.status == 0 && data.tables_meta) {
-            availableTables = data.tables_meta.map(function (t) {
-              return t.name;
-            }).join(' ');
-          }
-        },
-        sourceType: 'hive',
-        databaseName: 'default'
+
+      DataCatalog.getChildren({ sourceType: 'hive', path: ['default'], silenceErrors: true }).done(function (childEntries) {
+        availableTables = $.map(childEntries, function (entry) { return entry.name }).join(' ');
       });
     % endif
 

+ 63 - 685
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -181,13 +181,8 @@ var ApiHelper = (function () {
     TABLE_DETAILS: '/metadata/api/optimizer/table_details'
   };
 
-  var genericCacheCondition = function (data) {
-    return typeof data !== 'undefined' && typeof data.status !== 'undefined' && data.status === 0;
-  };
-
   function ApiHelper () {
     var self = this;
-    self.lastKnownDatabases = {};
     self.queueManager = ApiQueueManager.getInstance();
     self.invalidateImpala = null;
 
@@ -1176,395 +1171,6 @@ var ApiHelper = (function () {
     }
   };
 
-  /**
-   * @param {Object} options
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   *
-   * @param {string} options.sourceType
-   * @param {string} [options.database]
-   **/
-  ApiHelper.prototype.loadDatabases = function (options) {
-    var self = this;
-
-    var loadFunction = function () {
-
-      fetchAssistData.bind(self)($.extend({}, options, {
-        url: AUTOCOMPLETE_API_PREFIX,
-        successCallback: function (data) {
-          var databases = data.databases || [];
-          var cleanDatabases = [];
-          databases.forEach(function (database) {
-            // Blacklist of system databases
-            if (database !== '_impala_builtins') {
-              // Ensure lower case
-              cleanDatabases.push(database.toLowerCase());
-            }
-          });
-          self.lastKnownDatabases[options.sourceType] = cleanDatabases;
-
-          options.successCallback(self.lastKnownDatabases[options.sourceType]);
-        },
-        errorCallback: function (response) {
-          if (response.status == 401) {
-            $(document).trigger("showAuthModal", {
-              'type': options.sourceType, 'callback': function () {
-                self.loadDatabases(options);
-              }
-            });
-            return;
-          }
-          self.lastKnownDatabases[options.sourceType] = [];
-          self.assistErrorCallback(options)(response);
-        },
-        cacheCondition: genericCacheCondition
-      }));
-    };
-
-    if (options.sourceType === 'impala' && self.invalidateImpala !== null) {
-      if (self.invalidateImpala.database) {
-        $.post(IMPALA_INVALIDATE_API, { flush_all:  self.invalidateImpala.flush, database: self.invalidateImpala.database }, loadFunction);
-      } else {
-        $.post(IMPALA_INVALIDATE_API, { flush_all:  self.invalidateImpala.flush }, loadFunction);
-      }
-      self.invalidateImpala = null;
-    } else {
-      loadFunction();
-    }
-  };
-
-  /**
-   * @param {Object} options
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   *
-   * @param {string} options.databaseName
-   * @param {string} options.tableName
-   * @param {Object[]} options.identifierChain
-   * @param {string} options.defaultDatabase
-   */
-  // TODO: Move to DataCatalog
-  ApiHelper.prototype.fetchPartitions = function (options) {
-    var self = this;
-
-    var url;
-    if (typeof options.identifierChain !== 'undefined' && options.identifierChain.length === 1) {
-      url = '/metastore/table/' + options.defaultDatabase + '/' + options.identifierChain[0].name + '/partitions';
-    } else if (typeof options.identifierChain !== 'undefined' && options.identifierChain.length === 2) {
-      url = '/metastore/table/' + options.identifierChain[0].name + '/' + options.identifierChain[1].name + '/partitions';
-    } else {
-      url = '/metastore/table/' + options.databaseName + '/' + options.tableName + '/partitions';
-    }
-
-    $.ajax({
-      url: url,
-      data: {
-        format: 'json'
-      },
-      beforeSend: function (xhr) {
-        xhr.setRequestHeader('X-Requested-With', 'Hue');
-      },
-      success: function (response) {
-        if (! self.successResponseIsError(response)) {
-          options.successCallback(response);
-        } else {
-          self.assistErrorCallback(options)(response);
-        }
-
-      },
-      error: self.assistErrorCallback(options)
-    });
-  };
-
-  /**
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   *
-   * @param {string} options.databaseName
-   * @param {string} options.tableName
-   */
-  // TODO: Move to DataCatalog
-  ApiHelper.prototype.fetchTableDetails = function (options) {
-    var self = this;
-    $.ajax({
-      url: "/" + (options.sourceType == "hive" ? "beeswax" : options.sourceType) + "/api/table/" + options.databaseName + "/" + options.tableName,
-      data: {
-        "format" : 'json'
-      },
-      beforeSend: function (xhr) {
-        xhr.setRequestHeader("X-Requested-With", "Hue");
-      },
-      success: function (response) {
-        if (! self.successResponseIsError(response)) {
-          options.successCallback(response);
-        } else {
-          self.assistErrorCallback(options)(response);
-        }
-      },
-      error: self.assistErrorCallback(options)
-    });
-  };
-
-
-  /**
-   * Deprecated, use DataCatalog.getEntry(...).getSample()
-   *
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   *
-   * @param {string} options.databaseName
-   * @param {string} options.tableName
-   * @param {Number} [options.timeout]
-   * @param {string} [options.columnName]
-   * @param {Object} [options.editor] - Ace editor
-   */
-  // TODO: Delete once DataCatalog is used throughout
-  ApiHelper.prototype.fetchTableSample = function (options) {
-    var self = this;
-    var url = SAMPLE_API_PREFIX + options.databaseName + '/' + options.tableName + (options.columnName ? '/' + options.columnName : '');
-
-    var fetchFunction = function (storeInCache) {
-      if (options.timeout === 0) {
-        self.assistErrorCallback(options)({ status: -1 });
-        return;
-      }
-      $.ajax({
-        type: 'POST',
-        url: url,
-        data: {
-          notebook: {},
-          snippet: ko.mapping.toJSON({
-            type: options.sourceType
-          })
-        },
-        timeout: options.timeout
-      }).done(function (data) {
-        if (! self.successResponseIsError(data)) {
-          if ((typeof data.rows !== 'undefined' && data.rows.length > 0) || typeof data.sample !== 'undefined') {
-            storeInCache(data);
-          }
-          options.successCallback(data);
-        } else {
-          self.assistErrorCallback(options)(data);
-        }
-      })
-      .fail(self.assistErrorCallback(options))
-      .always(function () {
-        if (typeof options.editor !== 'undefined' && options.editor !== null) {
-          options.editor.hideSpinner();
-        }
-      });
-    };
-
-    if (options.columnName) {
-      fetchCached.bind(self)($.extend({}, options, {
-        url: url,
-        fetchFunction: fetchFunction
-      }));
-    } else {
-      fetchFunction($.noop);
-    }
-  };
-
-
-  /**
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   *
-   * @param {string} options.databaseName
-   * @param {string} options.tableName
-   * @param {string} options.columnName
-   */
-  ApiHelper.prototype.refreshTableStats = function (options) {
-    var self = this;
-    var pollRefresh = function (url) {
-      $.post(url, function (data) {
-        if (data.isSuccess) {
-          options.successCallback(data);
-        } else if (data.isFailure) {
-          self.assistErrorCallback(options)(data.message);
-        } else {
-          window.setTimeout(function () {
-            pollRefresh(url);
-          }, 1000);
-        }
-      }).fail(self.assistErrorCallback(options));
-    };
-
-    $.post("/" + (options.sourceType == "hive" ? "beeswax" : options.sourceType) + "/api/analyze/" + options.databaseName + "/" + options.tableName + "/"  + (options.columnName || ""), function (data) {
-      if (data.status == 0 && data.watch_url) {
-        pollRefresh(data.watch_url);
-      } else {
-        self.assistErrorCallback(options)(data);
-      }
-    }).fail(self.assistErrorCallback(options));
-  };
-
-  /**
-   * Returns a promise that will always be resolved with:
-   *
-   * 1. Cached databases
-   * 2. Fetched databases
-   * 3. Empty array
-   *
-   * @param sourceType
-   * @return {Promise}
-   */
-  ApiHelper.prototype.getDatabases = function (sourceType) {
-    var self = this;
-    var promise = $.Deferred();
-    if (typeof self.lastKnownDatabases[sourceType] !== 'undefined') {
-      promise.resolve(self.lastKnownDatabases[sourceType])
-    } else {
-      self.loadDatabases({
-        sourceType: sourceType,
-        silenceErrors: true,
-        successCallback: function (databases) {
-          promise.resolve(databases)
-        },
-        errorCallback: function () {
-          promise.resolve([]);
-        }
-      });
-    }
-    return promise;
-  };
-
-  /**
-   * Tests if a database exists or not for the given sourceType
-   * Returns a promise that will always be resolved with either true or false. In case of error it will be false.
-   *
-   * @param sourceType
-   * @param databaseName
-   * @return {Promise}
-   */
-  ApiHelper.prototype.containsDatabase = function (sourceType, databaseName) {
-    var self = this;
-    var promise = $.Deferred(); // Will always be resolved
-    if (databaseName) {
-      self.getDatabases(sourceType).done(function (databases) {
-        promise.resolve(databases && databases.indexOf(databaseName.toLowerCase()) > -1);
-      });
-    } else {
-      promise.resolve(false);
-    }
-    return promise;
-  };
-
-  ApiHelper.prototype.expandComplexIdentifierChain = function (sourceType, database, identifierChain, successCallback, errorCallback, cachedOnly) {
-    var self = this;
-
-    var fetchFieldsInternal =  function (table, database, identifierChain, callback, errorCallback, fetchedFields) {
-      if (!identifierChain) {
-        identifierChain = [];
-      }
-      if (identifierChain.length > 0) {
-        fetchedFields.push(identifierChain[0].name);
-        identifierChain = identifierChain.slice(1);
-      }
-
-      // Parser sometimes knows if it's a map or array.
-      if (identifierChain.length > 0 && (identifierChain[0].name === 'item' || identifierChain[0].name === 'value')) {
-        fetchedFields.push(identifierChain[0].name);
-        identifierChain = identifierChain.slice(1);
-      }
-
-
-      self.fetchFields({
-        sourceType: sourceType,
-        databaseName: database,
-        tableName: table,
-        fields: fetchedFields,
-        timeout: self.timeout,
-        cachedOnly: cachedOnly,
-        successCallback: function (data) {
-          if (sourceType === 'hive'
-              && typeof data.extended_columns !== 'undefined'
-              && data.extended_columns.length === 1
-              && data.extended_columns.length
-              && /^map|array|struct/i.test(data.extended_columns[0].type)) {
-            identifierChain.unshift({ name: data.extended_columns[0].name })
-          }
-          if (identifierChain.length > 0) {
-            if (typeof identifierChain[0].name !== 'undefined' && /value|item|key/i.test(identifierChain[0].name)) {
-              fetchedFields.push(identifierChain[0].name);
-              identifierChain.shift();
-            } else {
-              if (data.type === 'array') {
-                fetchedFields.push('item')
-              }
-              if (data.type === 'map') {
-                fetchedFields.push('value')
-              }
-            }
-            fetchFieldsInternal(table, database, identifierChain, callback, errorCallback, fetchedFields)
-          } else {
-            fetchedFields.unshift(table);
-            callback(fetchedFields);
-          }
-        },
-        silenceErrors: true,
-        errorCallback: errorCallback
-      });
-    };
-
-    fetchFieldsInternal(identifierChain.shift().name, database, identifierChain, successCallback, errorCallback, []);
-  };
-
-  /**
-   *
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {function} options.errorCallback
-   * @param {Object[]} options.identifierChain
-   * @param {string} options.identifierChain.name
-   * @param {string} options.defaultDatabase
-   * @param {boolean} [options.cachedOnly] - Default false
-   *
-   * @returns {Object} promise
-   */
-  ApiHelper.prototype.identifierChainToPath = function (options) {
-    var self = this;
-    var promise = $.Deferred();
-    if (options.identifierChain.length === 0) {
-      promise.resolve([options.defaultDatabase]);
-      return promise;
-    }
-
-    var identifierChainClone = options.identifierChain.concat();
-    var path = [];
-
-    var dbPromise = self.containsDatabase(options.sourceType, identifierChainClone[0].name);
-
-    dbPromise.done(function (firstIsDatabase) {
-      if (!firstIsDatabase) {
-        path.push(options.defaultDatabase);
-      } else {
-        path.push(identifierChainClone.shift().name)
-      }
-
-      if (identifierChainClone.length > 1) {
-        self.expandComplexIdentifierChain(options.sourceType, path[0], identifierChainClone, function (fetchedFields) {
-          promise.resolve(path.concat(fetchedFields))
-        }, options.errorCallback, options.cachedOnly);
-      } else {
-        promise.resolve(path.concat($.map(identifierChainClone, function (identifier) { return identifier.name })))
-      }
-    });
-    return promise;
-  };
-
   /**
    * @param {Object} options
    * @param {string} options.sourceType
@@ -1691,7 +1297,6 @@ var ApiHelper = (function () {
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    *
-   * @param {string} options.sourceType
    * @param {string[]} options.path
    *
    * @return {CancellablePromise}
@@ -1723,6 +1328,7 @@ var ApiHelper = (function () {
       successCallback: function (response) {
         if (options.path.length === 1) {
           if (response.data) {
+            response.data.hueTimestamp = Date.now();
             deferred.resolve(response.data);
           } else {
             deferred.reject();
@@ -1737,6 +1343,58 @@ var ApiHelper = (function () {
     return new CancellablePromise(deferred, request);
   };
 
+  /**
+   * Fetches the partitions for the given path
+   *
+   * @param {Object} options
+   * @param {boolean} [options.silenceErrors]
+   *
+   * @param {string[]} options.path
+   *
+   * @return {CancellablePromise}
+   */
+  ApiHelper.prototype.fetchPartitions = function (options) {
+    var self = this;
+    var deferred = $.Deferred();
+
+    // TODO: No sourceType needed?
+    var request = $.ajax({
+      url: '/metastore/table/' + options.path.join('/') + '/partitions',
+      data: { format: 'json' },
+      success: function (response) {
+        if (!self.successResponseIsError(response)) {
+          if (!response) {
+            response = {};
+          }
+          response.hueTimestamp = Date.now();
+          deferred.resolve(response);
+        } else {
+          self.assistErrorCallback({
+            silenceErrors: options.silenceErrors,
+            errorCallback: deferred.reject
+          })(response);
+        }
+      },
+      error: function (response) {
+        // Don't report any partitions if it's not partitioned instead of error to prevent unnecessary calls
+        if (response && response.responseText && response.responseText.indexOf('is not partitioned') !== -1) {
+          deferred.resolve({
+            hueTimestamp: Date.now(),
+            partition_keys_json: [],
+            partition_values_json: []
+          })
+        } else {
+          self.assistErrorCallback({
+            silenceErrors: options.silenceErrors,
+            errorCallback: deferred.reject
+          })(response);
+        }
+      }
+    });
+
+    return new CancellablePromise(deferred, request);
+  };
+
   /**
    * Refreshes the analysis for the given source and path
    *
@@ -1984,6 +1642,7 @@ var ApiHelper = (function () {
       silenceErrors: options.silenceErrors,
       successCallback: function (response) {
         if (response.status === 0 && response.details) {
+          resonse.details.hueTimestamp = Date.now();
           deferred.resolve(response.details);
         } else {
           deferred.reject();
@@ -1995,193 +1654,6 @@ var ApiHelper = (function () {
     return new CancellablePromise(deferred, request);
   };
 
-  /**
-   * Deprecated, use DataCatalog.getEntry(...).getSourceDetails()
-   *
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   * @param {Number} [options.timeout]
-   * @param {Object} [options.editor] - Ace editor
-   * @param {boolean} [options.cachedOnly] - Default false
-   *
-   * @param {Object[]} options.identifierChain
-   * @param {string} options.identifierChain.name
-   * @param {string} options.defaultDatabase
-   */
-  // TODO: Drop and use DataCatalog.getEntry(...).getSourceDetails() throughout
-  ApiHelper.prototype.fetchAutocomplete = function (options) {
-    var self = this;
-    self.identifierChainToPath(options).done(function (path) {
-      fetchAssistData.bind(self)($.extend({}, options, {
-        url: AUTOCOMPLETE_API_PREFIX + path.join('/'),
-        errorCallback: self.assistErrorCallback(options),
-        cacheCondition: genericCacheCondition
-      }));
-    });
-  };
-
-  /**
-   * Deprecated, use DataCatalog.getEntry(...).getSourceDetails()
-   *
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   * @param {Number} [options.timeout]
-   * @param {Object} [options.editor] - Ace editor
-   *
-   * @param {string} options.databaseName
-   */
-  // TODO: Drop and use DataCatalog.getEntry(...).getSourceDetails() throughout
-  ApiHelper.prototype.fetchTables = function (options) {
-    var self = this;
-    return fetchAssistData.bind(self)($.extend({}, options, {
-      url: AUTOCOMPLETE_API_PREFIX + options.databaseName,
-      errorCallback: self.assistErrorCallback(options),
-      cacheCondition: genericCacheCondition
-    }));
-  };
-
-  /**
-   * Deprecated, use DataCatalog.getEntry(...).getSourceDetails()
-   *
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   * @param {Number} [options.timeout]
-   * @param {boolean} [options.cachedOnly] - Default false
-   * @param {Object} [options.editor] - Ace editor
-   *
-   * @param {string} options.databaseName
-   * @param {string} options.tableName
-   * @param {string[]} options.fields
-   */
-  // TODO: Drop and use DataCatalog.getEntry(...).getSourceDetails() throughout
-  ApiHelper.prototype.fetchFields = function (options) {
-    var self = this;
-    var fieldPart = options.fields.length > 0 ? "/" + options.fields.join("/") : "";
-    return fetchAssistData.bind(self)($.extend({}, options, {
-      url: AUTOCOMPLETE_API_PREFIX + options.databaseName + "/" + options.tableName + fieldPart,
-      errorCallback: self.assistErrorCallback(options),
-      cacheCondition: genericCacheCondition
-    }));
-  };
-
-  /**
-   * Deprecated, use DataCatalog.getEntry(...).getSample()
-   *
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   * @param {Number} [options.timeout]
-   * @param {Object} [options.editor] - Ace editor
-   *
-   * @param {Object[]} options.identifierChain
-   * @param {string} options.identifierChain.name
-   * @param {string} options.defaultDatabase
-   */
-  // TODO: Drop and use DataCatalog.getEntry(...).getSample() throughout
-  ApiHelper.prototype.fetchSamples = function (options) {
-    var self = this;
-    self.identifierChainToPath(options).done(function (path) {
-      fetchAssistData.bind(self)($.extend({}, options, {
-        url: SAMPLE_API_PREFIX + path.join('/'),
-        errorCallback: self.assistErrorCallback(options),
-        cacheCondition: genericCacheCondition
-      }));
-    });
-  };
-
-
-  /**
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {Function} options.successCallback
-   * @param {Function} [options.errorCallback]
-   * @param {boolean} [options.silenceErrors]
-   * @param {Number} [options.timeout]
-   * @param {Object} [options.editor] - Ace editor
-   *
-   * @param {Object[]} options.identifierChain
-   * @param {string} options.identifierChain.name
-   * @param {string} options.defaultDatabase
-   */
-  // TODO: Add to DataCatalog
-  ApiHelper.prototype.fetchAnalysis_OLD = function (options) {
-    var self = this;
-    var clonedIdentifierChain = options.identifierChain.concat();
-
-    var hierarchy = '';
-
-    var dbPromise = self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name);
-
-    dbPromise.done(function (firstIsDatabase) {
-      // Database
-      if (firstIsDatabase) {
-        hierarchy = clonedIdentifierChain.shift().name
-      } else {
-        hierarchy = options.defaultDatabase;
-      }
-
-      // Table
-      if (clonedIdentifierChain.length > 0) {
-        hierarchy += '/' + clonedIdentifierChain.shift().name;
-      }
-
-      // Column/Complex
-      if (clonedIdentifierChain.length > 0) {
-        hierarchy += '/stats/' + $.map(clonedIdentifierChain, function (identifier) { return identifier.name }).join('/')
-      }
-
-      var url = "/" + (options.sourceType == "hive" ? "beeswax" : options.sourceType) + "/api/table/" + hierarchy;
-
-      var fetchFunction = function (storeInCache) {
-        if (options.timeout === 0) {
-          self.assistErrorCallback(options)({ status: -1 });
-          return;
-        }
-        $.ajax({
-          url: url,
-          data: {
-            "format" : 'json'
-          },
-          beforeSend: function (xhr) {
-            xhr.setRequestHeader("X-Requested-With", "Hue");
-          },
-          timeout: options.timeout
-        }).done(function (data) {
-          if (! self.successResponseIsError(data)) {
-            if ((typeof data.cols !== 'undefined' && data.cols.length > 0) || typeof data.sample !== 'undefined') {
-              storeInCache(data);
-            }
-            options.successCallback(data);
-          } else {
-            self.assistErrorCallback(options)(data);
-          }
-        })
-          .fail(self.assistErrorCallback(options))
-          .always(function () {
-            if (typeof options.editor !== 'undefined' && options.editor !== null) {
-              options.editor.hideSpinner();
-            }
-          });
-      };
-
-      fetchCached.bind(self)($.extend({}, options, {
-        url: url,
-        fetchFunction: fetchFunction
-      }));
-    });
-  };
-
   ApiHelper.prototype.getClusterConfig = function (data) {
     return $.post(FETCH_CONFIG, data);
   };
@@ -2193,7 +1665,12 @@ var ApiHelper = (function () {
 
     var promise = $.Deferred();
 
-    self.getDatabases(options.sourceType).done(function (databases){
+    DataCatalog.getChildren({
+      sourceType: options.sourceType,
+      path: [],
+      silenceErrors: options.silenceErrors
+    }).done(function (dbEntries) {
+      var databases = $.map(dbEntries, function (entry) { return entry.name; });
       options.tables.forEach(function (table) {
         if (table.subQuery || !table.identifierChain) {
           return;
@@ -2214,7 +1691,9 @@ var ApiHelper = (function () {
           tableIndex[identifier] = true;
         }
       });
-      promise.resolve(ko.mapping.toJSON(tables))
+      promise.resolve(ko.mapping.toJSON(tables));
+    }).fail(function () {
+      promise.resolve(ko.mapping.toJSON(tables));
     });
 
     return promise;
@@ -2451,107 +1930,6 @@ var ApiHelper = (function () {
     });
   };
 
-  /**
-   * @param {Object} options
-   * @param {string} options.sourceType
-   * @param {string} options.url
-   * @param {boolean} [options.noCache]
-   * @param {boolean} [options.refreshCache] - Default false
-   * @param {Function} options.cacheCondition - Determines whether it should be cached or not
-   * @param {Function} options.successCallback
-   * @param {Function} options.errorCallback
-   * @param {string} [options.cacheType] - Possible values 'default', 'optimizer'. Default value 'default'
-   * @param {Number} [options.timeout]
-   * @param {boolean} [options.cachedOnly] - Default false
-   * @param {Object} [options.editor] - Ace editor
-   */
-  var fetchAssistData = function (options) {
-    var self = this;
-    if (!options.sourceType) {
-      options.errorCallback('No sourceType supplied');
-      console.warn('No sourceType supplied to fetchAssistData');
-      return
-    }
-
-    if (!options.noCache && !options.refreshCache) {
-      var cachedData = $.totalStorage(self.getAssistCacheIdentifier(options)) || {};
-      if (typeof cachedData[options.url] !== "undefined" && ! self.hasExpired(cachedData[options.url].timestamp, options.cacheType || 'default')) {
-        options.successCallback(cachedData[options.url].data);
-        return;
-      }
-    }
-    if (options.cachedOnly) {
-      if (options.errorCallback) {
-        options.errorCallback(false);
-      }
-      return;
-    }
-    if (typeof options.editor !== 'undefined' && options.editor !== null) {
-      options.editor.showSpinner();
-    }
-
-    var promise = self.queueManager.getQueued(options.url, options.sourceType);
-    var firstInQueue = typeof promise === 'undefined';
-    if (firstInQueue) {
-      promise = $.Deferred();
-      self.queueManager.addToQueue(promise, options.url, options.sourceType);
-    }
-
-    promise
-      .done(function (data) {
-        options.successCallback(data)
-      })
-      .fail(self.assistErrorCallback(options))
-      .always(function () {
-        if (typeof options.editor !== 'undefined' && options.editor !== null) {
-          options.editor.hideSpinner();
-        }
-      });
-
-    if (!firstInQueue) {
-      return;
-    }
-
-    if (options.timeout === 0) {
-      promise.reject({ status: -1 });
-      return;
-    }
-
-    return $.ajax({
-      type: 'POST',
-      url: options.url,
-      data: {
-        notebook: {},
-        snippet: ko.mapping.toJSON({
-          type: options.sourceType
-        })
-      },
-      timeout: options.timeout
-    }).success(function (data) {
-      data.notFound = data.status === 0 && data.code === 500 && data.error && (data.error.indexOf('Error 10001') !== -1 || data.error.indexOf('AnalysisException') !== -1);
-
-      // TODO: Display warning in autocomplete when an entity can't be found
-      // Hive example: data.error: [...] SemanticException [Error 10001]: Table not found default.foo
-      // Impala example: data.error: [...] AnalysisException: Could not resolve path: 'default.foo'
-      if (!data.notFound && self.successResponseIsError(data)) {
-        // When not found we at least cache the response to prevent a bunch of unnecessary calls
-        promise.reject(data);
-      } else {
-        // Safe to assume all requests in the queue have the same cacheCondition
-        if (!options.noCache && data.status === 0 && options.cacheCondition(data)) {
-          var cacheIdentifier = self.getAssistCacheIdentifier(options);
-          cachedData = $.totalStorage(cacheIdentifier) || {};
-          cachedData[options.url] = {
-            timestamp: (new Date()).getTime(),
-            data: data
-          };
-          $.totalStorage(cacheIdentifier, cachedData);
-        }
-        promise.resolve(data);
-      }
-    }).fail(promise.reject);
-  };
-
   /**
    *
    * @param {Object} options

+ 1 - 12
desktop/core/src/desktop/static/desktop/js/autocompleter.js

@@ -38,22 +38,11 @@ var Autocompleter = (function () {
           timeout: self.timeout
         });
       } else {
-        var hdfsAutocompleter = new HdfsAutocompleter({
+        self.autocompleter = new HdfsAutocompleter({
           user: options.user,
           snippet: options.snippet,
           timeout: options.timeout
         });
-        if (self.snippet.isSqlDialect()) {
-          self.autocompleter = new SqlAutocompleter({
-            hdfsAutocompleter: hdfsAutocompleter,
-            snippet: options.snippet,
-            oldEditor: options.oldEditor,
-            optEnabled: options.optEnabled,
-            timeout: self.timeout
-          })
-        } else {
-          self.autocompleter = hdfsAutocompleter;
-        }
       }
     };
     self.snippet.type.subscribe(function () {

+ 43 - 0
desktop/core/src/desktop/static/desktop/js/dataCatalog.js

@@ -104,6 +104,7 @@ var DataCatalog = (function () {
       definition: dataCatalogEntry.definition,
       sourceMeta: dataCatalogEntry.sourceMeta,
       analysis: dataCatalogEntry.analysis,
+      partitions: dataCatalogEntry.partitions,
       sample: dataCatalogEntry.sample,
       navigatorMeta: dataCatalogEntry.navigatorMeta,
       navOptMeta:  dataCatalogEntry.navOptMeta,
@@ -271,6 +272,7 @@ var DataCatalog = (function () {
     mergeAttribute('definition', CACHEABLE_TTL.default);
     mergeAttribute('sourceMeta', CACHEABLE_TTL.default, 'sourceMetaPromise');
     mergeAttribute('analysis', CACHEABLE_TTL.default, 'analysisPromise');
+    mergeAttribute('partitions', CACHEABLE_TTL.default, 'partitionsPromise');
     mergeAttribute('sample', CACHEABLE_TTL.default, 'samplePromise');
     mergeAttribute('navigatorMeta', CACHEABLE_TTL.default, 'navigatorMetaPromise');
     mergeAttribute('navOptMeta', CACHEABLE_TTL.optimizer, 'navOptMetaPromise');
@@ -398,6 +400,19 @@ var DataCatalog = (function () {
       fetchAndSave(apiOptions && apiOptions.refreshAnalysis ? 'refreshAnalysis' : 'fetchAnalysis', 'analysis', dataCatalogEntry, apiOptions));
   };
 
+  /**
+   * Helper function to reload the partitions for the given entry
+   *
+   * @param {DataCatalogEntry} dataCatalogEntry
+   * @param {Object} [apiOptions]
+   * @param {boolean} [apiOptions.silenceErrors]
+   *
+   * @return {CancellablePromise}
+   */
+  var reloadPartitions = function (dataCatalogEntry, apiOptions) {
+    return dataCatalogEntry.trackedPromise('partitionsPromise', fetchAndSave('fetchPartitions', 'partitions', dataCatalogEntry, apiOptions));
+  };
+
   /**
    * Helper function to reload the sample for the given entry
    *
@@ -462,6 +477,9 @@ var DataCatalog = (function () {
     self.analysis = undefined;
     self.analysisPromise = undefined;
 
+    self.partitions = undefined;
+    self.partitionsPromise = undefined;
+
     self.samplePromise = undefined;
     self.sample = undefined;
 
@@ -1340,6 +1358,31 @@ var DataCatalog = (function () {
     return applyCancellable(self.analysisPromise || reloadAnalysis(self, options), options);
   };
 
+  /**
+   * Gets the partitions for the entry. It will fetch it if not cached or if the refresh option is set.
+   *
+   * @param {Object} [options]
+   * @param {boolean} [options.silenceErrors]
+   * @param {boolean} [options.cachedOnly]
+   * @param {boolean} [options.refreshCache] - Clears the browser cache
+   * @param {boolean} [options.cancellable] - Default false
+   *
+   * @return {CancellablePromise}
+   */
+  DataCatalogEntry.prototype.getPartitions = function (options) {
+    var self = this;
+    if (!self.isTableOrView()) {
+      return $.Deferred().reject(false).promise();
+    }
+    if (options && options.cachedOnly) {
+      return applyCancellable(self.partitionsPromise, options) || $.Deferred().reject(false).promise();
+    }
+    if (options && options.refreshCache) {
+      return applyCancellable(reloadPartitions(self, options), options);
+    }
+    return applyCancellable(self.partitionsPromise || reloadPartitions(self, options), options);
+  };
+
   /**
    * Gets the Navigator metadata for the entry. It will fetch it if not cached or if the refresh option is set.
    *

+ 9 - 18
desktop/core/src/desktop/static/desktop/js/jquery.hiveautocomplete.js

@@ -237,33 +237,24 @@
 
     self.getDatabases = function (callback) {
       var self = this;
-      self.apiHelper.loadDatabases({
-        sourceType: self.options.apiHelperType,
-        successCallback: callback,
-        silenceErrors: false
+      DataCatalog.getChildren({ sourceType: self.options.apiHelperType, path: [] }).done(function (dbEntries) {
+        callback($.map(dbEntries, function (entry) { return entry.name }));
       });
-    }
+    };
 
     self.getTables = function (database, callback) {
       var self = this;
-      self.apiHelper.fetchTables({
-        sourceType: self.options.apiHelperType,
-        databaseName: database,
-        successCallback: callback,
-        silenceErrors: false
+      DataCatalog.getEntry({ sourceType: self.options.apiHelperType, path: [database] }).done(function (entry) {
+        entry.getSourceMeta().done(callback)
       });
-    }
+    };
 
     self.getColumns = function (database, table, callback) {
       var self = this;
-      self.apiHelper.fetchTableDetails({
-        sourceType: self.options.apiHelperType,
-        databaseName: database,
-        tableName: table,
-        successCallback: callback,
-        silenceErrors: false
+      DataCatalog.getEntry({ sourceType: self.options.apiHelperType, path: [database, table] }).done(function (entry) {
+        entry.getSourceMeta().done(callback)
       });
-    }
+    };
 
     function autocompleteLogic(autocompleteUrl, data) {
       if (data.error == null) {

+ 10 - 17
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -4097,13 +4097,8 @@
             token.notFound = true;
 
             if (token.parseLocation && token.parseLocation.type === 'table') {
-              ApiHelper.getInstance().identifierChainToPath({
-                identifierChain: token.parseLocation.identifierChain,
-                sourceType: self.snippet.type(),
-                defaultDatabase: self.snippet.database()
-              }).done(function (path) {
-                token.qualifiedIdentifier = path.join('.');
-              })
+              var path = $.map(token.parseLocation.identifierChain, function (identifier) { return identifier.name; });
+              token.qualifiedIdentifier = path.join('.');
             }
 
             if (token.parseLocation && weightedExpected.length > 0) {
@@ -4647,14 +4642,13 @@
                       if (tableChain.length > 0 && lastIdentifier && lastIdentifier.name) {
                         var colName = lastIdentifier.name.toLowerCase();
                         // Note, as cachedOnly is set to true it will call the successCallback right away (or not at all)
-                        ApiHelper.getInstance().fetchAutocomplete({
+                        DataCatalog.getEntry({
                           sourceType: snippet.type(),
-                          defaultDatabase: snippet.database(),
-                          identifierChain: tableChain,
-                          cachedOnly: true,
-                          successCallback: function (details) {
-                            if (details && details.extended_columns) {
-                              details.extended_columns.every(function (col) {
+                          path: $.map(tableChain, function (identifier) { return identifier.name })
+                        }).done(function (entry) {
+                          entry.getSourceMeta({ cachedOnly: true, silenceErrors: true }).done(function (sourceMeta) {
+                            if (sourceMeta && sourceMeta.extended_columns) {
+                              sourceMeta.extended_columns.every(function (col) {
                                 if (col.name.toLowerCase() === colName) {
                                   colType = col.type.match(/^[^<]*/g)[0];
                                   return false;
@@ -4662,9 +4656,8 @@
                                 return true;
                               })
                             }
-                          },
-                          silenceErrors: true
-                        })
+                          });
+                        });
                       }
                     }
                     if (token.parseLocation.identifierChain) {

+ 0 - 907
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter.js

@@ -1,907 +0,0 @@
-// 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.
-
-var SqlAutocompleter = (function () {
-
-  var SQL_TERMS = /\b(FROM|TABLE|STATS|REFRESH|METADATA|DESCRIBE|ORDER BY|JOIN|ON|WHERE|SELECT|LIMIT|GROUP BY|SORT|USE|LOCATION|INPATH)\b/g;
-
-  /**
-   * @param {Object} options
-   * @param {Snippet} options.snippet
-   * @param {Number} options.timeout
-   * @param {HdfsAutocompleter} options.hdfsAutocompleter
-   * @constructor
-   */
-  function SqlAutocompleter(options) {
-    var self = this;
-    self.timeout = options.timeout;
-    self.snippet = options.snippet;
-    self.hdfsAutocompleter = options.hdfsAutocompleter;
-    self.oldEditor = options.oldEditor || false;
-    self.optEnabled = options.optEnabled || false;
-
-    self.topTablesPerDb = {};
-
-    // Speed up by caching the databases
-    var initDatabases = function () {
-      self.snippet.getApiHelper().loadDatabases({
-        sourceType: self.snippet.type(),
-        silenceErrors: true,
-        successCallback: function () {
-          if (self.optEnabled) {
-            $.each(self.snippet.getApiHelper().lastKnownDatabases[self.snippet.type()], function (idx, db) {
-              if (db === 'default') {
-                $.post('/metadata/api/optimizer_api/top_tables', {
-                  database: db
-                }, function(data){
-                  if (! self.topTablesPerDb[db]) {
-                    self.topTablesPerDb[db] = {};
-                  }
-                  data.top_tables.forEach(function (table) {
-                    self.topTablesPerDb[db][table.name] = table;
-                  });
-                });
-              }
-            });
-          }
-        }
-      });
-    };
-    self.snippet.type.subscribe(function() {
-      if (self.snippet.isSqlDialect()) {
-        initDatabases();
-      }
-    });
-    initDatabases();
-  }
-
-  var fixScore = function (suggestions) {
-    $.each(suggestions, function (idx, value) {
-      value.score = 1000 - idx;
-    });
-    return suggestions;
-  };
-
-  SqlAutocompleter.prototype.getFromReferenceIndex = function (statement) {
-    var self = this;
-    var result = {
-      tables: {},
-      complex: {}
-    };
-    var fromMatch = statement.match(/\s*from\s*([^;]*).*$/i);
-    if (fromMatch) {
-      var refsRaw = fromMatch[1];
-      var upToMatch = refsRaw.match(/\s+(LATERAL|VIEW|EXPLODE|POSEXPLODE|ON|LIMIT|WHERE|GROUP BY|SORT|ORDER BY)\s+/i);
-      if (upToMatch) {
-        refsRaw = $.trim(refsRaw.substring(0, upToMatch.index));
-      }
-      var refs = $.map(refsRaw.split(/\s*(?:,|\bJOIN\b)\s*/i), function (ref) {
-        if (ref.indexOf('.') > 0) {
-          var refParts = ref.split('.');
-
-          if(self.snippet.getApiHelper().lastKnownDatabases[self.snippet.type()].indexOf(refParts[0]) > -1) {
-            return {
-              database: refParts.shift(),
-              table: refParts.join('.')
-            }
-          }
-        }
-        return {
-          database: null,
-          table: ref
-        }
-      });
-
-      refs.sort(function (a, b) {
-        return a.table.localeCompare(b.table);
-      });
-      $.each(refs, function(index, tableRefRaw) {
-        if (tableRefRaw.table.indexOf('(') == -1) {
-          var refMatch = tableRefRaw.table.match(/\s*(\S+)\s*(\S+)?\s*/);
-
-          var refParts = refMatch[1].split('.');
-          if (refMatch[2]) {
-            if (refParts.length == 1) {
-              result.tables[refMatch[2]] = {
-                table: refParts[0],
-                database: tableRefRaw.database
-              };
-            } else {
-              result.complex[refMatch[2]] = refParts;
-            }
-          } else {
-            result.tables[refMatch[1]] = {
-              table: refMatch[1],
-              database: tableRefRaw.database
-            };
-          }
-        }
-      })
-    }
-    return result;
-  };
-
-  SqlAutocompleter.prototype.getViewReferenceIndex = function (statement, hiveSyntax) {
-    var result = {
-      allViewReferences: [],
-      index: {}
-    };
-
-    // For now we only support LATERAL VIEW references for Hive
-    if (! hiveSyntax) {
-      return result;
-    }
-
-    // Matches both arrays and maps "AS ref" or "AS (keyRef, valueRef)" and with
-    // or without view reference.
-    // group 1 = pos for posexplode or undefined
-    // group 2 = argument to table generating function
-    // group 3 = view reference or undefined
-    // group 4 = array item reference
-    //           array index reference (if posexplode)
-    //           map key reference (if group 5 is exists)
-    // group 5 = array value (if posexplode)
-    //           map value reference (if ! posexplode)
-    var lateralViewRegex = /LATERAL\s+VIEW\s+(pos)?explode\(([^\)]+)\)\s+(?:(\S+)\s+)?AS\s+\(?([^\s,\)]*)(?:\s*,\s*([^\s,]*)\))?/gi;
-    var lateralViewMatch;
-
-    while (lateralViewMatch = lateralViewRegex.exec(statement)) {
-      var isMapRef = (!lateralViewMatch[1] && lateralViewMatch[5]) || false ;
-      var isPosexplode = lateralViewMatch[1] || false;
-
-      var pathToField = lateralViewMatch[2].split(".");
-
-      var viewRef = {
-        leadingPath: pathToField,
-        references: []
-      };
-
-      if (isMapRef) {
-        // TODO : use lateralViewMatch[4] for key ref once API supports map key lookup
-        result.index[lateralViewMatch[5]] = {
-          leadingPath: pathToField,
-          addition: 'value'
-        };
-        viewRef.references.push({ name: lateralViewMatch[4], type: 'key'});
-        viewRef.references.push({ name: lateralViewMatch[5], type: 'value'});
-      } else if (isPosexplode) {
-        // TODO : use lateralViewMatch[4] for array index ref once API supports array index lookup
-        // Currently we don't support array key refs
-        result.index[lateralViewMatch[5]] = {
-          leadingPath: pathToField,
-          addition: 'item'
-        };
-        viewRef.references.push({ name: lateralViewMatch[4], type: 'index'});
-        viewRef.references.push({ name: lateralViewMatch[5], type: 'item'});
-      } else {
-        // exploded array without position
-        result.index[lateralViewMatch[4]] = {
-          leadingPath: pathToField,
-          addition: 'item'
-        };
-        viewRef.references.push({ name: lateralViewMatch[4], type: 'item'});
-      }
-
-      result.allViewReferences = result.allViewReferences.concat(viewRef.references);
-
-      if (lateralViewMatch[3]) {
-        result.allViewReferences.push({ name: lateralViewMatch[3], type: 'view' });
-        result.index[lateralViewMatch[3]] = viewRef;
-      }
-    }
-
-    // Expand any references in paths of references
-    var foundRef = false;
-    // Limit iterations to 10
-    for (var i = 0; i < 10 && (i == 0 || foundRef); i++) {
-      $.each(result.index, function(alias, value) {
-        var newLeadingPath = [];
-        $.each(value.leadingPath, function(index, path) {
-          if (result.index[path]) {
-            foundRef = true;
-            newLeadingPath = newLeadingPath.concat(result.index[path].leadingPath);
-            newLeadingPath.push(result.index[path].addition);
-          } else {
-            newLeadingPath.push(path);
-          }
-        });
-        value.leadingPath = newLeadingPath;
-      });
-    }
-
-    return result;
-  };
-
-  SqlAutocompleter.prototype.getValueReferences = function (conditionMatch, database, fromReferences, tableAndComplexRefs, callback, editor) {
-    var self = this;
-    var fields = conditionMatch[1].split(".");
-    var tableName = null;
-    if (fields[0] in fromReferences.complex) {
-      var complexRef = fields.shift();
-      fields = fromReferences.complex[complexRef].concat(fields);
-    }
-    if (fields[0] in fromReferences.tables) {
-      var tableRef = fromReferences.tables[fields.shift()];
-      tableName = tableRef.table;
-      if (tableRef.database !== null) {
-        database = tableRef.database;
-      }
-    }
-    if (! tableName && tableAndComplexRefs.length === 1) {
-      tableName = tableAndComplexRefs[0].value;
-    }
-    if (tableName) {
-      var completeFields = [];
-      // For impala we need to check each part with the API, it could be a map or array in which case we need to add
-      // either "value" or "item" in between.
-      var fetchImpalaFields = function (remainingParts, topValues) {
-        completeFields.push(remainingParts.shift());
-        if (remainingParts.length > 0 && remainingParts[0] == "value" || remainingParts[0] == "key") {
-          fetchImpalaFields(remainingParts, topValues);
-        } else {
-          self.snippet.getApiHelper().fetchFields({
-            sourceType: self.snippet.type(),
-            databaseName: database,
-            tableName: tableName,
-            fields: completeFields,
-            editor: editor,
-            timeout: self.timeout,
-            successCallback: function (data) {
-              if (data.type === "map") {
-                completeFields.push("value");
-                fetchImpalaFields(remainingParts, topValues);
-              } else if (data.type === "array") {
-                completeFields.push("item");
-                fetchImpalaFields(remainingParts, topValues);
-              } else if (remainingParts.length == 0 && data.sample) {
-                var isString = data.type === "string";
-                var values = $.map(data.sample.sort(), function(value, index) {
-                  return {
-                    meta: "value",
-                    score: 900 - index,
-                    value: isString ? "'" + value + "'" : new String(value)
-                  }
-                });
-                callback(fixScore(topValues.concat(tableAndComplexRefs).concat(values)));
-              } else {
-                callback(fixScore(topValues.concat(tableAndComplexRefs)));
-              }
-            },
-            silenceErrors: true,
-            errorCallback: function () {
-              callback(fixScore(topValues.concat(tableAndComplexRefs)));
-            }
-          });
-        }
-      };
-
-      if (fields.length === 1 && !self.optEnabled) {
-        self.snippet.getApiHelper().fetchTableSample({
-          sourceType: self.snippet.type(),
-          databaseName: database,
-          tableName: tableName,
-          columnName: fields.length === 1 ? fields[0] : null,
-          editor: editor,
-          timeout: self.timeout,
-          silenceErrors: true,
-          successCallback: function (data) {
-            if (data.status === 0 && data.headers.length === 1) {
-              var values = $.map(data.rows, function (row, index) {
-                return {
-                  meta: 'value',
-                  score: 1000 - index,
-                  value: typeof row[0] === 'string' ? "'" + row[0] + "'" :  '' + row[0]
-                }
-              });
-              if (self.snippet.type() === 'impala') {
-                fetchImpalaFields(fields, values);
-              } else {
-                callback(values);
-              }
-            } else {
-              if (self.snippet.type() === 'impala') {
-                fetchImpalaFields(fields, []);
-              }
-            }
-          },
-          errorCallback: function () {
-            if (self.snippet.type() === 'impala') {
-              fetchImpalaFields(fields, []);
-            } else {
-              callback([]);
-            }
-          }
-        });
-      } else if (fields.length === 1 && self.optEnabled) {
-        $.post('/metadata/api/optimizer_api/popular_values', {
-          database: database,
-          tableName: tableName,
-          columnName: fields[0]
-        }).done(function(data){
-          if (data.status === 0) {
-            var foundCol = data.values.filter(function (col) {
-              return col.columnName === fields[0];
-            });
-            var topValues = [];
-            if (foundCol.length === 1) {
-              topValues = $.map(foundCol[0].values, function (value, index) {
-                return {
-                  meta: "popular",
-                  score: 1000 - index,
-                  value: value,
-                  caption: value.length > 28 ? value.substring(0, 25) + '...' : null
-                };
-              })
-            }
-            if (self.snippet.type() === "impala") {
-              fetchImpalaFields(fields, topValues);
-            } else {
-              callback(fixScore(topValues.concat(tableAndComplexRefs)));
-            }
-          }
-        }).fail(function () {
-          if (self.snippet.type() === "impala") {
-            fetchImpalaFields(fields, []);
-          }
-        });
-      } else if (self.snippet.type() === "impala") {
-        fetchImpalaFields(fields, []);
-      }
-    }
-  };
-
-  SqlAutocompleter.prototype.extractFields = function (data, tableName, database, valuePrefix, includeStar, extraSuggestions, excludeDatabases) {
-    var self = this;
-    var fields = [];
-    var result = [];
-    var prependedFields = extraSuggestions || [];
-
-    if (data.type == "struct") {
-      fields = $.map(data.fields, function(field) {
-        return {
-          name: field.name,
-          type: field.type
-        };
-      });
-    } else if (typeof data.columns != "undefined") {
-      fields = $.map(data.columns, function(column) {
-        return {
-          name: column,
-          type: "column"
-        }
-      });
-      if (includeStar) {
-        result.push({value: '*', score: 10000, meta: "column"});
-      }
-    } else if (typeof data.tables_meta != "undefined") {
-      fields = $.map(data.tables_meta, function(tableMeta) {
-        return {
-          name: tableMeta.name,
-          type: "table"
-        }
-      });
-      if (! excludeDatabases && ! self.oldEditor) {
-        // No FROM prefix
-        prependedFields = prependedFields.concat(fields);
-        fields = $.map(self.snippet.getApiHelper().lastKnownDatabases[self.snippet.type()], function(database) {
-          return {
-            name: database + ".",
-            type: "database"
-          }
-        })
-      }
-    }
-
-    fields.sort(function (a, b) {
-      return a.name.localeCompare(b.name);
-    });
-
-    if (prependedFields) {
-      prependedFields.sort(function (a, b) {
-        return a.name.localeCompare(b.name);
-      });
-      fields = prependedFields.concat(fields);
-    }
-
-    fields.forEach(function(field, idx) {
-      if (field.name != "") {
-        result.push({
-          value: typeof valuePrefix != "undefined" ? valuePrefix + field.name : field.name,
-          score: 1000 - idx,
-          meta: field.type,
-          database: database,
-          tableName: tableName
-        });
-      }
-    });
-    return result;
-  };
-
-  SqlAutocompleter.prototype.autocomplete = function(beforeCursor, upToNextStatement, realCallback, editor) {
-    var self = this;
-    var callback = function (values) {
-      if (! self.optEnabled) {
-        realCallback(values);
-        return;
-      }
-      if (values.length > 0) {
-        var foundTables = {};
-        values.forEach(function (value) {
-          if (value.meta === 'column' && value.tableName) {
-            if (! foundTables[value.tableName]) {
-              foundTables[value.tableName] = [];
-            }
-            foundTables[value.tableName].push(value)
-          }
-        });
-        if (Object.keys(foundTables).length === 1) {
-          $.post('/metadata/api/optimizer_api/popular_values', {
-            database: database,
-            tableName: tableName
-          }).done(function (data) {
-            var valueByColumn = {};
-            data.values.forEach(function (colValue) {
-              valueByColumn[colValue.columnName] = colValue.values;
-            });
-            foundTables[Object.keys(foundTables)[0]].forEach(function (colSuggestion) {
-              if (valueByColumn[colSuggestion.value]) {
-                colSuggestion.popularValues = valueByColumn[colSuggestion.value];
-              }
-            });
-            realCallback(values);
-          }).fail(function () {
-            realCallback(values);
-          });
-        } else {
-          realCallback(values);
-        }
-      } else {
-        realCallback([]);
-      }
-    };
-
-    var onFailure = function() {
-      callback([]);
-    };
-
-    var allStatements = beforeCursor.split(';');
-
-
-    var hiveSyntax = self.snippet.type() === "hive";
-    var impalaSyntax = self.snippet.type() === "impala";
-
-    var beforeCursorU = allStatements.pop().toUpperCase();
-    var afterCursorU = upToNextStatement.toUpperCase();
-
-    var beforeMatcher = beforeCursorU.match(SQL_TERMS);
-    var afterMatcher = afterCursorU.match(SQL_TERMS);
-
-    if (beforeMatcher == null || beforeMatcher.length == 0) {
-      callback([]);
-      return;
-    }
-
-    var database = self.snippet.database();
-    for (var i = allStatements.length - 1; i >= 0; i--) {
-      var useMatch = allStatements[i].match(/\s*use\s+([^\s;]+)\s*;?/i);
-      if (useMatch) {
-        database = useMatch[1];
-        break;
-      }
-    }
-    if (! database) {
-      onFailure();
-      return;
-    }
-
-
-    var keywordBeforeCursor = beforeMatcher[beforeMatcher.length - 1];
-
-    var impalaFieldRef = impalaSyntax && beforeCursor.slice(-1) === '.';
-
-    if (keywordBeforeCursor === "USE") {
-      var databases = self.snippet.getApiHelper().lastKnownDatabases[self.snippet.type()];
-      databases.sort();
-      callback($.map(databases, function(db, idx) {
-        return {
-          value: db,
-          score: 1000 - idx,
-          meta: 'database'
-        };
-      }));
-      return;
-    }
-
-    if (keywordBeforeCursor === "LOCATION" || keywordBeforeCursor === "INPATH") {
-      var pathMatch = beforeCursor.match(/.*(?:inpath|location)\s+('[^']*)$/i);
-      if (pathMatch) {
-        var existingPath = pathMatch[1].length == 1 ? pathMatch[1] + "/" : pathMatch[1];
-        self.hdfsAutocompleter.autocomplete(existingPath, "", function (hdfsSuggestions) {
-          var addLeadingSlash = pathMatch[1].length == 1;
-          var addTrailingSlash = afterCursorU.length == 0 || afterCursorU[0] == "'";
-          var addTrailingApostrophe = afterCursorU.length == 0;
-          $.each(hdfsSuggestions, function (idx, hdfsSuggestion) {
-            if (addLeadingSlash) {
-              hdfsSuggestion.value = "/" + hdfsSuggestion.value;
-            }
-            if (addTrailingSlash && hdfsSuggestion.meta === "dir") {
-              hdfsSuggestion.value += "/";
-            }
-            if (addTrailingApostrophe && hdfsSuggestion.meta === "file") {
-              hdfsSuggestion.value += "'";
-            }
-          });
-          callback(hdfsSuggestions);
-        });
-      } else {
-        onFailure();
-      }
-      return;
-    }
-
-    var tableNameAutoComplete = (keywordBeforeCursor === "FROM" ||
-      keywordBeforeCursor === "TABLE" ||
-      keywordBeforeCursor === "STATS" ||
-      keywordBeforeCursor === "JOIN" ||
-      keywordBeforeCursor === "REFRESH" ||
-      keywordBeforeCursor === "METADATA" ||
-      keywordBeforeCursor === "DESCRIBE") && !impalaFieldRef;
-
-    var selectBefore = keywordBeforeCursor === "SELECT";
-
-    var fieldTermBefore = keywordBeforeCursor === "WHERE" ||
-      keywordBeforeCursor === "ON" ||
-      keywordBeforeCursor === "GROUP BY" ||
-      keywordBeforeCursor === "ORDER BY";
-
-    var fromAfter = afterMatcher != null && afterMatcher[0] === "FROM";
-
-    if (tableNameAutoComplete || (selectBefore && !fromAfter)) {
-      var dbRefMatch = beforeCursor.match(/.*from\s+([^\.\s]+).$/i);
-      var partialMatch = beforeCursor.match(/.*from\s+([\S]+)$/i);
-      var partialTableOrDb = null;
-      if (dbRefMatch && self.snippet.getApiHelper().lastKnownDatabases[self.snippet.type()].indexOf(dbRefMatch[1]) > -1) {
-        database = dbRefMatch[1];
-      } else if (dbRefMatch && partialMatch) {
-        partialTableOrDb = partialMatch[1].toLowerCase();
-        database = self.snippet.database();
-      }
-
-      self.snippet.getApiHelper().fetchTables({
-        sourceType: self.snippet.type(),
-        databaseName: database,
-        successCallback: function (data) {
-          var fromKeyword = "";
-          if (selectBefore) {
-            if (beforeCursor.indexOf("SELECT") > -1) {
-              fromKeyword = "FROM";
-            } else {
-              fromKeyword = "from";
-            }
-            if (beforeCursor.match(/select\s*$/i)) {
-              fromKeyword = "? " + fromKeyword;
-            }
-            if (!beforeCursor.match(/(\s+|f|fr|fro|from)$/)) {
-              fromKeyword = " " + fromKeyword;
-            }
-            fromKeyword += " ";
-          }
-          var result = self.extractFields(data, null, database, fromKeyword, false, [], dbRefMatch !== null && partialTableOrDb === null);
-          if (partialTableOrDb !== null) {
-            callback($.grep(result, function (suggestion) {
-              return suggestion.value.indexOf(partialTableOrDb) === 0;
-            }))
-          } else {
-            callback(result);
-          }
-        },
-        silenceErrors: true,
-        errorCallback: onFailure,
-        editor: editor,
-        timeout: self.timeout
-      });
-      return;
-    } else if ((selectBefore && fromAfter) || fieldTermBefore || impalaFieldRef) {
-      var partialTermsMatch = beforeCursor.match(/([^\s\(\-\+\<\>\,]*)$/);
-      var parts = partialTermsMatch ? partialTermsMatch[0].split(".") : [];
-
-      // Drop the last part, empty or not. If it's not empty it's the start of a
-      // field (or a complete one) for that case we suggest the same.
-      // SELECT tablename.colu => suggestion: "column"
-      parts.pop();
-
-      var fromReferences = self.getFromReferenceIndex(beforeCursor + upToNextStatement);
-      var viewReferences = self.getViewReferenceIndex(beforeCursor + upToNextStatement, hiveSyntax);
-      var conditionMatch = beforeCursor.match(/(\S+)\s*=\s*([^\s;]+)?$/);
-
-      var tableName = "";
-
-      if (parts.length > 0 && fromReferences.tables[parts[0]]) {
-        // SELECT tableref.column.
-        var tableRef = fromReferences.tables[parts.shift()];
-        tableName = tableRef.table;
-        if (tableRef.database !== null) {
-          database = tableRef.database;
-        }
-      } else if (parts.length > 0 && fromReferences.complex[parts[0]]) {
-        var complexRefList = fromReferences.complex[parts.shift()];
-        if (fromReferences.tables[complexRefList[0]]) {
-          tableName = fromReferences.tables[complexRefList[0]].table;
-          if (fromReferences.tables[complexRefList[0]].database !== null) {
-            database = fromReferences.tables[complexRefList[0]].database;
-          }
-          // The first part is a table ref, the rest are col, struct etc.
-          parts = complexRefList.slice(1).concat(parts);
-        } else {
-          onFailure();
-          return;
-        }
-      } else if (parts.length === 0 && (Object.keys(fromReferences.tables).length + Object.keys(fromReferences.complex).length) > 1) {
-        // There are multiple table or complex type references possible so we suggest those
-        var count = 0;
-        var tableRefs = $.map(Object.keys(fromReferences.tables), function (key, idx) {
-          return {
-            value: key + (upToNextStatement.indexOf(".") == 0 ? "" : "."),
-            score: 1000 - count++,
-            meta: fromReferences.tables[key].table == key ? 'table' : 'alias'
-          };
-        });
-
-        var complexRefs = $.map(Object.keys(fromReferences.complex), function (key, idx) {
-          return {
-            value: key + (upToNextStatement.indexOf(".") == 0 ? "" : "."),
-            score: 1000 - count++,
-            meta: 'alias'
-          };
-        });
-
-        if (conditionMatch) {
-          self.getValueReferences(conditionMatch, database, fromReferences, tableRefs.concat(complexRefs), callback, editor);
-        } else {
-          callback(tableRefs.concat(complexRefs));
-        }
-        return;
-      } else if (Object.keys(fromReferences.tables).length == 1) {
-        // SELECT column. or just SELECT
-        // We use first and only table reference if exist
-        // if there are no parts the call to getFields will fetch the columns
-        var tableRef = fromReferences.tables[Object.keys(fromReferences.tables)[0]];
-        tableName = tableRef.table;
-        if (tableRef.database !== null) {
-          database = tableRef.database;
-        }
-        if (conditionMatch) {
-          var tableRefs = [{
-            value: tableName,
-            score: 1000,
-            meta: 'tables'
-          }];
-
-          self.getValueReferences(conditionMatch, database, fromReferences, tableRefs, callback);
-          return;
-        }
-      } else if (parts.length > 0 && viewReferences.index[parts[0]] && viewReferences.index[parts[0]].leadingPath.length > 0) {
-        var tableRef = fromReferences.tables[viewReferences.index[parts[0]].leadingPath[0]];
-        tableName = tableRef.table;
-        if (tableRef.database !== null) {
-          database = tableRef.database;
-        }
-      } else {
-        // Can't complete without table reference
-        onFailure();
-        return;
-      }
-
-      var getFields = function (database, remainingParts, fields) {
-        if (remainingParts.length == 0) {
-          self.snippet.getApiHelper().fetchFields({
-            sourceType: self.snippet.type(),
-            databaseName: database,
-            tableName: tableName,
-            fields: fields,
-            editor: editor,
-            timeout: self.timeout,
-            successCallback: function (data) {
-              var suggestions = [];
-              if (fields.length == 0) {
-                suggestions = self.extractFields(data, tableName, database, "", !fieldTermBefore && !impalaFieldRef, viewReferences.allViewReferences);
-              } else {
-                suggestions = self.extractFields(data, tableName, database, "", !fieldTermBefore);
-              }
-
-              var startedMatch = beforeCursorU.match(/.* (WHERE|AND|OR)\s+(\S+)$/);
-              if (self.optEnabled && keywordBeforeCursor === "WHERE" && startedMatch) {
-                $.post('/metadata/api/optimizer_api/popular_values', {
-                  database: database,
-                  tableName: tableName
-                }).done(function (data) {
-                  var popular = [];
-                  if (data.status === 0) {
-                    $.each(data.values, function (idx, colValue) {
-                      $.each(colValue.values, function (idx, value) {
-                        var suggestion = '';
-                        if (colValue.tableName !== tableName) {
-                          suggestion += colValue.tableName + ".";
-                        }
-                        suggestion += colValue.columnName + ' = ' + value;
-                        popular.push({
-                          meta: "popular",
-                          score: 1000,
-                          value: suggestion,
-                          caption: suggestion.length > 28 ? suggestion.substring(0, 25) + '...' : null
-                        });
-                      });
-                    });
-                  }
-                  callback(fixScore(popular.concat(suggestions)));
-                }).fail(function () {
-                  callback(suggestions);
-                });
-              } else {
-                callback(suggestions);
-              }
-            },
-            silenceErrors: true,
-            errorCallback: onFailure
-          });
-          return; // break recursion
-        }
-        var part = remainingParts.shift();
-
-        if (part != '' && part !== tableName) {
-          if (hiveSyntax) {
-            if (viewReferences.index[part]) {
-              if (viewReferences.index[part].references && remainingParts.length == 0) {
-                callback(self.extractFields([], tableName, database, "", true, viewReferences.index[part].references));
-                return;
-              }
-              if (fields.length == 0 && viewReferences.index[part].leadingPath.length > 0) {
-                // Drop first if table ref
-                if (fromReferences.tables[viewReferences.index[part].leadingPath[0]]) {
-                  fields = fields.concat(viewReferences.index[part].leadingPath.slice(1));
-                } else {
-                  fields = fields.concat(viewReferences.index[part].leadingPath);
-                }
-              }
-              if (viewReferences.index[part].addition) {
-                fields.push(viewReferences.index[part].addition);
-              }
-              getFields(database, remainingParts, fields);
-              return;
-            }
-            var mapOrArrayMatch = part.match(/([^\[]*)\[[^\]]*\]$/i);
-            if (mapOrArrayMatch !== null) {
-              fields.push(mapOrArrayMatch[1]);
-              self.snippet.getApiHelper().fetchFields({
-                sourceType: self.snippet.type(),
-                databaseName: database,
-                tableName: tableName,
-                fields: fields,
-                editor: editor,
-                timeout: self.timeout,
-                successCallback: function(data) {
-                  if (data.type === "map") {
-                    fields.push("value");
-                    getFields(database, remainingParts, fields);
-                  } else if (data.type === "array") {
-                    fields.push("item");
-                    getFields(database, remainingParts, fields);
-                  } else {
-                    onFailure();
-                  }
-                },
-                silenceErrors: true,
-                errorCallback: onFailure
-              });
-              return; // break recursion, it'll be async above
-            }
-          } else if (impalaSyntax) {
-            var isValueCompletion = part == "value" && fields.length > 0 && fields[fields.length - 1] == "value";
-            if (!isValueCompletion) {
-              fields.push(part);
-            }
-
-            var successCallback = function (data) {
-              if (data.type === "map") {
-                remainingParts.unshift("value");
-              } else if (data.type === "array") {
-                remainingParts.unshift("item");
-              } else if (remainingParts.length == 0 && fields.length > 0) {
-                var extraFields = [];
-                if (fields[fields.length - 1] == "value") {
-                  // impala map completion
-                  if (!fieldTermBefore) {
-                    extraFields.push({ name: "*", type: "all" });
-                  }
-                  if (!isValueCompletion) {
-                    if (fieldTermBefore || (data.type !== "map" && data.type !== "array" && data.type !== "struct")) {
-                      extraFields.push({ name: "value", type: "value" });
-                    }
-                    extraFields.push({ name: "key", type: "key" });
-                  }
-                } else if (fields[fields.length - 1] == "item") {
-                  if (!fieldTermBefore) {
-                    extraFields.push({ name: "*", type: "all" });
-                  }
-                  if (fieldTermBefore || (data.type !== "map" && data.type !== "array" && data.type !== "struct")) {
-                    extraFields.push({ name: "items", type: "items" });
-                  }
-                }
-                callback(self.extractFields(data, tableName, database, "", false, extraFields));
-                return;
-              }
-              getFields(database, remainingParts, fields);
-            };
-            // For impala we have to fetch info about each field as we don't know
-            // whether it's a map or array for hive the [ and ] gives it away...
-            self.snippet.getApiHelper().fetchFields({
-              sourceType: self.snippet.type(),
-              databaseName: database,
-              tableName: tableName,
-              fields: fields,
-              editor: editor,
-              timeout: self.timeout,
-              successCallback: successCallback,
-              silenceErrors: true,
-              errorCallback: onFailure
-            });
-            return; // break recursion, it'll be async above
-          }
-          fields.push(part);
-        }
-        getFields(database, remainingParts, fields);
-      };
-
-      getFields(database, parts, []);
-    } else {
-      onFailure();
-    }
-  };
-  
-  SqlAutocompleter.prototype.getDocTooltip = function (item) {
-    var self = this;
-    if (! self.optEnabled) {
-      return;
-    }
-    if (item.meta === 'table' && !item.docHTML) {
-      var table = item.value;
-      if (table.lastIndexOf(' ') > -1) {
-        table = table.substring(table.lastIndexOf(' ') + 1);
-      }
-      if (self.topTablesPerDb[item.database] && self.topTablesPerDb[item.database][table]) {
-        var optData = self.topTablesPerDb[item.database][table];
-        item.docHTML = '<table style="margin:10px;">' +
-            '<tr style="height: 20px;"><td style="width: 80px;">Table:</td><td style="width: 100px;">' + optData.name + '</td></tr>' +
-            '<tr style="height: 20px;"><td style="width: 80px;">Popularity:</td><td style="width: 100px;"><div class="progress" style="height: 10px; width: 100px;"><div class="bar" style="background-color: #0B7FAD; width: ' + optData.popularity + '%" ></div></div></td></tr>' +
-            '<tr style="height: 20px;"><td style="width: 80px;">Columns:</td><td style="width: 100px;">' + optData.column_count + '</td></tr>' +
-            '<tr style="height: 20px;"><td style="width: 80px;">Fact:</td><td style="width: 100px;">' + optData.is_fact + '</td></tr>' +
-            '</table>';
-
-      }
-    } else if (item.meta === 'column' && item.popularValues && !item.docHTML) {
-      item.docHTML = '<div style="width: 400px; height: 120px; overflow-y: auto;"><div style="margin:10px; font-size: 14px; margin-bottom: 8px;">Popular Values</div><table style="width: 380px; margin: 5px 10px 0 10px;" class="table"><tbody>';
-      item.popularValues.forEach(function (value) {
-        item.docHTML += '<tr><td><div style=" width: 360px; overflow-x: hidden; font-family: monospace; white-space: nowrap; text-overflow: ellipsis" title="' + value + '">' + value + '</div></td></tr>';
-      });
-      item.docHTML += '</tbody></table></div>';
-    }
-    if (item.value.length > 28 && !item.docHTML) {
-      item.docHTML = '<div style="margin:10px; width: 220px; overflow-y: auto;"><div style="font-size: 15px; margin-bottom: 6px;">Popular</div><div style="text-wrap: normal; white-space: pre-wrap; font-family: monospace;">' + item.value + '</div></div>';
-    }
-  };
-
-  return SqlAutocompleter;
-})();

+ 78 - 96
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter2.js

@@ -463,7 +463,8 @@ var SqlAutocompleter2 = (function () {
 
       $.when(topColumnsDeferral, suggestColumnsDeferral).then(function (topColumns, suggestions) {
         if (topColumns.length > 0) {
-          self.snippet.getApiHelper().getDatabases(self.snippet.type()).done(function (databases) {
+          DataCatalog.getChildren({ sourceType: self.snippet.type(), path: [], silenceErrors: true }).done(function (dbEntries) {
+            var databases = $.map(dbEntries, function (dbEntry) { return dbEntry.name });
             suggestions.forEach(function (suggestion) {
               var path = '';
               if (!suggestion.table.identifierChain[0].name || databases.indexOf(suggestion.table.identifierChain[0].name.toLowerCase() === -1)) {
@@ -481,7 +482,7 @@ var SqlAutocompleter2 = (function () {
               }
             });
             mergeNavOptColDeferral.resolve();
-          });
+          }).fail(mergeNavOptColDeferral.resolve);
         } else {
           mergeNavOptColDeferral.resolve();
         }
@@ -587,28 +588,27 @@ var SqlAutocompleter2 = (function () {
     };
 
     if (parseResult.suggestTables) {
-      if (self.snippet.type() == 'impala' && parseResult.suggestTables.identifierChain && parseResult.suggestTables.identifierChain.length === 1) {
+      if (self.snippet.type() === 'impala' && parseResult.suggestTables.identifierChain && parseResult.suggestTables.identifierChain.length === 1) {
         var checkDbDeferral = $.Deferred();
-        self.snippet.getApiHelper().loadDatabases({
+        DataCatalog.getChildren({
           sourceType: self.snippet.type(),
-          successCallback: function (data) {
-            var foundDb = data.filter(function (db) {
-              return db.toLowerCase() === parseResult.suggestTables.identifierChain[0].name.toLowerCase();
-            });
-            if (foundDb.length > 0) {
-              var tableDeferral = self.addTables(parseResult, database, completions);
-              deferrals.push(tableDeferral);
-              adjustWeightsForTopTables(database, tableDeferral);
-            } else {
-              parseResult.suggestColumns = { tables: [{ identifierChain: parseResult.suggestTables.identifierChain }] };
-              delete parseResult.suggestTables;
-              deferrals.push(self.addColumns(parseResult, parseResult.suggestColumns.tables[0], database, parseResult.suggestColumns.types || ['T'], columnSuggestions));
-            }
-            checkDbDeferral.resolve();
-          },
-          silenceErrors: true,
-          errorCallback: checkDbDeferral.resolve
-        });
+          path: [],
+          silenceErrors: true
+        }).done(function (dbEntries) {
+          var firstIsDb = dbEntries.some(function (db) {
+            return db.name.toLowerCase() === parseResult.suggestTables.identifierChain[0].name.toLowerCase();
+          });
+          if (firstIsDb) {
+            var tableDeferral = self.addTables(parseResult, database, completions);
+            deferrals.push(tableDeferral);
+            adjustWeightsForTopTables(database, tableDeferral);
+          } else {
+            parseResult.suggestColumns = { tables: [{ identifierChain: parseResult.suggestTables.identifierChain }] };
+            delete parseResult.suggestTables;
+            deferrals.push(self.addColumns(parseResult, parseResult.suggestColumns.tables[0], database, parseResult.suggestColumns.types || ['T'], columnSuggestions));
+          }
+          checkDbDeferral.resolve();
+        }).fail(checkDbDeferral.resolve);
         deferrals.push(checkDbDeferral);
       } else if (self.snippet.type() == 'impala' && parseResult.suggestTables.identifierChain && parseResult.suggestTables.identifierChain.length > 1) {
         parseResult.suggestColumns = { tables: [{ identifierChain: parseResult.suggestTables.identifierChain }] };
@@ -781,40 +781,33 @@ var SqlAutocompleter2 = (function () {
         identifierChain = identifierChain.slice(1);
       }
 
-      self.snippet.getApiHelper().fetchFields({
-        sourceType: self.snippet.type(),
-        databaseName: database,
-        tableName: table,
-        fields: fetchedFields,
-        timeout: self.timeout,
-        successCallback: function (data) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), path: [database, table].concat(fetchedFields) }).done(function (entry) {
+        entry.getSourceMeta({ silenceErrors: true }).done(function (sourceMeta) {
           if (self.snippet.type() === 'hive'
-              && typeof data.extended_columns !== 'undefined'
-              && data.extended_columns.length === 1
-              && data.extended_columns.length
-              && /^map|array|struct/i.test(data.extended_columns[0].type)) {
-            identifierChain.unshift({ name: data.extended_columns[0].name })
+            && typeof sourceMeta.extended_columns !== 'undefined'
+            && sourceMeta.extended_columns.length === 1
+            && sourceMeta.extended_columns.length
+            && /^map|array|struct/i.test(sourceMeta.extended_columns[0].type)) {
+            identifierChain.unshift({ name: sourceMeta.extended_columns[0].name })
           }
           if (identifierChain.length > 0) {
             if (typeof identifierChain[0].name !== 'undefined' && /value|item|key/i.test(identifierChain[0].name)) {
               fetchedFields.push(identifierChain[0].name);
               identifierChain.shift();
             } else {
-              if (data.type === 'array') {
+              if (sourceMeta.type === 'array') {
                 fetchedFields.push('item')
               }
-              if (data.type === 'map') {
+              if (sourceMeta.type === 'map') {
                 fetchedFields.push('value')
               }
             }
             fetchFieldsInternal(table, database, identifierChain, callback, errorCallback, fetchedFields)
           } else {
-            callback(data);
+            callback(sourceMeta);
           }
-        },
-        silenceErrors: true,
-        errorCallback: errorCallback
-      });
+        }).fail(errorCallback)
+      }).fail(errorCallback);
     };
 
     // For Impala the first parts of the identifier chain could be either database or table, either:
@@ -824,23 +817,22 @@ var SqlAutocompleter2 = (function () {
     // SELECT col.struct FROM db.tbl -or- SELECT col.struct FROM tbl
     if (self.snippet.type() === 'impala' || self.snippet.type() === 'hive') {
       if (identifierChain.length > 1) {
-        self.snippet.getApiHelper().loadDatabases({
+        DataCatalog.getChildren({
           sourceType: self.snippet.type(),
-          successCallback: function (data) {
-            try {
-              var foundDb = data.filter(function (db) {
-                return db.toLowerCase() === identifierChain[0].name.toLowerCase();
-              });
-              var databaseName = foundDb.length > 0 ? identifierChain.shift().name : defaultDatabase;
-              var tableName = identifierChain.shift().name;
-              fetchFieldsInternal(tableName, databaseName, identifierChain, callback, errorCallback, []);
-            } catch(e) {
-              callback([]);
-            } // TODO: Ignore for subqueries
-          },
-          silenceErrors: true,
-          errorCallback: errorCallback
-        });
+          path: [],
+          silenceErrors: true
+        }).done(function (dbEntries) {
+          try {
+            var firstIsDb = dbEntries.some(function (db) {
+              return db.name.toLowerCase() === identifierChain[0].name.toLowerCase();
+            });
+            var databaseName = firstIsDb ? identifierChain.shift().name : defaultDatabase;
+            var tableName = identifierChain.shift().name;
+            fetchFieldsInternal(tableName, databaseName, identifierChain, callback, errorCallback, []);
+          } catch(e) {
+            callback([]);
+          } // TODO: Ignore for subqueries
+        }).fail(errorCallback);
       } else {
         var databaseName = defaultDatabase;
         var tableName = identifierChain.shift().name;
@@ -863,32 +855,23 @@ var SqlAutocompleter2 = (function () {
         prefix += parseResult.lowerCase ? 'from ' : 'FROM ';
       }
 
-      self.snippet.getApiHelper().fetchTables({
-        sourceType: self.snippet.type(),
-        databaseName: databaseName,
-        successCallback: function (data) {
-          var tables = [];
-          data.tables_meta.forEach(function (tablesMeta) {
-            if (parseResult.suggestTables.onlyTables && tablesMeta.type.toLowerCase() !== 'table' ||
-                parseResult.suggestTables.onlyViews && tablesMeta.type.toLowerCase() !== 'view') {
-              return;
-            }
-            var table = {
-              value: prefix + self.backTickIfNeeded(tablesMeta.name),
-              meta: tablesMeta.type.toLowerCase(),
-              weight: DEFAULT_WEIGHTS.TABLE,
-              name: tablesMeta.name
-            };
-            completions.push(table);
-            tables.push(table);
-          });
-          tableDeferred.resolve(tables);
-        },
-        silenceErrors: true,
-        errorCallback: function () {
-          tableDeferred.resolve([]);
-        },
-        timeout: self.timeout
+      DataCatalog.getChildren({ sourceType: self.snippet.type(), path: [databaseName], silenceErrors: true }).done(function (tableEntries) {
+        var tables = [];
+        tableEntries.forEach(function (tableEntry) {
+          if (parseResult.suggestTables.onlyTables && tableEntry.isTable() ||
+            parseResult.suggestTables.onlyViews && tableEntry.isView()) {
+            return;
+          }
+          var table = {
+            value: prefix + self.backTickIfNeeded(tableEntry.name),
+            meta: tableEntry.getType(),
+            weight: DEFAULT_WEIGHTS.TABLE,
+            name: tableEntry.name
+          };
+          completions.push(table);
+          tables.push(table);
+        });
+        tableDeferred.resolve(tables);
       });
     };
 
@@ -1041,21 +1024,20 @@ var SqlAutocompleter2 = (function () {
     if (parseResult.suggestDatabases.prependFrom) {
       prefix += parseResult.lowerCase ? 'from ' : 'FROM ';
     }
-    self.snippet.getApiHelper().loadDatabases({
+    DataCatalog.getChildren({
       sourceType: self.snippet.type(),
-      successCallback: function (data) {
-        data.forEach(function (db) {
-          completions.push({
-            value: prefix + self.backTickIfNeeded(db) + (parseResult.suggestDatabases.appendDot ? '.' : ''),
-            meta: 'database',
-            weight: DEFAULT_WEIGHTS.DATABASE
-          });
+      path: [],
+      silenceErrors: true
+    }).done(function (dbEntries) {
+      dbEntries.forEach(function (db) {
+        completions.push({
+          value: prefix + self.backTickIfNeeded(db.name) + (parseResult.suggestDatabases.appendDot ? '.' : ''),
+          meta: 'database',
+          weight: DEFAULT_WEIGHTS.DATABASE
         });
-        databasesDeferred.resolve();
-      },
-      silenceErrors: true,
-      errorCallback: databasesDeferred.resolve
-    });
+      });
+      databasesDeferred.resolve();
+    }).fail(databasesDeferred.resolve);
     return databasesDeferred;
   };
 

+ 0 - 1616
desktop/core/src/desktop/static/desktop/spec/sqlAutocompleterSpec.js

@@ -1,1616 +0,0 @@
-// 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.
-(function () {
-  describe('sqlAutocompleter.js', function () {
-    var apiHelper = ApiHelper.getInstance({
-      i18n: {},
-      user: 'testUser'
-    });
-
-    var langTools = ace.require('ace/ext/language_tools');
-
-    beforeAll(function () {
-      jasmine.addMatchers(SqlTestUtils.autocompleteMatcher);
-      $.totalStorage = function (key, value) {
-        return null;
-      };
-    });
-
-    var getCompleter = function (options) {
-      langTools.textCompleter.setSqlMode(true);
-      var sqlAutocompleter = new SqlAutocompleter(options);
-      return {
-        autocomplete: function (before, after, callback) {
-          var textCompleterCallback = function (values) {
-            langTools.textCompleter.getCompletions(null, {
-              getValue: function () {
-                return before + after;
-              },
-              getTextRange: function () {
-                return before;
-              }
-            }, before.length, null, function (ignore, textCompletions) {
-              callback(textCompletions.concat(values))
-            });
-          };
-          return sqlAutocompleter.autocomplete(before, after, textCompleterCallback);
-        }
-      };
-    };
-
-    var createCallbackSpyForValues = function (values, includeLocal) {
-      return jasmine.createSpy('callback', function (value) {
-        if (!includeLocal) {
-          expect(value.filter(function (val) {
-            return val.meta !== 'local';
-          })).toEqualAutocompleteValues(values, includeLocal)
-        } else {
-          expect(value).toEqualAutocompleteValues(values, includeLocal)
-        }
-      }).and.callThrough();
-    };
-
-    var assertAutoComplete = function (testDefinition) {
-      var snippet = {
-        type: ko.observable(testDefinition.type ? testDefinition.type : 'genericSqlType'),
-        database: ko.observable('database_one'),
-        isSqlDialect: function () {
-          return true;
-        },
-        getContext: function () {
-          return ko.mapping.fromJS(null)
-        },
-        getApiHelper: function () {
-          return apiHelper
-        }
-      };
-
-      jasmine.Ajax.withMock(function() {
-        jasmine.Ajax.stubRequest(/.*\/notebook\/api\/autocomplete\//).andReturn({
-          status: 200,
-          statusText: 'HTTP/1.1 200 OK',
-          contentType: 'application/json;charset=UTF-8',
-          responseText: ko.mapping.toJSON({
-            status: 0,
-            databases: ['database_one', 'database_two']
-          })
-        });
-        testDefinition.serverResponses.forEach(function (responseDef) {
-          jasmine.Ajax.stubRequest(responseDef.url).andReturn({
-            status: 200,
-            statusText: 'HTTP/1.1 200 OK',
-            contentType: 'application/json;charset=UTF-8',
-            responseText: responseDef.response ? ko.mapping.toJSON(responseDef.response) : ''
-          });
-        });
-        var subject = getCompleter({
-          hdfsAutocompleter: {
-            autocomplete: function (before, after, callback) {
-              callback([
-                {
-                  meta: 'file',
-                  score: 1000,
-                  value: 'file_one'
-                },
-                {
-                  meta: 'dir',
-                  score: 999,
-                  value: 'folder_one'
-                }
-              ])
-            }
-          },
-          snippet: snippet,
-          optEnabled: false
-        });
-        var callback = createCallbackSpyForValues(testDefinition.expectedSuggestions, testDefinition.includeLocal);
-        subject.autocomplete(testDefinition.beforeCursor, testDefinition.afterCursor, callback);
-      });
-    };
-
-    it('should return empty suggestions for empty statement', function () {
-      assertAutoComplete({
-        type: 'hive',
-        serverResponses: [],
-        beforeCursor: '',
-        afterCursor: '',
-        expectedSuggestions: []
-      });
-    });
-
-    it('should return empty suggestions for bogus statement', function () {
-      assertAutoComplete({
-        type: 'hive',
-        serverResponses: [],
-        beforeCursor: 'foo',
-        afterCursor: 'bar',
-        expectedSuggestions: []
-      });
-    });
-
-    describe('database awareness', function () {
-      it('should suggest databases after use', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'USE ',
-          afterCursor: '',
-          expectedSuggestions: ['database_one', 'database_two']
-        });
-      });
-
-      it('should use a use statement before the cursor if present', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_two/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/asdf/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
-            }
-          }],
-          beforeCursor: 'USE database_two; \n\tSELECT ',
-          afterCursor: '',
-          expectedSuggestions: ['? FROM otherTable1', '? FROM otherTable2', '? FROM database_one.', '? FROM database_two.']
-        });
-      });
-
-      it('should use the last use statement before the cursor if multiple are present', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/closest_db/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
-            }
-          }],
-          beforeCursor: 'USE other_db; USE closest_db; \n\tSELECT ',
-          afterCursor: '',
-          expectedSuggestions: ['? FROM otherTable1', '? FROM otherTable2', '? FROM database_one.', '? FROM database_two.']
-        });
-      });
-
-      it('should use the use statement before the cursor if multiple are present after the cursor', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/closest_db/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
-            }
-          }],
-          beforeCursor: 'USE other_db; USE closest_db; \n\tSELECT ',
-          afterCursor: 'USE some_other_db;',
-          expectedSuggestions: ['? FROM otherTable1', '? FROM otherTable2', '? FROM database_one.', '? FROM database_two.']
-        });
-      });
-    });
-
-    describe('text completer', function () {
-      it('should ignore line comments for local suggestions', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          includeLocal: true,
-          beforeCursor: '-- line comment\nSELECT * from testTable1;\n',
-          afterCursor: '\n-- other line comment',
-          expectedSuggestions: ['SELECT', 'from', 'testTable1']
-        });
-      });
-
-      it('should ignore multi-line comments for local suggestions', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          includeLocal: true,
-          beforeCursor: '/* line 1\nline 2\n*/\nSELECT * from testTable1;\n',
-          afterCursor: '',
-          expectedSuggestions: ['SELECT', 'from', 'testTable1']
-        });
-      });
-    });
-
-    describe('table completion', function () {
-      it('should suggest table names with no columns', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'SELECT ',
-          afterCursor: '',
-          expectedSuggestions: ['? FROM testTable1', '? FROM testTable2', '? FROM database_one.', '? FROM database_two.']
-        });
-      });
-
-      it('should follow keyword case for table name completion', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'select ',
-          afterCursor: '',
-          expectedSuggestions: ['? from testTable1', '? from testTable2', '? from database_one.', '? from database_two.']
-        });
-      });
-
-      it('should suggest table names with *', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'SELECT *',
-          afterCursor: '',
-          expectedSuggestions: [' FROM testTable1', ' FROM testTable2', ' FROM database_one.', ' FROM database_two.']
-        });
-      });
-
-      it('should suggest table names with started FROM', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'SELECT * fr',
-          afterCursor: '',
-          expectedSuggestions: ['FROM testTable1', 'FROM testTable2', 'FROM database_one.', 'FROM database_two.']
-        });
-      });
-
-      it('should suggest table names after FROM', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM ',
-          afterCursor: '',
-          expectedSuggestions: ['testTable1', 'testTable2', 'database_one.', 'database_two.']
-        });
-      });
-
-      it('should suggest table names after FROM with started name', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM tes',
-          afterCursor: '',
-          expectedSuggestions: ['testTable1', 'testTable2']
-        });
-      });
-
-      it('should suggest database names after FROM with started name', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM dat',
-          afterCursor: '',
-          expectedSuggestions: ['database_one.', 'database_two.']
-        });
-      });
-
-      it('should suggest table names after FROM with database reference', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_two/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable3'}, {name: 'testTable4'}]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM database_two.',
-          afterCursor: '',
-          expectedSuggestions: ['testTable3', 'testTable4']
-        });
-      });
-
-      it('should suggest aliases', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM testTableA   tta, testTableB',
-          expectedSuggestions: ['tta.', 'testTableB.']
-        });
-      });
-
-      it('should suggest aliases in GROUP BY', function () {
-        assertAutoComplete({
-          type: 'hive',
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY ',
-          afterCursor: '',
-          expectedSuggestions: ['tta.', 'testTableB.']
-        });
-      });
-
-      it('should only suggest table aliases', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM testTableA tta, (SELECT SUM(A*B) total FROM tta.array) ttaSum, testTableB ttb',
-          expectedSuggestions: ['tta.', 'ttb.']
-        });
-      });
-
-      // TODO: Fix me...
-      xit('should suggest aliases from nested selects', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM testTableA tta, testTableB ttb, (SELECT SUM(A*B) total FROM tta.array) ttaSum',
-          expectedSuggestions: ['tta.', 'ttb.', 'ttaSum.']
-        });
-      });
-    });
-
-    describe('hive-specific stuff', function () {
-
-      describe('HDFS autocompletion', function () {
-        it('should autocomplete hdfs paths in location references without initial /', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [],
-            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'',
-            afterCursor: '\'',
-            expectedSuggestions: ['/file_one', '/folder_one/']
-          });
-        });
-
-        it('should autocomplete hdfs paths in location references from root', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [],
-            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'/',
-            afterCursor: '\'',
-            expectedSuggestions: ['file_one', 'folder_one/']
-          });
-        });
-
-        it('should autocomplete hdfs paths and suggest trailing apostrophe if empty after cursor', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [],
-            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'/',
-            afterCursor: '',
-            expectedSuggestions: ['file_one\'', 'folder_one/']
-          });
-        });
-
-        it('should autocomplete hdfs paths in location references from inside a path', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [],
-            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'/',
-            afterCursor: '/bar\'',
-            expectedSuggestions: ['file_one', 'folder_one']
-          });
-        });
-      });
-
-      it('should suggest struct from map values', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'string', name: 'fieldB'},
-                {
-                  type: 'struct', name: 'fieldC', 'fields': [
-                  {type: 'string', name: 'fieldC_A'},
-                  {type: 'boolean', name: 'fieldC_B'}
-                ]
-                }],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT testMap[\'anyKey\'].',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['fieldA', 'fieldB', 'fieldC']
-        });
-      });
-
-      it('should suggest struct from map values without a given key', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'string', name: 'fieldB'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT testMap[].',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['fieldA', 'fieldB']
-        });
-      });
-
-      it('should suggest struct from structs from map values', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value\/fieldC/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldC_A'},
-                {type: 'boolean', name: 'fieldC_B'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT testMap[\'anyKey\'].fieldC.',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['fieldC_A', 'fieldC_B']
-        });
-      });
-
-      it('should suggest struct from structs from arrays', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
-            response: {
-              status: 0,
-              type: 'array'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item\/fieldC/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldC_A'},
-                {type: 'boolean', name: 'fieldC_B'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT testArray[1].fieldC.',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['fieldC_A', 'fieldC_B']
-        });
-      });
-
-      it('should suggest structs from maps from arrays', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
-            response: {
-              status: 0,
-              type: 'array'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item\/testMap\/value/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'boolean', name: 'fieldB'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT testArray[1].testMap[\'key\'].',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['fieldA', 'fieldB']
-        });
-      });
-
-      describe('lateral views', function () {
-        it('should suggest structs from exploded item references to arrays', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT testItem.',
-            afterCursor: ' FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should suggest structs from multiple exploded item references to arrays', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArrayA\/item/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT testItemA.',
-            afterCursor: ' FROM testTable' +
-            ' LATERAL VIEW explode(testArrayA) explodedTableA AS testItemA' +
-            ' LATERAL VIEW explode(testArrayB) explodedTableB AS testItemB',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should support table references as arguments of explode function', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable2\/testArrayB\/item/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT\n testItemA,\n testItemB.',
-            afterCursor: '\n\tFROM\n\t testTable2 tt2\n' +
-            '\t LATERAL VIEW EXPLODE(tt2.testArrayA) explodedTableA AS testItemA\n' +
-            '\t LATERAL VIEW EXPLODE(tt2.testArrayB) explodedTableB AS testItemB',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should suggest structs from exploded item references to exploded item references to arrays ', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray1\/item\/testArray2\/item/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT ta2_exp.',
-            afterCursor: ' FROM ' +
-            '   testTable tt' +
-            ' LATERAL VIEW explode(tt.testArray1) ta1 AS ta1_exp\n' +
-            '   LATERAL VIEW explode(ta1_exp.testArray2)    ta2   AS  ta2_exp',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should suggest structs from references to exploded arrays', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT explodedTable.testItem.',
-            afterCursor: ' FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should suggest posexploded references to arrays', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT testValue.',
-            afterCursor: ' FROM testTable LATERAL VIEW posexplode(testArray) explodedTable AS (testIndex, testValue)',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should suggest exploded references to map values', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT testMapValue.',
-            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) AS (testMapKey, testMapValue)',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should suggest exploded references to map values from view references', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'string', name: 'fieldB'}
-                ],
-                type: 'struct'
-              }
-            }],
-            beforeCursor: 'SELECT explodedMap.testMapValue.',
-            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)',
-            expectedSuggestions: ['fieldA', 'fieldB']
-          });
-        });
-
-        it('should suggest references to exploded references from view reference', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [],
-            beforeCursor: 'SELECT explodedMap.',
-            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)',
-            expectedSuggestions: ['testMapKey', 'testMapValue']
-          });
-        });
-
-        it('should suggest references to exploded references', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-              response: {
-                status: 0,
-                columns: ['testTableColumn1', 'testTableColumn2']
-              }
-            }],
-            beforeCursor: 'SELECT ',
-            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)',
-            expectedSuggestions: ['*', 'explodedMap', 'testMapKey', 'testMapValue', 'testTableColumn1', 'testTableColumn2']
-          });
-        });
-      });
-    });
-
-    describe('impala-specific stuff', function () {
-      describe('HDFS autocompletion', function () {
-        it('should autocomplete hdfs paths in location references without initial /', function () {
-          assertAutoComplete({
-            type: 'impala',
-            serverResponses: [],
-            beforeCursor: 'LOAD DATA INPATH \'',
-            afterCursor: '\'',
-            expectedSuggestions: ['/file_one', '/folder_one/']
-          });
-        });
-
-        it('should autocomplete hdfs paths in location references from root', function () {
-          assertAutoComplete({
-            type: 'impala',
-            serverResponses: [],
-            beforeCursor: 'LOAD DATA INPATH \'/',
-            afterCursor: '\'',
-            expectedSuggestions: ['file_one', 'folder_one/']
-          });
-        });
-
-        it('should autocomplete hdfs paths and suggest trailing apostrophe if empty after cursor', function () {
-          assertAutoComplete({
-            type: 'impala',
-            serverResponses: [],
-            beforeCursor: 'LOAD DATA INPATH \'/',
-            afterCursor: '',
-            expectedSuggestions: ['file_one\'', 'folder_one/']
-          });
-        });
-
-        it('should autocomplete hdfs paths in location references from inside a path', function () {
-          assertAutoComplete({
-            type: 'impala',
-            serverResponses: [],
-            beforeCursor: 'LOAD DATA INPATH \'/',
-            afterCursor: '/bar\' INTO TABLE foo',
-            expectedSuggestions: ['file_one', 'folder_one']
-          });
-        });
-      });
-
-      it('should not suggest struct from map values with hive style syntax', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\['anyKey'\]/,
-            response: {
-              status: 0,
-              someResponse: true
-            }
-          }],
-          beforeCursor: 'SELECT testMap[\'anyKey\'].',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: []
-        });
-      });
-
-      it('should suggest fields from nested structs', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA/,
-            response: {
-              status: 0
-              // Impala has to query every part for it's type, for hive '[' and ']' is used to indicate map or array.
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA\/fieldC/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldC_A'},
-                {type: 'boolean', name: 'fieldC_B'}
-              ],
-              type: 'struct',
-              name: 'fieldC'
-            }
-          }],
-          beforeCursor: 'SELECT columnA.fieldC.',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['fieldC_A', 'fieldC_B']
-        });
-      });
-
-      it('should suggest fields from map values of type structs', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'string', name: 'fieldB'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT tm.',
-          afterCursor: ' FROM testTable t, t.testMap tm;',
-          expectedSuggestions: ['*', 'key', 'fieldA', 'fieldB']
-        });
-      });
-
-      it('should suggest map value if type is scalar', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-            response: {
-              status: 0,
-              type: 'int'
-            }
-          }],
-          beforeCursor: 'SELECT tm.',
-          afterCursor: ' FROM testTable t, t.testMap tm;',
-          expectedSuggestions: ['*', 'key', 'value']
-        });
-      });
-
-      it('should not suggest items from arrays if complex in select clause', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
-            response: {
-              status: 0,
-              type: 'array'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'string', name: 'fieldB'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT ta.',
-          afterCursor: ' FROM testTable t, t.testArray ta;',
-          expectedSuggestions: ['*', 'fieldA', 'fieldB']
-        });
-      });
-
-      it('should suggest items from arrays if scalar in select clause', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
-            response: {
-              status: 0,
-              type: 'array'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
-            response: {
-              status: 0,
-              type: 'int'
-            }
-          }],
-          beforeCursor: 'SELECT ta.',
-          afterCursor: ' FROM testTable t, t.testArray ta;',
-          expectedSuggestions: ['*', 'items']
-        });
-      });
-
-      it('should suggest items from arrays if complex in from clause', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
-            response: {
-              status: 0,
-              type: 'array'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'string', name: 'fieldB'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT ta.* FROM testTable t, t.testArray ta WHERE ta.',
-          afterCursor: '',
-          expectedSuggestions: ['items', 'fieldA', 'fieldB']
-        });
-      });
-
-
-      it('should suggest columns from table refs in from clause', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT t.*  FROM testTable t, t.',
-          afterCursor: '',
-          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest map references in select', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM testTable t, t.testMap tm;',
-          expectedSuggestions: ['t.', 'tm.']
-        });
-      });
-
-      it('should suggest fields with key and value in where clause from map values of type structs', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'string', name: 'fieldB'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.',
-          afterCursor: '',
-          expectedSuggestions: ['key', 'value', 'fieldA', 'fieldB']
-        });
-      });
-
-      it('should suggest fields in where clause from map values of type structs', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
-            response: {
-              status: 0,
-              fields: [
-                {type: 'string', name: 'fieldA'},
-                {type: 'string', name: 'fieldB'}
-              ],
-              type: 'struct'
-            }
-          }],
-          beforeCursor: 'SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.value.',
-          afterCursor: '',
-          expectedSuggestions: ['fieldA', 'fieldB']
-        });
-      });
-
-      it('should suggest values for map keys', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/key/,
-            response: {
-              status: 0,
-              sample: ['value1', 'value2'],
-              type: 'string'
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable t, t.testMap tm WHERE tm.key =',
-          afterCursor: '',
-          expectedSuggestions: ['t.', 'tm.', '\'value1\'', '\'value2\'']
-        });
-      });
-
-      it('should suggest values for columns in conditions', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/id/,
-            response: {
-              status: 0,
-              sample: [1, 2, 3],
-              type: 'int'
-            }
-          }, {
-            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/id/,
-            response: {
-              status: 0,
-              headers: [],
-              rows: []
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable WHERE id =',
-          afterCursor: '',
-          expectedSuggestions: ['testTable', '1', '2', '3']
-        });
-      });
-
-      it('should suggest values from fields in map values in conditions', function () {
-        assertAutoComplete({
-          type: 'impala',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
-            response: {
-              status: 0,
-              type: 'map'
-            }
-          }, {
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value\/field/,
-            response: {
-              status: 0,
-              sample: [1, 2, 3],
-              type: 'int'
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable t, t.testMap m WHERE m.field = ',
-          afterCursor: '',
-          expectedSuggestions: ['t.', 'm.', '1', '2', '3']
-        });
-      })
-    });
-
-    describe('value completion', function () {
-      it('should suggest numeric sample values for columns in conditions', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/id/,
-            response: {
-              status: 0,
-              headers: ['id'],
-              rows: [[1], [2], [3]]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable WHERE id =',
-          afterCursor: '',
-          expectedSuggestions: ['1', '2', '3']
-        });
-      });
-
-      it('should suggest string sample values for columns in conditions with started value', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/string/,
-            response: {
-              status: 0,
-              headers: ['id'],
-              rows: [['abc'], ['def'], ['ghi']]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable WHERE string = \'d',
-          afterCursor: '',
-          expectedSuggestions: ['\'abc\'', '\'def\'', '\'ghi\'']
-        });
-      });
-
-      it('should suggest string sample values for columns in conditions with started value after AND', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/string/,
-            response: {
-              status: 0,
-              headers: ['id'],
-              rows: [['abc'], ['def'], ['ghi']]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable WHERE id = 1 AND string =',
-          afterCursor: '',
-          expectedSuggestions: ['\'abc\'', '\'def\'', '\'ghi\'']
-        });
-      });
-
-      it('should suggest string sample values for columns in conditions with started value after OR', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/string/,
-            response: {
-              status: 0,
-              headers: ['id'],
-              rows: [['ab'], ['cd'], ['ef']]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable WHERE id = 1 OR string =',
-          afterCursor: '',
-          expectedSuggestions: ['\'ab\'', '\'cd\'', '\'ef\'']
-        });
-      });
-    });
-
-    describe('field completion', function () {
-      it('should suggest columns for table', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for tables with where keyword in name', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testwhere/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM testwhere',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for tables with on keyword in name', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/teston/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM teston',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table with database prefix', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT ',
-          afterCursor: ' FROM database_two.testTable',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table after WHERE', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable WHERE ',
-          afterCursor: '',
-          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table after ORDER BY ', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable ORDER BY ',
-          afterCursor: '',
-          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table after ORDER BY with db reference', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY ',
-          afterCursor: '',
-          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table after GROUP BY ', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable GROUP BY ',
-          afterCursor: '',
-          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table after ON ', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable1/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.',
-          afterCursor: '',
-          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table after ON with database reference', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable1/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM database_two.testTable1 t1 JOIN testTable2 t2 ON t1.',
-          afterCursor: '',
-          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns for table with table ref', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT testTable.',
-          afterCursor: ' FROM testTable',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns with table alias', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT tt.',
-          afterCursor: ' FROM testTable tt',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns with table alias from database reference', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable/,
-            response: {
-              status: 0,
-              columns: ['testTableColumn1', 'testTableColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT tt.',
-          afterCursor: ' FROM database_two.testTable tt',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-      });
-
-      it('should suggest columns with multiple table aliases', function () {
-        var serverResponses = [{
-          url: /.*\/notebook\/api\/autocomplete\/database_one\/testTableA/,
-          response: {
-            status: 0,
-            columns: ['testTableColumn1', 'testTableColumn2']
-          }
-        }, {
-          url: /.*\/notebook\/api\/autocomplete\/database_one\/testTableB/,
-          response: {
-            status: 0,
-            columns: ['testTableColumn3', 'testTableColumn4']
-          }
-        }];
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: serverResponses,
-          beforeCursor: 'SELECT tta.',
-          afterCursor: ' FROM testTableA tta, testTableB ttb',
-          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
-        });
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: serverResponses,
-          beforeCursor: 'SELECT ttb.',
-          afterCursor: ' FROM testTableA tta, testTableB ttb',
-          expectedSuggestions: ['*', 'testTableColumn3', 'testTableColumn4']
-        });
-      });
-
-      describe('struct completion', function () {
-        it('should suggest fields from columns that are structs', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldA'},
-                  {type: 'boolean', name: 'fieldB'},
-                  {
-                    type: 'struct', name: 'fieldC', 'fields': [
-                    {type: 'string', name: 'fieldC_A'},
-                    {type: 'boolean', name: 'fieldC_B'}
-                  ]
-                  }
-                ],
-                type: 'struct',
-                name: 'columnB'
-              }
-            }],
-            beforeCursor: 'SELECT columnA.',
-            afterCursor: ' FROM testTable',
-            expectedSuggestions: ['fieldA', 'fieldB', 'fieldC']
-          });
-        });
-
-        it('should suggest fields from nested structs', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA\/fieldC/,
-              response: {
-                status: 0,
-                fields: [
-                  {type: 'string', name: 'fieldC_A'},
-                  {type: 'boolean', name: 'fieldC_B'}
-                ],
-                type: 'struct',
-                name: 'fieldC'
-              }
-            }],
-            beforeCursor: 'SELECT columnA.fieldC.',
-            afterCursor: ' FROM testTable',
-            expectedSuggestions: ['fieldC_A', 'fieldC_B']
-          });
-        });
-
-        it('should suggest fields from nested structs with database reference', function () {
-          assertAutoComplete({
-            type: 'hive',
-            serverResponses: [{
-              url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable\/columnA\/fieldC/,
-              response: {
-                status: 0,
-                fields: [
-                  {'type': 'string', 'name': 'fieldC_A'},
-                  {'type': 'boolean', 'name': 'fieldC_B'}
-                ],
-                type: 'struct',
-                name: 'fieldC'
-              }
-            }],
-            beforeCursor: 'SELECT columnA.fieldC.',
-            afterCursor: ' FROM database_two.testTable',
-            expectedSuggestions: ['fieldC_A', 'fieldC_B']
-          });
-        });
-      });
-    });
-
-    describe('joins', function () {
-      it('should suggest tables to join with', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one/,
-            response: {
-              status: 0,
-              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
-            }
-          }],
-          beforeCursor: 'SELECT * FROM testTable1 JOIN ',
-          afterCursor: '',
-          expectedSuggestions: ['testTable1', 'testTable2', 'database_one.', 'database_two.']
-        });
-      });
-
-      it('should suggest table references in join condition if not already there', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (',
-          afterCursor: '',
-          expectedSuggestions: ['testTable1.', 'testTable2.']
-        });
-      });
-
-      it('should suggest table references in join condition if not already there for multiple conditions', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND ',
-          afterCursor: '',
-          expectedSuggestions: ['testTable1.', 'testTable2.']
-        });
-      });
-
-      it('should suggest table references in join condition if not already there for multiple conditions', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [],
-          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (',
-          afterCursor: ' AND testTable1.testColumn1 = testTable2.testColumn3',
-          expectedSuggestions: ['testTable1.', 'testTable2.']
-        });
-      });
-
-      it('should suggest field references in join condition if table reference is present', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable2/,
-            response: {
-              status: 0,
-              columns: ['testColumn3', 'testColumn4']
-            }
-          }],
-          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable2.',
-          afterCursor: '',
-          expectedSuggestions: ['testColumn3', 'testColumn4']
-        });
-      });
-
-      it('should suggest field references in join condition if table reference is present from multiple tables', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable2/,
-            response: {
-              status: 0,
-              columns: ['testColumn3', 'testColumn4']
-            }
-          }],
-          beforeCursor: 'select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = testTable2.',
-          afterCursor: '',
-          expectedSuggestions: ['testColumn3', 'testColumn4']
-        });
-      });
-
-      it('should suggest field references in join condition if table reference is present from multiple tables for multiple conditions', function () {
-        assertAutoComplete({
-          type: 'hive',
-          serverResponses: [{
-            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable1/,
-            response: {
-              status: 0,
-              columns: ['testColumn1', 'testColumn2']
-            }
-          }],
-          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND testTable1.',
-          afterCursor: '',
-          expectedSuggestions: ['testColumn1', 'testColumn2']
-        });
-      });
-    })
-  });
-})();

+ 0 - 1
desktop/core/src/desktop/templates/common_header.mako

@@ -189,7 +189,6 @@ if USE_NEW_EDITOR.get():
   <script src="${ static('desktop/js/autocomplete/solrQueryParser.js') }"></script>
   <script src="${ static('desktop/js/autocomplete/solrFormulaParser.js') }"></script>
   <script src="${ static('desktop/js/autocomplete/globalSearchParser.js') }"></script>
-  <script src="${ static('desktop/js/sqlAutocompleter.js') }"></script>
   <script src="${ static('desktop/js/sqlAutocompleter2.js') }"></script>
   <script src="${ static('desktop/js/sqlAutocompleter3.js') }"></script>
   <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>

+ 0 - 1
desktop/core/src/desktop/templates/hue.mako

@@ -564,7 +564,6 @@ ${ commonshare() | n,unicode }
 <script src="${ static('desktop/js/autocomplete/globalSearchParser.js') }"></script>
 <script src="${ static('desktop/js/autocomplete/solrQueryParser.js') }"></script>
 <script src="${ static('desktop/js/autocomplete/solrFormulaParser.js') }"></script>
-<script src="${ static('desktop/js/sqlAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/sqlAutocompleter2.js') }"></script>
 <script src="${ static('desktop/js/sqlAutocompleter3.js') }"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>

+ 0 - 3
desktop/core/src/desktop/templates/jasmineRunner.html

@@ -81,9 +81,6 @@
 
   <script type="text/javascript" src="../static/desktop/spec/apiHelperSpec.js"></script>
 
-  <script type="text/javascript" src="../static/desktop/js/sqlAutocompleter.js"></script>
-  <script type="text/javascript" src="../static/desktop/spec/sqlAutocompleterSpec.js"></script>
-
   <script type="text/javascript" src="../static/desktop/js/sqlAutocompleter2.js"></script>
   <script type="text/javascript" src="../static/desktop/spec/sqlAutocompleter2Spec.js"></script>
 

+ 134 - 130
desktop/core/src/desktop/templates/ko_components/ko_context_popover.mako

@@ -150,10 +150,10 @@ from metadata.conf import has_navigator
   <script type="text/html" id="context-popover-table-and-column-sample">
     <div class="context-popover-flex-fill context-popover-sample-container" data-bind="with: fetchedData">
       <div class="context-popover-sample sample-scroll">
-        <!-- ko if: rows.length == 0 -->
+        <!-- ko if: typeof rows === 'undefined' || rows.length === 0 -->
         <div class="alert">${ _('The selected table has no data.') }</div>
         <!-- /ko -->
-        <!-- ko if: rows.length > 0 -->
+        <!-- ko if: typeof rows !== 'undefined' && rows.length > 0 -->
         <table id="samples-table" class="samples-table table table-condensed">
           <thead>
           <tr>
@@ -514,13 +514,13 @@ from metadata.conf import has_navigator
         }
       };
 
-      function GenericTabContents(identifierChain, sourceType, defaultDatabase, apiFunction, parent) {
+      function GenericTabContents(identifierChain, sourceType, defaultDatabase, catalogEntryFnName, parent) {
         var self = this;
         self.identifierChain = identifierChain;
         self.sourceType = sourceType;
         self.defaultDatabase = defaultDatabase;
         self.apiHelper = ApiHelper.getInstance();
-        self.apiFunction = apiFunction;
+        self.catalogEntryFnName = catalogEntryFnName;
         self.parent = parent;
 
         self.fetchedData = ko.observable();
@@ -559,58 +559,79 @@ from metadata.conf import has_navigator
         self.loading(true);
         self.hasErrors(false);
 
-        self.apiFunction.bind(self.apiHelper)({
-          sourceType: self.sourceType,
-          identifierChain: self.identifierChain,
-          defaultDatabase: self.defaultDatabase,
-          silenceErrors: true,
-          successCallback: function (data) {
-            if (data.code === 500) {
-              self.loading(false);
-              self.hasErrors(true);
-              if (data.notFound) {
-                self.parent.notFound(data);
-              }
-              return;
-            }
-            if (typeof data.extended_columns !== 'undefined') {
-              data.extended_columns.forEach(function (column) {
-                column.extendedType = column.type.replace(/</g, '&lt;').replace(/>/g, '&lt;');
-                if (column.type.indexOf('<') !== -1) {
-                  column.type = column.type.substring(0, column.type.indexOf('<'));
-                }
-              });
-            }
-            if (typeof data.properties !== 'undefined') {
-              data.properties.forEach(function (property) {
-                if (property.col_name.toLowerCase() === 'view original text:') {
-                  data.viewSql = ko.observable();
-                  data.loadingViewSql = ko.observable(true);
-                  ApiHelper.getInstance().formatSql(property.data_type).done(function (formatResponse) {
-                    if (formatResponse.status == 0) {
-                      data.viewSql(formatResponse.formatted_statements);
-                    } else {
-                      data.viewSql(property.data_type);
-                    }
-                  }).fail(function () {
-                    data.viewSql(property.data_type);
-                  }).always(function () {
-                    data.loadingViewSql(false);
-                  })
-                }
-              })
-            }
-            self.fetchedData(data);
-            self.loading(false);
-            if (typeof callback === 'function') {
-              callback(data);
-            }
-          },
-          errorCallback: function () {
-            self.loading(false);
-            self.hasErrors(true);
+        if (self.catalogEntryFnName) {
+          var path = $.map(self.identifierChain, function (identifier) { return identifier.name });
+          if (path.length === 0) {
+            path.push(self.defaultDatabase);
           }
-        });
+          DataCatalog.getChildren({ sourceType: self.sourceType, path: [], silenceErrors: true }).done(function (dbEntries) {
+            var firstIsDatabase = dbEntries.some(function (dbEntry) {
+              return dbEntry.name.toLowerCase() === path[0].toLowerCase();
+            });
+
+            if (!firstIsDatabase) {
+              path.unshift(self.defaultDatabase);
+            }
+
+            DataCatalog.getEntry({ sourceType: self.sourceType, path: path }).done(function (catalogEntry) {
+              if (catalogEntry[self.catalogEntryFnName]) {
+                catalogEntry[self.catalogEntryFnName]({ silenceErrors: true }).done(function (response) {
+                  if (response.notFound) {
+                    self.hasErrors(true);
+                    self.parent.notFound(true);
+                    return;
+                  }
+
+                  var details = $.extend({}, response); // shallow clone
+
+                  // Adapt columns if there
+                  if (typeof details.extended_columns !== 'undefined') {
+                    var newExtendedColumns = [];
+                    details.extended_columns.forEach(function (column) {
+                      var clonedColumn = $.extend({}, column);
+                      clonedColumn.extendedType = clonedColumn.type.replace(/</g, '&lt;').replace(/>/g, '&lt;');
+                      if (clonedColumn.type.indexOf('<') !== -1) {
+                        clonedColumn.type = clonedColumn.type.substring(0, clonedColumn.type.indexOf('<'));
+                      }
+                      newExtendedColumns.push(clonedColumn);
+                    });
+                    details.extended_columns = newExtendedColumns;
+                  }
+
+                  // Adapt and format view SQL if there
+                  if (typeof details.properties !== 'undefined') {
+                    details.properties.forEach(function (property) {
+                      if (property.col_name.toLowerCase() === 'view original text:') {
+                        details.viewSql = ko.observable();
+                        details.loadingViewSql = ko.observable(true);
+                        ApiHelper.getInstance().formatSql(property.data_type).done(function (formatResponse) {
+                          if (formatResponse.status == 0) {
+                            details.viewSql(formatResponse.formatted_statements);
+                          } else {
+                            details.viewSql(property.data_type);
+                          }
+                        }).fail(function () {
+                          details.viewSql(property.data_type);
+                        }).always(function () {
+                          details.loadingViewSql(false);
+                        })
+                      }
+                    })
+                  }
+
+                  self.fetchedData(details);
+                  if (typeof callback === 'function') {
+                    callback(details);
+                  }
+                }).fail(function () {
+                  self.hasErrors(true);
+                }).always(function () {
+                  self.loading(false);
+                });
+              }
+            });
+          });
+        }
       };
 
       function TableAndColumnContextTabs(data, sourceType, defaultDatabase, isColumn, isComplex) {
@@ -620,12 +641,12 @@ from metadata.conf import has_navigator
 
         var apiHelper = ApiHelper.getInstance();
 
-        self.columns = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchAutocomplete, self);
-        self.columnDetails = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchAutocomplete, self);
-        self.tableDetails = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, sourceType === 'solr' ? $.noop : apiHelper.fetchAnalysis_OLD, self);
-        self.sample = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchSamples, self);
-        self.analysis = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchAnalysis_OLD, self);
-        self.partitions = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchPartitions, self);
+        self.columns = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, 'getSourceMeta', self);
+        self.columnDetails = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, 'getSourceMeta', self);
+        self.tableDetails = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, sourceType === 'solr' ? '' : 'getAnalysis', self);
+        self.sample = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, 'getSample', self);
+        self.analysis = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, 'getAnalysis', self);
+        self.partitions = new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, 'getPartitions', self);
 
         self.hasErrors = false;
         self.isTable = !isColumn && !isComplex;
@@ -642,9 +663,11 @@ from metadata.conf import has_navigator
               } else {
                 var data = self.columnDetails.fetchedData();
                 var rows = [];
-                data.sample.forEach(function (sample) {
-                  rows.push([sample]);
-                });
+                if (data.sample) {
+                  data.sample.forEach(function (sample) {
+                    rows.push([sample]);
+                  });
+                }
                 self.sample.fetchedData({
                   headers: [ data.name || self.title ],
                   rows: rows
@@ -818,20 +841,14 @@ from metadata.conf import has_navigator
           scrollPubSub.remove();
         });
 
-        apiHelper.identifierChainToPath({
-          sourceType: sourceType,
-          defaultDatabase: defaultDatabase,
-          identifierChain: data.identifierChain
-        }).done(function (path) {
-          var showInAssistPubSub = huePubSub.subscribe('context.popover.show.in.assist', function () {
-            huePubSub.publish('assist.db.highlight', {
-              sourceType: sourceType,
-              path: path
-            });
+        var showInAssistPubSub = huePubSub.subscribe('context.popover.show.in.assist', function () {
+          huePubSub.publish('assist.db.highlight', {
+            sourceType: sourceType,
+            path: $.map(data.identifierChain, function (identifier) { return identifier.name })
           });
-          self.disposals.push(function () {
-            showInAssistPubSub.remove();
-          })
+        });
+        self.disposals.push(function () {
+          showInAssistPubSub.remove();
         });
 
         self.initializeSamplesTable = function (data) {
@@ -950,7 +967,7 @@ from metadata.conf import has_navigator
         });
 
         self.tabs = [
-          { id: 'details', label: '${ _("Details") }', comment : self.dbComment, template: 'context-popover-database-details', templateData: new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, ApiHelper.getInstance().fetchAutocomplete) }
+          { id: 'details', label: '${ _("Details") }', comment : self.dbComment, template: 'context-popover-database-details', templateData: new GenericTabContents(data.identifierChain, sourceType, defaultDatabase, 'getSourceMeta') }
         ];
         self.activeTab = ko.observable('details');
 
@@ -1033,36 +1050,36 @@ from metadata.conf import has_navigator
           huePubSub.publish('context.popover.hide');
         };
 
-        var apiHelper = ApiHelper.getInstance();
         var deferrals = [];
         data.tables.forEach(function (table) {
           if (table.identifierChain) {
             var fetchDeferred = $.Deferred();
             deferrals.push(fetchDeferred);
-            apiHelper.fetchAutocomplete({
+            DataCatalog.getEntry({
               sourceType: sourceType,
-              defaultDatabase: defaultDatabase,
-              identifierChain: table.identifierChain,
-              successCallback: function (data) {
-                if (typeof data.extended_columns !== 'undefined') {
-                  data.extended_columns.forEach(function (column) {
-                    column.extendedType = column.type.replace(/</g, '&lt;').replace(/>/g, '&lt;');
-                    if (column.type.indexOf('<') !== -1) {
-                      column.type = column.type.substring(0, column.type.indexOf('<'));
+              path: $.map(table.identifierChain, function (identifier) { return identifier.name })
+            }).done(function (entry) {
+              entry.getSourceMeta({ silenceErrors: true }).done(function (sourceMeta) {
+                if (typeof sourceMeta.extended_columns !== 'undefined') {
+                  var newColumns = [];
+                  sourceMeta.extended_columns.forEach(function (column) {
+                    var clonedColumn = $.extend({}, column);
+                    clonedColumn.extendedType = clonedColumn.type.replace(/</g, '&lt;').replace(/>/g, '&lt;');
+                    if (clonedColumn.type.indexOf('<') !== -1) {
+                      clonedColumn.type = clonedColumn.type.substring(0, clonedColumn.type.indexOf('<'));
                     }
-                    column.selected = ko.observable(false);
-                    column.table = table.identifierChain[table.identifierChain.length - 1].name;
+                    clonedColumn.selected = ko.observable(false);
+                    clonedColumn.table = table.identifierChain[table.identifierChain.length - 1].name;
                     if (table.alias) {
-                      column.tableAlias = table.alias
+                      clonedColumn.tableAlias = table.alias
                     }
+                    newColumns.push(clonedColumn);
                   });
+                  self.columns = self.columns.concat(newColumns);
                 }
-                self.columns = self.columns.concat(data.extended_columns);
                 fetchDeferred.resolve();
-              },
-              silenceErrors: true,
-              errorCallback: fetchDeferred.reject
-            })
+              }).fail(fetchDeferred.reject);
+            }).fail(fetchDeferred.reject);
           }
         });
 
@@ -1257,22 +1274,15 @@ from metadata.conf import has_navigator
         ];
         self.activeTab = ko.observable('terms');
 
-        self.apiHelper.identifierChainToPath({
-          sourceType: 'solr',
-          identifierChain: data.identifierChain,
-          defaultDatabase: 'default'
-        }).done(function (path) {
-          var showInAssistPubSub = huePubSub.subscribe('context.popover.show.in.assist', function () {
-            huePubSub.publish('assist.db.highlight', {
-              sourceType: 'solr',
-              path: path
-            });
+        var showInAssistPubSub = huePubSub.subscribe('context.popover.show.in.assist', function () {
+          huePubSub.publish('assist.db.highlight', {
+            sourceType: 'solr',
+            path: $.map(data.identifierChain, function (identifier){ return identifier.name; })
           });
-          self.disposals.push(function () {
-            showInAssistPubSub.remove();
-          })
         });
-
+        self.disposals.push(function () {
+          showInAssistPubSub.remove();
+        })
       }
 
       CollectionContextTabs.prototype.loadTerms = function () {
@@ -1666,24 +1676,19 @@ from metadata.conf import has_navigator
         self.orientationClass = 'hue-popover-' + orientation;
 
         if ((self.isDatabase || self.isTable || self.isView) && self.data.identifierChain) {
-          apiHelper.identifierChainToPath({
-            sourceType: self.sourceType,
-            identifierChain: self.data.identifierChain,
-            defaultDatabase: self.defaultDatabase
-          }).done(function (path) {
-
-            var showInMetastorePubSub = huePubSub.subscribe('context.popover.open.in.metastore', function (type) {
-              if (IS_HUE_4) {
-                huePubSub.publish('open.link', '/metastore/table' + (type === 'table' || type === 'view' ? '/' : 's/') + path.join('/'));
-                huePubSub.publish('context.popover.hide');
-              } else {
-                window.open('/metastore/table' + (type === 'table' || type === 'view' ? '/' : 's/') + path.join('/'), '_blank');
-              }
-            });
-            self.disposals.push(function () {
-              showInMetastorePubSub.remove();
-            });
-            % if HAS_SQL_ENABLED.get():
+          var path = $.map(self.data.identifierChain, function (identifier) { return identifier.name });
+          var showInMetastorePubSub = huePubSub.subscribe('context.popover.open.in.metastore', function (type) {
+            if (IS_HUE_4) {
+              huePubSub.publish('open.link', '/metastore/table' + (type === 'table' || type === 'view' ? '/' : 's/') + path.join('/'));
+              huePubSub.publish('context.popover.hide');
+            } else {
+              window.open('/metastore/table' + (type === 'table' || type === 'view' ? '/' : 's/') + path.join('/'), '_blank');
+            }
+          });
+          self.disposals.push(function () {
+            showInMetastorePubSub.remove();
+          });
+          % if HAS_SQL_ENABLED.get():
             var openInDashboardPubSub = huePubSub.subscribe('context.popover.open.in.dashboard', function () {
               if (IS_HUE_4) {
                 huePubSub.publish('open.link', '/hue/dashboard/browse/' + path.join('.') + '?engine=' + self.sourceType);
@@ -1695,8 +1700,7 @@ from metadata.conf import has_navigator
             self.disposals.push(function () {
               openInDashboardPubSub.remove();
             });
-            % endif
-          });
+          % endif
         }
 
         if (params.delayedHide) {

+ 0 - 1
desktop/libs/indexer/src/indexer/templates/importer.mako

@@ -33,7 +33,6 @@ ${ commonheader(_("Importer"), "indexer", user, request, "60px") | n,unicode }
 ## TODO lot of those re-imported
 <script src="${ static('desktop/js/autocomplete/sqlParseSupport.js') }"></script>
 <script src="${ static('desktop/js/autocomplete/sqlAutocompleteParser.js') }"></script>
-<script src="${ static('desktop/js/sqlAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/sqlAutocompleter2.js') }"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/autocompleter.js') }"></script>

+ 0 - 1
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -62,7 +62,6 @@ from notebook.conf import ENABLE_QUERY_BUILDER, ENABLE_QUERY_SCHEDULING, ENABLE_
 <script src="${ static('desktop/js/autocomplete/sqlStatementsParser.js') }"></script>
 <script src="${ static('desktop/js/autocomplete/sqlAutocompleteParser.js') }"></script>
 <script src="${ static('desktop/js/autocomplete/globalSearchParser.js') }"></script>
-<script src="${ static('desktop/js/sqlAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/sqlAutocompleter2.js') }"></script>
 <script src="${ static('desktop/js/sqlAutocompleter3.js') }"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>

+ 0 - 1
desktop/libs/notebook/src/notebook/templates/editor_m.mako

@@ -167,7 +167,6 @@ ${ commonheader_m(editor_type, editor_type, user, request, "68px") | n,unicode }
 <script src="${ static('desktop/js/sqlFunctions.js') }"></script>
 <script src="${ static('desktop/js/autocomplete/sqlParseSupport.js') }"></script>
 <script src="${ static('desktop/js/autocomplete/sqlAutocompleteParser.js') }"></script>
-<script src="${ static('desktop/js/sqlAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/sqlAutocompleter2.js') }"></script>
 <script src="${ static('desktop/js/sqlAutocompleter3.js') }"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>