Browse Source

[core] Make the SQL autocompleter aware of any USE statements before the cursor

Johan Ahlen 10 years ago
parent
commit
b884fc1

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

@@ -1530,7 +1530,7 @@ $(document).ready(function () {
     $(".CodeMirror-spinner").css("top", pos.top + "px").css("left", (pos.left - 4) + "px").show();
 
     var _statementAtCursor = getStatementAtCursor();
-    var _before = _statementAtCursor.statement.substr(0, _statementAtCursor.relativeIndex).replace(/;+$/, "");
+    var _before = codeMirror.getValue().substr(0, codeMirror.indexFromPos(codeMirror.getCursor()));
     var _after = _statementAtCursor.statement.substr(_statementAtCursor.relativeIndex).replace(/;+$/, "");
 
     autocompleter.autocomplete(_before, _after, function(suggestions) {

+ 4 - 4
desktop/core/src/desktop/static/desktop/js/assistHelper.js

@@ -184,16 +184,16 @@
     });
   };
 
-  AssistHelper.prototype.fetchTables = function(snippet, successCallback, errorCallback) {
+  AssistHelper.prototype.fetchTables = function(snippet, database, successCallback, errorCallback) {
     var self = this;
-    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + self.activeDatabase(), successCallback, errorCallback);
+    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + database, successCallback, errorCallback);
   };
 
-  AssistHelper.prototype.fetchFields = function(snippet, tableName, fields, successCallback, errorCallback, editor) {
+  AssistHelper.prototype.fetchFields = function(snippet, database, tableName, fields, successCallback, errorCallback, editor) {
     var self = this;
 
     var fieldPart = fields.length > 0 ? "/" + fields.join("/") : "";
-    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + self.activeDatabase() + "/" + tableName + fieldPart, successCallback, errorCallback, editor);
+    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + database + "/" + tableName + fieldPart, successCallback, errorCallback, editor);
   };
 
   AssistHelper.prototype.clearCache = function(snippet) {

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

@@ -64,7 +64,7 @@
       return;
     }
 
