Browse Source

HUE-1959 [core] Include database references in SQL autocomplete suggestions

Database references are added below any table or alias suggestions.
Johan Ahlen 10 years ago
parent
commit
1682087a2c

+ 4 - 2
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -2124,8 +2124,10 @@
         if (e.command.name === "insertstring") {
           var triggerAutocomplete = ((editor.session.getMode().$id == "ace/mode/hive" || editor.session.getMode().$id == "ace/mode/impala") && e.args == ".") || /["']\/[^\/]*/.test(editor.getTextBeforeCursor());
           if(e.args.toLowerCase().indexOf("? from ") == 0) {
-            editor.moveCursorTo(editor.getCursorPosition().row, editor.getCursorPosition().column - e.args.length + 1);
-            editor.removeTextBeforeCursor(1);
+            if (e.args[e.args.length - 1] !== '.') {
+              editor.moveCursorTo(editor.getCursorPosition().row, editor.getCursorPosition().column - e.args.length + 1);
+              editor.removeTextBeforeCursor(1);
+            }
             triggerAutocomplete = true;
           }
 

+ 78 - 17
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter.js

@@ -48,6 +48,7 @@
   }
 
   SqlAutocompleter.prototype.getFromReferenceIndex = function (statement) {
+    var self = this;
     var result = {
       tables: {},
       complex: {}
@@ -59,21 +60,44 @@
       if (upToMatch) {
         refsRaw = $.trim(refsRaw.substring(0, upToMatch.index));
       }
-      var refs = refsRaw.split(/\s*(?:,|\bJOIN\b)\s*/i);
-      refs.sort();
+      var refs = $.map(refsRaw.split(/\s*(?:,|\bJOIN\b)\s*/i), function (ref) {
+        if (ref.indexOf('.') > 0) {
+          var refParts = ref.split('.');
+          if(self.snippet.getAssistHelper().availableDatabases().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.indexOf('(') == -1) {
-          var refMatch = tableRefRaw.match(/\s*(\S+)\s*(\S+)?\s*/);
+        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]] = refParts[0];
+              result.tables[refMatch[2]] = {
+                table: refParts[0],
+                database: tableRefRaw.database
+              };
             } else {
               result.complex[refMatch[2]] = refParts;
             }
           } else {
-            result.tables[refMatch[1]] = refMatch[1];
+            result.tables[refMatch[1]] = {
+              table: refMatch[1],
+              database: tableRefRaw.database
+            };
           }
         }
       })
@@ -182,7 +206,11 @@
       fields = fromReferences.complex[complexRef].concat(fields);
     }
     if (fields[0] in fromReferences.tables) {
-      tableName = fromReferences.tables[fields.shift()];
+      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;
@@ -223,9 +251,11 @@
     }
   };
 
