浏览代码

[spark] Hive and Impala autocomplete

Enrico Berti 11 年之前
父节点
当前提交
145fa36
共有 3 个文件被更改,包括 279 次插入17 次删除
  1. 1 1
      apps/spark/src/spark/models.py
  2. 276 7
      apps/spark/src/spark/templates/editor.mako
  3. 2 9
      apps/spark/static/css/spark.css

+ 1 - 1
apps/spark/src/spark/models.py

@@ -39,7 +39,7 @@ class Notebook():
     else:    
       self.data = json.dumps({
           'name': 'My Notebook', 
-          'snippets': [{'type': 'hive', 'result': {}}]
+          'snippets': [{'type': 'scala', 'result': {}}]
       })
 
   def get_json(self):

+ 276 - 7
apps/spark/src/spark/templates/editor.mako

@@ -81,7 +81,6 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
   <div class="row-fluid">
     <div class="span2">
       <div class="assist">
-        <h1 class="assist-heading"><i class="fa fa-compass"></i> ${_('Assist')}</h1>
         <a href="#" title="${_('Double click on a table name or field to insert it in the editor')}" rel="tooltip" data-placement="top" class="pull-right" style="margin:3px; margin-top:7px">
           <i class="fa fa-question-circle"></i>
         </a>
@@ -279,20 +278,271 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
 
 
   ko.bindingHandlers.codemirror = {
-    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
+    init: function (element, valueAccessor, allBindingsAccessor, vm) {
 
-      $(document).on("error.autocomplete", function(){
+      $(document).on("error.autocomplete", function () {
         $(".CodeMirror-spinner").remove();
       });
 
-      function hiveImpalaAutocomplete(cm, autocompleteSet) {
+      function hiveImpalaAutocomplete(cm, autocompleteSet, comingFromKeyEvent) {
+        CodeMirror.fromDot = false;
 
+        CodeMirror.onAutocomplete = function (data, from, to) {
+          if (data.indexOf("(") > -1) {
+            cm.setCursor({line: from.line, ch: from.ch + data.length - 1});
+            hiveImpalaAutocomplete(cm, autocompleteSet);
+          }
+          if (CodeMirror.tableFieldMagic) {
+            cm.replaceRange(" ", from, from);
+            cm.setCursor(from);
+            hiveImpalaAutocomplete(cm, autocompleteSet);
+          }
+        };
+
+        function splitStatements(hql) {
+          var statements = [];
+          var current = "";
+          var betweenQuotes = null;
+          for (var i = 0, len = hql.length; i < len; i++) {
+            var c = hql[i];
+            current += c;
+            if ($.inArray(c, ['"', "'"]) > -1) {
+              if (betweenQuotes == c) {
+                betweenQuotes = null;
+              }
+              else if (betweenQuotes == null) {
+                betweenQuotes = c;
+              }
+            }
+            else if (c == ";") {
+              if (betweenQuotes == null) {
+                statements.push(current);
+                current = "";
+              }
+            }
+          }
+
+          if (current != "" && current != ";") {
+            statements.push(current);
+          }
+          return statements;
+        }
+
+        function getStatementAtCursor(cm) {
+          var _pos = cm.indexFromPos(cm.getCursor());
+          var _statements = splitStatements(cm.getValue());
+          var _cumulativePos = 0;
+          var _statementAtCursor = "";
+          var _relativePos = 0;
+          for (var i = 0; i < _statements.length; i++) {
+            if (_cumulativePos + _statements[i].length >= _pos && _statementAtCursor == "") {
+              _statementAtCursor = _statements[i].split("\n").join(" ");
+              _relativePos = _pos - _cumulativePos;
+            }
+            _cumulativePos += _statements[i].length;
+          }
+          return {
+            statement: _statementAtCursor,
+            relativeIndex: _relativePos
+          };
+        }
+
+        function getTableAliases(textScanned) {
+          var _aliases = {};
+          var _val = textScanned.split("\n").join(" ");
+          var _from = _val.toUpperCase().indexOf("FROM ");
+          if (_from > -1) {
+            var _match = _val.toUpperCase().substring(_from).match(/ ON| LIMIT| WHERE| GROUP| SORT| ORDER BY|;/);
+            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 tableHasAlias(tableName, textScanned) {
+          var _aliases = getTableAliases(textScanned);
+          for (var alias in _aliases) {
+            if (_aliases[alias] == tableName) {
+              return true;
+            }
+          }
+          return false;
+        }
+
+
+        function fieldsAutocomplete(cm) {
+          CodeMirror.possibleSoloField = true;
+          try {
+            var _statement = getStatementAtCursor(cm).statement;
+            var _from = _statement.toUpperCase().indexOf("FROM");
+            if (_from > -1) {
+              var _match = _statement.toUpperCase().substring(_from).match(/ ON| LIMIT| WHERE| GROUP| SORT| ORDER BY|;/);
+              var _to = _statement.length;
+              if (_match) {
+                _to = _match.index;
+              }
+              var _found = _statement.substr(_from, _to).replace(/(\r\n|\n|\r)/gm, "").replace(/from/gi, "").replace(/join/gi, ",").split(",");
+            }
+
+            var _foundTable = "";
+            for (var i = 0; i < _found.length; i++) {
+              if ($.trim(_found[i]) != "" && _foundTable == "") {
+                _foundTable = $.trim(_found[i]).split(" ")[0];
+              }
+            }
+            if (_foundTable != "") {
+              if (tableHasAlias(_foundTable, _statement)) {
+                CodeMirror.possibleSoloField = false;
+                CodeMirror.showHint(cm, autocompleteSet);
+              }
+              else {
+                assist.options.onDataReceived = function (data) {
+                  if (data.columns) {
+                    CodeMirror.catalogFields = data.columns.join(" ");
+                    CodeMirror.showHint(cm, autocompleteSet);
+                  }
+                }
+
+                if (_foundTable.indexOf("(") > -1) {
+                  _foundTable = _foundTable.substr(_foundTable.indexOf("(") + 1);
+                }
+
+                var _aliases = getTableAliases(_statement);
+                if (_aliases[_foundTable]) {
+                  _foundTable = _aliases[_foundTable];
+                }
+
+                assist.getData(viewModel.assistContent().selectedMainObject() + "/" + _foundTable);
+              }
+            }
+          }
+          catch (e) {
+          }
+        }
+
+        var pos = cm.cursorCoords();
+        if ($(".CodeMirror-spinner").length == 0) {
+          $("<i class='fa fa-spinner fa-spin CodeMirror-spinner'></i>").appendTo($("body"));
+        }
+        $(".CodeMirror-spinner").css("top", pos.top + "px").css("left", (pos.left - 4) + "px").show();
+
+        if (comingFromKeyEvent) {
+
+          var _statement = getStatementAtCursor(cm).statement;
+          var _line = cm.getLine(cm.getCursor().line);
+          var _partial = _line.substring(0, cm.getCursor().ch);
+          var _table = _partial.substring(_partial.lastIndexOf(" ") + 1, _partial.length - 1);
+          if (_statement.indexOf("FROM") > -1) {
+
+            assist.options.onDataReceived = function (data) {
+              if (data.columns) {
+                var _cols = data.columns;
+                for (var col in _cols) {
+                  _cols[col] = "." + _cols[col];
+                }
+                CodeMirror.catalogFields = _cols.join(" ");
+                CodeMirror.fromDot = true;
+                CodeMirror.showHint(cm, autocompleteSet);
+              }
+            }
+
+            if (_table.indexOf("(") > -1) {
+              _table = _table.substr(_table.indexOf("(") + 1);
+            }
 
+            var _aliases = getTableAliases(_statement);
+            if (_aliases[_table]) {
+              _table = _aliases[_table];
+            }
+
+            assist.getData(viewModel.assistContent().selectedMainObject() + "/" + _table);
+
+          }
+
+        }
+        else {
+          assist.options.onDataReceived = function (data) {
+            if (data.tables) {
+              CodeMirror.catalogTables = data.tables.join(" ");
+              var _statementAtCursor = getStatementAtCursor(cm);
+              var _before = _statementAtCursor.statement.substr(0, _statementAtCursor.relativeIndex).replace(/;+$/, "");
+              var _after = _statementAtCursor.statement.substr(_statementAtCursor.relativeIndex).replace(/;+$/, "");
+              if ($.trim(_before).substr(-1) == ".") {
+                var _statement = _statementAtCursor.statement;
+                var _line = cm.getLine(cm.getCursor().line);
+                var _partial = _line.substring(0, cm.getCursor().ch);
+                var _table = _partial.substring(_partial.lastIndexOf(" ") + 1, _partial.length - 1);
+                if (_statement.indexOf("FROM") > -1) {
+                  assist.options.onDataReceived = function (data) {
+                    if (data.columns) {
+                      var _cols = data.columns;
+                      for (var col in _cols) {
+                        _cols[col] = "." + _cols[col];
+                      }
+                      CodeMirror.catalogFields = _cols.join(" ");
+                      CodeMirror.showHint(cm, autocompleteSet);
+                    }
+                  }
+
+                  if (_table.indexOf("(") > -1) {
+                    _table = _table.substr(_table.indexOf("(") + 1);
+                  }
+
+                  var _aliases = getTableAliases(_statement);
+                  if (_aliases[_table]) {
+                    _table = _aliases[_table];
+                  }
+
+                  assist.getData(viewModel.assistContent().selectedMainObject() + "/" + _table);
+                }
+              }
+              else {
+                CodeMirror.possibleTable = false;
+                CodeMirror.tableFieldMagic = false;
+                if ((_before.toUpperCase().indexOf(" FROM ") > -1 || _before.toUpperCase().indexOf(" TABLE ") > -1 || _before.toUpperCase().indexOf(" STATS ") > -1) && _before.toUpperCase().indexOf(" ON ") == -1 && _before.toUpperCase().indexOf(" ORDER BY ") == -1 && _before.toUpperCase().indexOf(" WHERE ") == -1 ||
+                    _before.toUpperCase().indexOf("REFRESH") > -1 || _before.toUpperCase().indexOf("METADATA") > -1 || _before.toUpperCase().indexOf("DESCRIBE") > -1) {
+                  CodeMirror.possibleTable = true;
+                }
+                CodeMirror.possibleSoloField = false;
+                if (_before.toUpperCase().indexOf("SELECT ") > -1 && _before.toUpperCase().indexOf(" FROM ") == -1 && !CodeMirror.fromDot) {
+                  if (_after.toUpperCase().indexOf("FROM ") > -1 || $.trim(_before).substr(-1) == "(") {
+                    fieldsAutocomplete(cm);
+                  }
+                  else {
+                    CodeMirror.tableFieldMagic = true;
+                    CodeMirror.showHint(cm, autocompleteSet);
+                  }
+                }
+                else {
+                  if ((_before.toUpperCase().indexOf("WHERE ") > -1 || _before.toUpperCase().indexOf("ORDER BY ") > -1) && !CodeMirror.fromDot && _before.toUpperCase().match(/ ON| LIMIT| GROUP| SORT/) == null) {
+                    fieldsAutocomplete(cm);
+                  }
+                  else {
+                    CodeMirror.showHint(cm, autocompleteSet);
+                  }
+                }
+              }
+            }
+          }
+          assist.getData(viewModel.assistContent().selectedMainObject());
+        }
       }
 
       var options = $.extend(valueAccessor(), {
         extraKeys: {
           "Ctrl-Space": function (cm) {
+            $(document.body).on("contextmenu", function (e) {
+              e.preventDefault(); // prevents native menu on FF for Mac from being shown
+            });
             switch (valueAccessor().mode) {
               case "text/x-pig":
                 CodeMirror.availableVariables = [];
@@ -305,11 +555,9 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
                 CodeMirror.showHint(cm, CodeMirror.scalaHint);
                 break;
               case "text/x-hiveql":
-                HIVE_AUTOCOMPLETE_APP = "beeswax";
                 hiveImpalaAutocomplete(cm, CodeMirror.hiveQLHint);
                 break;
               case "text/x-impalaql":
-                HIVE_AUTOCOMPLETE_APP = "impala";
                 hiveImpalaAutocomplete(cm, CodeMirror.impalaSQLHint);
                 break;
               default:
@@ -319,12 +567,28 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
           "Ctrl-Enter": function () {
             valueAccessor().enter();
           }
+        },
+        onKeyEvent: function (cm, e) {
+          switch (valueAccessor().mode) {
+            case "text/x-hiveql":
+              if (e.type == "keyup" && e.keyCode == 190) {
+                hiveImpalaAutocomplete(cm, CodeMirror.hiveQLHint, true);
+              }
+              break;
+            case "text/x-impalaql":
+              if (e.type == "keyup" && e.keyCode == 190) {
+                hiveImpalaAutocomplete(cm, CodeMirror.impalaSQLHint, true);
+              }
+              break;
+            default:
+              break;
+          }
         }
       });
       var editor = CodeMirror.fromTextArea(element, options);
 
       element.editor = editor;
-      $("#snippet_"+options.id).data("editor", editor);
+      $("#snippet_" + options.id).data("editor", editor);
       editor.setValue(allBindingsAccessor().value());
       window.setTimeout(function () {
         editor.refresh();
@@ -423,6 +687,11 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
     return escapeString(level.name + " (" + level.type + ")");
   }
 
+$(document).ready(function(){
+  $(".assist").width($(".assist").parents(".span2").width());
+  $(".assist").parents(".span2").height($(".assist").height() + 100);
+});
+
 </script>
 
 ${ commonfooter(messages) | n,unicode }

+ 2 - 9
apps/spark/static/css/spark.css

@@ -111,13 +111,6 @@
 }
 
 .assist {
-  /*position: fixed;*/
+  padding: 4px;
+  position: fixed;
 }
-
-.assist-heading {
-  font-size: 16px;
-  font-weight: 300;
-  color: #777;
-  border-bottom: 1px solid #e5e5e5;
-  padding-left: 10px;
-}