浏览代码

HUE-1766 [dbquery] Autocomplete behaviour should match Beeswax/Impala's ones

Magic autocomplete now shows also the default SQL keywords
Corrected bug on single table autocomplete
Enrico Berti 12 年之前
父节点
当前提交
c1fc7a2e1e
共有 2 个文件被更改,包括 156 次插入127 次删除
  1. 39 32
      apps/rdbms/src/rdbms/templates/execute.mako
  2. 117 95
      desktop/core/static/js/codemirror-sql-hint.js

+ 39 - 32
apps/rdbms/src/rdbms/templates/execute.mako

@@ -377,8 +377,7 @@ ${ commonheader(_('Query'), app_name, user) | n,unicode }
 
     CodeMirror.onAutocomplete = function (data, from, to) {
       if (CodeMirror.tableFieldMagic) {
-        codeMirror.replaceRange("  ", from, from);
-        from.ch = from.ch + 1;
+        codeMirror.replaceRange(" ", from, from);
         codeMirror.setCursor(from);
         codeMirror.execCommand("autocomplete");
       }
@@ -389,52 +388,47 @@ ${ commonheader(_('Query'), app_name, user) | n,unicode }
         e.preventDefault(); // prevents native menu on FF for Mac from being shown
       });
 
-      var options = {
-        'tables': {}
-      };
-
       var pos = cm.cursorCoords();
+      $(".CodeMirror-spinner").remove();
       $("<i class='fa fa-spinner fa-spin CodeMirror-spinner'></i>").css("top", pos.top + "px").css("left", (pos.left - 4) + "px").appendTo($("body"));
 
       if ($.totalStorage('rdbms_tables_' + viewModel.server().name() + "_" + viewModel.database()) == null) {
-        CodeMirror.showHint(codeMirror, AUTOCOMPLETE_SET);
-        rdbms_getTables(viewModel.server().name(), viewModel.database(), function () {}); // if preload didn't work, tries again
+        CodeMirror.showHint(cm, AUTOCOMPLETE_SET);
+        rdbms_getTables(viewModel.server().name(), viewModel.database(), function () {
+        }); // if preload didn't work, tries again
       }
       else {
         rdbms_getTables(viewModel.server().name(), viewModel.database(), function (tables) {
-          $.each(tables.split(' '), function(index, table) {
-            if (!(table in options['tables'])) {
-              options['tables'][table] = [];
-            }
-          });
+          CodeMirror.catalogTables = tables;
+          var _before = codeMirror.getRange({line: 0, ch: 0}, {line: codeMirror.getCursor().line, ch: codeMirror.getCursor().ch}).replace(/(\r\n|\n|\r)/gm, " ");
           CodeMirror.possibleTable = false;
           CodeMirror.tableFieldMagic = false;
-          CodeMirror.possibleSoloField = false;
-          var _before = codeMirror.getRange({line: 0, ch: 0}, {line: codeMirror.getCursor().line, ch: codeMirror.getCursor().ch}).replace(/(\r\n|\n|\r)/gm, " ");
           if (_before.toUpperCase().indexOf(" FROM ") > -1 && _before.toUpperCase().indexOf(" ON ") == -1 && _before.toUpperCase().indexOf(" WHERE ") == -1) {
             CodeMirror.possibleTable = true;
           }
+          CodeMirror.possibleSoloField = false;
           if (_before.toUpperCase().indexOf("SELECT ") > -1 && _before.toUpperCase().indexOf(" FROM ") == -1 && !CodeMirror.fromDot) {
             if (codeMirror.getValue().toUpperCase().indexOf("FROM ") > -1) {
-              fieldsAutocomplete(codeMirror, options);
-            } else {
+              fieldsAutocomplete(cm);
+            }
+            else {
               CodeMirror.tableFieldMagic = true;
-              CodeMirror.showHint(codeMirror, AUTOCOMPLETE_SET, options);
+              CodeMirror.showHint(cm, AUTOCOMPLETE_SET);
             }
           }
           else {
             if (_before.toUpperCase().indexOf("WHERE ") > -1 && !CodeMirror.fromDot && _before.match(/ON|GROUP|SORT/) == null) {
-              fieldsAutocomplete(codeMirror);
+              fieldsAutocomplete(cm);
             }
             else {
-              CodeMirror.showHint(codeMirror, AUTOCOMPLETE_SET, options);
+              CodeMirror.showHint(cm, AUTOCOMPLETE_SET);
             }
           }
         });
       }
     }
 