-  SqlAutocompleter.prototype.extractFields = function (data, valuePrefix, includeStar, extraSuggestions) {
+  SqlAutocompleter.prototype.extractFields = function (data, valuePrefix, includeStar, extraSuggestions, excludeDatabases) {
+    var self = this;
     var fields = [];
     var result = [];
+    var prependedFields = extraSuggestions || [];
 
     if (data.type == "struct") {
       fields = $.map(data.fields, function(field) {
@@ -251,17 +281,27 @@
           type: "table"
         }
       });
+      if (! excludeDatabases) {
+        // No FROM prefix
+        prependedFields = prependedFields.concat(fields);
+        fields = $.map(self.snippet.getAssistHelper().availableDatabases(), function(database) {
+          return {
+            name: database + ".",
+            type: "database"
+          }
+        })
+      }
     }
 
     fields.sort(function (a, b) {
       return a.name.localeCompare(b.name);
     });
 
-    if (extraSuggestions) {
-      extraSuggestions.sort(function (a, b) {
+    if (prependedFields) {
+      prependedFields.sort(function (a, b) {
         return a.name.localeCompare(b.name);
       });
-      fields = extraSuggestions.concat(fields);
+      fields = prependedFields.concat(fields);
     }
 
     fields.forEach(function(field, idx) {
@@ -346,6 +386,10 @@
     var fromAfter = afterMatcher != null && afterMatcher[0] === "FROM";
 
     if (tableNameAutoComplete || (selectBefore && !fromAfter)) {
+      var dbRefMatch = beforeCursor.match(/.*from\s+([^\.\s]+).$/i);
+      if (dbRefMatch) {
+        database = dbRefMatch[1];
+      }
 
       self.snippet.getAssistHelper().fetchTables(self.snippet, database, function (data) {
         var fromKeyword = "";
@@ -363,8 +407,9 @@
           }
           fromKeyword += " ";
         }
-        callback(self.extractFields(data, fromKeyword));
+        callback(self.extractFields(data, fromKeyword, false, [], dbRefMatch !== null));
       }, onFailure, editor);
+      return;
     } else if ((selectBefore && fromAfter) || fieldTermBefore || impalaFieldRef) {
       var partialTermsMatch = beforeCursor.match(/([^\s\(\-\+\<\>\,]*)$/);
       var parts = partialTermsMatch ? partialTermsMatch[0].split(".") : [];
@@ -382,11 +427,18 @@
 
       if (parts.length > 0 && fromReferences.tables[parts[0]]) {
         // SELECT tableref.column.
-        tableName = fromReferences.tables[parts.shift()];
+        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]];
+          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 {
@@ -400,7 +452,7 @@
           return {
             value: key + (upToNextStatement.indexOf(".") == 0 ? "" : "."),
             score: 1000 - count++,
-            meta: fromReferences.tables[key] == key ? 'table' : 'alias'
+            meta: fromReferences.tables[key].table == key ? 'table' : 'alias'
           };
         });
 
@@ -422,7 +474,12 @@
         // 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
-        tableName = fromReferences.tables[Object.keys(fromReferences.tables)[0]];
+        var tableRef = fromReferences.tables[Object.keys(fromReferences.tables)[0]];
+        tableName = tableRef.table;
+        if (tableRef.database !== null) {
+          database = tableRef.database;
+        }
+
         if (conditionMatch && impalaSyntax) {
           var tableRefs = [{
             value: tableName,
@@ -433,7 +490,11 @@
           return;
         }
       } else if (parts.length > 0 && viewReferences.index[parts[0]] && viewReferences.index[parts[0]].leadingPath.length > 0) {
-        tableName = fromReferences.tables[viewReferences.index[parts[0]].leadingPath[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();

+ 158 - 75
desktop/core/src/desktop/static/desktop/spec/sqlAutocompleterSpec.js

@@ -30,7 +30,7 @@ define([
       notebook: {
         getContext: function() { return ko.mapping.fromJS(null) }
       },
-      activeDatabase: "testDb",
+      activeDatabase: "database_one",
       user: "testUser"
     });
 
@@ -48,6 +48,8 @@ define([
       }
       snippet.type(newType);
       assistHelper.load(snippet, $.noop);
+      assistHelper.availableDatabases(["database_one", "database_two"]);
+      assistHelper.activeDatabase("database_one");
       window.setTimeout(function() {
         callback();
       }, 0);
@@ -64,7 +66,7 @@ define([
         var response;
         if (firstUrlPart == "/notebook/api/autocomplete/") {
           response = {
-            databases: ["testDb"]
+            databases: ["database_one"]
           };
         } else {
           expect(ajaxHelper.responseForUrls[firstUrlPart]).toBeDefined("fake response for url " + firstUrlPart + " not found");
@@ -128,14 +130,12 @@ define([
 
     describe("database awareness", function() {
       it("should suggest databases after use", function () {
-        assistHelper.availableDatabases(["database_one", "database_two"]);
         assertAutoComplete({
           serverResponses: {},
           beforeCursor: "USE ",
           afterCursor: "",
           expectedSuggestions: ["database_one", "database_two"]
         });
-        assistHelper.availableDatabases(["testDb"]);
       });
 
       it("should use a use statement before the cursor if present", function () {
@@ -147,7 +147,7 @@ define([
           },
           beforeCursor: "USE other_db; \n\tSELECT ",
           afterCursor: "",
-          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2"]
+          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2", "? FROM database_one.", "? FROM database_two."]
         });
       });
 
@@ -160,7 +160,7 @@ define([
           },
           beforeCursor: "USE other_db; USE closest_db; \n\tSELECT ",
           afterCursor: "",
-          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2"]
+          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2", "? FROM database_one.", "? FROM database_two."]
         });
       });
 
@@ -173,7 +173,7 @@ define([
           },
           beforeCursor: "USE other_db; USE closest_db; \n\tSELECT ",
           afterCursor: "USE some_other_db;",
-          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2"]
+          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2", "? FROM database_one.", "? FROM database_two."]
         });
       });
     });
@@ -182,65 +182,78 @@ define([
       it("should suggest table names with no columns", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb" : {
+            "/notebook/api/autocomplete/database_one" : {
               tables: ["testTable1", "testTable2"]
             }
           },
           beforeCursor: "SELECT ",
           afterCursor: "",
-          expectedSuggestions: ["? FROM testTable1", "? FROM testTable2"]
+          expectedSuggestions: ["? FROM testTable1", "? FROM testTable2", "? FROM database_one.", "? FROM database_two."]
         });
       });
 
       it("should follow keyword case for table name completion", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb" : {
+            "/notebook/api/autocomplete/database_one" : {
               tables: ["testTable1", "testTable2"]
             }
           },
           beforeCursor: "select ",
           afterCursor: "",
-          expectedSuggestions: ["? from testTable1", "? from testTable2"]
+          expectedSuggestions: ["? from testTable1", "? from testTable2", "? from database_one.", "? from database_two."]
         });
       });
 
       it("should suggest table names with *", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb" : {
+            "/notebook/api/autocomplete/database_one" : {
               tables: ["testTable1", "testTable2"]
             }
           },
           beforeCursor: "SELECT *",
           afterCursor: "",
-          expectedSuggestions: [" FROM testTable1", " FROM testTable2"]
+          expectedSuggestions: [" FROM testTable1", " FROM testTable2", " FROM database_one.", " FROM database_two."]
         });
       });
 
       it("should suggest table names with started FROM", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb" : {
+            "/notebook/api/autocomplete/database_one" : {
               tables: ["testTable1", "testTable2"]
             }
           },
           beforeCursor: "SELECT * fr",
           afterCursor: "",
