Эх сурвалжийг харах

HUE-1138 [beeswax] Autocomplete table and fields

Intelligent table completion, field completion and support for table aliases
Enrico Berti 12 жил өмнө
parent
commit
2ead5f9

+ 123 - 2
apps/beeswax/src/beeswax/templates/execute.mako

@@ -585,11 +585,116 @@ ${layout.menubar(section='query')}
         }
       }
 
+      var dbTree = ${ autocomplete | n,unicode };
+
+      function getTableAliases() {
+        var _aliases = {};
+        var _val = codeMirror.getValue();
+        var _from = _val.toUpperCase().indexOf("FROM");
+        if (_from > -1) {
+          var _match = _val.toUpperCase().substring(_from).match(/ON|WHERE|GROUP|SORT/);
+          var _to = _val.length;
+          if (_match) {
+            _to = _match.index;
+          }
+          var _found = _val.substr(_from, _to).replace(/(\r\n|\n|\r)/gm, "").replace(/from/gi, "").replace(/join/gi, ",").split(",");
+          for (var i = 0; i < _found.length; i++) {
+            var _tablealias = $.trim(_found[i]).split(" ");
+            if (_tablealias.length > 1) {
+              _aliases[_tablealias[1]] = _tablealias[0];
+            }
+          }
+        }
+        return _aliases;
+      }
+
+      function getTableFields(tableName, withDot) {
+        if (tableName.indexOf("(") > -1) {
+          tableName = tableName.substr(tableName.indexOf("(") + 1);
+        }
+        var _branch = dbTree[$("#id_query-database").val()];
+        if (_branch != null && _branch.tables) {
+          for (var i = 0; i < _branch.tables.length; i++) {
+            var _aliases = getTableAliases();
+            if (_aliases[tableName]) {
+              tableName = _aliases[tableName];
+            }
+            if (_branch.tables[i].name == tableName) {
+              var _cols = _branch.tables[i].cols.slice(0);
+              if (withDot) {
+                for (var idx = 0; idx < _cols.length; idx++) {
+                  _cols[idx] = "." + _cols[idx];
+                }
+              }
+              return _cols.join(" ");
+            }
+          }
+        }
+        return "";
+      }
+
+      function tableHasAlias(tableName) {
+        var _aliases = getTableAliases();
+        for (var alias in _aliases) {
+          if (_aliases[alias] == tableName) {
+            return true;
+          }
+        }
+        return false;
+      }
+
+      function getTables() {
+        var _branch = dbTree[$("#id_query-database").val()];
+        if (_branch != null && _branch.tables) {
+          var _names = "";
+          for (var i = 0; i < _branch.tables.length; i++) {
+            if (i > 0) {
+              _names += " ";
+            }
+            _names += _branch.tables[i].name;
+          }
+          return _names;
+        }
+        return "";
+      }
+
       var queryEditor = $("#queryField")[0];
 
       CodeMirror.commands.autocomplete = function (cm) {
+        CodeMirror.catalogTables = getTables();
+        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;
+        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 ") && !CodeMirror.fromDot) {
+          CodeMirror.possibleSoloField = true;
+          try {
+            var _possibleTables = $.trim(codeMirror.getValue().substr(codeMirror.getValue().toUpperCase().indexOf("FROM") + 4)).split(" ");
+            var _foundTable = "";
+            for (var i = 0; i < _possibleTables.length; i++) {
+              if ($.trim(_possibleTables[i]) != "" && _foundTable == "") {
+                _foundTable = _possibleTables[i];
+              }
+            }
+            if (_foundTable != "") {
+              if (tableHasAlias(_foundTable)) {
+                CodeMirror.possibleSoloField = false;
+              }
+              else {
+                CodeMirror.catalogFields = getTableFields(_foundTable, false);
+              }
+            }
+          }
+          catch (e) {
+          }
+        }
         CodeMirror.showHint(cm, CodeMirror.hiveQLHint);
       }
+
+      CodeMirror.fromDot = false;
+
       var codeMirror = CodeMirror(function (elt) {
         queryEditor.parentNode.replaceChild(elt, queryEditor);
       }, {
@@ -598,10 +703,27 @@ ${layout.menubar(section='query')}
         lineNumbers: true,
         mode: "text/x-hiveql",
         extraKeys: {
-          "Shift-Space": "autocomplete",
+          "Shift-Space": function () {
+            CodeMirror.fromDot = false;
+            codeMirror.execCommand("autocomplete");
+          },
           Tab: function (cm) {
             $("#executeQuery").focus();
           }
+        },
+        onKeyEvent: function (e, s) {
+          if (s.type == "keyup") {
+            if (s.keyCode == 190) {
+              window.setTimeout(function () {
+                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);
+                CodeMirror.catalogFields = getTableFields(_table, true);
+                CodeMirror.fromDot = true;
+                codeMirror.execCommand("autocomplete");
+              }, 100); // timeout for IE8
+            }
+          }
         }
       });
 
