Explorar el Código

HUE-5524 [editor] Add column suggestions to the new autocomplete dropdown

Johan Ahlen hace 9 años
padre
commit
569b23c

+ 205 - 51
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter3.js

@@ -66,38 +66,6 @@ var SqlAutocompleter3 = (function () {
     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;
-  };
-
-  /**
-   * Represents a single suggestion
-   *
-   * @param value
-   * @param meta
-   * @param weight
-   * @constructor
-   */
-  function Suggestion (value, meta, weight) {
-    var self = this;
-    self.value = value;
-    self.meta = meta;
-    self.weight = weight;
-  }
-
   /**
    *
    * @param options
@@ -107,6 +75,7 @@ var SqlAutocompleter3 = (function () {
     var self = this;
     self.apiHelper = ApiHelper.getInstance();
     self.snippet = options.snippet;
+    self.editor = options.editor;
 
     self.keywords = ko.observableArray();
     self.loadingKeywords = ko.observable(false);
@@ -131,9 +100,9 @@ var SqlAutocompleter3 = (function () {
             return false;
           }
           if (foundIndex === 0) {
-            suggestion.sortWeight = 2;
+            suggestion.filterWeight = 2;
           } else if (foundIndex > 0) {
-            suggestion.sortWeight = 1;
+            suggestion.filterWeight = 1;
           }
           suggestion.matchIndex = foundIndex;
           suggestion.matchLength = self.filter().length;
@@ -142,13 +111,13 @@ var SqlAutocompleter3 = (function () {
       }
       result.sort(function (a, b) {
         if (self.filter()) {
-          if (typeof a.sortWeight !== 'undefined' && typeof b.sortWeight !== 'undefined' && b.sortWeight !== a.sortWeight) {
-            return b.sortWeight - a.sortWeight;
+          if (typeof a.filterWeight !== 'undefined' && typeof b.filterWeight !== 'undefined' && b.filterWeight !== a.filterWeight) {
+            return b.filterWeight - a.filterWeight;
           }
-          if (typeof a.sortWeight !== 'undefined' && typeof b.sortWeight === 'undefined') {
+          if (typeof a.filterWeight !== 'undefined' && typeof b.filterWeight === 'undefined') {
             return -1;
           }
-          if (typeof a.sortWeight === 'undefined' && typeof b.sortWeight !== 'undefined') {
+          if (typeof a.filterWeight === 'undefined' && typeof b.filterWeight !== 'undefined') {
             return 1;
           }
         }
@@ -167,6 +136,24 @@ var SqlAutocompleter3 = (function () {
     });
   }
 
+  Suggestions.prototype.backTickIfNeeded = function (text) {
+    var self = this;
+    if (text.indexOf('`') === 0) {
+      return text;
+    }
+    var upperText = text.toUpperCase();
+    if (self.snippet.type() === 'hive' && (hiveReservedKeywords[upperText] || extraHiveReservedKeywords[upperText])) {
+      return '`' + text + '`';
+    } else if (self.snippet.type() === '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;
+  };
+
   Suggestions.prototype.update = function (parseResult) {
     var self = this;
     self.defaultDatabase = parseResult.useDatabase || self.snippet.database();
@@ -184,6 +171,7 @@ var SqlAutocompleter3 = (function () {
 
     self.handleKeywords(colRefDeferral);
     self.handleTables(allDbsDeferral);
+    self.handleColumns(colRefDeferral);
   };
 
   Suggestions.prototype.loadDatabases = function () {
@@ -205,7 +193,7 @@ var SqlAutocompleter3 = (function () {
     var self = this;
     if (self.parseResult.suggestKeywords) {
       var keywordSuggestions = $.map(self.parseResult.suggestKeywords, function (keyword) {
-        return new Suggestion(self.parseResult.lowerCase ? keyword.value.toLowerCase() : keyword.value, AutocompleterGlobals.i18n.meta.keyword, keyword.weight);
+        return { value: self.parseResult.lowerCase ? keyword.value.toLowerCase() : keyword.value, meta: AutocompleterGlobals.i18n.meta.keyword, weight: keyword.weight };
       });
       self.keywords(keywordSuggestions);
     }
@@ -214,17 +202,15 @@ var SqlAutocompleter3 = (function () {
       self.loadingKeywords(true);
       // Wait for the column reference type to be resolved to pick the right keywords
       colRefDeferral.done(function (colRef) {
-        if (colRef !== null) {
-          var keywordSuggestions = self.keywords();
-          Object.keys(self.parseResult.suggestColRefKeywords).forEach(function (typeForKeywords) {
-            if (SqlFunctions.matchesType(sourceType, [typeForKeywords], [type.toUpperCase()])) {
-              self.parseResult.suggestColRefKeywords[typeForKeywords].forEach(function (keyword) {
-                keywordSuggestions.push(new Suggestion(self.parseResult.lowerCase ? keyword.toLowerCase() : keyword, AutocompleterGlobals.i18n.meta.keyword, DEFAULT_WEIGHTS.COLREF_KEYWORD));
-              })
-            }
-          });
-          self.keywords(keywordSuggestions);
-        }
+        var keywordSuggestions = self.keywords();
+        Object.keys(self.parseResult.suggestColRefKeywords).forEach(function (typeForKeywords) {
+          if (SqlFunctions.matchesType(sourceType, [typeForKeywords], [colRef.type.toUpperCase()])) {
+            self.parseResult.suggestColRefKeywords[typeForKeywords].forEach(function (keyword) {
+              keywordSuggestions.push({ value: self.parseResult.lowerCase ? keyword.toLowerCase() : keyword, meta: AutocompleterGlobals.i18n.meta.keyword, weight: DEFAULT_WEIGHTS.COLREF_KEYWORD });
+            })
+          }
+        });
+        self.keywords(keywordSuggestions);
         self.loadingKeywords(false);
       });
     }
@@ -251,7 +237,7 @@ var SqlAutocompleter3 = (function () {
                   suggestTables.onlyViews && tablesMeta.type.toLowerCase() !== 'view') {
                 return;
               }
-              tableSuggestions.push(new Suggestion(prefix + backTickIfNeeded(self.snippet.type(), tablesMeta.name), AutocompleterGlobals.i18n.meta[tablesMeta.type.toLowerCase()], DEFAULT_WEIGHTS.TABLE));
+              tableSuggestions.push({ value: prefix + self.backTickIfNeeded(tablesMeta.name), meta: AutocompleterGlobals.i18n.meta[tablesMeta.type.toLowerCase()], weight: DEFAULT_WEIGHTS.TABLE });
             });
             self.loadingTables(false);
             self.tables(tableSuggestions);
@@ -286,7 +272,173 @@ var SqlAutocompleter3 = (function () {
   };
 
   Suggestions.prototype.handleColumns = function (colRefDeferral) {
+    var self = this;
+
+    if (self.parseResult.suggestColumns) {
+      var suggestColumns = self.parseResult.suggestColumns;
+      var columnSuggestions = [];
+      // For multiple tables we need to merge and make sure identifiers are unique
+      var columnDeferrals = [];
+
+      if (suggestColumns.types && suggestColumns.types[0] === 'COLREF') {
+        self.loadingColumns(true);
+        colRefDeferral.done(function (colRef) {
+          suggestColumns.tables.forEach(function (table) {
+            columnDeferrals.push(self.addColumns(table, [colRef.type.toUpperCase()], columnSuggestions));
+          });
+        });
+      } else {
+        self.loadingColumns(true);
+        suggestColumns.tables.forEach(function (table) {
+          columnDeferrals.push(self.addColumns(table, suggestColumns.types || ['T'], columnSuggestions));
+        });
+      }
+
+      $.when.apply($, columnDeferrals).done(function () {
+        self.mergeColumns(columnSuggestions);
+        if (self.snippet.type() === 'hive' && /[^\.]$/.test(self.editor())) {
+          columnSuggestions.push({ value: 'BLOCK__OFFSET__INSIDE__FILE', meta: AutocompleterGlobals.i18n.meta.virtual, weight: DEFAULT_WEIGHTS.VIRTUAL_COLUMN });
+          columnSuggestions.push({ value: 'INPUT__FILE__NAME', meta: AutocompleterGlobals.i18n.meta.virtual, weight: DEFAULT_WEIGHTS.VIRTUAL_COLUMN });
+        }
+        self.columns(columnSuggestions);
+        self.loadingColumns(false);
+      });
+    }
+  };
+
+  Suggestions.prototype.addColumns = function (table, types, columnSuggestions) {
+    var self = this;
+    var addColumnsDeferred = $.Deferred();
+
+    if (typeof table.identifierChain !== 'undefined' && table.identifierChain.length === 1 && typeof table.identifierChain[0].subQuery !== 'undefined') {
+      var foundSubQuery = self.locateSubQuery(self.parseResult.subQueries, table.identifierChain[0].subQuery);
+
+      var addSubQueryColumns = function (subQueryColumns) {
+        subQueryColumns.forEach(function (column) {
+          if (column.alias || column.identifierChain) {
+            // TODO: Potentially fetch column types for sub-queries, possible performance hit.
+            var type = typeof column.type !== 'undefined' && column.type !== 'COLREF' ? column.type : 'T';
+            if (column.alias) {
+              columnSuggestions.push({ value: self.backTickIfNeeded(column.alias), meta: type, weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            } else if (column.identifierChain && column.identifierChain.length > 0) {
+              columnSuggestions.push({ value: self.backTickIfNeeded(column.identifierChain[column.identifierChain.length - 1].name), meta: type, weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            }
+          } else if (column.subQuery && foundSubQuery.subQueries) {
+            var foundNestedSubQuery = self.locateSubQuery(foundSubQuery.subQueries, column.subQuery);
+            if (foundNestedSubQuery !== null) {
+              addSubQueryColumns(foundNestedSubQuery.columns);
+            }
+          }
+        });
+      };
+      if (foundSubQuery !== null && foundSubQuery.columns.length > 0) {
+        addSubQueryColumns(foundSubQuery.columns);
+      }
+      addColumnsDeferred.resolve();
+    } else {
+      var callback = function (data) {
+        if (data.extended_columns) {
+          data.extended_columns.forEach(function (column) {
+            if (column.type.indexOf('map') === 0 && self.snippet.type() === 'hive') {
+              columnSuggestions.push({value: self.backTickIfNeeded(column.name) + '[]', meta: 'map', weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            } else if (column.type.indexOf('map') === 0) {
+              columnSuggestions.push({value: self.backTickIfNeeded(column.name), meta: 'map', weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            } else if (column.type.indexOf('struct') === 0) {
+              columnSuggestions.push({value: self.backTickIfNeeded(column.name), meta: 'struct', weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            } else if (column.type.indexOf('array') === 0 && self.snippet.type() === 'hive') {
+              columnSuggestions.push({value: self.backTickIfNeeded(column.name) + '[]', meta: 'array', weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            } else if (column.type.indexOf('array') === 0) {
+              columnSuggestions.push({value: self.backTickIfNeeded(column.name), meta: 'array', weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            } else if (types[0].toUpperCase() !== 'T' && types.filter(function (type) { return type.toUpperCase() === column.type.toUpperCase() }).length > 0) {
+              columnSuggestions.push({value: self.backTickIfNeeded(column.name), meta: column.type, weight: DEFAULT_WEIGHTS.COLUMN + 1, table: table })
+            } else if (SqlFunctions.matchesType(self.snippet.type(), types, [column.type.toUpperCase()]) ||
+                SqlFunctions.matchesType(self.snippet.type(), [column.type.toUpperCase()], types)) {
+              columnSuggestions.push({value: self.backTickIfNeeded(column.name), meta: column.type, weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+            }
+          });
+        } else if (data.columns) {
+          data.columns.forEach(function (column) {
+            columnSuggestions.push({value: self.backTickIfNeeded(column), meta: 'column', weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+          });
+        }
+        if (data.type === 'map' && self.snippet.type() === 'impala') {
+          columnSuggestions.push({value: 'key', meta: 'key', weight: DEFAULT_WEIGHTS.COLUMN, table: table });
+          columnSuggestions.push({value: 'value', meta: 'value', weight: DEFAULT_WEIGHTS.COLUMN, table: table });
+        }
+        if (data.type === 'struct') {
+          data.fields.forEach(function (field) {
+            columnSuggestions.push({value: self.backTickIfNeeded(field.name), meta: field.type, weight: DEFAULT_WEIGHTS.COLUMN, table: table })
+          });
+        } else if (data.type === 'map' && (data.value && data.value.fields)) {
+          data.value.fields.forEach(function (field) {
+            if (SqlFunctions.matchesType(self.snippet.type(), types, [field.type.toUpperCase()]) ||
+                SqlFunctions.matchesType(self.snippet.type(), [field.type.toUpperCase()], types)) {
+              columnSuggestions.push({value: self.backTickIfNeeded(field.name), meta: field.type, weight: DEFAULT_WEIGHTS.COLUMN, table: table });
+            }
+          });
+        } else if (data.type === 'array' && data.item) {
+          if (data.item.fields) {
+            data.item.fields.forEach(function (field) {
+              if ((field.type === 'array' || field.type === 'map')) {
+                if (self.snippet.type() === 'hive') {
+                  columnSuggestions.push({value: self.backTickIfNeeded(field.name) + '[]', meta: field.type, weight: DEFAULT_WEIGHTS.COLUMN, table: table });
+                } else {
+                  columnSuggestions.push({value: self.backTickIfNeeded(field.name), meta: field.type, weight: DEFAULT_WEIGHTS.COLUMN, table: table });
+                }
+              } else if (SqlFunctions.matchesType(self.snippet.type(), types, [field.type.toUpperCase()]) ||
+                  SqlFunctions.matchesType(self.snippet.type(), [field.type.toUpperCase()], types)) {
+                columnSuggestions.push({value: self.backTickIfNeeded(field.name), meta: field.type, weight: DEFAULT_WEIGHTS.COLUMN, table: table });
+              }
+            });
+          } else if (typeof data.item.type !== 'undefined') {
+            if (SqlFunctions.matchesType(self.snippet.type(), types, [data.item.type.toUpperCase()])) {
+              columnSuggestions.push({value: 'item', meta: data.item.type, weight: DEFAULT_WEIGHTS.COLUMN, table: table });
+            }
+          }
+        }
+        addColumnsDeferred.resolve();
+      };
 
+      self.fetchFieldsForIdentifiers(table.identifierChain, callback, addColumnsDeferred.resolve);
+    }
+    return addColumnsDeferred;
+  };
+
+  Suggestions.prototype.mergeColumns = function (columnSuggestions) {
+    columnSuggestions.sort(function (a, b) {
+      return a.value.localeCompare(b.value);
+    });
+
+    for (var i = 0; i < columnSuggestions.length; i++) {
+      var suggestion = columnSuggestions[i];
+      suggestion.isColumn = true;
+      var hasDuplicates = false;
+      for (i; i + 1 < columnSuggestions.length && columnSuggestions[i + 1].value === suggestion.value; i++) {
+        var nextTable = columnSuggestions[i + 1].table;
+        if (typeof nextTable.alias !== 'undefined') {
+          columnSuggestions[i + 1].value = nextTable.alias + '.' + columnSuggestions[i + 1].value
+        } else if (typeof nextTable.identifierChain !== 'undefined' && nextTable.identifierChain.length > 0) {
+          var lastIdentifier = nextTable.identifierChain[nextTable.identifierChain.length - 1];
+          if (typeof lastIdentifier.name !== 'undefined') {
+            columnSuggestions[i + 1].value = lastIdentifier.name + '.' + columnSuggestions[i + 1].value;
+          } else if (typeof lastIdentifier.subQuery !== 'undefined') {
+            columnSuggestions[i + 1].value = lastIdentifier.subQuery + '.' + columnSuggestions[i + 1].value;
+          }
+        }
+        hasDuplicates = true;
+      }
+      if (typeof suggestion.table.alias !== 'undefined') {
+        suggestion.value = suggestion.table.alias + '.' + suggestion.value;
+      } else if (hasDuplicates && typeof suggestion.table.identifierChain !== 'undefined' && suggestion.table.identifierChain.length > 0) {
+        var lastIdentifier = suggestion.table.identifierChain[suggestion.table.identifierChain.length - 1];
+        if (typeof lastIdentifier.name !== 'undefined') {
+          suggestion.value = lastIdentifier.name + '.' + suggestion.value;
+        } else if (typeof lastIdentifier.subQuery !== 'undefined') {
+          suggestion.value = lastIdentifier.subQuery + '.' + suggestion.value;
+        }
+      }
+      delete suggestion.table;
+    }
   };
 
   /**
@@ -306,6 +458,8 @@ var SqlAutocompleter3 = (function () {
           colRefDeferral.resolve(data);
         } else if (typeof data.extended_columns !== 'undefined' && data.extended_columns.length === 1) {
           colRefDeferral.resolve(data.extended_columns[0]);
+        } else {
+          colRefDeferral.resolve({ type: 'T' })
         }
       };
 

+ 2 - 6
desktop/core/src/desktop/templates/common_header_footer_components.mako

@@ -91,15 +91,11 @@ from metadata.conf import has_optimizer, OPTIMIZER
 
     AutocompleterGlobals = {
       i18n: {
-        keywords: '${ _('Keywords') }',
-        columns: '${ _('Columns') }',
-        tables: '${ _('Tables') }',
-        databases: '${ _('Databases') }',
-        popular: '${ _('Popular') }',
         meta: {
           keyword: '${ _('keyword') }',
           table: '${ _('table') }',
-          view: '${ _('view') }'
+          view: '${ _('view') }',
+          virtual: '${ _('virtual') }'
         }
       }
     };