-          expectedSuggestions: ["FROM testTable1", "FROM testTable2"]
+          expectedSuggestions: ["FROM testTable1", "FROM testTable2", "FROM database_one.", "FROM database_two."]
         });
       });
 
       it("should suggest table names after FROM", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb" : {
+            "/notebook/api/autocomplete/database_one" : {
               tables: ["testTable1", "testTable2"]
             }
           },
           beforeCursor: "SELECT * FROM ",
           afterCursor: "",
-          expectedSuggestions: ["testTable1", "testTable2"]
+          expectedSuggestions: ["testTable1", "testTable2", "database_one.", "database_two."]
+        });
+      });
+
+      it("should suggest table names after FROM with database reference", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/database_two" : {
+              tables: ["testTable3", "testTable4"]
+            }
+          },
+          beforeCursor: "SELECT * FROM database_two.",
+          afterCursor: "",
+          expectedSuggestions: ["testTable3", "testTable4"]
         });
       });
 
@@ -291,10 +304,10 @@ define([
       it("should suggest struct from map values", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "string", "name": "fieldB" },
@@ -314,10 +327,10 @@ define([
       it("should suggest struct from map values without a given key", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "string", "name": "fieldB" }
@@ -334,10 +347,10 @@ define([
       it("should suggest struct from structs from map values", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value/fieldC" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value/fieldC" : {
               fields: [
                 {"type": "string", "name": "fieldC_A" },
                 {"type": "boolean", "name": "fieldC_B"}
@@ -354,10 +367,10 @@ define([
       it("should suggest struct from structs from arrays", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
               type: "array"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item/fieldC" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray/item/fieldC" : {
               fields: [
                 {"type": "string", "name": "fieldC_A" },
                 {"type": "boolean", "name": "fieldC_B"}
@@ -374,13 +387,13 @@ define([
       it("should suggest structs from maps from arrays", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
               type: "array"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray/item/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item/testMap/value" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray/item/testMap/value" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "boolean", "name": "fieldB"}
@@ -398,7 +411,7 @@ define([
         it("should suggest structs from exploded item references to arrays", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
+              "/notebook/api/autocomplete/database_one/testTable/testArray/item": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -415,7 +428,7 @@ define([
         it("should suggest structs from multiple exploded item references to arrays", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/testArrayA/item": {
+              "/notebook/api/autocomplete/database_one/testTable/testArrayA/item": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -434,7 +447,7 @@ define([
         it("should support table references as arguments of explode function", function() {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable2/testArrayB/item": {
+              "/notebook/api/autocomplete/database_one/testTable2/testArrayB/item": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -453,7 +466,7 @@ define([
         it("should suggest structs from exploded item references to exploded item references to arrays ", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/testArray1/item/testArray2/item": {
+              "/notebook/api/autocomplete/database_one/testTable/testArray1/item/testArray2/item": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -473,7 +486,7 @@ define([
         it("should suggest structs from references to exploded arrays", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
+              "/notebook/api/autocomplete/database_one/testTable/testArray/item": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -490,7 +503,7 @@ define([
         it("should suggest posexploded references to arrays", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/testArray/item": {
+              "/notebook/api/autocomplete/database_one/testTable/testArray/item": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -507,7 +520,7 @@ define([
         it("should suggest exploded references to map values", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/testMap/value": {
+              "/notebook/api/autocomplete/database_one/testTable/testMap/value": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -524,7 +537,7 @@ define([
         it("should suggest exploded references to map values from view references", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/testMap/value": {
+              "/notebook/api/autocomplete/database_one/testTable/testMap/value": {
                 fields: [
                   {"type": "string", "name": "fieldA"},
                   {"type": "string", "name": "fieldB"}
@@ -550,7 +563,7 @@ define([
         it("should suggest references to exploded references", function () {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable" : {
+              "/notebook/api/autocomplete/database_one/testTable" : {
                 columns: ["testTableColumn1", "testTableColumn2"]
               }
             },
@@ -571,7 +584,7 @@ define([
       it("should not suggest struct from map values with hive style syntax", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap[\"anyKey\"]" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap[\"anyKey\"]" : {
               someResponse: true
             }
           },
@@ -584,10 +597,10 @@ define([
       it("should suggest fields from nested structs", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/columnA" : {
+            "/notebook/api/autocomplete/database_one/testTable/columnA" : {
               // Impala has to query every part for it's type, for hive '[' and ']' is used to indicate map or array.
             },
-            "/notebook/api/autocomplete/testDb/testTable/columnA/fieldC" : {
+            "/notebook/api/autocomplete/database_one/testTable/columnA/fieldC" : {
               fields: [
                 {"type": "string", "name": "fieldC_A" },
                 {"type": "boolean", "name": "fieldC_B"}
@@ -605,10 +618,10 @@ define([
       it("should suggest fields from map values of type structs", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "string", "name": "fieldB" }
@@ -625,10 +638,10 @@ define([
       it("should suggest map value if type is scalar", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
               type: "int"
             }
           },
@@ -641,10 +654,10 @@ define([
       it("should not suggest items from arrays if complex in select clause", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
               type: "array"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray/item" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "string", "name": "fieldB" }
@@ -661,10 +674,10 @@ define([
       it("should suggest items from arrays if scalar in select clause", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
               type: "array"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray/item" : {
               type: "int"
             }
           },
@@ -677,10 +690,10 @@ define([
       it("should suggest items from arrays if complex in from clause", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testArray" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
               type: "array"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testArray/item" : {
+            "/notebook/api/autocomplete/database_one/testTable/testArray/item" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "string", "name": "fieldB" }
@@ -698,7 +711,7 @@ define([
       it("should suggest columns from table refs in from clause", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
+            "/notebook/api/autocomplete/database_one/testTable" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -720,10 +733,10 @@ define([
       it("should suggest fields with key and value in where clause from map values of type structs", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "string", "name": "fieldB" }
@@ -740,10 +753,10 @@ define([
       it("should suggest fields in where clause from map values of type structs", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
               fields: [
                 {"type": "string", "name": "fieldA" },
                 {"type": "string", "name": "fieldB" }
@@ -760,7 +773,7 @@ define([
       it("should suggest values for map keys", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap/key" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/key" : {
               sample: ["value1", "value2"],
               type: "string"
             }
@@ -774,7 +787,7 @@ define([
       it("should suggest values for columns in conditions", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/id" : {
+            "/notebook/api/autocomplete/database_one/testTable/id" : {
               sample: [1, 2, 3],
               type: "int"
             }
@@ -788,10 +801,10 @@ define([
       it("should suggest values from fields in map values in conditions", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable/testMap" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
               type: "map"
             },
-            "/notebook/api/autocomplete/testDb/testTable/testMap/value/field" : {
+            "/notebook/api/autocomplete/database_one/testTable/testMap/value/field" : {
               sample: [1, 2, 3],
               type: "int"
             }
@@ -808,7 +821,7 @@ define([
       it("should suggest columns for table", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
+            "/notebook/api/autocomplete/database_one/testTable" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -818,10 +831,23 @@ define([
         });
       });
 
+      it("should suggest columns for table with database prefix", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/database_two/testTable" : {
+              columns: ["testTableColumn1", "testTableColumn2"]
+            }
+          },
+          beforeCursor: "SELECT ",
+          afterCursor: " FROM database_two.testTable",
+          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+        });
+      });
+
       it("should suggest columns for table after WHERE", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
+            "/notebook/api/autocomplete/database_one/testTable" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -834,7 +860,7 @@ define([
       it("should suggest columns for table after ORDER BY ", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
+            "/notebook/api/autocomplete/database_one/testTable" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -844,10 +870,23 @@ define([
         });
       });
 
+      it("should suggest columns for table after ORDER BY with db reference", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/database_two/testTable" : {
+              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({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
+            "/notebook/api/autocomplete/database_one/testTable" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -860,7 +899,7 @@ define([
       it("should suggest columns for table after ON ", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable1" : {
+            "/notebook/api/autocomplete/database_one/testTable1" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -870,10 +909,23 @@ define([
         });
       });
 
+      it("should suggest columns for table after ON with database reference", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/database_two/testTable1" : {
+              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({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
+            "/notebook/api/autocomplete/database_one/testTable" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -886,7 +938,7 @@ define([
       it("should suggest columns with table alias", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable" : {
+            "/notebook/api/autocomplete/database_one/testTable" : {
               columns: ["testTableColumn1", "testTableColumn2"]
             }
           },
@@ -896,12 +948,25 @@ define([
         });
       });
 
+      it("should suggest columns with table alias from database reference", function() {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/database_two/testTable" : {
+              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 = {
-          "/notebook/api/autocomplete/testDb/testTableA": {
+          "/notebook/api/autocomplete/database_one/testTableA": {
             columns: ["testTableColumn1", "testTableColumn2"]
           },
-          "/notebook/api/autocomplete/testDb/testTableB": {
+          "/notebook/api/autocomplete/database_one/testTableB": {
             columns: ["testTableColumn3", "testTableColumn4"]
           }
         };
@@ -923,7 +988,7 @@ define([
         it("should suggest fields from columns that are structs", function() {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/columnA" : {
+              "/notebook/api/autocomplete/database_one/testTable/columnA" : {
                 fields: [
                   {"type": "string", "name": "fieldA" },
                   {"type": "boolean", "name": "fieldB" },
@@ -945,7 +1010,7 @@ define([
         it("should suggest fields from nested structs", function() {
           assertAutoComplete({
             serverResponses: {
-              "/notebook/api/autocomplete/testDb/testTable/columnA/fieldC" : {
+              "/notebook/api/autocomplete/database_one/testTable/columnA/fieldC" : {
                 fields: [
                   {"type": "string", "name": "fieldC_A" },
                   {"type": "boolean", "name": "fieldC_B"}
@@ -959,6 +1024,24 @@ define([
             expectedSuggestions: ["fieldC_A", "fieldC_B"]
           });
         });
+
+        it("should suggest fields from nested structs with database reference", function() {
+          assertAutoComplete({
+            serverResponses: {
+              "/notebook/api/autocomplete/database_two/testTable/columnA/fieldC" : {
+                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"]
+          });
+        });
       });
     });
 
@@ -967,13 +1050,13 @@ define([
       it("should suggest tables to join with", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb" : {
+            "/notebook/api/autocomplete/database_one" : {
               tables: ["testTable1", "testTable2"]
             }
           },
           beforeCursor: "SELECT * FROM testTable1 JOIN ",
           afterCursor: "",
-          expectedSuggestions: ["testTable1", "testTable2"]
+          expectedSuggestions: ["testTable1", "testTable2", "database_one.", "database_two."]
         });
       });
 
@@ -1007,7 +1090,7 @@ define([
       it("should suggest field references in join condition if table reference is present", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable2" : {
+            "/notebook/api/autocomplete/database_one/testTable2" : {
               columns: ["testColumn3", "testColumn4"]
             }
           },
@@ -1020,7 +1103,7 @@ define([
       it("should suggest field references in join condition if table reference is present from multiple tables", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable2" : {
+            "/notebook/api/autocomplete/database_one/testTable2" : {
               columns: ["testColumn3", "testColumn4"]
             }
           },
@@ -1033,7 +1116,7 @@ define([
       it("should suggest field references in join condition if table reference is present from multiple tables for multiple conditions", function() {
         assertAutoComplete({
           serverResponses: {
-            "/notebook/api/autocomplete/testDb/testTable1" : {
+            "/notebook/api/autocomplete/database_one/testTable1" : {
               columns: ["testColumn1", "testColumn2"]
             }
           },