Browse Source

HUE-7577 [editor] Don't assume databases have been loaded for all sources

Johan Ahlen 8 years ago
parent
commit
d6ca048

+ 218 - 159
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -170,12 +170,6 @@ var ApiHelper = (function () {
     }
   }
 
-  ApiHelper.prototype.isDatabase = function (name, sourceType) {
-    var self = this;
-    return typeof self.lastKnownDatabases[sourceType] !== 'undefined'
-        && self.lastKnownDatabases[sourceType].filter(function (knownDb) { return knownDb.toLowerCase() === name.toLowerCase() }).length === 1;
-  };
-
   ApiHelper.prototype.hasExpired = function (timestamp, cacheType) {
     if (typeof hueDebug !== 'undefined' && typeof hueDebug.cacheTimeout !== 'undefined') {
       return (new Date()).getTime() - timestamp > hueDebug.cacheTimeout;
@@ -1465,9 +1459,55 @@ var ApiHelper = (function () {
     }));
   };
 
+  /**
+   * 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;
-    return typeof self.lastKnownDatabases[sourceType] !== 'undefined' && self.lastKnownDatabases[sourceType].indexOf(databaseName.toLowerCase()) > -1;
+    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) {
@@ -1544,22 +1584,31 @@ var ApiHelper = (function () {
    */
   ApiHelper.prototype.identifierChainToPath = function (options, successCallback) {
     var self = this;
+    if (options.identifierChain.length === 0) {
+      successCallback([options.defaultDatabase]);
+      return;
+    }
+
     var identifierChainClone = options.identifierChain.concat();
     var path = [];
-    if (identifierChainClone.length === 0 || ! self.containsDatabase(options.sourceType, identifierChainClone[0].name)) {
-      path.push(options.defaultDatabase);
-    } else {
-      path.push(identifierChainClone.shift().name)
-    }
 
-    if (identifierChainClone.length > 1) {
-      self.expandComplexIdentifierChain(options.sourceType, path[0], identifierChainClone, function (fetchedFields) {
-        successCallback(path.concat(fetchedFields))
-      }, options.errorCallback, options.cachedOnly);
-    } else {
-      successCallback(path.concat($.map(identifierChainClone, function (identifier) { return identifier.name })))
-    }
+    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) {
+          successCallback(path.concat(fetchedFields))
+        }, options.errorCallback, options.cachedOnly);
+      } else {
+        successCallback(path.concat($.map(identifierChainClone, function (identifier) { return identifier.name })))
+      }
+    });
   };
 
   /**
@@ -1631,67 +1680,64 @@ var ApiHelper = (function () {
 
     var hierarchy = '';
 
-    self.loadDatabases({
-      sourceType: options.sourceType,
-      successCallback: function () {
-        // Database
-        if (clonedIdentifierChain.length > 1 && self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name)) {
-          hierarchy = clonedIdentifierChain.shift().name
-        } else {
-          hierarchy = options.defaultDatabase;
-        }
+    var dbPromise = self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name);
 
-        // Table
-        if (clonedIdentifierChain.length > 0) {
-          hierarchy += '/' + clonedIdentifierChain.shift().name;
-        }
+    dbPromise.done(function (firstIsDatabase) {
+      // Database
+      if (firstIsDatabase) {
+        hierarchy = clonedIdentifierChain.shift().name
+      } else {
+        hierarchy = options.defaultDatabase;
+      }
 
-        // Column/Complex
-        if (clonedIdentifierChain.length > 0) {
-          hierarchy += '/stats/' + $.map(clonedIdentifierChain, function (identifier) { return identifier.name }).join('/')
-        }
+      // Table
+      if (clonedIdentifierChain.length > 0) {
+        hierarchy += '/' + clonedIdentifierChain.shift().name;
+      }
 
-        var url = "/" + (options.sourceType == "hive" ? "beeswax" : options.sourceType) + "/api/table/" + hierarchy;
+      // Column/Complex
+      if (clonedIdentifierChain.length > 0) {
+        hierarchy += '/stats/' + $.map(clonedIdentifierChain, function (identifier) { return identifier.name }).join('/')
+      }
 
-        var fetchFunction = function (storeInCache) {
-          if (options.timeout === 0) {
-            self.assistErrorCallback(options)({ status: -1 });
-            return;
+      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);
           }
-          $.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();
             }
-          })
-            .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
-        }));
-      },
-      silenceErrors: options.silenceErrors,
-      errorCallback: options.errorCallback
+      fetchCached.bind(self)($.extend({}, options, {
+        url: url,
+        fetchFunction: fetchFunction
+      }));
     });
   };
 
@@ -1736,25 +1782,28 @@ var ApiHelper = (function () {
 
     var clonedIdentifierChain = options.identifierChain.concat();
 
-    var database = options.defaultDatabase && !self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name) ? options.defaultDatabase : clonedIdentifierChain.shift().name;
+    var dpPromise = self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name);
 
-    var url = NAV_URLS.FIND_ENTITY + '?type=database&name=' + database;
+    dpPromise.done(function (firstIsDatabase) {
+      var database = options.defaultDatabase && !firstIsDatabase ? options.defaultDatabase : clonedIdentifierChain.shift().name;
+      var url = NAV_URLS.FIND_ENTITY + '?type=database&name=' + database;
 
-    var isView = !!options.isView;
+      var isView = !!options.isView;
 
-    if (clonedIdentifierChain.length > 0) {
-      var table = clonedIdentifierChain.shift().name;
-      url = NAV_URLS.FIND_ENTITY + (isView ? '?type=view' : '?type=table') + '&database=' + database + '&name=' + table;
       if (clonedIdentifierChain.length > 0) {
-        url = NAV_URLS.FIND_ENTITY + '?type=field&database=' + database + '&table=' + table + '&name=' + clonedIdentifierChain.shift().name;
+        var table = clonedIdentifierChain.shift().name;
+        url = NAV_URLS.FIND_ENTITY + (isView ? '?type=view' : '?type=table') + '&database=' + database + '&name=' + table;
+        if (clonedIdentifierChain.length > 0) {
+          url = NAV_URLS.FIND_ENTITY + '?type=field&database=' + database + '&table=' + table + '&name=' + clonedIdentifierChain.shift().name;
+        }
       }
-    }
 
-    fetchAssistData.bind(self)($.extend({ sourceType: 'nav' }, options, {
-      url: url,
-      errorCallback: self.assistErrorCallback(options),
-      noCache: true
-    }));
+      fetchAssistData.bind(self)($.extend({ sourceType: 'nav' }, options, {
+        url: url,
+        errorCallback: self.assistErrorCallback(options),
+        noCache: true
+      }));
+    });
   };
 
   ApiHelper.prototype.addNavTags = function (entityId, tags) {
@@ -1792,27 +1841,34 @@ var ApiHelper = (function () {
     var self = this;
     var tables = [];
     var tableIndex = {};
-    options.tables.forEach(function (table) {
-      if (table.subQuery || !table.identifierChain) {
-        return;
-      }
-      var clonedIdentifierChain = table.identifierChain.concat();
 
-      var databasePrefix;
-      if (clonedIdentifierChain.length > 1 && self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name)) {
-        databasePrefix = clonedIdentifierChain.shift().name + '.';
-      } else if (options.defaultDatabase) {
-        databasePrefix = options.defaultDatabase + '.';
-      } else {
-        databasePrefix = '';
-      }
-      var identifier = databasePrefix  + $.map(clonedIdentifierChain, function (identifier) { return identifier.name }).join('.');
-      if (!tableIndex[databasePrefix  + $.map(clonedIdentifierChain, function (identifier) { return identifier.name }).join('.')]) {
-        tables.push(identifier);
-        tableIndex[identifier] = true;
-      }
+    var promise = $.Deferred();
+
+    self.getDatabases(options.sourceType).done(function (databases){
+      options.tables.forEach(function (table) {
+        if (table.subQuery || !table.identifierChain) {
+          return;
+        }
+        var clonedIdentifierChain = table.identifierChain.concat();
+
+        var databasePrefix;
+        if (clonedIdentifierChain.length > 1 && clonedIdentifierChain[0].name && databases.indexOf(clonedIdentifierChain[0].name.toLowerCase()) > -1) {
+          databasePrefix = clonedIdentifierChain.shift().name + '.';
+        } else if (options.defaultDatabase) {
+          databasePrefix = options.defaultDatabase + '.';
+        } else {
+          databasePrefix = '';
+        }
+        var identifier = databasePrefix  + $.map(clonedIdentifierChain, function (identifier) { return identifier.name }).join('.');
+        if (!tableIndex[databasePrefix  + $.map(clonedIdentifierChain, function (identifier) { return identifier.name }).join('.')]) {
+          tables.push(identifier);
+          tableIndex[identifier] = true;
+        }
+      });
+      promise.resolve(ko.mapping.toJSON(tables))
     });
-    return ko.mapping.toJSON(tables);
+
+    return promise;
   };
 
   /**
@@ -1921,68 +1977,71 @@ var ApiHelper = (function () {
   ApiHelper.prototype.fetchNavOptCached = function (url, options, cacheCondition) {
     var self = this;
 
-    var data, hash;
-    if (options.tables) {
-      data = {
-        dbTables: self.createNavOptDbTablesJson(options)
-      };
-      hash = data.dbTables.hashCode();
-    } else if (options.database) {
-      data = {
-        database: options.database
-      };
-      hash = data.database;
-    }
-
-    var promise = self.queueManager.getQueued(url, hash);
-    var firstInQueue = typeof promise === 'undefined';
-    if (firstInQueue) {
-      promise = $.Deferred();
-      self.queueManager.addToQueue(promise, url, hash);
-    }
-
-    promise.done(options.successCallback).fail(self.assistErrorCallback(options)).always(function () {
-      if (typeof options.editor !== 'undefined' && options.editor !== null) {
-        options.editor.hideSpinner();
+    var performFetch = function (data, hash) {
+      var promise = self.queueManager.getQueued(url, hash);
+      var firstInQueue = typeof promise === 'undefined';
+      if (firstInQueue) {
+        promise = $.Deferred();
+        self.queueManager.addToQueue(promise, url, hash);
       }
-    });
 
-    if (!firstInQueue) {
-      return;
-    }
+      promise.done(options.successCallback).fail(self.assistErrorCallback(options)).always(function () {
+        if (typeof options.editor !== 'undefined' && options.editor !== null) {
+          options.editor.hideSpinner();
+        }
+      });
 
-    var fetchFunction = function (storeInCache) {
-      if (options.timeout === 0) {
-        self.assistErrorCallback(options)({ status: -1 });
+      if (!firstInQueue) {
         return;
       }
 
-      return $.ajax({
-        type: 'post',
-        url: url,
-        data: data,
-        timeout: options.timeout
-      })
-      .done(function (data) {
-        if (data.status === 0) {
-          if (cacheCondition(data)) {
-            storeInCache(data);
-          }
-          promise.resolve(data);
-        } else {
-          promise.reject(data);
+      var fetchFunction = function (storeInCache) {
+        if (options.timeout === 0) {
+          self.assistErrorCallback(options)({ status: -1 });
+          return;
         }
-      })
-      .fail(promise.reject);
-    };
 
-    return fetchCached.bind(self)($.extend({}, options, {
-      url: url,
-      hash: hash,
-      cacheType: 'optimizer',
-      fetchFunction: fetchFunction,
-      promise: promise
-    }));
+        return $.ajax({
+          type: 'post',
+          url: url,
+          data: data,
+          timeout: options.timeout
+        })
+          .done(function (data) {
+            if (data.status === 0) {
+              if (cacheCondition(data)) {
+                storeInCache(data);
+              }
+              promise.resolve(data);
+            } else {
+              promise.reject(data);
+            }
+          })
+          .fail(promise.reject);
+      };
+
+      return fetchCached.bind(self)($.extend({}, options, {
+        url: url,
+        hash: hash,
+        cacheType: 'optimizer',
+        fetchFunction: fetchFunction,
+        promise: promise
+      }));
+    }
+
+    var promise = $.Deferred();
+    if (options.tables) {
+      self.createNavOptDbTablesJson(options).done(function (json) {
+        promise.resolve(performFetch({
+          dbTables: json
+        }, json.hashCode()))
+      });
+    } else if (options.database) {
+      promise.resolve(performFetch({
+        database: options.database
+      }, options.database));
+    }
+    return promise;
   };
 
   ApiHelper.prototype.fetchHueDocsInteractive = function (query) {

+ 91 - 78
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -3884,101 +3884,114 @@
         getLocationsSub.remove();
       });
 
+      // The parser isn't aware of the DDL so sometimes it marks complex columns as tables
+      // I.e. "Impala SELECT a FROM b.c" Is 'b' a database or a table? If table then 'c' is complex
+      var identifyComplexLocations = function (locations, callback) {
+        apiHelper.getDatabases(self.snippet.type()).done(function (databases) {
+          locations.forEach(function (location) {
+            if (location.type === 'statement' || ((location.type === 'table' || location.type === 'column') && typeof location.identifierChain === 'undefined')) {
+              return;
+            }
+            if ((location.type === 'table' && location.identifierChain.length > 1) || (location.type === 'column' && location.identifierChain.length > 2)) {
+              var clonedChain = location.identifierChain.concat();
+              if (databases.indexOf(clonedChain[0].name.toLowerCase()) > -1) {
+                clonedChain.shift();
+                if (clonedChain.length > 1) {
+                  location.type = 'complex';
+                }
+              }
+            }
+          });
+          callback();
+        });
+      };
+
       var locationWorkerSub = huePubSub.subscribe('ace.sql.location.worker.message', function (e) {
         if (e.data.id !== self.snippet.id()) {
           return;
         }
 
-        lastKnownLocations = {
-          id: self.editorId,
-          type: self.snippet.type(),
-          defaultDatabase: self.snippet.database(),
-          locations: e.data.locations,
-          activeStatementLocations: e.data.activeStatementLocations,
-          totalStatementCount: e.data.totalStatementCount,
-          activeStatementIndex: e.data.activeStatementIndex
-        };
-
-        // Clear out old parse locations to prevent them from being shown when there's a syntax error in the statement
-        while(activeTokens.length > 0) {
-          delete activeTokens.pop().parseLocation;
-        }
+        identifyComplexLocations(e.data.locations, function () {
+          lastKnownLocations = {
+            id: self.editorId,
+            type: self.snippet.type(),
+            defaultDatabase: self.snippet.database(),
+            locations: e.data.locations,
+            activeStatementLocations: e.data.activeStatementLocations,
+            totalStatementCount: e.data.totalStatementCount,
+            activeStatementIndex: e.data.activeStatementIndex
+          };
 
-        e.data.locations.forEach(function (location) {
-          if (location.type === 'statement' || ((location.type === 'table' || location.type === 'column') && typeof location.identifierChain === 'undefined')) {
-            return;
+          // Clear out old parse locations to prevent them from being shown when there's a syntax error in the statement
+          while(activeTokens.length > 0) {
+            delete activeTokens.pop().parseLocation;
           }
-          if ((location.type === 'table' && location.identifierChain.length > 1) || (location.type === 'column' && location.identifierChain.length > 2)) {
-            var clonedChain = location.identifierChain.concat();
-            var dbFound = false;
-            if (apiHelper.containsDatabase(self.snippet.type(), clonedChain[0].name)) {
-              clonedChain.shift();
-              dbFound = true;
-            }
-            if (dbFound && clonedChain.length > 1) {
-              location.type = 'complex';
+
+          e.data.locations.forEach(function (location) {
+            if (location.type === 'statement' || ((location.type === 'table' || location.type === 'column') && typeof location.identifierChain === 'undefined')) {
+              return;
             }
-          }
 
-          var token = self.editor.getSession().getTokenAt(location.location.first_line - 1, location.location.first_column);
+            var token = self.editor.getSession().getTokenAt(location.location.first_line - 1, location.location.first_column);
 
-          if (token && token.value && /`$/.test(token.value)) {
-            // Ace getTokenAt() thinks the first ` is a token, column +1 will include the first and last.
-            token = self.editor.getSession().getTokenAt(location.location.first_line - 1, location.location.first_column + 1);
-          }
-          if (token && token.value && /^\s*\$\{\s*$/.test(token.value)) {
-            token = null;
-          }
-          if (token && token.value) {
-            var AceRange = ace.require('ace/range').Range;
-            // The Ace tokenizer also splits on '{', '(' etc. hence the actual value;
-            token.actualValue = self.editor.getSession().getTextRange(new AceRange(location.location.first_line - 1, location.location.first_column - 1, location.location.last_line - 1, location.location.last_column - 1));
-          }
-
-          if (token !== null) {
-            token.parseLocation = location;
-            activeTokens.push(token);
-            if (location.type === 'column' && typeof location.tables !== 'undefined' && location.identifierChain.length === 1) {
-              var findIdentifierChainInTable = function (tablesToGo) {
-                var nextTable = tablesToGo.shift();
-                if (typeof nextTable.subQuery === 'undefined') {
-                  apiHelper.fetchAutocomplete({
-                    sourceType: self.snippet.type(),
-                    defaultDatabase: self.snippet.database(),
-                    identifierChain: nextTable.identifierChain,
-                    silenceErrors: true,
-                    successCallback: function (data) {
-                      if (typeof data.columns !== 'undefined' && data.columns.indexOf(location.identifierChain[0].name.toLowerCase()) !== -1) {
-                        location.identifierChain = nextTable.identifierChain.concat(location.identifierChain);
-                        delete location.tables;
-                        self.verifyExists(token, e.data.locations);
-                      } else if (tablesToGo.length > 0) {
-                        findIdentifierChainInTable(tablesToGo);
-                      } else {
-                        self.verifyExists(token, e.data.locations);
+            if (token && token.value && /`$/.test(token.value)) {
+              // Ace getTokenAt() thinks the first ` is a token, column +1 will include the first and last.
+              token = self.editor.getSession().getTokenAt(location.location.first_line - 1, location.location.first_column + 1);
+            }
+            if (token && token.value && /^\s*\$\{\s*$/.test(token.value)) {
+              token = null;
+            }
+            if (token && token.value) {
+              var AceRange = ace.require('ace/range').Range;
+              // The Ace tokenizer also splits on '{', '(' etc. hence the actual value;
+              token.actualValue = self.editor.getSession().getTextRange(new AceRange(location.location.first_line - 1, location.location.first_column - 1, location.location.last_line - 1, location.location.last_column - 1));
+            }
+
+            if (token !== null) {
+              token.parseLocation = location;
+              activeTokens.push(token);
+              if (location.type === 'column' && typeof location.tables !== 'undefined' && location.identifierChain.length === 1) {
+                var findIdentifierChainInTable = function (tablesToGo) {
+                  var nextTable = tablesToGo.shift();
+                  if (typeof nextTable.subQuery === 'undefined') {
+                    apiHelper.fetchAutocomplete({
+                      sourceType: self.snippet.type(),
+                      defaultDatabase: self.snippet.database(),
+                      identifierChain: nextTable.identifierChain,
+                      silenceErrors: true,
+                      successCallback: function (data) {
+                        if (typeof data.columns !== 'undefined' && data.columns.indexOf(location.identifierChain[0].name.toLowerCase()) !== -1) {
+                          location.identifierChain = nextTable.identifierChain.concat(location.identifierChain);
+                          delete location.tables;
+                          self.verifyExists(token, e.data.locations);
+                        } else if (tablesToGo.length > 0) {
+                          findIdentifierChainInTable(tablesToGo);
+                        } else {
+                          self.verifyExists(token, e.data.locations);
+                        }
                       }
-                    }
-                  })
-                } else if (tablesToGo.length > 0) {
-                  findIdentifierChainInTable(tablesToGo);
-                } else {
+                    })
+                  } else if (tablesToGo.length > 0) {
+                    findIdentifierChainInTable(tablesToGo);
+                  } else {
+                    self.verifyExists(token, e.data.locations);
+                  }
+                };
+                if (location.tables.length > 1) {
+                  findIdentifierChainInTable(location.tables.concat());
+                } else if (location.tables.length == 1 && location.tables[0].identifierChain) {
+                  location.identifierChain = location.tables[0].identifierChain.concat(location.identifierChain);
+                  delete location.tables;
                   self.verifyExists(token, e.data.locations);
                 }
-              };
-              if (location.tables.length > 1) {
-                findIdentifierChainInTable(location.tables.concat());
-              } else if (location.tables.length == 1 && location.tables[0].identifierChain) {
-                location.identifierChain = location.tables[0].identifierChain.concat(location.identifierChain);
-                delete location.tables;
+              } else {
                 self.verifyExists(token, e.data.locations);
               }
-            } else {
-              self.verifyExists(token, e.data.locations);
             }
-          }
-        });
+          });
 
-        huePubSub.publish('editor.active.locations', lastKnownLocations);
+          huePubSub.publish('editor.active.locations', lastKnownLocations);
+        });
       });
 
       self.disposeFunctions.push(function () {

+ 19 - 15
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter2.js

@@ -463,24 +463,28 @@ var SqlAutocompleter2 = (function () {
 
       $.when(topColumnsDeferral, suggestColumnsDeferral).then(function (topColumns, suggestions) {
         if (topColumns.length > 0) {
-          suggestions.forEach(function (suggestion) {
-            var path = '';
-            if (!self.snippet.getApiHelper().isDatabase(suggestion.table.identifierChain[0].name, self.snippet.type())) {
-              path = database + '.';
-            }
-            path += $.map(suggestion.table.identifierChain, function (identifier) { return identifier.name }).join('.') + '.' + suggestion.value.replace(/[\[\]]/g, '');
-            for (var i = 0; i < topColumns.length; i++) {
-              // TODO: Switch to map once nav opt API is stable
-              if (path.toLowerCase().indexOf(topColumns[i].path.toLowerCase()) !== -1) {
-                suggestion.weight += Math.min(topColumns[i].columnCount, 99);
-                suggestion.meta = suggestion.meta + ' *';
-                suggestion.docHTML = self.createTopColumnHtml(topColumns[i]);
-                break;
+          self.snippet.getApiHelper().getDatabases(self.snippet.type()).done(function (databases) {
+            suggestions.forEach(function (suggestion) {
+              var path = '';
+              if (!suggestion.table.identifierChain[0].name || databases.indexOf(suggestion.table.identifierChain[0].name.toLowerCase() === -1)) {
+                path = database + '.';
               }
-            }
+              path += $.map(suggestion.table.identifierChain, function (identifier) { return identifier.name }).join('.') + '.' + suggestion.value.replace(/[\[\]]/g, '');
+              for (var i = 0; i < topColumns.length; i++) {
+                // TODO: Switch to map once nav opt API is stable
+                if (path.toLowerCase().indexOf(topColumns[i].path.toLowerCase()) !== -1) {
+                  suggestion.weight += Math.min(topColumns[i].columnCount, 99);
+                  suggestion.meta = suggestion.meta + ' *';
+                  suggestion.docHTML = self.createTopColumnHtml(topColumns[i]);
+                  break;
+                }
+              }
+            });
+            mergeNavOptColDeferral.resolve();
           });
+        } else {
+          mergeNavOptColDeferral.resolve();
         }
-        mergeNavOptColDeferral.resolve();
       });
 
       if (self.snippet.type() === 'hive' && /[^\.]$/.test(beforeCursor)) {

+ 44 - 25
desktop/core/src/desktop/static/desktop/spec/apiHelperSpec.js

@@ -59,85 +59,101 @@
         expect(subject.successResponseIsError({ traceback: {} })).toBeTruthy();
       });
     });
-    
+
     describe('NavOpt', function () {
       describe('Tables JSON generation', function () {
         it('should add the default database when no database is found in the identifier chain', function () {
-          spyOn(subject, 'containsDatabase').and.callFake(function () {
-            return false;
+          spyOn(subject, 'getDatabases').and.callFake(function () {
+            return $.Deferred().resolve([]);
           });
 
-          var result = subject.createNavOptDbTablesJson({
+          var promise = subject.createNavOptDbTablesJson({
             defaultDatabase: 'default',
             sourceType: 'hive',
             tables: [{ identifierChain: [{ name: 'some_table' }] }]
           });
 
-          expect(result).toEqual('["default.some_table"]');
+          expect(promise.state()).toEqual('resolved');
+          promise.done(function (result) {
+            expect(result).toEqual('["default.some_table"]');
+          });
         });
 
         it('should add the database from the identifier chain if found', function () {
-          spyOn(subject, 'containsDatabase').and.callFake(function () {
-            return true;
+          spyOn(subject, 'getDatabases').and.callFake(function () {
+            return $.Deferred().resolve(['some_db']);
           });
 
-          var result = subject.createNavOptDbTablesJson({
+          var promise = subject.createNavOptDbTablesJson({
             defaultDatabase: 'default',
             sourceType: 'hive',
             tables: [{ identifierChain: [{ name: 'some_db' }, { name: 'some_table' }] }]
           });
 
-          expect(result).toEqual('["some_db.some_table"]');
+          expect(promise.state()).toEqual('resolved');
+          promise.done(function (result) {
+            expect(result).toEqual('["some_db.some_table"]');
+          });
+
         });
 
         it('should support tables with same names as databases', function () {
-          spyOn(subject, 'containsDatabase').and.callFake(function () {
-            return true;
+          spyOn(subject, 'getDatabases').and.callFake(function () {
+            return $.Deferred().resolve(['table_and_db_name']);
           });
 
-          var result = subject.createNavOptDbTablesJson({
+          var promise = subject.createNavOptDbTablesJson({
             defaultDatabase: 'default',
             sourceType: 'hive',
             tables: [{ identifierChain: [{ name: 'table_and_db_name' }] }]
           });
 
-          expect(result).toEqual('["default.table_and_db_name"]');
+          expect(promise.state()).toEqual('resolved');
+          promise.done(function (result) {
+            expect(result).toEqual('["default.table_and_db_name"]');
+          });
         });
 
         it('should support tables with same names as databases', function () {
-          spyOn(subject, 'containsDatabase').and.callFake(function () {
-            return true;
+          spyOn(subject, 'getDatabases').and.callFake(function () {
+            return $.Deferred().resolve(['table_and_db_name']);
           });
 
-          var result = subject.createNavOptDbTablesJson({
+          var promise = subject.createNavOptDbTablesJson({
             defaultDatabase: 'default',
             sourceType: 'hive',
             tables: [{ identifierChain: [{ name: 'table_and_db_name' }, { name: 'table_and_db_name' }] }]
           });
 
-          expect(result).toEqual('["table_and_db_name.table_and_db_name"]');
+          expect(promise.state()).toEqual('resolved');
+          promise.done(function (result) {
+            expect(result).toEqual('["table_and_db_name.table_and_db_name"]');
+          });
         });
 
         it('should support multiple tables some with databases some without', function () {
-          spyOn(subject, 'containsDatabase').and.callFake(function () {
-            return true;
+          spyOn(subject, 'getDatabases').and.callFake(function () {
+            return $.Deferred().resolve(['a_table_from_default', 'other_db']);
           });
 
-          var result = subject.createNavOptDbTablesJson({
+          var promise = subject.createNavOptDbTablesJson({
             defaultDatabase: 'default',
             sourceType: 'hive',
             tables: [{ identifierChain: [{ name: 'a_table_from_default' }] }, { identifierChain: [{ name: 'other_db' }, { name: 'a_table_from_other_db' }] }]
           });
 
-          expect(result).toEqual('["default.a_table_from_default","other_db.a_table_from_other_db"]');
+          expect(promise.state()).toEqual('resolved');
+          promise.done(function (result) {
+            expect(result).toEqual('["default.a_table_from_default","other_db.a_table_from_other_db"]');
+          });
         });
 
         it('should remove duplicates', function () {
-          spyOn(subject, 'containsDatabase').and.callFake(function () {
-            return true;
+          spyOn(subject, 'getDatabases').and.callFake(function () {
+            return $.Deferred().resolve(['sometable', 'somedb', 'default']);
           });
 
-          var result = subject.createNavOptDbTablesJson({
+          var promise = subject.createNavOptDbTablesJson({
             defaultDatabase: 'default',
             sourceType: 'hive',
             tables: [
@@ -152,7 +168,10 @@
             ]
           });
 
-          expect(result).toEqual('["default.someTable","someDb.someTable","someDb.otherTable"]');
+          expect(promise.state()).toEqual('resolved');
+          promise.done(function (result) {
+            expect(result).toEqual('["default.someTable","someDb.someTable","someDb.otherTable"]');
+          });
         })
       });
     })