-    function fieldsAutocomplete(cm, options) {
+    function fieldsAutocomplete(cm) {
       CodeMirror.possibleSoloField = true;
       try {
         var _possibleTables = $.trim(codeMirror.getValue(" ").substr(codeMirror.getValue().toUpperCase().indexOf("FROM ") + 4)).split(" ");
@@ -444,17 +438,17 @@ ${ commonheader(_('Query'), app_name, user) | n,unicode }
             _foundTable = _possibleTables[i];
           }
         }
-        if (_foundTable != "" && _foundTable in options['tables']) {
+        if (_foundTable != "") {
           if (rdbms_tableHasAlias(viewModel.server().name(), _foundTable, codeMirror.getValue())) {
             CodeMirror.possibleSoloField = false;
-            CodeMirror.showHint(cm, AUTOCOMPLETE_SET, options);
-          } else {
+            CodeMirror.showHint(cm, AUTOCOMPLETE_SET);
+          }
+          else {
             rdbms_getTableColumns(viewModel.server().name(), viewModel.database(), _foundTable, codeMirror.getValue(),
-                function (columns) {
-                  CodeMirror.table = _foundTable;
-                  options['tables'][_foundTable] = columns.split(' ');
-                  CodeMirror.showHint(cm, AUTOCOMPLETE_SET, options);
-                });
+                    function (columns) {
+                      CodeMirror.catalogFields = columns;
+                      CodeMirror.showHint(cm, AUTOCOMPLETE_SET);
+                    });
           }
         }
       }
@@ -483,10 +477,22 @@ ${ commonheader(_('Query'), app_name, user) | n,unicode }
       onKeyEvent: function (e, s) {
         if (s.type == "keyup") {
           if (s.keyCode == 190) {
+            var _line = codeMirror.getLine(codeMirror.getCursor().line);
+            var _partial = _line.substring(0, codeMirror.getCursor().ch);
+            var _table = _partial.substring(_partial.lastIndexOf(" ") + 1, _partial.length - 1);
             if (codeMirror.getValue().toUpperCase().indexOf("FROM") > -1) {
-              window.setTimeout(function () {
-                codeMirror.execCommand("autocomplete");
-              }, 100);  // timeout for IE8
+              rdbms_getTableColumns(viewModel.server().name(), viewModel.database(), _table, codeMirror.getValue(),
+                      function (columns) {
+                        var _cols = columns.split(" ");
+                        for (var col in _cols) {
+                          _cols[col] = "." + _cols[col];
+                        }
+                        CodeMirror.catalogFields = _cols.join(" ");
+                        CodeMirror.fromDot = true;
+                        window.setTimeout(function () {
+                          codeMirror.execCommand("autocomplete");
+                        }, 100);  // timeout for IE8
+                      });
             }
           }
         }
@@ -502,6 +508,7 @@ ${ commonheader(_('Query'), app_name, user) | n,unicode }
       errorWidget = codeMirror.addLineWidget(selectedLine - 1, $("<div>").addClass("editorError").html("<i class='fa fa-exclamation-circle'></i> " + err)[0], {coverGutter: true, noHScroll: true})
     }
 
