Преглед на файлове

HUE-7547 [editor] Improve location handling for Impala complex identifiers

Johan Ahlen преди 8 години
родител
ревизия
e1acf5b

+ 1 - 1
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/sql_main.jison

@@ -3108,7 +3108,7 @@ TableSubQueryInner
    {
      var subQuery = parser.getSubQuery($2);
      subQuery.columns.forEach(function (column) {
-       parser.expandIdentifierChain(column);
+       parser.expandIdentifierChain({ wrapper: column });
        delete column.linked;
      });
      parser.popQueryState(subQuery);

+ 68 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/spec/sqlSpecLocations.js

@@ -35,6 +35,74 @@
       });
     };
 
+    it('should report locations for "SELECT * FROM customers c, c.orders o;"', function () {
+      assertLocations({
+        beforeCursor: 'SELECT * FROM customers c, c.orders o;',
+        dialect: 'impala',
+        expectedLocations: [
+          { type: 'statement', location: { first_line: 1, last_line: 1, first_column: 1, last_column: 38 } },
+          { type: 'selectList', missing: false, location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 } },
+          { type: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }, tables: [{ alias: 'c', identifierChain: [{ name: 'customers' }] }, { alias: 'o', identifierChain: [{ name: 'customers' }, { name: 'orders' }] }] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24 }, identifierChain: [{ name: 'customers' }] },
+          { type: 'alias', source: 'table', alias: 'c', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 26 }, identifierChain: [{ name: 'customers' }] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 29 }, identifierChain: [{ name: 'customers' }] },
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 36 }, identifierChain: [{ name: 'orders' }], tables: [{ identifierChain: [{ name: 'customers' }], alias: 'c' }], qualified: false },
+          { type: 'alias', source: 'table', alias: 'o', location: { first_line: 1, last_line: 1, first_column: 37, last_column: 38 }, identifierChain: [{ name: 'c' }, { name: 'orders' }] },
+          { type: 'whereClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 38, last_column: 38 } },
+          { type: 'limitClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 38, last_column: 38 } }
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT tm.| FROM testTable t, t.testMap tm;"', function() {
+      assertLocations({
+        beforeCursor: 'SELECT tm.',
+        afterCursor: ' FROM testTable t, t.testMap tm;',
+        dialect: 'impala',
+        expectedLocations: [
+          { type: 'statement', location: { first_line: 1, last_line: 1, first_column: 1, last_column: 42 } },
+          { type: 'selectList', missing: false, location: { first_line: 1, last_line: 1, first_column: 8, last_column: 12 } },
+          // TODO: Does it matter if we mark following as complex or column?
+          { type: 'complex', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 }, identifierChain: [{ name: 'testTable' }, { name: 'testMap' }], qualified: false },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 17, last_column: 26 }, identifierChain: [{ name: 'testTable' }] },
+          { type: 'alias', source: 'table', alias: 't', location: { first_line: 1, last_line: 1, first_column: 27, last_column: 28 }, identifierChain: [{ name: 'testTable' }] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 31 }, identifierChain: [{ name: 'testTable' }] },
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 32, last_column: 39 }, identifierChain: [{ name: 'testMap' }], tables: [{ identifierChain: [{ name: 'testTable' }], alias: 't' }], qualified: false },
+          { type: 'alias', source: 'table', alias: 'tm', location: { first_line: 1, last_line: 1, first_column: 40, last_column: 42 }, identifierChain: [{ name: 't' }, { name: 'testMap' }] },
+          { type: 'whereClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 42, last_column: 42 } },
+          { type: 'limitClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 42, last_column: 42 } }
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT * FROM customers c, c.orders o, (SELECT price FROM o.items) v;"', function () {
+      assertLocations({
+        beforeCursor: 'SELECT * FROM customers c, c.orders o, (SELECT price FROM o.items) v;',
+        dialect: 'impala',
+        expectedLocations: [
+          { type: 'statement', location: { first_line: 1, last_line: 1, first_column: 1, last_column: 69 } },
+          { type: 'selectList', missing: false, location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 } },
+          { type: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }, tables: [{ alias: 'c', identifierChain: [{ name: 'customers' }] }, { alias: 'o', identifierChain: [{ name: 'customers' }, { name: 'orders' }] }, { identifierChain: [{ subQuery: 'v' }] }] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24 }, identifierChain: [{ name: 'customers' }] },
+          { type: 'alias', source: 'table', alias: 'c', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 26 }, identifierChain: [{ name: 'customers' }] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 29 }, identifierChain: [{ name: 'customers' }] },
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 36 }, identifierChain: [{ name: 'orders' }], tables: [{ identifierChain: [{ name: 'customers' }], alias: 'c' }], qualified: false },
+          { type: 'alias', source: 'table', alias: 'o', location: { first_line: 1, last_line: 1, first_column: 37, last_column: 38 }, identifierChain: [{ name: 'c' }, { name: 'orders' }] },
+          { type: 'selectList', missing: false, location: { first_line: 1, last_line: 1, first_column: 48, last_column: 53 }, subquery: true },
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 48, last_column: 53 }, identifierChain: [{ name: 'price' }], qualified: false, tables: [{ identifierChain: [{ name: 'customers' }, { name: 'orders' }, { name: 'items' }] }] },
+          { type: 'complex', location: { first_line: 1, last_line: 1, first_column: 59, last_column: 60 }, identifierChain: [{ name: 'customers' }, { name: 'orders' }] },
+          { type: 'complex', location: { first_line: 1, last_line: 1, first_column: 61, last_column: 66 }, identifierChain: [{ name: 'o' }, { name: 'items' }], tables: [{ identifierChain: [{ name: 'customers' }, { name: 'orders' }, { name: 'items' }] }], qualified: false },
+          { type: 'whereClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 66, last_column: 66 }, subquery: true },
+          { type: 'limitClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 66, last_column: 66 }, subquery: true },
+          { type: 'alias', source: 'subquery', alias: 'v', location: { first_line: 1, last_line: 1, first_column: 68, last_column: 69 } },
+          { type: 'whereClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 69, last_column: 69 } },
+          { type: 'limitClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 69, last_column: 69 } }
+        ]
+      });
+    });
+
+
+
     it('should report locations for "select cos(1) as foo from customers order by foo;"', function () {
       assertLocations({
         beforeCursor: 'select cos(1) as foo from customers order by foo; ',

+ 0 - 12
desktop/core/src/desktop/static/desktop/js/autocomplete/spec/sqlSpecSelect.js

@@ -3375,18 +3375,6 @@
           afterCursor: ' FROM testTable t, t.testMap tm;',
           dialect: 'impala',
           expectedResult: {
-            locations: [
-              { type: 'statement', location: { first_line: 1, last_line: 1, first_column: 1, last_column: 42 } },
-              { type: 'selectList', missing: false, location: { first_line: 1, last_line: 1, first_column: 8, last_column: 12 } },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10}, identifierChain: [{ name: 'testMap' }], tables: [{ identifierChain: [{ name: 'testTable' }], alias: 't' }], qualified: false },
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 17, last_column :26}, identifierChain: [{ name: 'testTable' }]},
-              { type: 'alias', source: 'table', alias: 't', location: { first_line: 1, last_line: 1, first_column: 27, last_column: 28 }, identifierChain: [{ name: 'testTable' }] },
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 31}, identifierChain: [{ name: 'testTable' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 32, last_column :39}, identifierChain: [{ name: 'testMap'}], tables: [{ identifierChain: [{ name: 'testTable' }], alias: 't'  }], qualified: false },
-              { type: 'alias', source: 'table', alias: 'tm', location: { first_line: 1, last_line: 1, first_column: 40, last_column: 42 }, identifierChain: [{ name: 't' }, { name: 'testMap' }] },
-              { type: 'whereClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 42, last_column: 42 } },
-              { type: 'limitClause', missing: true, location: { first_line: 1, last_line: 1, first_column: 42, last_column: 42 } }
-            ],
             suggestKeywords: ['*'],
             lowerCase: false,
             suggestColumns: { source: 'select', identifierChain: [{ name: 'testMap' }], tables: [{ identifierChain: [{ name: 'testTable' }] }] }

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/sqlAutocompleteParser.js


+ 99 - 29
desktop/core/src/desktop/static/desktop/js/autocomplete/sqlParseSupport.js

@@ -374,15 +374,72 @@ var SqlParseSupport = (function () {
       }
     };
 
+    var getCleanImpalaPrimaries = function (primaries) {
+      var cleanPrimaries = [];
+      for (var i = primaries.length - 1; i >= 0; i--) {
+        var cleanPrimary = primaries[i];
+        if (cleanPrimary.identifierChain && cleanPrimary.identifierChain.length > 0) {
+          for (var j = i - 1; j >=0; j--) {
+            var parentPrimary = primaries[j];
+            if (parentPrimary.alias && cleanPrimary.identifierChain[0].name === parentPrimary.alias) {
+              var restOfChain = cleanPrimary.identifierChain.concat();
+              restOfChain.shift();
+              if (cleanPrimary.alias) {
+                cleanPrimary = { identifierChain: parentPrimary.identifierChain.concat(restOfChain), alias: cleanPrimary.alias, impalaComplex: true };
+              } else {
+                cleanPrimary = { identifierChain: parentPrimary.identifierChain.concat(restOfChain), impalaComplex: true };
+              }
+            }
+          }
+        }
+        cleanPrimaries.push(cleanPrimary);
+      }
+      return cleanPrimaries;
+    };
+
     parser.commitLocations = function () {
+      if (parser.yy.locations.length === 0) {
+        return;
+      }
+
+      var tablePrimaries = parser.yy.latestTablePrimaries;
+
+      if (parser.isImpala()) {
+        tablePrimaries = [];
+        getCleanImpalaPrimaries(parser.yy.latestTablePrimaries).forEach(function (primary) {
+          var cleanPrimary = primary;
+          if (primary.identifierChain && primary.identifierChain.length > 0) {
+            for (var j = parser.yy.primariesStack.length - 1; j >= 0; j--) {
+              getCleanImpalaPrimaries(parser.yy.primariesStack[j]).every(function (parentPrimary) {
+                if (parentPrimary.alias && parentPrimary.alias === primary.identifierChain[0].name) {
+                  var identifierChain = primary.identifierChain.concat();
+                  identifierChain.shift();
+                  cleanPrimary = { identifierChain: parentPrimary.identifierChain.concat(identifierChain) };
+                  if (primary.alias) {
+                    cleanPrimary.alias = primary.alias;
+                  }
+                  return false;
+                }
+                return true;
+              });
+            }
+          }
+          tablePrimaries.unshift(cleanPrimary);
+        });
+      }
       var i = parser.yy.locations.length;
+
       while (i--) {
         var location = parser.yy.locations[i];
 
         // Impala can have references to previous tables after FROM, i.e. FROM testTable t, t.testArray
         // In this testArray would be marked a type table so we need to switch it to column.
-        if (location.type === 'table' && typeof location.identifierChain !== 'undefined' && location.identifierChain.length > 1 && parser.yy.latestTablePrimaries) {
-          var found = parser.yy.latestTablePrimaries.filter(function (primary) {
+        if (location.type === 'table' && typeof location.identifierChain !== 'undefined' && location.identifierChain.length > 1 && tablePrimaries) {
+          var allPrimaries = tablePrimaries;
+          parser.yy.primariesStack.forEach(function (parentPrimaries) {
+            allPrimaries = getCleanImpalaPrimaries(parentPrimaries).concat(allPrimaries);
+          });
+          var found = allPrimaries.filter(function (primary) {
             return equalIgnoreCase(primary.alias, location.identifierChain[0].name);
           });
           if (found.length > 0) {
@@ -390,24 +447,30 @@ var SqlParseSupport = (function () {
           }
         }
 
-        if (location.type === 'database' && typeof location.identifierChain !== 'undefined' && location.identifierChain.length > 0 && parser.yy.latestTablePrimaries) {
-          var foundAlias = parser.yy.latestTablePrimaries.filter(function (primary) {
+        if (location.type === 'database' && typeof location.identifierChain !== 'undefined' && location.identifierChain.length > 0 && tablePrimaries) {
+          var allPrimaries = tablePrimaries;
+          parser.yy.primariesStack.forEach(function (parentPrimaries) {
+            allPrimaries = getCleanImpalaPrimaries(parentPrimaries).concat(allPrimaries);
+          });
+          var foundAlias = allPrimaries.filter(function (primary) {
             return equalIgnoreCase(primary.alias, location.identifierChain[0].name);
           });
-          if (foundAlias.length > 0) {
+          if (foundAlias.length > 0 && parser.isImpala()) {
             // Impala complex reference in FROM clause, i.e. FROM testTable t, t.testMap tm
             location.type = 'table';
-            parser.expandIdentifierChain(location, true);
+            parser.expandIdentifierChain({ tablePrimaries: allPrimaries, wrapper: location, anyOwner: true });
+            location.type = location.identifierChain.length === 1 ? 'table' : 'complex';
+            continue;
           }
         }
 
         if (location.type === 'unknown') {
-          if (typeof location.identifierChain !== 'undefined' && location.identifierChain.length > 0 && location.identifierChain.length <= 2 && parser.yy.latestTablePrimaries) {
-            var found = parser.yy.latestTablePrimaries.filter(function (primary) {
+          if (typeof location.identifierChain !== 'undefined' && location.identifierChain.length > 0 && location.identifierChain.length <= 2 && tablePrimaries) {
+            var found = tablePrimaries.filter(function (primary) {
               return equalIgnoreCase(primary.alias, location.identifierChain[0].name) || (primary.identifierChain && equalIgnoreCase(primary.identifierChain[0].name, location.identifierChain[0].name));
             });
             if (!found.length && location.firstInChain) {
-              found = parser.yy.latestTablePrimaries.filter(function (primary) {
+              found = tablePrimaries.filter(function (primary) {
                 return !primary.alias && primary.identifierChain && equalIgnoreCase(primary.identifierChain[primary.identifierChain.length - 1].name, location.identifierChain[0].name);
               });
             }
@@ -417,13 +480,13 @@ var SqlParseSupport = (function () {
                 location.type = 'database';
               } else if (found[0].alias && equalIgnoreCase(location.identifierChain[0].name, found[0].alias) && location.identifierChain.length > 1) {
                 location.type = 'column';
-                parser.expandIdentifierChain(location, true);
+                parser.expandIdentifierChain({ tablePrimaries: tablePrimaries, wrapper: location, anyOwner: true });
               } else if (!found[0].alias && found[0].identifierChain && equalIgnoreCase(location.identifierChain[0].name, found[0].identifierChain[found[0].identifierChain.length - 1].name) && location.identifierChain.length > 1) {
                 location.type = 'column';
-                parser.expandIdentifierChain(location, true);
+                parser.expandIdentifierChain({ tablePrimaries: tablePrimaries, wrapper: location, anyOwner: true });
               } else {
-                location.type = 'table';
-                parser.expandIdentifierChain(location, true);
+                location.type = found[0].impalaComplex ? 'complex' : 'table';
+                parser.expandIdentifierChain({ tablePrimaries: tablePrimaries, wrapper: location, anyOwner: true });
               }
             } else {
               if (parser.yy.subQueries) {
@@ -440,10 +503,11 @@ var SqlParseSupport = (function () {
         }
 
         if (location.type === 'asterisk' && !location.linked) {
-          if (parser.yy.latestTablePrimaries && parser.yy.latestTablePrimaries.length > 0) {
+
+          if (tablePrimaries && tablePrimaries.length > 0) {
             location.tables = [];
             location.linked = false;
-            parser.expandIdentifierChain(location, true);
+            parser.expandIdentifierChain({ tablePrimaries: tablePrimaries, wrapper: location, anyOwner: true });
             if (location.tables.length === 0) {
               parser.yy.locations.splice(i, 1);
             }
@@ -481,7 +545,8 @@ var SqlParseSupport = (function () {
           }
 
           var initialIdentifierChain = location.identifierChain ? location.identifierChain.concat() : undefined;
-          parser.expandIdentifierChain(location, true, true, true);
+
+          parser.expandIdentifierChain({ tablePrimaries: tablePrimaries, wrapper: location, anyOwner: true, isColumnWrapper: true, isColumnLocation: true });
 
           if (typeof location.identifierChain === 'undefined') {
             parser.yy.locations.splice(i, 1);
@@ -742,12 +807,17 @@ var SqlParseSupport = (function () {
       }
     };
 
-    parser.expandIdentifierChain = function (wrapper, anyOwner, isColumnWrapper, isColumnLocation) {
-      if (typeof wrapper.identifierChain === 'undefined' || typeof parser.yy.latestTablePrimaries === 'undefined') {
+    parser.expandIdentifierChain = function (options) {
+      var wrapper = options.wrapper;
+      var anyOwner = options.anyOwner;
+      var isColumnWrapper = options.isColumnWrapper;
+      var isColumnLocation = options.isColumnLocation;
+      var tablePrimaries = options.tablePrimaries || parser.yy.latestTablePrimaries;
+
+      if (typeof wrapper.identifierChain === 'undefined' || typeof tablePrimaries === 'undefined') {
         return;
       }
       var identifierChain = wrapper.identifierChain.concat();
-      var tablePrimaries = parser.yy.latestTablePrimaries;
 
       if (tablePrimaries.length === 0) {
         delete wrapper.identifierChain;
@@ -784,7 +854,7 @@ var SqlParseSupport = (function () {
       }
 
       if (!anyOwner) {
-        tablePrimaries = filterTablePrimariesForOwner(wrapper.owner);
+        tablePrimaries = filterTablePrimariesForOwner(tablePrimaries, wrapper.owner);
       }
       // Impala can have references to maps or array, i.e. FROM table t, t.map m
       // We need to replace those in the identifierChain
@@ -913,9 +983,9 @@ var SqlParseSupport = (function () {
       }
     };
 
-    var filterTablePrimariesForOwner = function (owner) {
+    var filterTablePrimariesForOwner = function (tablePrimaries, owner) {
       var result = [];
-      parser.yy.latestTablePrimaries.forEach(function (primary) {
+      tablePrimaries.forEach(function (primary) {
         if (typeof owner === 'undefined' && typeof primary.owner === 'undefined') {
           result.push(primary);
         } else if (owner === primary.owner) {
@@ -992,7 +1062,7 @@ var SqlParseSupport = (function () {
       });
 
       if (typeof parser.yy.result.suggestColumns !== 'undefined' && !parser.yy.result.suggestColumns.linked) {
-        var tablePrimaries = filterTablePrimariesForOwner(parser.yy.result.suggestColumns.owner);
+        var tablePrimaries = filterTablePrimariesForOwner(parser.yy.latestTablePrimaries, parser.yy.result.suggestColumns.owner);
         if (!parser.yy.result.suggestColumns.tables) {
           parser.yy.result.suggestColumns.tables = [];
         }
@@ -1007,7 +1077,7 @@ var SqlParseSupport = (function () {
             if (tablePrimaries.length === 1 && (tablePrimaries[0].alias || tablePrimaries[0].subQueryAlias)) {
               convertTablePrimariesToSuggestions(tablePrimaries);
             }
-            parser.expandIdentifierChain(parser.yy.result.suggestColumns, false, true);
+            parser.expandIdentifierChain({ wrapper: parser.yy.result.suggestColumns, anyOwner: false, isColumnWrapper: true });
           }
         } else {
           // Expand exploded views in the identifier chain
@@ -1022,24 +1092,24 @@ var SqlParseSupport = (function () {
                 parser.yy.result.suggestKeywords[0].value === '*') {
                 delete parser.yy.result.suggestKeywords;
               }
-              parser.expandIdentifierChain(parser.yy.result.suggestColumns, false, true);
+              parser.expandIdentifierChain({ wrapper: parser.yy.result.suggestColumns, anyOwner: false, isColumnWrapper: true });
             }
           } else {
-            parser.expandIdentifierChain(parser.yy.result.suggestColumns, false, true);
+            parser.expandIdentifierChain({ wrapper: parser.yy.result.suggestColumns, anyOwner: false, isColumnWrapper: true });
           }
         }
       }
 
       if (typeof parser.yy.result.colRef !== 'undefined' && !parser.yy.result.colRef.linked) {
-        parser.expandIdentifierChain(parser.yy.result.colRef);
+        parser.expandIdentifierChain({ wrapper: parser.yy.result.colRef });
 
-        var primaries = filterTablePrimariesForOwner();
+        var primaries = filterTablePrimariesForOwner(parser.yy.latestTablePrimaries);
         if (primaries.length === 0 || (primaries.length > 1 && parser.yy.result.colRef.identifierChain.length === 1)) {
           parser.yy.result.colRef.identifierChain = [];
         }
       }
       if (typeof parser.yy.result.suggestKeyValues !== 'undefined' && !parser.yy.result.suggestKeyValues.linked) {
-        parser.expandIdentifierChain(parser.yy.result.suggestKeyValues);
+        parser.expandIdentifierChain({ wrapper: parser.yy.result.suggestKeyValues });
       }
     };
 

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/sqlSyntaxParser.js


Някои файлове не бяха показани, защото твърде много файлове са промени