Forráskód Böngészése

HUE-5592 [editor] Add popular joins to the new autocomplete dropdown

Johan Ahlen 9 éve
szülő
commit
ced4f38

+ 158 - 43
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter3.js

@@ -88,6 +88,8 @@ var SqlAutocompleter3 = (function () {
     self.values = ko.observableArray();
     self.paths = ko.observableArray();
 
+    self.joins = ko.observableArray();
+
     self.loadingKeywords = ko.observable(false);
     self.loadingFunctions = ko.observable(false);
     self.loadingDatabases = ko.observable(false);
@@ -96,11 +98,13 @@ var SqlAutocompleter3 = (function () {
     self.loadingValues = ko.observable(false);
     self.loadingPaths = ko.observable(false);
 
+    self.loadingJoins = ko.observable(false);
+
     self.filter = ko.observable();
 
     self.filtered = ko.pureComputed(function () {
       var result = self.keywords().concat(self.identifiers(), self.columnAliases(), self.commonTableExpressions(),
-          self.functions(), self.databases(), self.tables(), self.columns(), self.values(), self.paths());
+          self.functions(), self.databases(), self.tables(), self.columns(), self.values(), self.paths(), self.joins());
 
       if (self.filter()) {
         var lowerCaseFilter = self.filter().toLowerCase();
@@ -167,7 +171,7 @@ var SqlAutocompleter3 = (function () {
 
   Suggestions.prototype.update = function (parseResult) {
     var self = this;
-    self.defaultDatabase = parseResult.useDatabase || self.snippet.database();
+    self.activeDatabase = parseResult.useDatabase || self.snippet.database();
     self.parseResult = parseResult;
 
     self.keywords([]);
@@ -181,6 +185,8 @@ var SqlAutocompleter3 = (function () {
     self.values([]);
     self.paths([]);
 
+    self.joins([]);
+
     self.loadingKeywords(false);
     self.loadingFunctions(false);
     self.loadingDatabases(false);
@@ -189,23 +195,27 @@ var SqlAutocompleter3 = (function () {
     self.loadingValues(false);
     self.loadingPaths(false);
 
+    self.loadingJoins(false);
+
     self.filter('');
 
-    var colRefDeferral = self.handleColumnReference();
-    var databasesDeferral = self.loadDatabases();
+    var colRefDeferred = self.handleColumnReference();
+    var databasesDeferred = self.loadDatabases();
 
-    self.handleKeywords(colRefDeferral);
+    self.handleKeywords(colRefDeferred);
     self.handleIdentifiers();
     self.handleColumnAliases();
     self.handleCommonTableExpressions();
-    self.handleFunctions(colRefDeferral);
-    self.handleDatabases(databasesDeferral);
-    var tablesDeferral = self.handleTables(databasesDeferral);
-    var columnsDeferral = self.handleColumns(colRefDeferral);
-    var valuesDeferral = self.handleValues(colRefDeferral);
-    var pathsDeferral = self.handlePaths();
-
-    $.when(colRefDeferral, databasesDeferral, tablesDeferral, columnsDeferral, valuesDeferral, pathsDeferral).done(function () {
+    self.handleFunctions(colRefDeferred);
+    self.handleDatabases(databasesDeferred);
+    var tablesDeferred = self.handleTables(databasesDeferred);
+    var columnsDeferred = self.handleColumns(colRefDeferred);
+    self.handleValues(colRefDeferred);
+    var pathsDeferred = self.handlePaths();
+
+    var joinsDeferred = self.handleJoins();
+
+    $.when(colRefDeferred, databasesDeferred, tablesDeferred, columnsDeferred, pathsDeferred, joinsDeferred).done(function () {
       huePubSub.publish('hue.ace.autocompleter.done');
     });
   };
@@ -220,15 +230,15 @@ var SqlAutocompleter3 = (function () {
    */
   Suggestions.prototype.handleColumnReference = function () {
     var self = this;
-    var colRefDeferral = $.Deferred();
+    var colRefDeferred = $.Deferred();
     if (self.parseResult.colRef) {
       var colRefCallback = function (data) {
         if (typeof data.type !== 'undefined') {
-          colRefDeferral.resolve(data);
+          colRefDeferred.resolve(data);
         } else if (typeof data.extended_columns !== 'undefined' && data.extended_columns.length === 1) {
-          colRefDeferral.resolve(data.extended_columns[0]);
+          colRefDeferred.resolve(data.extended_columns[0]);
         } else {
-          colRefDeferral.resolve({ type: 'T' })
+          colRefDeferred.resolve({ type: 'T' })
         }
       };
 
@@ -237,34 +247,34 @@ var SqlAutocompleter3 = (function () {
       });
 
       if (foundVarRef.length > 0) {
-        colRefDeferral.resolve({ type: 'T' });
+        colRefDeferred.resolve({ type: 'T' });
       } else {
         self.fetchFieldsForIdentifiers(self.parseResult.colRef.identifierChain, colRefCallback, function () {
-          colRefDeferral.resolve({ type: 'T' });
+          colRefDeferred.resolve({ type: 'T' });
         });
       }
     } else {
-      colRefDeferral.resolve({ type: 'T' });
+      colRefDeferred.resolve({ type: 'T' });
     }
-    return colRefDeferral;
+    return colRefDeferred;
   };
 
   Suggestions.prototype.loadDatabases = function () {
     var self = this;
-    var dbsDeferral = $.Deferred();
+    var databasesDeferred = $.Deferred();
     self.apiHelper.loadDatabases({
       sourceType: self.snippet.type(),
-      successCallback: dbsDeferral.resolve,
+      successCallback: databasesDeferred.resolve,
       timeout: AUTOCOMPLETE_TIMEOUT,
       silenceErrors: true,
       errorCallback: function () {
-        dbsDeferral.resolve([]);
+        databasesDeferred.resolve([]);
       }
     });
-    return dbsDeferral;
+    return databasesDeferred;
   };
 
-  Suggestions.prototype.handleKeywords = function (colRefDeferral) {
+  Suggestions.prototype.handleKeywords = function (colRefDeferred) {
     var self = this;
     if (self.parseResult.suggestKeywords) {
       var keywordSuggestions = $.map(self.parseResult.suggestKeywords, function (keyword) {
@@ -276,7 +286,7 @@ var SqlAutocompleter3 = (function () {
     if (self.parseResult.suggestColRefKeywords) {
       self.loadingKeywords(true);
       // Wait for the column reference type to be resolved to pick the right keywords
-      colRefDeferral.done(function (colRef) {
+      colRefDeferred.done(function (colRef) {
         var keywordSuggestions = self.keywords();
         Object.keys(self.parseResult.suggestColRefKeywords).forEach(function (typeForKeywords) {
           if (SqlFunctions.matchesType(sourceType, [typeForKeywords], [colRef.type.toUpperCase()])) {
@@ -333,13 +343,13 @@ var SqlAutocompleter3 = (function () {
     }
   };
 
-  Suggestions.prototype.handleFunctions = function (colRefDeferral) {
+  Suggestions.prototype.handleFunctions = function (colRefDeferred) {
     var self = this;
     if (self.parseResult.suggestFunctions) {
       var functionSuggestions = [];
       if (self.parseResult.suggestFunctions.types && self.parseResult.suggestFunctions.types[0] === 'COLREF') {
         self.loadingFunctions(true);
-        colRefDeferral.done(function (colRef) {
+        colRefDeferred.done(function (colRef) {
           SqlFunctions.suggestFunctions(self.snippet.type(), [colRef.type.toUpperCase()], self.parseResult.suggestAggregateFunctions || false, self.parseResult.suggestAnalyticFunctions || false, functionSuggestions, DEFAULT_WEIGHTS.UDF);
           self.functions(functionSuggestions);
           self.loadingFunctions(false);
@@ -351,7 +361,7 @@ var SqlAutocompleter3 = (function () {
     }
   };
 
-  Suggestions.prototype.handleDatabases = function (allDbsDeferral) {
+  Suggestions.prototype.handleDatabases = function (databasesDeferred) {
     var self = this;
     var suggestDatabases = self.parseResult.suggestDatabases;
     if (suggestDatabases) {
@@ -361,7 +371,7 @@ var SqlAutocompleter3 = (function () {
       }
       var databaseSuggestions = [];
       self.loadingDatabases(true);
-      allDbsDeferral.done(function (dbs) {
+      databasesDeferred.done(function (dbs) {
         dbs.forEach(function (db) {
           databaseSuggestions.push({ value: prefix + self.backTickIfNeeded(db) + (suggestDatabases.appendDot ? '.' : ''), meta: AutocompleterGlobals.i18n.meta.database, weight: DEFAULT_WEIGHTS.DATABASE })
         });
@@ -371,7 +381,7 @@ var SqlAutocompleter3 = (function () {
     }
   };
 
-  Suggestions.prototype.handleTables = function (allDbsDeferral, colRefDeferral) {
+  Suggestions.prototype.handleTables = function (databasesDeferred, colRefDeferred) {
     var self = this;
     var tablesDeferred = $.Deferred();
     if (self.parseResult.suggestTables) {
@@ -385,7 +395,7 @@ var SqlAutocompleter3 = (function () {
 
         self.apiHelper.fetchTables({
           sourceType: self.snippet.type(),
-          databaseName: suggestTables.identifierChain && suggestTables.identifierChain.length === 1 ? suggestTables.identifierChain[0].name : self.defaultDatabase,
+          databaseName: suggestTables.identifierChain && suggestTables.identifierChain.length === 1 ? suggestTables.identifierChain[0].name : self.activeDatabase,
           successCallback: function (data) {
             var tableSuggestions = [];
             data.tables_meta.forEach(function (tablesMeta) {
@@ -409,7 +419,7 @@ var SqlAutocompleter3 = (function () {
       };
 
       if (self.snippet.type() == 'impala' && self.parseResult.suggestTables.identifierChain && self.parseResult.suggestTables.identifierChain.length === 1) {
-        allDbsDeferral.done(function (databases) {
+        databasesDeferred.done(function (databases) {
           var foundDb = databases.filter(function (db) {
             return db.toLowerCase() === self.parseResult.suggestTables.identifierChain[0].name.toLowerCase();
           });
@@ -417,12 +427,12 @@ var SqlAutocompleter3 = (function () {
             fetchTables();
           } else {
             self.parseResult.suggestColumns = { tables: [{ identifierChain: self.parseResult.suggestTables.identifierChain }] };
-            return self.handleColumns(colRefDeferral);
+            return self.handleColumns(colRefDeferred);
           }
         });
       } else if (self.snippet.type() == 'impala' && self.parseResult.suggestTables.identifierChain && self.parseResult.suggestTables.identifierChain.length > 1) {
         self.parseResult.suggestColumns = { tables: [{ identifierChain: self.parseResult.suggestTables.identifierChain }] };
-        return self.handleColumns(colRefDeferral);
+        return self.handleColumns(colRefDeferred);
       } else {
         fetchTables();
       }
@@ -433,7 +443,7 @@ var SqlAutocompleter3 = (function () {
     return tablesDeferred;
   };
 
-  Suggestions.prototype.handleColumns = function (colRefDeferral) {
+  Suggestions.prototype.handleColumns = function (colRefDeferred) {
     var self = this;
     var columnsDeferred = $.Deferred();
     if (self.parseResult.suggestColumns) {
@@ -444,7 +454,7 @@ var SqlAutocompleter3 = (function () {
 
       if (suggestColumns.types && suggestColumns.types[0] === 'COLREF') {
         self.loadingColumns(true);
-        colRefDeferral.done(function (colRef) {
+        colRefDeferred.done(function (colRef) {
           suggestColumns.tables.forEach(function (table) {
             columnDeferrals.push(self.addColumns(table, [colRef.type.toUpperCase()], columnSuggestions));
           });
@@ -607,7 +617,7 @@ var SqlAutocompleter3 = (function () {
     }
   };
 
-  Suggestions.prototype.handleValues = function (colRefDeferral) {
+  Suggestions.prototype.handleValues = function (colRefDeferred) {
     var self = this;
     var suggestValues = self.parseResult.suggestValues;
     if (suggestValues) {
@@ -615,7 +625,7 @@ var SqlAutocompleter3 = (function () {
       if (self.parseResult.colRef && self.parseResult.colRef.identifierChain) {
         valueSuggestions.push({ value: '${' + self.parseResult.colRef.identifierChain[self.parseResult.colRef.identifierChain.length - 1].name + '}', meta: AutocompleterGlobals.i18n.meta.variable, weight: DEFAULT_WEIGHTS.VARIABLE });
       }
-      colRefDeferral.done(function (colRef) {
+      colRefDeferred.done(function (colRef) {
         if (colRef.sample) {
           var isString = colRef.type === "string";
           var startQuote = suggestValues.partialQuote ? '' : '\'';
@@ -632,6 +642,7 @@ var SqlAutocompleter3 = (function () {
   Suggestions.prototype.handlePaths = function () {
     var self = this;
     var suggestHdfs = self.parseResult.suggestHdfs;
+    var pathsDeferred = $.Deferred();
     if (suggestHdfs) {
       var parts = suggestHdfs.path.split('/');
       // Drop the first " or '
@@ -654,17 +665,121 @@ var SqlAutocompleter3 = (function () {
                 });
               }
             });
+            pathsDeferred.resolve(pathSuggestions);
             self.paths(pathSuggestions);
           }
           self.loadingPaths(false);
         },
         silenceErrors: true,
         errorCallback: function () {
+          pathsDeferred.resolve([]);
           self.loadingPaths(false);
         },
         timeout: AUTOCOMPLETE_TIMEOUT
       });
+    } else {
+      pathsDeferred.resolve([]);
+    }
+    return pathsDeferred;
+  };
+
+  Suggestions.prototype.handleJoins = function () {
+    var self = this;
+    var joinsDeferred = $.Deferred();
+    var suggestJoins = self.parseResult.suggestJoins;
+    if (HAS_OPTIMIZER && suggestJoins) {
+      self.loadingJoins(true);
+      self.snippet.getApiHelper().fetchNavOptPopularJoins({
+        sourceType: self.snippet.type(),
+        timeout: AUTOCOMPLETE_TIMEOUT,
+        defaultDatabase: self.activeDatabase,
+        silenceErrors: true,
+        tables: suggestJoins.tables,
+        successCallback: function (data) {
+          var joinSuggestions = [];
+          data.values.forEach(function (value) {
+            var suggestionString = suggestJoins.prependJoin ? (self.parseResult.lowerCase ? 'join ' : 'JOIN ') : '';
+            var first = true;
+
+            var existingTables = {};
+            suggestJoins.tables.forEach(function (table) {
+              existingTables[table.identifierChain[table.identifierChain.length - 1].name] = true;
+            });
+
+            var joinRequired = false;
+            var tablesAdded = false;
+            value.tables.forEach(function (table) {
+              var tableParts = table.split('.');
+              if (!existingTables[tableParts[tableParts.length - 1]]) {
+                tablesAdded = true;
+                var identifier = self.convertNavOptQualifiedIdentifier(table, suggestJoins.tables);
+                suggestionString += joinRequired ? (self.parseResult.lowerCase ? ' join ' : ' JOIN ') + identifier : identifier;
+                joinRequired = true;
+              }
+            });
+
+            if (value.joinCols.length > 0) {
+              if (!tablesAdded && suggestJoins.prependJoin) {
+                suggestionString = '';
+                tablesAdded = true;
+              }
+              suggestionString += self.parseResult.lowerCase ? ' on ' : ' ON ';
+            }
+            if (tablesAdded) {
+              value.joinCols.forEach(function (joinColPair) {
+                if (!first) {
+                  suggestionString += self.parseResult.lowerCase ? ' and ' : ' AND ';
+                }
+                suggestionString += self.convertNavOptQualifiedIdentifier(joinColPair.columns[0], suggestJoins.tables) + ' = ' + self.convertNavOptQualifiedIdentifier(joinColPair.columns[1], suggestJoins.tables);
+                first = false;
+              });
+              joinSuggestions.push({
+                value: suggestionString,
+                meta: AutocompleterGlobals.i18n.meta.join,
+                weight: suggestJoins.prependJoin ? DEFAULT_WEIGHTS.JOIN : DEFAULT_WEIGHTS.POPULAR_ACTIVE_JOIN,
+                detailsTemplate: 'join',
+                details: {
+                  // TODO: Add more details about the join
+                  completeValue: suggestionString
+                }
+              });
+            }
+          });
+          self.joins(joinSuggestions);
+          self.loadingJoins(false);
+          joinsDeferred.resolve(joinSuggestions);
+        },
+        errorCallback: function () {
+          self.loadingJoins(false);
+          joinsDeferred.resolve([]);
+        }
+      });
+    } else {
+      joinsDeferred.resolve([]);
+    }
+    return joinsDeferred;
+  };
+
+  Suggestions.prototype.convertNavOptQualifiedIdentifier = function (qualifiedIdentifier, tables) {
+    var self = this;
+    var aliases = [];
+    var tablesHasDefaultDatabase = false;
+    tables.forEach(function (table) {
+      tablesHasDefaultDatabase = tablesHasDefaultDatabase || table.identifierChain[0].name.toLowerCase() === self.activeDatabase.toLowerCase();
+      if (table.alias) {
+        aliases.push({ qualifiedName: $.map(table.identifierChain, function (identifier) { return identifier.name }).join('.').toLowerCase(), alias: table.alias });
+      }
+    });
+
+    for (var i = 0; i < aliases.length; i++) {
+      if (qualifiedIdentifier.toLowerCase().indexOf(aliases[i].qualifiedName) === 0) {
+        return aliases[i].alias + qualifiedIdentifier.substring(aliases[i].qualifiedName.length);
+      } else if (qualifiedIdentifier.toLowerCase().indexOf(self.activeDatabase.toLowerCase() + '.' + aliases[i].qualifiedName) === 0) {
+        return aliases[i].alias + qualifiedIdentifier.substring((self.activeDatabase + '.' + aliases[i].qualifiedName).length);
+      }
     }
+
+    return qualifiedIdentifier.toLowerCase().indexOf(self.activeDatabase.toLowerCase()) === 0 && !tablesHasDefaultDatabase ? qualifiedIdentifier.substring(self.activeDatabase.length + 1) : qualifiedIdentifier;
   };
 
   /**
@@ -745,7 +860,7 @@ var SqlAutocompleter3 = (function () {
             var foundDb = data.filter(function (db) {
               return db.toLowerCase() === identifierChain[0].name.toLowerCase();
             });
-            var databaseName = foundDb.length > 0 ? identifierChain.shift().name : self.defaultDatabase;
+            var databaseName = foundDb.length > 0 ? identifierChain.shift().name : self.activeDatabase;
             var tableName = identifierChain.shift().name;
             fetchFieldsInternal(tableName, databaseName, identifierChain, callback, errorCallback, []);
           },
@@ -753,12 +868,12 @@ var SqlAutocompleter3 = (function () {
           errorCallback: errorCallback
         });
       } else {
-        var databaseName = self.defaultDatabase;
+        var databaseName = self.activeDatabase;
         var tableName = identifierChain.shift().name;
         fetchFieldsInternal(tableName, databaseName, identifierChain, callback, errorCallback, []);
       }
     } else {
-      var databaseName = identifierChain.length > 1 ? identifierChain.shift().name : self.defaultDatabase;
+      var databaseName = identifierChain.length > 1 ? identifierChain.shift().name : self.activeDatabase;
       var tableName = identifierChain.shift().name;
       fetchFieldsInternal(tableName, databaseName, identifierChain, callback, errorCallback, []);
     }

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

@@ -96,6 +96,7 @@ from metadata.conf import has_optimizer, OPTIMIZER
           alias:  '${ _('alias') }',
           commonTableExpression:  '${ _('cte') }',
           database:  '${ _('database') }',
+          join: '${ _('join') }',
           keyword: '${ _('keyword') }',
           table: '${ _('table') }',
           value: '${ _('value') }',