-    var before = editor.getTextBeforeCursor(";");
+    var before = editor.getTextBeforeCursor();
     var after = editor.getTextAfterCursor(";");
 
     self.autocomplete(before, after, function(result) {

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

@@ -1868,7 +1868,7 @@
       var refreshTables = function() {
         currentAssistTables = {};
         if (typeof assistHelper.activeDatabase() !== "undefined" && assistHelper.activeDatabase() != null) {
-          assistHelper.fetchTables(snippet, function(data) {
+          assistHelper.fetchTables(snippet, assistHelper.activeDatabase(), function(data) {
             $.each(data.tables, function(index, table) {
               currentAssistTables[table] = true;
             });

+ 33 - 22
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter.js

@@ -172,7 +172,7 @@
     return result;
   };
 
-  SqlAutocompleter.prototype.getValueReferences = function (conditionMatch, fromReferences, tableAndComplexRefs, callback) {
+  SqlAutocompleter.prototype.getValueReferences = function (conditionMatch, database, fromReferences, tableAndComplexRefs, callback) {
     var self = this;
 
     var fields = conditionMatch[1].split(".");
@@ -196,7 +196,7 @@
         if (remainingParts.length > 0 && remainingParts[0] == "value" || remainingParts[0] == "key") {
           fetchImpalaFields(remainingParts);
         } else {
-          self.snippet.getAssistHelper().fetchFields(self.snippet, tableName, completeFields, function (data) {
+          self.snippet.getAssistHelper().fetchFields(self.snippet, database, tableName, completeFields, function (data) {
             if (data.type === "map") {
               completeFields.push("value");
               fetchImpalaFields(remainingParts);
@@ -272,11 +272,13 @@
     return result;
   };
 
-  SqlAutocompleter.prototype.autocomplete = function(beforeCursor, afterCursor, callback, editor) {
+  SqlAutocompleter.prototype.autocomplete = function(beforeCursor, upToNextStatement, callback, editor) {
     var onFailure = function() {
       callback([]);
     };
 
+    var allStatements = beforeCursor.split(';');
+
     var self = this;
 
     var hiveSyntax = self.snippet.type() === "hive";
@@ -289,8 +291,8 @@
       return;
     }
 
-    var beforeCursorU = beforeCursor.toUpperCase();
-    var afterCursorU = afterCursor.toUpperCase();
+    var beforeCursorU = allStatements.pop().toUpperCase();
+    var afterCursorU = upToNextStatement.toUpperCase();
 
     var beforeMatcher = beforeCursorU.match(SQL_TERMS);
     var afterMatcher = afterCursorU.match(SQL_TERMS);
@@ -300,6 +302,15 @@
       return;
     }
 
+    var database = self.snippet.getAssistHelper().activeDatabase();
+    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;
+      }
+    }
+
     var keywordBeforeCursor = beforeMatcher[beforeMatcher.length - 1];
 
     var impalaFieldRef = impalaSyntax && beforeCursor.slice(-1) === '.';
@@ -323,7 +334,7 @@
 
     if (tableNameAutoComplete || (selectBefore && !fromAfter)) {
 
-      self.snippet.getAssistHelper().fetchTables(self.snippet, function (data) {
+      self.snippet.getAssistHelper().fetchTables(self.snippet, database, function (data) {
         var fromKeyword = "";
         if (selectBefore) {
           if (beforeCursor.indexOf("SELECT") > -1) {
@@ -350,8 +361,8 @@
       // SELECT tablename.colu => suggestion: "column"
       parts.pop();
 
-      var fromReferences = self.getFromReferenceIndex(beforeCursor + afterCursor);
-      var viewReferences = self.getViewReferenceIndex(beforeCursor + afterCursor, hiveSyntax);
+      var fromReferences = self.getFromReferenceIndex(beforeCursor + upToNextStatement);
+      var viewReferences = self.getViewReferenceIndex(beforeCursor + upToNextStatement, hiveSyntax);
       var conditionMatch = beforeCursor.match(/(\S+)\s*=\s*$/);
 
       var tableName = "";
@@ -374,7 +385,7 @@
         var count = 0;
         var tableRefs = $.map(Object.keys(fromReferences.tables), function (key, idx) {
           return {
-            value: key + (afterCursor.indexOf(".") == 0 ? "" : "."),
+            value: key + (upToNextStatement.indexOf(".") == 0 ? "" : "."),
             score: 1000 - count++,
             meta: fromReferences.tables[key] == key ? 'table' : 'alias'
           };
@@ -382,14 +393,14 @@
 
         var complexRefs = $.map(Object.keys(fromReferences.complex), function (key, idx) {
           return {
-            value: key + (afterCursor.indexOf(".") == 0 ? "" : "."),
+            value: key + (upToNextStatement.indexOf(".") == 0 ? "" : "."),
             score: 1000 - count++,
             meta: 'alias'
           };
         });
 
         if (conditionMatch && impalaSyntax) {
-          self.getValueReferences(conditionMatch, fromReferences, tableRefs.concat(complexRefs), callback);
+          self.getValueReferences(conditionMatch, database, fromReferences, tableRefs.concat(complexRefs), callback);
         } else {
           callback(tableRefs.concat(complexRefs));
         }
@@ -405,7 +416,7 @@
             score: 1000,
             meta: 'table'
           }];
-          self.getValueReferences(conditionMatch, fromReferences, tableRefs, callback);
+          self.getValueReferences(conditionMatch, database, fromReferences, tableRefs, callback);
           return;
         }
       } else if (parts.length > 0 && viewReferences.index[parts[0]] && viewReferences.index[parts[0]].leadingPath.length > 0) {
@@ -416,9 +427,9 @@
         return;
       }
 
-      var getFields = function (remainingParts, fields) {
+      var getFields = function (database, remainingParts, fields) {
         if (remainingParts.length == 0) {
-          self.snippet.getAssistHelper().fetchFields(self.snippet, tableName, fields, function(data) {
+          self.snippet.getAssistHelper().fetchFields(self.snippet, database, tableName, fields, function(data) {
             if (fields.length == 0) {
               callback(self.extractFields(data, "", !fieldTermBefore && !impalaFieldRef, viewReferences.allViewReferences));
             } else {
@@ -447,19 +458,19 @@
               if (viewReferences.index[part].addition) {
                 fields.push(viewReferences.index[part].addition);
               }
-              getFields(remainingParts, fields);
+              getFields(database, remainingParts, fields);
               return;
             }
             var mapOrArrayMatch = part.match(/([^\[]*)\[[^\]]*\]$/i);
             if (mapOrArrayMatch !== null) {
               fields.push(mapOrArrayMatch[1]);
-              self.snippet.getAssistHelper().fetchFields(self.snippet, tableName, fields, function(data) {
+              self.snippet.getAssistHelper().fetchFields(self.snippet, database, tableName, fields, function(data) {
                 if (data.type === "map") {
                   fields.push("value");
-                  getFields(remainingParts, fields);
+                  getFields(database, remainingParts, fields);
                 } else if (data.type === "array") {
                   fields.push("item");
-                  getFields(remainingParts, fields);
+                  getFields(database, remainingParts, fields);
                 } else {
                   onFailure();
                 }
@@ -473,7 +484,7 @@
             }
             // 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.getAssistHelper().fetchFields(self.snippet, tableName, fields, function(data) {
+            self.snippet.getAssistHelper().fetchFields(self.snippet, database, tableName, fields, function(data) {
               if (data.type === "map") {
                 remainingParts.unshift("value");
               } else if (data.type === "array") {
@@ -502,16 +513,16 @@
                 callback(self.extractFields(data, "", false, extraFields));
                 return;
               }
-              getFields(remainingParts, fields);
+              getFields(database, remainingParts, fields);
             }, onFailure, editor);
             return; // break recursion, it'll be async above
           }
           fields.push(part);
         }
-        getFields(remainingParts, fields);
+        getFields(database, remainingParts, fields);
       };
 
-      getFields(parts, []);
+      getFields(database, parts, []);
     } else {
       onFailure();
     }

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

@@ -126,6 +126,47 @@ define([
       });
     });
 
+    describe("database awareness", function() {
+      it("should use a use statement before the cursor if present", function () {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/other_db" : {
+              tables: ["otherTable1", "otherTable2"]
+            }
+          },
+          beforeCursor: "USE other_db; \n\tSELECT ",
+          afterCursor: "",
+          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2"]
+        });
+      });
+
+      it("should use the last use statement before the cursor if multiple are present", function () {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/closest_db" : {
+              tables: ["otherTable1", "otherTable2"]
+            }
+          },
+          beforeCursor: "USE other_db; USE closest_db; \n\tSELECT ",
+          afterCursor: "",
+          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2"]
+        });
+      });
+
+      it("should use the use statement before the cursor if multiple are present after the cursor", function () {
+        assertAutoComplete({
+          serverResponses: {
+            "/notebook/api/autocomplete/closest_db" : {
+              tables: ["otherTable1", "otherTable2"]
+            }
+          },
+          beforeCursor: "USE other_db; USE closest_db; \n\tSELECT ",
+          afterCursor: "USE some_other_db;",
+          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2"]
+        });
+      });
+    });
+
     describe("table completion", function() {
       it("should suggest table names with no columns", function() {
         assertAutoComplete({