瀏覽代碼

HUE-5575 [editor] Create autocompleter category for tables

Johan Ahlen 9 年之前
父節點
當前提交
9e121da

+ 151 - 27
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter3.js

@@ -38,19 +38,50 @@ var SqlAutocompleter3 = (function () {
     JOIN: -1
   };
 
-  var sortByWeight = function (suggestions) {
-    suggestions.sort(function (a, b) {
-      if (typeof a.weight !== 'undefined' && typeof b.weight !== 'undefined' && b.weight !== a.weight) {
-        return b.weight - a.weight;
-      }
-      if (typeof a.weight !== 'undefined' && typeof b.weight === 'undefined') {
-        return -1;
-      }
-      if (typeof b.weight !== 'undefined' && typeof a.weight === 'undefined') {
-        return 1;
-      }
-      return a.value.localeCompare(b.value);
-    })
+
+  var hiveReservedKeywords = {
+    ALL: true, ALTER: true, AND: true, ARRAY: true, AS: true, AUTHORIZATION: true, BETWEEN: true, BIGINT: true, BINARY: true, BOOLEAN: true, BOTH: true, BY: true, CASE: true, CAST: true,
+    CHAR: true, COLUMN: true, CONF: true, CREATE: true, CROSS: true, CUBE: true, CURRENT: true, CURRENT_DATE: true, CURRENT_TIMESTAMP: true, CURSOR: true,
+    DATABASE: true, DATE: true, DECIMAL: true, DELETE: true, DESCRIBE: true, DISTINCT: true, DOUBLE: true, DROP: true, ELSE: true, END: true, EXCHANGE: true, EXISTS: true,
+    EXTENDED: true, EXTERNAL: true, FALSE: true, FETCH: true, FLOAT: true, FOLLOWING: true, FOR: true, FROM: true, FULL: true, FUNCTION: true, GRANT: true, GROUP: true,
+    GROUPING: true, HAVING: true, IF: true, IMPORT: true, IN: true, INNER: true, INSERT: true, INT: true, INTERSECT: true, INTERVAL: true, INTO: true, IS: true, JOIN: true, LATERAL: true,
+    LEFT: true, LESS: true, LIKE: true, LOCAL: true, MACRO: true, MAP: true, MORE: true, NONE: true, NOT: true, NULL: true, OF: true, ON: true, OR: true, ORDER: true, OUT: true, OUTER: true, OVER: true,
+    PARTIALSCAN: true, PARTITION: true, PERCENT: true, PRECEDING: true, PRESERVE: true, PROCEDURE: true, RANGE: true, READS: true, REDUCE: true,
+    REGEXP: true, REVOKE: true, RIGHT: true, RLIKE: true, ROLLUP: true, ROW: true, ROWS: true,
+    SELECT: true, SET: true, SMALLINT: true, TABLE: true, TABLESAMPLE: true, THEN: true, TIMESTAMP: true, TO: true, TRANSFORM: true, TRIGGER: true, TRUE: true,
+    TRUNCATE: true, UNBOUNDED: true, UNION: true, UNIQUEJOIN: true, UPDATE: true, USER: true, USING: true, VALUES: true, VARCHAR: true, WHEN: true, WHERE: true,
+    WINDOW: true, WITH: true
+  };
+
+  var extraHiveReservedKeywords = {
+    ASC: true, CLUSTER: true, DESC: true, DISTRIBUTE: true, FORMATTED: true, FUNCTION: true, INDEX: true, INDEXES: true, LIMIT: true, LOCK: true, SCHEMA: true, SORT: true
+  };
+
+  var impalaReservedKeywords = {
+    ADD: true, AGGREGATE: true, ALL: true, ALTER: true, AND: true, API_VERSION: true, AS: true, ASC: true, AVRO: true, BETWEEN: true, BIGINT: true, BINARY: true, BOOLEAN: true, BY: true, CACHED: true, CASE: true, CAST: true, CHANGE: true, CHAR: true, CLASS: true, CLOSE_FN: true,
+    COLUMN: true, COLUMNS: true, COMMENT: true, COMPUTE: true, CREATE: true, CROSS: true, DATA: true, DATABASE: true, DATABASES: true, DATE: true, DATETIME: true, DECIMAL: true, DELIMITED: true, DESC: true, DESCRIBE: true, DISTINCT: true, DIV: true, DOUBLE: true, DROP: true, ELSE: true, END: true,
+    ESCAPED: true, EXISTS: true, EXPLAIN: true, EXTERNAL: true, FALSE: true, FIELDS: true, FILEFORMAT: true, FINALIZE_FN: true, FIRST: true, FLOAT: true, FORMAT: true, FORMATTED: true, FROM: true, FULL: true, FUNCTION: true, FUNCTIONS: true, GROUP: true, HAVING: true, IF: true, IN: true, INCREMENTAL: true,
+    INIT_FN: true, INNER: true, INPATH: true, INSERT: true, INT: true, INTEGER: true, INTERMEDIATE: true, INTERVAL: true, INTO: true, INVALIDATE: true, IS: true, JOIN: true, LAST: true, LEFT: true, LIKE: true, LIMIT: true, LINES: true, LOAD: true, LOCATION: true, MERGE_FN: true, METADATA: true,
+    NOT: true, NULL: true, NULLS: true, OFFSET: true, ON: true, OR: true, ORDER: true, OUTER: true, OVERWRITE: true, PARQUET: true, PARQUETFILE: true, PARTITION: true, PARTITIONED: true, PARTITIONS: true, PREPARE_FN: true, PRODUCED: true, RCFILE: true, REAL: true, REFRESH: true, REGEXP: true, RENAME: true,
+    REPLACE: true, RETURNS: true, RIGHT: true, RLIKE: true, ROW: true, SCHEMA: true, SCHEMAS: true, SELECT: true, SEMI: true, SEQUENCEFILE: true, SERDEPROPERTIES: true, SERIALIZE_FN: true, SET: true, SHOW: true, SMALLINT: true, STATS: true, STORED: true, STRAIGHT_JOIN: true, STRING: true, SYMBOL: true, TABLE: true,
+    TABLES: true, TBLPROPERTIES: true, TERMINATED: true, TEXTFILE: true, THEN: true, TIMESTAMP: true, TINYINT: true, TO: true, TRUE: true, UNCACHED: true, UNION: true, UPDATE_FN: true, USE: true, USING: true, VALUES: true, VIEW: true, WHEN: true, WHERE: true, WITH: true,
+  };
+
+  var backTickIfNeeded = function (sourceType, text) {
+    if (text.indexOf('`') === 0) {
+      return text;
+    }
+    var upperText = text.toUpperCase();
+    if (sourceType === 'hive' && (hiveReservedKeywords[upperText] || extraHiveReservedKeywords[upperText])) {
+      return '`' + text + '`';
+    } else if (sourceType === 'impala' && impalaReservedKeywords[upperText]) {
+      return '`' + text + '`';
+    } else if (impalaReservedKeywords[upperText] || hiveReservedKeywords[upperText] || extraHiveReservedKeywords[upperText]) {
+      return '`' + text + '`';
+    } else if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(text)) {
+      return '`' + text + '`';
+    }
+    return text;
   };
 
   /**
@@ -71,39 +102,36 @@ var SqlAutocompleter3 = (function () {
   /**
    * Represents the keyword category of suggestions
    *
-   * @param parseResult
+   * @param parent
    * @param colRefDeferral
    * @constructor
    */
-  function KeywordsCategory (parseResult, colRefDeferral, sourceType) {
+  function KeywordsCategory (parent, colRefDeferral) {
     var self = this;
     self.label = AutocompleterGlobals.i18n.keywords;
     self.suggestions = ko.observableArray([]);
     self.loading = ko.observable(false);
-    self.sourceType = sourceType;
 
-    if (parseResult.suggestKeywords) {
-      var keywordSuggestions = $.map(parseResult.suggestKeywords, function (keyword) {
-        return new Suggestion(parseResult.lowerCase ? keyword.value.toLowerCase() : keyword.value, AutocompleterGlobals.i18n.meta.keyword, keyword.weight);
+    if (parent.parseResult.suggestKeywords) {
+      var keywordSuggestions = $.map(parent.parseResult.suggestKeywords, function (keyword) {
+        return new Suggestion(parent.parseResult.lowerCase ? keyword.value.toLowerCase() : keyword.value, AutocompleterGlobals.i18n.meta.keyword, keyword.weight);
       });
-      sortByWeight(keywordSuggestions);
       self.suggestions(keywordSuggestions);
     }
 
-    if (parseResult.suggestColRefKeywords) {
+    if (parent.parseResult.suggestColRefKeywords) {
       self.loading(true);
       // We have to wait for the column reference type to be resolved
       colRefDeferral.done(function (colRef) {
         if (colRef !== null) {
           var keywordSuggestions = self.suggestions();
-          Object.keys(parseResult.suggestColRefKeywords).forEach(function (typeForKeywords) {
-            if (SqlFunctions.matchesType(self.sourceType, [typeForKeywords], [type.toUpperCase()])) {
-              parseResult.suggestColRefKeywords[typeForKeywords].forEach(function (keyword) {
-                keywordSuggestions.push(new Suggestion(parseResult.lowerCase ? keyword.toLowerCase() : keyword, AutocompleterGlobals.i18n.meta.keyword, DEFAULT_WEIGHTS.COLREF_KEYWORD));
+          Object.keys(parent.parseResult.suggestColRefKeywords).forEach(function (typeForKeywords) {
+            if (SqlFunctions.matchesType(sourceType, [typeForKeywords], [type.toUpperCase()])) {
+              parent.parseResult.suggestColRefKeywords[typeForKeywords].forEach(function (keyword) {
+                keywordSuggestions.push(new Suggestion(parent.parseResult.lowerCase ? keyword.toLowerCase() : keyword, AutocompleterGlobals.i18n.meta.keyword, DEFAULT_WEIGHTS.COLREF_KEYWORD));
               })
             }
           });
-          sortByWeight(keywordSuggestions);
           self.suggestions(keywordSuggestions);
         }
         self.loading(false);
@@ -111,6 +139,49 @@ var SqlAutocompleter3 = (function () {
     }
   }
 
+  /**
+   * @param parent
+   * @constructor
+   */
+  function TablesCategory (parent) {
+    var self = this;
+    self.label = AutocompleterGlobals.i18n.keywords;
+    self.suggestions = ko.observableArray([]);
+    self.loading = ko.observable(true);
+
+    var prefix = parent.parseResult.suggestTables.prependQuestionMark ? '? ' : '';
+    if (parent.parseResult.suggestTables.prependFrom) {
+      prefix += parent.parseResult.lowerCase ? 'from ' : 'FROM ';
+    }
+
+    parent.apiHelper.fetchTables({
+      sourceType: parent.sourceType,
+      databaseName: parent.parseResult.suggestTables.identifierChain && parent.parseResult.suggestTables.identifierChain.length === 1 ? parent.parseResult.suggestTables.identifierChain[0].name : parent.defaultDatabase,
+      successCallback: function (data) {
+        var tables = [];
+        data.tables_meta.forEach(function (tablesMeta) {
+          if (parent.parseResult.suggestTables.onlyTables && tablesMeta.type.toLowerCase() !== 'table' ||
+              parent.parseResult.suggestTables.onlyViews && tablesMeta.type.toLowerCase() !== 'view') {
+            return;
+          }
+          tables.push(new Suggestion(prefix + backTickIfNeeded(parent.sourceType, tablesMeta.name), AutocompleterGlobals.i18n.meta[tablesMeta.type.toLowerCase()], DEFAULT_WEIGHTS.TABLE));
+        });
+        self.loading(false);
+        self.suggestions(tables);
+      },
+      silenceErrors: true,
+      errorCallback: function () {
+        self.loading(false);
+      },
+      timeout: parent.timeout
+    });
+  }
+
+  /**
+   *
+   * @param options
+   * @constructor
+   */
   function Suggestions (options) {
     var self = this;
     self.apiHelper = ApiHelper.getInstance();
@@ -121,11 +192,64 @@ var SqlAutocompleter3 = (function () {
     self.callback = options.callback;
 
     var colRefDeferral = self.handleColumnReference();
-    self.keywords = new KeywordsCategory(options.parseResult, colRefDeferral);
+    var allDbsDeferral = self.loadAllDatabases();
+
+    self.handleKeywords(colRefDeferral);
+    self.handleTables(allDbsDeferral);
 
     colRefDeferral.done(self.callback)
   }
 
+  Suggestions.prototype.loadAllDatabases = function () {
+    var self = this;
+    var dbsDeferral = $.Deferred();
+    self.apiHelper.loadDatabases({
+      sourceType: self.sourceType,
+      successCallback: dbsDeferral.resolve,
+      timeout: self.timeout,
+      silenceErrors: true,
+      errorCallback: function () {
+        dbsDeferral.resolve([]);
+      }
+    });
+    return dbsDeferral;
+  };
+
+  Suggestions.prototype.handleKeywords = function (colRefDeferral) {
+    var self = this;
+    if (self.parseResult.suggestKeywords || self.parseResult.suggestColRefKeywords) {
+      self.keywords = new KeywordsCategory(self, colRefDeferral);
+    }
+  };
+
+  Suggestions.prototype.handleTables = function (allDbsDeferral, colRefDeferral) {
+    var self = this;
+    if (self.parseResult.suggestTables) {
+      if (self.sourceType == 'impala' && self.parseResult.suggestTables.identifierChain && self.parseResult.suggestTables.identifierChain.length === 1) {
+        allDbsDeferral.done(function (databases) {
+          var foundDb = databases.filter(function (db) {
+            return db.toLowerCase() === self.parseResult.suggestTables.identifierChain[0].name.toLowerCase();
+          });
+          if (foundDb.length > 0) {
+            self.tables = new TablesCategory(self);
+          } else {
+            self.parseResult.suggestColumns = { tables: [{ identifierChain: self.parseResult.suggestTables.identifierChain }] };
+            self.handleColumns(colRefDeferral);
+          }
+        });
+      } else if (self.sourceType == 'impala' && self.parseResult.suggestTables.identifierChain && self.parseResult.suggestTables.identifierChain.length > 1) {
+        self.parseResult.suggestColumns = { tables: [{ identifierChain: self.parseResult.suggestTables.identifierChain }] };
+        self.handleColumns(colRefDeferral);
+      } else {
+        self.tables = new TablesCategory(self);
+      }
+    }
+  };
+
+  Suggestions.prototype.handleColumns = function (colRefDeferral) {
+
+  };
+
   /**
    * For some suggestions the column type is needed, for instance with functions we should only suggest
    * columns that matches the argument type, cos(|) etc.

+ 3 - 1
desktop/core/src/desktop/templates/common_header_footer_components.mako

@@ -97,7 +97,9 @@ from metadata.conf import has_optimizer, OPTIMIZER
         databases: '${ _('Databases') }',
         popular: '${ _('Popular') }',
         meta: {
-          keyword: '${ _('keyword') }'
+          keyword: '${ _('keyword') }',
+          table: '${ _('table') }',
+          view: '${ _('view') }'
         }
       }
     };

+ 31 - 26
desktop/libs/notebook/src/notebook/templates/hue_ace_autocompleter.mako

@@ -67,7 +67,7 @@ from desktop.views import _ko
   <script type="text/html" id="hue-ace-autocompleter">
     <div class="autocompleter-header"><div class="pull-right">header</div></div>
     <div class="autocompleter-list" data-bind="foreach: filteredSuggestions">
-      <div data-bind="click: function () { $parent.selectedIndex($index()); $parent.insertSuggestion(); }, css: { 'selected': $index() === $parent.selectedIndex() }"><div class="pull-left" data-bind="matchedText: { suggestion: $data, filter: $parent.prefixFilter }"></div><div class="pull-right" data-bind="text: meta"></div></div>
+      <div data-bind="click: function () { $parent.selectedIndex($index()); $parent.insertSuggestion(); $parent.editor.focus(); }, css: { 'selected': $index() === $parent.selectedIndex() }"><div class="pull-left" data-bind="matchedText: { suggestion: $data, filter: $parent.prefixFilter }"></div><div class="pull-right" data-bind="text: meta"></div></div>
     </div>
     <!-- /ko -->
   </script>
@@ -115,16 +115,20 @@ from desktop.views import _ko
         self.prefixFilter = ko.observable(prefix.toLowerCase());
 
         self.activeSuggestions = ko.pureComputed(function () {
+          var result = [];
           if (self.suggestions.keywords) {
-            return self.suggestions.keywords.suggestions();
+            result = result.concat(self.suggestions.keywords.suggestions());
           }
-
-          return [];
+          if (self.suggestions.tables) {
+            result = result.concat(self.suggestions.tables.suggestions());
+          }
+          return result;
         });
 
         self.filteredSuggestions = ko.pureComputed(function () {
+          var result = [];
           if (self.prefixFilter()) {
-            var result = self.activeSuggestions().filter(function (suggestion) {
+            result = self.activeSuggestions().filter(function (suggestion) {
               // TODO: Extend with fuzzy matches
               var foundIndex = suggestion.value.toLowerCase().indexOf(self.prefixFilter());
               if (foundIndex === 0) {
@@ -137,7 +141,15 @@ from desktop.views import _ko
               return suggestion.value.toLowerCase().indexOf(self.prefixFilter()) !== -1;
             });
 
-            result.sort(function (a, b) {
+            if (self.selectedIndex() > result.length - 1) {
+              self.selectedIndex(result.length - 1);
+            }
+          } else {
+            result = self.activeSuggestions();
+          }
+
+          result.sort(function (a, b) {
+            if (self.prefixFilter()) {
               if (typeof a.sortWeight !== 'undefined' && typeof b.sortWeight !== 'undefined' && b.sortWeight !== a.sortWeight) {
                 return b.sortWeight - a.sortWeight;
               }
@@ -147,25 +159,19 @@ from desktop.views import _ko
               if (typeof b.sortWeight !== 'undefined' && typeof a.sortWeight === 'undefined') {
                 return 1;
               }
-              if (typeof a.weight !== 'undefined' && typeof b.weight !== 'undefined' && b.weight !== a.weight) {
-                return b.weight - a.weight;
-              }
-              if (typeof a.weight !== 'undefined' && typeof b.weight === 'undefined') {
-                return -1;
-              }
-              if (typeof b.weight !== 'undefined' && typeof a.weight === 'undefined') {
-                return 1;
-              }
-              return a.value.localeCompare(b.value);
-            });
-
-            if (self.selectedIndex() > result.length - 1) {
-              self.selectedIndex(result.length - 1);
             }
-
-            return result;
-          }
-          return self.activeSuggestions();
+            if (typeof a.weight !== 'undefined' && typeof b.weight !== 'undefined' && b.weight !== a.weight) {
+              return b.weight - a.weight;
+            }
+            if (typeof a.weight !== 'undefined' && typeof b.weight === 'undefined') {
+              return -1;
+            }
+            if (typeof b.weight !== 'undefined' && typeof a.weight === 'undefined') {
+              return 1;
+            }
+            return a.value.localeCompare(b.value);
+          });
+          return result;
         });
 
         self.selectedIndex = ko.observable(0);
@@ -273,8 +279,7 @@ from desktop.views import _ko
             self.editor.session.remove(range);
           }
         }
-        var value = self.filteredSuggestions()[self.selectedIndex()].value;
-        self.editor.execCommand('insertstring', /\.$/.test(value) ? value : value + ' ');
+        self.editor.execCommand('insertstring', self.filteredSuggestions()[self.selectedIndex()].value);
         self.editor.renderer.scrollCursorIntoView();
         self.destroy();
       };