فهرست منبع

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;
-})();
+})();