瀏覽代碼

HUE-7643 [editor] Prevent the syntax checker from marking stale editor identifiers as missing

We can't control when the syntax web workers come back with results so by adding a change timestamp we know whether it's based on stale data or not.
Johan Ahlen 8 年之前
父節點
當前提交
a4a13d1092

+ 108 - 97
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -3570,6 +3570,7 @@
 
         huePubSub.publish('editor.active.statement.changed', {
           id: self.editorId,
+          editorChangeTime: lastKnownStatements.editorChangeTime,
           activeStatementIndex: statementIndex,
           totalStatementCount: lastKnownStatements.length,
           precedingStatements: precedingStatements,
@@ -3584,7 +3585,10 @@
 
       var parseForStatements = function () {
         try {
+          var lastChangeTime = self.editor.lastChangeTime;
           lastKnownStatements = sqlStatementsParser.parse(self.editor.getValue());
+          lastKnownStatements.editorChangeTime = lastChangeTime;
+
           if (typeof hueDebug !== 'undefined' && hueDebug.logStatementLocations) {
             console.log(lastKnownStatements);
           }
@@ -3622,7 +3626,7 @@
         }, 100);
       });
 
-      var changeListener = self.editor.on("change", function () {
+      var changeListener = self.editor.on('change', function () {
         window.clearTimeout(changeThrottle);
         cursorChangePaused = true;
         changeThrottle = window.setTimeout(function () {
@@ -3631,6 +3635,7 @@
           updateActiveStatement();
           cursorChangePaused = false;
         }, 500);
+        self.editor.lastChangeTime = Date.now();
       });
 
       var locateSubscription = huePubSub.subscribe('editor.refresh.statement.locations', function (snippet) {
@@ -3653,10 +3658,10 @@
       });
     };
 
-    AceLocationHandler.prototype.clearMarkedErrors = function () {
+    AceLocationHandler.prototype.clearMarkedErrors = function (type) {
       var self = this;
       for (var marker in self.editor.getSession().$backMarkers) {
-        if (self.editor.getSession().$backMarkers[marker].clazz.indexOf('hue-ace-syntax-') === 0) {
+        if (self.editor.getSession().$backMarkers[marker].clazz.indexOf('hue-ace-syntax-' + (type || '')) === 0) {
           var token = self.editor.getSession().$backMarkers[marker].token;
           delete token.syntaxError;
           delete token.notFound;
@@ -3668,12 +3673,13 @@
     AceLocationHandler.prototype.checkForSyntaxErrors = function (statementLocation, cursorPosition) {
       var self = this;
       if (self.sqlSyntaxWorkerSub !== null) {
-        self.clearMarkedErrors();
         var AceRange = ace.require('ace/range').Range;
+        var editorChangeTime = self.editor.lastChangeTime;
         var beforeCursor = self.editor.getSession().getTextRange(new AceRange(statementLocation.first_line - 1, statementLocation.first_column, cursorPosition.row, cursorPosition.column));
         var afterCursor = self.editor.getSession().getTextRange(new AceRange(cursorPosition.row, cursorPosition.column, statementLocation.last_line - 1, statementLocation.last_column));
         huePubSub.publish('ace.sql.syntax.worker.post', {
           id: self.snippet.id(),
+          editorChangeTime: editorChangeTime,
           beforeCursor: beforeCursor,
           afterCursor: afterCursor,
           statementLocation: statementLocation,
@@ -3690,11 +3696,11 @@
       }
 
       self.sqlSyntaxWorkerSub = huePubSub.subscribe('ace.sql.syntax.worker.message', function (e) {
-        if (e.data.id !== self.snippet.id()) {
+        if (e.data.id !== self.snippet.id() || e.data.editorChangeTime !== self.editor.lastChangeTime) {
           return;
         }
+        self.clearMarkedErrors('error');
         var suppressedRules = ApiHelper.getInstance().getFromTotalStorage('hue.syntax.checker', 'suppressedRules', {});
-
         if (e.data.syntaxError && e.data.syntaxError.ruleId && !suppressedRules[e.data.syntaxError.ruleId.toString() + e.data.syntaxError.text.toLowerCase()]) {
           if (self.snippet.positionStatement() && SqlUtils.locationEquals(e.data.statementLocation, self.snippet.positionStatement().location)) {
             self.snippet.positionStatement().syntaxError = true;
@@ -3786,110 +3792,113 @@
       return promise;
     };
 
-    AceLocationHandler.prototype.verifyExists = function (token, allLocations) {
+    AceLocationHandler.prototype.verifyExists = function (tokens, allLocations) {
       var self = this;
-      delete token.notFound;
-      delete token.syntaxError;
-
-      if (self.sqlSyntaxWorkerSub !== null && token.parseLocation && (token.parseLocation.type === 'table' || token.parseLocation.type === 'column') && (token.parseLocation.identifierChain || token.parseLocation.tables)) {
-
-        // Ignore identifiers when the cursor is at the end to make it less annoying while editing
-        if (token && token.parseLocation) {
-          var cursorPos = self.editor.getCursorPosition();
-          if (cursorPos.row + 1 === token.parseLocation.location.last_line && cursorPos.column + 1 === token.parseLocation.location.first_column + token.value.length) {
-            return;
+      self.clearMarkedErrors('warning');
+      tokens.forEach(function (token) {
+        delete token.notFound;
+        delete token.syntaxError;
+
+        if (self.sqlSyntaxWorkerSub !== null && token.parseLocation && (token.parseLocation.type === 'table' || token.parseLocation.type === 'column') && (token.parseLocation.identifierChain || token.parseLocation.tables)) {
+          // Ignore identifiers when the cursor is at the end to make it less annoying while editing
+          if (token && token.parseLocation) {
+            var cursorPos = self.editor.getCursorPosition();
+            if (cursorPos.row + 1 === token.parseLocation.location.last_line && cursorPos.column + 1 === token.parseLocation.location.first_column + token.value.length) {
+              return;
+            }
           }
-        }
-
-        var aliases = [];
 
-        for (var i = 0; i < allLocations.length; i++) {
-          var location = allLocations[i];
-          if (location.type === 'alias') {
-            if (location.source === 'cte' && token.parseLocation.type === 'column') {
-              // We currently don't discover the columns from a CTE so we can't say if a column exists or not
-              if (!token.parseLocation.tables && token.parseLocation.identifierChain && token.parseLocation.identifierChain.length > 1 && token.parseLocation.identifierChain[0].name.toLowerCase() === location.alias.toLowerCase()) {
-                return;
-              }
+          var aliases = [];
 
-              if (token.parseLocation.tables) {
-                var found = token.parseLocation.tables.some(function (table) {
-                  return table.identifierChain && table.identifierChain.length === 1 && table.identifierChain[0].name && table.identifierChain[0].name.toLowerCase() === location.alias.toLowerCase();
-                });
-                if (found) {
+          for (var i = 0; i < allLocations.length; i++) {
+            var location = allLocations[i];
+            if (location.type === 'alias') {
+              if (location.source === 'cte' && token.parseLocation.type === 'column') {
+                // We currently don't discover the columns from a CTE so we can't say if a column exists or not
+                if (!token.parseLocation.tables && token.parseLocation.identifierChain && token.parseLocation.identifierChain.length > 1 && token.parseLocation.identifierChain[0].name.toLowerCase() === location.alias.toLowerCase()) {
                   return;
                 }
+
+                if (token.parseLocation.tables) {
+                  var found = token.parseLocation.tables.some(function (table) {
+                    return table.identifierChain && table.identifierChain.length === 1 && table.identifierChain[0].name && table.identifierChain[0].name.toLowerCase() === location.alias.toLowerCase();
+                  });
+                  if (found) {
+                    return;
+                  }
+                }
+              }
+              // If it's qualified i.e.'b' in SELECT a.b we shouldn't suggest aliases
+              if (token.parseLocation.type === 'column' && token.parseLocation.qualified) {
+                continue;
+              }
+              if (location.source === 'column' || location.source === 'table' || location.source === 'subquery' || location.source === 'cte') {
+                aliases.push({ name: location.alias.toLowerCase() });
               }
-            }
-            // If it's qualified i.e.'b' in SELECT a.b we shouldn't suggest aliases
-            if (token.parseLocation.type === 'column' && token.parseLocation.qualified) {
-              continue;
-            }
-            if (location.source === 'column' || location.source === 'table' || location.source === 'subquery' || location.source === 'cte') {
-              aliases.push({ name: location.alias.toLowerCase() });
             }
           }
-        }
 
-        self.fetchPossibleValues(token).done(function (possibleValues) {
-          // Append table aliases
-          possibleValues = possibleValues.concat(aliases);
+          self.fetchPossibleValues(token).done(function (possibleValues) {
+            // Append table aliases
+            possibleValues = possibleValues.concat(aliases);
 
-          var tokenValLower = token.actualValue.toLowerCase();
-          // Break if found
-          for (var i = 0; i < possibleValues.length; i++) {
-            possibleValues[i].name = SqlUtils.backTickIfNeeded(self.snippet.type(), possibleValues[i].name);
-            if ((possibleValues[i].name.toLowerCase() === tokenValLower) || (tokenValLower.indexOf('`') === 0 && tokenValLower.replace(/`/g, '') === possibleValues[i].name.toLowerCase())) {
-              return;
+            var tokenValLower = token.actualValue.toLowerCase();
+            // Break if found
+            for (var i = 0; i < possibleValues.length; i++) {
+              possibleValues[i].name = SqlUtils.backTickIfNeeded(self.snippet.type(), possibleValues[i].name);
+              if ((possibleValues[i].name.toLowerCase() === tokenValLower) || (tokenValLower.indexOf('`') === 0 && tokenValLower.replace(/`/g, '') === possibleValues[i].name.toLowerCase())) {
+                return;
+              }
             }
-          }
 
-          var uniqueIndex = {};
-          possibleValues = possibleValues.filter(function (value) {
-            if (uniqueIndex[value.name.toLowerCase()]) {
-              return false;
-            }
-            uniqueIndex[value.name.toLowerCase()] = true;
-            return true;
-          });
+            var uniqueIndex = {};
+            possibleValues = possibleValues.filter(function (value) {
+              if (uniqueIndex[value.name.toLowerCase()]) {
+                return false;
+              }
+              uniqueIndex[value.name.toLowerCase()] = true;
+              return true;
+            });
 
-          var isLowerCase = tokenValLower === token.value;
+            var isLowerCase = tokenValLower === token.value;
 
-          var weightedExpected = $.map(possibleValues, function (val) {
-            return {
-              text: isLowerCase ? val.name.toLowerCase() : val.name,
-              distance: SqlParseSupport.stringDistance(token.value, val.name)
+            var weightedExpected = $.map(possibleValues, function (val) {
+              return {
+                text: isLowerCase ? val.name.toLowerCase() : val.name,
+                distance: SqlParseSupport.stringDistance(token.value, val.name)
+              }
+            });
+            weightedExpected.sort(function (a, b) {
+              if (a.distance === b.distance) {
+                return a.text.localeCompare(b.text);
+              }
+              return a.distance - b.distance
+            });
+            token.syntaxError = {
+              expected: weightedExpected
+            };
+            token.notFound = true;
+
+            if (token.parseLocation && token.parseLocation.type === 'table') {
+              ApiHelper.getInstance().identifierChainToPath({
+                identifierChain: token.parseLocation.identifierChain,
+                sourceType: self.snippet.type(),
+                defaultDatabase: self.snippet.database()
+              }, function (path) {
+                token.qualifiedIdentifier = path.join('.');
+              })
             }
-          });
-          weightedExpected.sort(function (a, b) {
-            if (a.distance === b.distance) {
-              return a.text.localeCompare(b.text);
+
+            if (token.parseLocation) {
+              var AceRange = ace.require('ace/range').Range;
+              var range = new AceRange(token.parseLocation.location.first_line - 1, token.parseLocation.location.first_column - 1, token.parseLocation.location.last_line - 1, token.parseLocation.location.last_column - 1);
+              var markerId = self.editor.getSession().addMarker(range, 'hue-ace-syntax-warning');
+              self.editor.getSession().$backMarkers[markerId].token = token;
             }
-            return a.distance - b.distance
           });
-          token.syntaxError = {
-            expected: weightedExpected
-          };
-          token.notFound = true;
-
-          if (token.parseLocation && token.parseLocation.type === 'table') {
-            ApiHelper.getInstance().identifierChainToPath({
-              identifierChain: token.parseLocation.identifierChain,
-              sourceType: self.snippet.type(),
-              defaultDatabase: self.snippet.database()
-            }, function (path) {
-              token.qualifiedIdentifier = path.join('.');
-            })
-          }
+        }
 
-          if (token.parseLocation) {
-            var AceRange = ace.require('ace/range').Range;
-            var range = new AceRange(token.parseLocation.location.first_line - 1, token.parseLocation.location.first_column - 1, token.parseLocation.location.last_line - 1, token.parseLocation.location.last_column - 1);
-            var markerId = self.editor.getSession().addMarker(range, 'hue-ace-syntax-warning');
-            self.editor.getSession().$backMarkers[markerId].token = token;
-          }
-        });
-      }
+      })
     };
 
     AceLocationHandler.prototype.attachSqlWorker = function () {
@@ -3933,7 +3942,7 @@
       };
 
       var locationWorkerSub = huePubSub.subscribe('ace.sql.location.worker.message', function (e) {
-        if (e.data.id !== self.snippet.id()) {
+        if (e.data.id !== self.snippet.id() || e.data.editorChangeTime !== self.editor.lastChangeTime) {
           return;
         }
 
@@ -3953,6 +3962,7 @@
             delete activeTokens.pop().parseLocation;
           }
 
+          var tokensToVerify = [];
           e.data.locations.forEach(function (location) {
             if (location.type === 'statement' || ((location.type === 'table' || location.type === 'column') && typeof location.identifierChain === 'undefined')) {
               return;
@@ -3989,18 +3999,18 @@
                         if (typeof data.columns !== 'undefined' && data.columns.indexOf(location.identifierChain[0].name.toLowerCase()) !== -1) {
                           location.identifierChain = nextTable.identifierChain.concat(location.identifierChain);
                           delete location.tables;
-                          self.verifyExists(token, e.data.locations);
+                          tokensToVerify.push(token);
                         } else if (tablesToGo.length > 0) {
                           findIdentifierChainInTable(tablesToGo);
                         } else {
-                          self.verifyExists(token, e.data.locations);
+                          tokensToVerify.push(token);
                         }
                       }
                     })
                   } else if (tablesToGo.length > 0) {
                     findIdentifierChainInTable(tablesToGo);
                   } else {
-                    self.verifyExists(token, e.data.locations);
+                    tokensToVerify.push(token);
                   }
                 };
                 if (location.tables.length > 1) {
@@ -4008,14 +4018,15 @@
                 } else if (location.tables.length == 1 && location.tables[0].identifierChain) {
                   location.identifierChain = location.tables[0].identifierChain.concat(location.identifierChain);
                   delete location.tables;
-                  self.verifyExists(token, e.data.locations);
+                  tokensToVerify.push(token);
                 }
               } else {
-                self.verifyExists(token, e.data.locations);
+                tokensToVerify.push(token);
               }
             }
           });
 
+          self.verifyExists(tokensToVerify, e.data.locations);
           huePubSub.publish('editor.active.locations', lastKnownLocations);
         });
       });

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

@@ -77,6 +77,7 @@ importScripts('${ static('desktop/js/sqlFunctions.js') }');
 
         postMessage({
           id: msg.data.id,
+          editorChangeTime: msg.data.statementDetails.editorChangeTime,
           locations: locations,
           activeStatementLocations: activeStatementLocations,
           totalStatementCount: msg.data.statementDetails.totalStatementCount,

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

@@ -62,6 +62,7 @@ importScripts('${ static('desktop/js/autocomplete/sqlSyntaxParser.js') }');
       }
       postMessage({
         id: msg.data.id,
+        editorChangeTime: msg.data.editorChangeTime,
         syntaxError: syntaxError,
         statementLocation: msg.data.statementLocation
       });