@@ -680,5 +802,4 @@ ${layout.menubar(section='query')}
     });
 </script>
 
-
 ${ commonfooter(messages) | n,unicode }

+ 18 - 2
apps/beeswax/src/beeswax/views.py

@@ -333,7 +333,23 @@ def execute_query(request, design_id=None):
 
   query_server = get_query_server_config(app_name, requires_ddl=False)
   db = dbms.get(request.user, query_server)
-  databases = ((db, db) for db in db.get_databases())
+  dbs = db.get_databases()
+  databases = ((db, db) for db in dbs)
+
+  autocomplete = {}
+  for database in dbs:
+    tables = db.get_tables(database=database)
+    autocomplete_tables = []
+    for table in tables:
+      t = db.get_table(database, table)
+      autocomplete_tables.append({
+        'name': t.name,
+        'cols': [column.name for column in t.cols]
+      })
+
+    autocomplete[database] = {
+      'tables': autocomplete_tables
+    }
 
   if request.method == 'POST':
     form.bind(request.POST)
@@ -383,12 +399,12 @@ def execute_query(request, design_id=None):
       form.bind()
     form.query.fields['database'].choices = databases # Could not do it in the form
   return render('execute.mako', request, {
-
     'action': action,
     'design': design,
     'error_message': error_message,
     'form': form,
     'log': log,
+    'autocomplete': json.dumps(autocomplete),
     'on_success_url': on_success_url,
     'can_edit_name': design and not design.is_auto and design.name,
   })

+ 31 - 14
desktop/core/static/js/Source/jHue/codemirror-hql-hint.js

@@ -35,18 +35,17 @@
   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' token, ignore the token.
 
-    if (!/^[\w$_]*$/.test(token.string)) {
+    // 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};
+        className: token.string == "." ? "hiveql-type" : null};
     }
 
     if (!context) var context = [];
     context.push(tprop);
 
     var completionList = getCompletions(token, context);
-    completionList = completionList.sort();
     //prevent autocomplete for last word, instead show dropdown with one word
     if (completionList.length == 1) {
       completionList.push(" ");
@@ -57,6 +56,11 @@
       to: CodeMirror.Pos(cur.line, token.end)};
   }
 
+  CodeMirror.catalogTables = "";
+  CodeMirror.catalogFields = "";
+  CodeMirror.possibleTable = false;
+  CodeMirror.possibleSoloField = false;
+
   CodeMirror.hiveQLHint = function (editor) {
     return scriptHint(editor, hiveQLKeywordsU, function (e, cur) {
       return e.getTokenAt(cur);
@@ -76,23 +80,36 @@
   var hiveQLBuiltinsL = hiveQLBuiltins.toLowerCase().split(" ").join("() ").split(" ");
 
   function getCompletions(token, context) {
-    var found = [], start = token.string;
+    var catalogTablesL = CodeMirror.catalogTables.toLowerCase().split(" ");
+    var catalogFieldsL = CodeMirror.catalogFields.toLowerCase().split(" ");
+
+    var found = [], start = token.string, extraFound = [];
 
     function maybeAdd(str) {
       if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str);
     }
 
+    function maybeAddToExtra(str) {
+      if (str.indexOf(start) == 0 && !arrayContains(found, str)) extraFound.push(str);
+    }
+
     function gatherCompletions(obj) {
-      if (obj == ":") {
-        forEach(hiveQLTypesL, maybeAdd);
+      if (obj.indexOf(".") == 0) {
+        forEach(catalogFieldsL, maybeAdd);
       }
       else {
-        forEach(hiveQLBuiltinsU, maybeAdd);
-        forEach(hiveQLBuiltinsL, maybeAdd);
-        forEach(hiveQLTypesU, maybeAdd);
-        forEach(hiveQLTypesL, maybeAdd);
-        forEach(hiveQLKeywordsU, maybeAdd);
-        forEach(hiveQLKeywordsL, maybeAdd);
+        if (!CodeMirror.possibleTable) {
+          if (CodeMirror.possibleSoloField) {
+            forEach(catalogFieldsL, maybeAddToExtra);
+          }
+          forEach(hiveQLBuiltinsU, maybeAdd);
+          forEach(hiveQLBuiltinsL, maybeAdd);
+          forEach(hiveQLTypesU, maybeAdd);
+          forEach(hiveQLTypesL, maybeAdd);
+          forEach(hiveQLKeywordsU, maybeAdd);
+          forEach(hiveQLKeywordsL, maybeAdd);
+        }
+        forEach(catalogTablesL, maybeAdd);
       }
     }
 
@@ -106,6 +123,6 @@
         base = base[context.pop().string];
       if (base != null) gatherCompletions(base);
     }
-    return found;
+    return extraFound.concat(found.sort());
   }
 })();