+
     codeMirror.setSize("95%", 100);
 
     codeMirror.on("focus", function () {

+ 117 - 95
desktop/core/static/js/codemirror-sql-hint.js

@@ -1,124 +1,146 @@
+// 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 () {
-  "use strict";
 
-  var tables;
-  var keywords;
+  var keywords, keywordsU, keywordsL;
 
   function getKeywords(editor) {
     var mode = editor.doc.modeOption;
-    if(mode === "sql") mode = "text/x-sql";
-    return CodeMirror.resolveMode(mode).keywords;
+    if (mode === "sql") mode = "text/x-sql";
+    var _keywordsObj = CodeMirror.resolveMode(mode).keywords;
+    var _keywords = "";
+    for (var keyword in _keywordsObj) {
+      _keywords += keyword.toUpperCase() + " ";
+    }
+    return _keywords;
   }
 
-  function match(string, word) {
-    var len = string.length;
-    var sub = word.substr(0, len);
-    return string.toUpperCase() === sub.toUpperCase();
+  function forEach(arr, f) {
+    for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
   }
 
-  function addMatches(result, search, wordlist, formatter) {
-    for(var word in wordlist) {
-      if(!wordlist.hasOwnProperty(word)) continue;
-      if(Array.isArray(wordlist)) {
-        word = wordlist[word];
-      }
-      if(match(search, word)) {
-        result.push(formatter(word));
+  function arrayContains(arr, item) {
+    if (!Array.prototype.indexOf) {
+      var i = arr.length;
+      while (i--) {
+        if (arr[i] === item) {
+          return true;
+        }
       }
+      return false;
     }
+    return arr.indexOf(item) != -1;
   }
 
-  function columnCompletion(result, editor) {
-    var cur = editor.getCursor();
-    var token = editor.getTokenAt(cur);
-    var string = token.string.substr(1);
-    var prevCur = CodeMirror.Pos(cur.line, token.start);
-    var table = editor.getTokenAt(prevCur).string;
-    var columns = tables[table];
-    if(!columns) {
-      table = findTableByAlias(table, editor);
-    }
-    columns = tables[table];
-    if(!columns) {
-      return;
+  function scriptHint(editor, _keywords, getToken) {
+    // Find the token at the cursor
+    var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
+
+    // If it's not a 'word-style' or dot token, ignore the token.
+    if (!/^[\.\w$_]*$/.test(token.string)) {
+      token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
+        className: token.string == "." ? "hiveql-type" : null};
     }
-    addMatches(result, string, columns,
-        function(w) {return "." + w;});
-  }
 
-  function eachWord(line, f) {
-    var words = line.text.split(" ");
-    for(var i = 0; i < words.length; i++) {
-      f(words[i]);
+    if (!context) var context = [];
+    context.push(tprop);
+
+    var completionList = getCompletions(token, context);
+    //prevent autocomplete for last word, instead show dropdown with one word
+    if (completionList.length == 1) {
+      completionList.push(" ");
     }
-  }
 
-  // Tries to find possible table name from alias.
-  function findTableByAlias(alias, editor) {
-    var aliasUpperCase = alias.toUpperCase();
-    var previousWord = "";
-    var table = "";
-
-    editor.eachLine(function(line) {
-      eachWord(line, function(word) {
-        var wordUpperCase = word.toUpperCase();
-        if(wordUpperCase === aliasUpperCase) {
-          if(tables.hasOwnProperty(previousWord)) {
-            table = previousWord;
-          }
-        }
-        if(wordUpperCase !== "AS") {
-          previousWord = word;
-        }
-      });
-    });
-    return table;
+    return {list: completionList,
+      from: CodeMirror.Pos(cur.line, token.start),
+      to: CodeMirror.Pos(cur.line, token.end)};
   }
 
-  function sqlHint(editor, options) {
-    tables = (options && options.tables) || {};
-    keywords = keywords || getKeywords(editor);
-    var cur = editor.getCursor();
-    var token = editor.getTokenAt(cur);
+  CodeMirror.catalogTables = "";
+  CodeMirror.catalogFields = "";
+  CodeMirror.possibleTable = false;
+  CodeMirror.possibleSoloField = false;
+  CodeMirror.tableFieldMagic = false;
 
-    var result = [];
+  CodeMirror.sqlHint = function (editor) {
+    keywords = getKeywords(editor);
+    keywordsU = keywords.split(" ");
+    keywordsL = keywords.toLowerCase().split(" ");
+    return scriptHint(editor, keywords, function (e, cur) {
+      return e.getTokenAt(cur);
+    });
+  };
+
+  function getCompletions(token, context) {
+    var catalogTablesL = CodeMirror.catalogTables.toLowerCase().split(" ");
+    var catalogFieldsL = CodeMirror.catalogFields.toLowerCase().split(" ");
 
-    var search = token.string.trim();
+    var found = [], start = token.string, extraFound = [];
 
-    var from = CodeMirror.Pos(cur.line, token.start);
-    var to = CodeMirror.Pos(cur.line, token.end);
+    function maybeAdd(str) {
+      if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
+    }
 
-    if (CodeMirror.possibleSoloField && CodeMirror.table && CodeMirror.table in tables) {
-      var columns = tables[CodeMirror.table];
-      addMatches(result, search, columns, function(w) {return w;});
+    function maybeAddToExtra(str) {
+      var _match = str;
+      if (_match.indexOf("fa-magic") > -1) {
+        _match = _match.substring(_match.indexOf("FROM ") + 5);
+      }
+      if (_match.indexOf(start) == 0 && !arrayContains(found, str)) extraFound.push(str);
+    }
 
-      // Token search/replace replaces "SELECT  FROM test" with "SELECTtestFROM test".
-      // Changing the start position and end position fixes that.
-      if (!search) {
-        from = cur;
-        to = cur;
+    function gatherCompletions(obj) {
+      if (obj.indexOf(".") == 0) {
+        forEach(catalogFieldsL, maybeAdd);
       }
-    } else if(search.lastIndexOf('.') === 0) {
-      columnCompletion(result, editor);
-    } else if (CodeMirror.possibleTable) {
-      addMatches(result, search, tables,
-        function(w) {return w;});
-    } else {
-      if (CodeMirror.tableFieldMagic) {
-        addMatches(result, search, tables, function(w) {
-          return "<i class='fa fa-magic'></i> FROM " + w.trim();
-        });
-      } else {
-        addMatches(result, search, keywords,
-          function(w) {return w.toUpperCase();});
+      else {
+        if (!CodeMirror.possibleTable) {
+          if (CodeMirror.tableFieldMagic) {
+            var _specialCatalogTablesL = CodeMirror.catalogTables.toLowerCase().split(" ");
+            for (var i = 0; i < _specialCatalogTablesL.length; i++) {
+              _specialCatalogTablesL[i] = "<i class='fa fa-magic'></i> FROM " + _specialCatalogTablesL[i];
+            }
+            forEach(_specialCatalogTablesL, maybeAddToExtra);
+          }
+          if (CodeMirror.possibleSoloField) {
+            forEach(catalogFieldsL, maybeAddToExtra);
+          }
+          forEach(keywordsU, maybeAdd);
+          forEach(keywordsL, maybeAdd);
+
+        }
+        else {
+          forEach(catalogTablesL, maybeAddToExtra);
+          forEach(keywordsU, maybeAdd);
+          forEach(keywordsL, maybeAdd);
+        }
       }
     }
 
-    return {
-      list: result,
-      from: from,
-      to: to
-    };
+    if (context) {
+      // If this is a property, see if it belongs to some object we can
+      // find in the current environment.
+      var obj = context.pop(), base;
+      base = obj.string;
+
+      while (base != null && context.length)
+        base = base[context.pop().string];
+      if (base != null) gatherCompletions(base);
+    }
+    return extraFound.sort().concat(found.sort());
   }
-  CodeMirror.sqlHint = sqlHint;
-})();
+})();