Просмотр исходного кода

HUE-4857 [editor] Columns are not highlighted when there are multiple tables

Johan Ahlen 9 лет назад
Родитель
Сommit
5fbdee7

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

@@ -862,7 +862,7 @@ ColumnReference
    }
  | BasicIdentifierChain AnyDot '*'
    {
-     addColumnLocation(parser.yy.lastIdentifierChainLocation, $1);
+     // TODO: Add table/column locations for identifiers before *
    }
  ;
 

+ 34 - 24
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js

@@ -211,7 +211,7 @@ case 659:
 break;
 case 660:
 
-     addColumnLocation(parser.yy.lastIdentifierChainLocation, $$[$0-2]);
+     // TODO: Add table/column locations for identifiers before *
    
 break;
 case 662:
@@ -4223,8 +4223,17 @@ var commitLocations = function () {
     if (location.type === 'table' && (typeof location.identifierChain === 'undefined' || location.identifierChain.length === 0)) {
       parser.yy.locations.splice(i, 1);
     }
-    if (location.type === 'column' && (typeof location.identifierChain === 'undefined' || location.identifierChain.length < 2)) {
-      parser.yy.locations.splice(i, 1);
+    if (location.type === 'column') {
+      if (typeof location.identifierChain === 'undefined') {
+        parser.yy.locations.splice(i, 1);
+      } else if (location.identifierChain.length < 2) {
+        location.tables = [];
+        location.linked = false;
+        expandIdentifierChain(location, true);
+        if (location.tables.length === 0) {
+          parser.yy.locations.splice(i, 1);
+        }
+      }
     }
   }
   if (parser.yy.locations.length > 0) {
@@ -4463,9 +4472,9 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
 
   // IdentifierChain contains a possibly started identifier or empty, example: a.b.c = ['a', 'b', 'c']
   // Reduce the tablePrimaries to the one that matches the first identifier if found
+  var foundPrimary;
+  var doubleMatch = false;
   if (identifierChain.length > 0) {
-    var foundPrimary;
-    var doubleMatch = false;
     for (var i = 0; i < tablePrimaries.length; i++) {
       if (tablePrimaries[i].subQueryAlias) {
         if (tablePrimaries[i].subQueryAlias === identifierChain[0].name) {
@@ -4490,26 +4499,28 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
         break;
       }
     }
-    if (foundPrimary) {
+  }
+
+  if (foundPrimary) {
+    identifierChain.shift();
+    if (doubleMatch) {
       identifierChain.shift();
-      if (doubleMatch) {
-        identifierChain.shift();
-      }
-    } else if (tablePrimaries.length === 1) {
-      foundPrimary = tablePrimaries[0];
     }
-    if (foundPrimary) {
-      if (foundPrimary.subQueryAlias) {
-        identifierChain.unshift({ subQuery: foundPrimary.subQueryAlias });
-      } else {
-        identifierChain = foundPrimary.identifierChain.concat(identifierChain);
-      }
-      if (wrapper.tables) {
-        wrapper.tables.push({ identifierChain: identifierChain });
-        delete wrapper.identifierChain;
-      } else {
-        wrapper.identifierChain = identifierChain;
-      }
+  } else if (tablePrimaries.length === 1) {
+    foundPrimary = tablePrimaries[0];
+  }
+
+  if (foundPrimary) {
+    if (foundPrimary.subQueryAlias) {
+      identifierChain.unshift({ subQuery: foundPrimary.subQueryAlias });
+    } else {
+      identifierChain = foundPrimary.identifierChain.concat(identifierChain);
+    }
+    if (wrapper.tables) {
+      wrapper.tables.push({ identifierChain: identifierChain });
+      delete wrapper.identifierChain;
+    } else {
+      wrapper.identifierChain = identifierChain;
     }
   } else {
     tablePrimaries.forEach(function (tablePrimary) {
@@ -4520,7 +4531,6 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
       if (wrapper.tables) {
         wrapper.tables.push(targetTable)
       }
-
     });
   }
 

+ 33 - 23
desktop/core/src/desktop/static/desktop/js/autocomplete/sql_support.js

@@ -295,8 +295,17 @@ var commitLocations = function () {
     if (location.type === 'table' && (typeof location.identifierChain === 'undefined' || location.identifierChain.length === 0)) {
       parser.yy.locations.splice(i, 1);
     }
-    if (location.type === 'column' && (typeof location.identifierChain === 'undefined' || location.identifierChain.length < 2)) {
-      parser.yy.locations.splice(i, 1);
+    if (location.type === 'column') {
+      if (typeof location.identifierChain === 'undefined') {
+        parser.yy.locations.splice(i, 1);
+      } else if (location.identifierChain.length < 2) {
+        location.tables = [];
+        location.linked = false;
+        expandIdentifierChain(location, true);
+        if (location.tables.length === 0) {
+          parser.yy.locations.splice(i, 1);
+        }
+      }
     }
   }
   if (parser.yy.locations.length > 0) {
@@ -535,9 +544,9 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
 
   // IdentifierChain contains a possibly started identifier or empty, example: a.b.c = ['a', 'b', 'c']
   // Reduce the tablePrimaries to the one that matches the first identifier if found
+  var foundPrimary;
+  var doubleMatch = false;
   if (identifierChain.length > 0) {
-    var foundPrimary;
-    var doubleMatch = false;
     for (var i = 0; i < tablePrimaries.length; i++) {
       if (tablePrimaries[i].subQueryAlias) {
         if (tablePrimaries[i].subQueryAlias === identifierChain[0].name) {
@@ -562,26 +571,28 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
         break;
       }
     }
-    if (foundPrimary) {
+  }
+
+  if (foundPrimary) {
+    identifierChain.shift();
+    if (doubleMatch) {
       identifierChain.shift();
-      if (doubleMatch) {
-        identifierChain.shift();
-      }
-    } else if (tablePrimaries.length === 1) {
-      foundPrimary = tablePrimaries[0];
     }
-    if (foundPrimary) {
-      if (foundPrimary.subQueryAlias) {
-        identifierChain.unshift({ subQuery: foundPrimary.subQueryAlias });
-      } else {
-        identifierChain = foundPrimary.identifierChain.concat(identifierChain);
-      }
-      if (wrapper.tables) {
-        wrapper.tables.push({ identifierChain: identifierChain });
-        delete wrapper.identifierChain;
-      } else {
-        wrapper.identifierChain = identifierChain;
-      }
+  } else if (tablePrimaries.length === 1) {
+    foundPrimary = tablePrimaries[0];
+  }
+
+  if (foundPrimary) {
+    if (foundPrimary.subQueryAlias) {
+      identifierChain.unshift({ subQuery: foundPrimary.subQueryAlias });
+    } else {
+      identifierChain = foundPrimary.identifierChain.concat(identifierChain);
+    }
+    if (wrapper.tables) {
+      wrapper.tables.push({ identifierChain: identifierChain });
+      delete wrapper.identifierChain;
+    } else {
+      wrapper.identifierChain = identifierChain;
     }
   } else {
     tablePrimaries.forEach(function (tablePrimary) {
@@ -592,7 +603,6 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
       if (wrapper.tables) {
         wrapper.tables.push(targetTable)
       }
-
     });
   }
 

+ 25 - 1
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -2749,7 +2749,31 @@
           e.data.locations.forEach(function (location) {
             var token = editor.session.getTokenAt(location.location.first_line - 1, location.location.first_column);
             if (token !== null) {
-              token.parseLocation = location;
+              if (location.type === 'column' && typeof location.tables !== 'undefined' && location.identifierChain.length === 1) {
+
+                var findIdentifierChainInTable = function (tablesToGo) {
+                  var nextTable = tablesToGo.shift();
+                  apiHelper.fetchAutocomplete({
+                    sourceType: snippet.type(),
+                    defaultDatabase: snippet.database(),
+                    identifierChain: nextTable.identifierChain,
+                    silenceErrors: true,
+                    successCallback: function (data) {
+                      if (typeof data.columns !== 'undefined' && data.columns.indexOf(location.identifierChain[0].name) !== -1) {
+                        location.identifierChain = nextTable.identifierChain.concat(location.identifierChain);
+                        delete location.tables;
+                        token.parseLocation = location;
+                      } else if (tablesToGo.length > 0) {
+                        findIdentifierChainInTable(tablesToGo);
+                      }
+                    }
+                  })
+                };
+
+                findIdentifierChainInTable(location.tables.concat());
+              } else {
+                token.parseLocation = location;
+              }
             }
           });
         };

+ 1 - 0
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpec.js

@@ -25,6 +25,7 @@ define([
   'desktop/spec/autocomplete/sqlSpecError',
   'desktop/spec/autocomplete/sqlSpecInsert',
   'desktop/spec/autocomplete/sqlSpecLoad',
+  'desktop/spec/autocomplete/sqlSpecLocations',
   'desktop/spec/autocomplete/sqlSpecSelect',
   'desktop/spec/autocomplete/sqlSpecSet',
   'desktop/spec/autocomplete/sqlSpecShow',

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

@@ -0,0 +1,340 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+define([
+  'knockout',
+  'desktop/js/autocomplete/sql',
+  'desktop/spec/autocompleterTestUtils'
+], function(ko, sql, testUtils) {
+
+  describe('sql.js locations', function() {
+
+    beforeAll(function () {
+      sql.yy.parseError = function (msg) {
+        throw Error(msg);
+      };
+      jasmine.addMatchers(testUtils.testDefinitionMatcher);
+    });
+
+    var assertLocations = function (options) {
+      testUtils.assertAutocomplete({
+        dialect: options.dialect,
+        beforeCursor: options.beforeCursor,
+        afterCursor: options.afterCursor || '',
+        locationsOnly: true,
+        noErrors: true,
+        expectedLocations: options.expectedLocations
+      });
+    };
+
+    it('should report locations for "SELECT * FROM testTable1 JOIN db1.table2; |"', function() {
+      assertLocations({
+        beforeCursor: 'SELECT * FROM testTable1 JOIN db1.table2; ',
+        expectedLocations: [
+          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 25 }, identifierChain: [{ name: 'testTable1' }] },
+          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 41 }, identifierChain: [{ name: 'db1' }, { name: 'table2' }] }
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT t3.id, id FROM testTable1, db.testTable2, testTable3 t3;|"', function() {
+      assertLocations({
+        beforeCursor: 'SELECT t3.id, id FROM testTable1, db.testTable2, testTable3 t3;',
+        expectedLocations: [
+          { type:'column', location: { first_line: 1, last_line: 1, first_column: 11, last_column: 13 }, identifierChain: [{ name: 'testTable3' },{ name: 'id' }] },
+          { type:'column', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 17 }, identifierChain: [{ name: 'id' }], tables: [{ identifierChain: [{ name: 'testTable1' }]}, { identifierChain: [{ name: 'db' }, { name: 'testTable2' }]}, { identifierChain: [{ name: 'testTable3' }], alias: 't3'}] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 23, last_column: 33 }, identifierChain: [{ name: 'testTable1' }] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 38, last_column: 48 }, identifierChain: [{ name: 'db' },{ name: 'testTable2'}] },
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 50, last_column: 60 }, identifierChain: [{ name: 'testTable3' }] }
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT * FROM foo WHERE bar IN (1+1, 2+2);|"', function() {
+      assertLocations({
+        beforeCursor: 'SELECT * FROM foo WHERE bar IN (1+1, 2+2);',
+        expectedLocations: [
+          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
+          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]}
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);|"', function() {
+      assertLocations({
+        beforeCursor: 'SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);',
+        expectedLocations: [
+          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
+          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
+          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 33, last_column: 35 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]},
+          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 43 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]}
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT CASE cos(boo.a) > baa.boo \\n' +
+        '\\tWHEN baa.b THEN true \\n' +
+        '\\tWHEN boo.c THEN false \\n' +
+        '\\tWHEN baa.blue THEN boo.d \\n' +
+        '\\tELSE baa.e END \\n' +
+        '\\t FROM db1.foo boo, bar baa WHERE baa.bla IN (SELECT ble FROM bla);|"', function() {
+      assertLocations({
+        beforeCursor: 'SELECT CASE cos(boo.a) > baa.boo \n\tWHEN baa.b THEN true \n\tWHEN boo.c THEN false \n\tWHEN baa.blue THEN boo.d \n\tELSE baa.e END \n\t FROM db1.foo boo, bar baa WHERE baa.bla IN (SELECT ble FROM bla);',
+        expectedLocations: [
+          {type: 'function', location: { first_line: 1, last_line: 1, first_column: 13, last_column: 15 }, function: 'cos'},
+          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 21, last_column: 22 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'a' }]},
+          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 33 }, identifierChain: [{ name: 'bar' }, { name: 'boo' }]},
+          {type: 'column', location: { first_line: 2, last_line: 2, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'b' }]},
+          {type: 'column', location: { first_line: 3, last_line: 3, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'c' }] },
+          {type: 'column', location: { first_line: 4, last_line: 4, first_column: 11, last_column: 15 }, identifierChain: [{ name: 'bar' }, { name: 'blue' }] },
+          {type: 'column', location: { first_line: 4, last_line: 4, first_column: 25, last_column: 26 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'd'}] },
+          {type: 'column', location: { first_line: 5, last_line: 5, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'e' }] },
+          {type: 'table', location: { first_line: 6, last_line: 6, first_column: 12, last_column: 15 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }]},
+          {type: 'table', location: { first_line: 6, last_line: 6, first_column: 21, last_column: 24 }, identifierChain: [{ name: 'bar' }]},
+          {type: 'column', location: { first_line: 6, last_line: 6, first_column: 39, last_column: 42 }, identifierChain: [{ name: 'bar' }, { name: 'bla' }] },
+          {type: 'column', location: { first_line: 6, last_line: 6, first_column: 54, last_column: 57 }, identifierChain: [{ name: 'bla' }, { name: 'ble' }] },
+          {type: 'table', location: { first_line: 6, last_line: 6, first_column: 63, last_column: 66 }, identifierChain: [{ name: 'bla' }]}
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT tta.* FROM testTableA tta, testTableB; |"', function() {
+      assertLocations({
+        beforeCursor: 'SELECT tta.* FROM testTableA tta, testTableB; ',
+        expectedLocations: [
+          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 19, last_column: 29}, identifierChain: [{ name: 'testTableA' }]},
+          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 45}, identifierChain: [{ name: 'testTableB' }]}
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT COUNT(*) FROM testTable; |"', function () {
+      assertLocations({
+        beforeCursor: 'SELECT COUNT(*) FROM testTable;',
+        expectedLocations: [
+          {type: 'function', location:{ first_line: 1, last_line: 1, first_column: 8, last_column: 12}, function: 'count'},
+          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 31}, identifierChain: [{ name: 'testTable' }]}
+        ]
+      });
+    });
+
+    describe('Hive specific', function () {
+      it('should report locations for "SELECT * FROM testTable t1 ORDER BY t1.a ASC, t1.b, t1.c DESC, t1.d;\nSELECT t1.bla FROM testTable2 t1;\\nSELECT * FROM testTable3 t3, testTable4 t4; |"', function () {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT * FROM testTable t1 ORDER BY t1.a ASC, t1.b, t1.c DESC, t1.d;\nSELECT t1.bla FROM testTable2 t1;\nSELECT * FROM testTable3 t3, testTable4 t4; ',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24}, identifierChain: [{ name: 'testTable' }]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 40, last_column: 41},identifierChain: [{ name: 'testTable' }, { name: 'a'}]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 50, last_column: 51},identifierChain: [{ name: 'testTable' }, { name: 'b'}]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 56, last_column: 57},identifierChain: [{ name: 'testTable' }, { name: 'c'}]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 67, last_column: 68},identifierChain: [{ name: 'testTable' }, { name: 'd'}]},
+            { type: 'column', location: { first_line: 2, last_line: 2, first_column: 11, last_column: 14},identifierChain: [{ name: 'testTable2' }, { name: 'bla'}]},
+            { type: 'table', location: { first_line: 2, last_line: 2, first_column: 20, last_column: 30}, identifierChain: [{ name: 'testTable2' }]},
+            { type: 'table', location: { first_line: 3, last_line: 3, first_column: 15, last_column: 25}, identifierChain: [{ name: 'testTable3' }]},
+            { type: 'table', location: { first_line: 3, last_line: 3, first_column: 30, last_column: 40}, identifierChain: [{ name: 'testTable4' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT t1.foo FROM table1 t1 CROSS JOIN table2 LEFT OUTER JOIN table3 JOIN table4 t4 ON (t1.c1 = t2.c2); |"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT t1.foo FROM table1 t1 CROSS JOIN table2 LEFT OUTER JOIN table3 JOIN table4 t4 ON (t1.c1 = t2.c2); ',
+          expectedLocations: [
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 11, last_column: 14 }, identifierChain: [{ name: 'table1' }, { name: 'foo' }] },
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 20, last_column: 26 }, identifierChain: [{ name: 'table1' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 47 }, identifierChain: [{ name: 'table2' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 64, last_column: 70 }, identifierChain: [{ name: 'table3' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 76, last_column: 82 }, identifierChain: [{ name: 'table4' }]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 93, last_column: 95 }, identifierChain: [{ name: 'table1' }, { name: 'c1'}]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 101, last_column: 103 }, identifierChain: [{ name: 't2' }, { name: 'c2'}]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT * FROM foo WHERE bar IN (SELECT * FROM bla);|"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT * FROM foo WHERE bar IN (SELECT * FROM bla);',
+          expectedLocations: [
+            {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
+            {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
+            {type: 'table', location: { first_line: 1, last_line: 1, first_column: 47, last_column: 50 }, identifierChain: [{ name: 'bla' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT   |    FROM    testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT   ',
+          afterCursor: '    FROM    testTableA',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 32}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT   a.|    FROM    testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT   a.',
+          afterCursor: '    FROM    testTableA',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 24, last_column: 34}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT aaa| FROM testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT aaa',
+          afterCursor: ' FROM testTableA',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 17, last_column: 27}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT aaa| \\nFROM testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT aaa',
+          afterCursor: ' \nFROM testTableA',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 2, last_line: 2, first_column: 6, last_column: 16}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT |bbbb FROM testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT ',
+          afterCursor: 'bbbb FROM testTableA',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 18, last_column: 28}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT a.aaaaa|bbbb FROM testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT aaaaa',
+          afterCursor: 'bbbb FROM testTableA',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 23, last_column: 33}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT foo.aaaaa|bbbb FROM testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT foo.aaaaa',
+          afterCursor: 'bbbb FROM testTableA',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 27, last_column: 37}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT b, foo.aaaaa|bbbb FROM testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT b, foo.aaaaa',
+          afterCursor: 'bbbb FROM testTableA',
+          expectedLocations: [
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9}, identifierChain: [{ name: 'testTableA' }, { name: 'b' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 40}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT foo, aaaaa|bbbb FROM testTableA"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT foo, aaaaa',
+          afterCursor: 'bbbb FROM testTableA',
+          expectedLocations: [
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'testTableA' }, { name: 'foo' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 38}, identifierChain: [{ name: 'testTableA' }]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT testTableB.a, cos(1), tta.abcdefg|hijk, tta.bla, cos(1) FROM testTableA tta, testTableB;"', function() {
+        assertLocations({
+          dialect: 'hive',
+          beforeCursor: 'SELECT testTableB.a, cos(1), tta.abcdefg',
+          afterCursor: 'hijk, tta.bla, cos(1) FROM testTableA tta, testTableB;',
+          expectedLocations: [
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 19, last_column: 20}, identifierChain: [{ name: 'testTableB' }, { name: 'a'}]},
+            { type: 'function', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 24}, function: 'cos'},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 51, last_column: 54}, identifierChain: [{ name: 'testTableA' }, { name: 'bla'}]},
+            { type: 'function', location: { first_line: 1, last_line: 1, first_column: 56, last_column: 58}, function: 'cos'},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 68, last_column: 78}, identifierChain: [{ name: 'testTableA' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 84, last_column: 94}, identifierChain: [{ name: 'testTableB' }]}
+          ]
+        });
+      });
+    });
+
+    describe('Impala specific', function () {
+      it('should report locations for "SELECT tmp.bc, ROUND(tmp.r, 2) AS r FROM ( SELECT tstDb1.b1.cat AS bc, SUM(tstDb1.b1.price * tran.qua) AS r FROM tstDb1.b1 JOIN [SHUFFLE] tran ON ( tran.b_id = tstDb1.b1.id AND YEAR(tran.tran_d) BETWEEN 2008 AND 2010) GROUP BY tstDb1.b1.cat) tmp ORDER BY r DESC LIMIT 60; |"', function () {
+        assertLocations({
+          dialect: 'impala',
+          beforeCursor: 'SELECT tmp.bc, ROUND(tmp.r, 2) AS r FROM ( SELECT tstDb1.b1.cat AS bc, SUM(tstDb1.b1.price * tran.qua) AS r FROM tstDb1.b1 JOIN [SHUFFLE] tran ON ( tran.b_id = tstDb1.b1.id AND YEAR(tran.tran_d) BETWEEN 2008 AND 2010) GROUP BY tstDb1.b1.cat) tmp ORDER BY r DESC LIMIT 60;',
+          afterCursor: '',
+          expectedLocations: [
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 12, last_column: 14}, identifierChain: [{ subQuery: 'tmp' }, { name: 'bc' }]},
+            { type: 'function', location: { first_line: 1, last_line: 1, first_column: 16, last_column: 20 }, function: 'round' },
+            { type: 'column', location: { first_line:1, last_line: 1, first_column: 26, last_column: 27}, identifierChain: [{ subQuery: 'tmp' }, { name: 'r' }]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 61, last_column: 64 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'cat' }]},
+            { type: 'function', location: { first_line: 1, last_line: 1, first_column: 72, last_column: 74 }, function: 'sum' },
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 86, last_column: 91 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'price' }] },
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 99, last_column: 102 }, identifierChain: [{ name: 'tran' }, { name: 'qua' }] },
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 121, last_column: 123 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }] },
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 139, last_column: 143 }, identifierChain: [{ name: 'tran' }] },
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 154, last_column: 158 }, identifierChain: [{ name: 'tran' }, { name: 'b_id' }] },
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 171, last_column: 173 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'id' }]},
+            { type: 'function', location: { first_line: 1, last_line: 1, first_column: 178, last_column: 181 }, function: 'year' },
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 188, last_column: 194 }, identifierChain: [{ name: 'tran' }, { name: 'tran_d' }] },
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 238, last_column: 241 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'cat' }] },
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 256, last_column: 257 }, identifierChain: [{ subQuery: 'tmp' },{ name: 'r'}]}
+          ]
+        });
+      });
+
+      it('should report locations for "SELECT * FROM testTable ORDER BY a ASC, b, c DESC, d; |"', function () {
+        assertLocations({
+          dialect: 'impala',
+          beforeCursor: 'SELECT * FROM testTable ORDER BY a ASC, b, c DESC, d; ',
+          afterCursor: '',
+          expectedLocations: [
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24}, identifierChain: [{ name: 'testTable' }]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 34, last_column: 35},identifierChain: [{ name: 'testTable' }, { name: 'a'}]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 42},identifierChain: [{ name: 'testTable' }, { name: 'b'}]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 44, last_column: 45},identifierChain: [{ name: 'testTable' }, { name: 'c'}]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 52, last_column: 53},identifierChain: [{ name: 'testTable' }, { name: 'd'}]}
+          ]
+        });
+      });
+    })
+  });
+});

+ 3 - 413
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecSelect.js

@@ -240,225 +240,7 @@ define([
       });
     });
 
-    describe('Locations', function () {
-      it('should suggest locations for "SELECT   |    FROM    testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT   ',
-          afterCursor: '    FROM    testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }] }] },
-            suggestKeywords: ['*', 'ALL', 'DISTINCT'],
-            suggestAggregateFunctions: true,
-            suggestAnalyticFunctions: true,
-            suggestFunctions: {},
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 32}, identifierChain: [{ name: 'testTableA' }]}
-            ]
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT   a.|    FROM    testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT   a.',
-          afterCursor: '    FROM    testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 24, last_column: 34}, identifierChain: [{ name: 'testTableA' }]}
-            ],
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }, { name: 'a' }] }] },
-            suggestKeywords: ['*'],
-            lowerCase: false
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT aaa| FROM testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT aaa',
-          afterCursor: ' FROM testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }] }] },
-            suggestKeywords: ['*', 'ALL', 'DISTINCT'],
-            suggestAggregateFunctions: true,
-            suggestAnalyticFunctions: true,
-            suggestFunctions: {},
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 17, last_column: 27}, identifierChain: [{ name: 'testTableA' }]}
-            ]
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT aaa| \\nFROM testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT aaa',
-          afterCursor: ' \nFROM testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }] }] },
-            suggestKeywords: ['*', 'ALL', 'DISTINCT'],
-            suggestAggregateFunctions: true,
-            suggestAnalyticFunctions: true,
-            suggestFunctions: {},
-            locations: [
-              { type: 'table', location: { first_line: 2, last_line: 2, first_column: 6, last_column: 16}, identifierChain: [{ name: 'testTableA' }]}
-            ]
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT |bbbb FROM testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT ',
-          afterCursor: 'bbbb FROM testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }] }] },
-            suggestKeywords: ['*', 'ALL', 'DISTINCT'],
-            suggestAggregateFunctions: true,
-            suggestAnalyticFunctions: true,
-            suggestFunctions: {},
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 18, last_column: 28}, identifierChain: [{ name: 'testTableA' }]}
-            ]
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT a.aaaaa|bbbb FROM testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT aaaaa',
-          afterCursor: 'bbbb FROM testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }] }] },
-            suggestKeywords: ['*', 'ALL', 'DISTINCT'],
-            suggestAggregateFunctions: true,
-            suggestAnalyticFunctions: true,
-            suggestFunctions: {},
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 23, last_column: 33}, identifierChain: [{ name: 'testTableA' }]}
-            ]
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT foo.aaaaa|bbbb FROM testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT foo.aaaaa',
-          afterCursor: 'bbbb FROM testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }, { name: 'foo' }] }] },
-            suggestKeywords: ['*'],
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 27, last_column: 37}, identifierChain: [{ name: 'testTableA' }]}
-            ]
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT b, foo.aaaaa|bbbb FROM testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT b, foo.aaaaa',
-          afterCursor: 'bbbb FROM testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            locations: [
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9}, identifierChain: [{ name: 'testTableA' }, { name: 'b' }]},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 40}, identifierChain: [{ name: 'testTableA' }]}
-            ],
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }, { name: 'foo' }] }] },
-            suggestKeywords: ['*'],
-            lowerCase: false
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT foo, aaaaa|bbbb FROM testTableA"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT foo, aaaaa',
-          afterCursor: 'bbbb FROM testTableA',
-          dialect: 'hive',
-          expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }] }] },
-            suggestKeywords: ['*'],
-            suggestAggregateFunctions: true,
-            suggestAnalyticFunctions: true,
-            suggestFunctions: {},
-            locations: [
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'testTableA' }, { name: 'foo' }]},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 38}, identifierChain: [{ name: 'testTableA' }]}
-            ]
-          }
-        });
-      });
-
-      it('should suggest locations for "SELECT testTableB.a, cos(1), tta.abcdefg|hijk, tta.bla, cos(1) FROM testTableA tta, testTableB;"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT testTableB.a, cos(1), tta.abcdefg',
-          afterCursor: 'hijk, tta.bla, cos(1) FROM testTableA tta, testTableB;',
-          dialect: 'hive',
-          noErrors: true,
-          expectedResult: {
-            locations: [
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 19, last_column: 20}, identifierChain: [{ name: 'testTableB' }, { name: 'a'}]},
-              { type: 'function', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 24}, function: 'cos'},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 51, last_column: 54}, identifierChain: [{ name: 'testTableA' }, { name: 'bla'}]},
-              { type: 'function', location: { first_line: 1, last_line: 1, first_column: 56, last_column: 58}, function: 'cos'},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 68, last_column: 78}, identifierChain: [{ name: 'testTableA' }]},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 84, last_column: 94}, identifierChain: [{ name: 'testTableB' }]}
-            ],
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTableA' }] }] },
-            suggestKeywords: ['*'],
-            lowerCase: false
-          }
-        });
-      });
-    });
-
     describe('Complete Statements', function () {
-      it('should handle "SELECT tta.* FROM testTableA tta, testTableB; |"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT tta.* FROM testTableA tta, testTableB; ',
-          afterCursor: '',
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            lowerCase: false,
-            locations: [
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 19, last_column: 29}, identifierChain: [{ name: 'testTableA' }]},
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 45}, identifierChain: [{ name: 'testTableB' }]}
-            ]
-          }
-        });
-      });
-
-      it('should handle "SELECT COUNT(*) FROM testTable; |"', function () {
-        assertAutoComplete({
-          beforeCursor: 'SELECT COUNT(*) FROM testTable;',
-          afterCursor: '',
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            lowerCase: false,
-            locations: [
-              {type: 'function', location:{ first_line: 1, last_line: 1, first_column: 8, last_column: 12}, function: 'count'},
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 31}, identifierChain: [{ name: 'testTable' }]}
-            ]
-          }
-        });
-      });
-
       it('should suggest columns "SELECT IF(baa, boo, bee) AS b, | FROM testTable;"', function () {
         assertAutoComplete({
           beforeCursor: 'SELECT IF(baa, boo, bee) AS b, ',
@@ -491,117 +273,6 @@ define([
         });
       });
 
-
-      it('should handle "SELECT tmp.bc, ROUND(tmp.r, 2) AS r FROM ( SELECT tstDb1.b1.cat AS bc, SUM(tstDb1.b1.price * tran.qua) AS r FROM tstDb1.b1 JOIN [SHUFFLE] tran ON ( tran.b_id = tstDb1.b1.id AND YEAR(tran.tran_d) BETWEEN 2008 AND 2010) GROUP BY tstDb1.b1.cat) tmp ORDER BY r DESC LIMIT 60; |"', function () {
-        assertAutoComplete({
-          beforeCursor: 'SELECT tmp.bc, ROUND(tmp.r, 2) AS r FROM ( SELECT tstDb1.b1.cat AS bc, SUM(tstDb1.b1.price * tran.qua) AS r FROM tstDb1.b1 JOIN [SHUFFLE] tran ON ( tran.b_id = tstDb1.b1.id AND YEAR(tran.tran_d) BETWEEN 2008 AND 2010) GROUP BY tstDb1.b1.cat) tmp ORDER BY r DESC LIMIT 60;',
-          afterCursor: '',
-          dialect: 'impala',
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            locations: [
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 12, last_column: 14}, identifierChain: [{ subQuery: 'tmp' }, { name: 'bc' }]},
-              { type: 'function', location: { first_line: 1, last_line: 1, first_column: 16, last_column: 20 }, function: 'round' },
-              { type: 'column', location: { first_line:1, last_line: 1, first_column: 26, last_column: 27}, identifierChain: [{ subQuery: 'tmp' }, { name: 'r' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 61, last_column: 64 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'cat' }]},
-              { type: 'function', location: { first_line: 1, last_line: 1, first_column: 72, last_column: 74 }, function: 'sum' },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 86, last_column: 91 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'price' }] },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 99, last_column: 102 }, identifierChain: [{ name: 'tran' }, { name: 'qua' }] },
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 121, last_column: 123 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }] },
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 139, last_column: 143 }, identifierChain: [{ name: 'tran' }] },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 154, last_column: 158 }, identifierChain: [{ name: 'tran' }, { name: 'b_id' }] },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 171, last_column: 173 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'id' }]},
-              { type: 'function', location: { first_line: 1, last_line: 1, first_column: 178, last_column: 181 }, function: 'year' },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 188, last_column: 194 }, identifierChain: [{ name: 'tran' }, { name: 'tran_d' }] },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 238, last_column: 241 }, identifierChain: [{ name: 'tstDb1' }, { name: 'b1' }, { name: 'cat' }] },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 256, last_column: 257 }, identifierChain: [{ subQuery: 'tmp' },{ name: 'r'}]}
-            ],
-            lowerCase: false
-          }
-        });
-      });
-
-      it('should handle "SELECT * FROM testTable t1 ORDER BY t1.a ASC, t1.b, t1.c DESC, t1.d;\nSELECT t1.bla FROM testTable2 t1;\\nSELECT * FROM testTable3 t3, testTable4 t4 WHERE |"', function () {
-        assertAutoComplete({
-          beforeCursor: 'SELECT * FROM testTable t1 ORDER BY t1.a ASC, t1.b, t1.c DESC, t1.d;\nSELECT t1.bla FROM testTable2 t1;\nSELECT * FROM testTable3 t3, testTable4 t4 WHERE ',
-          afterCursor: '',
-          containsKeywords: ['EXISTS'],
-          dialect: 'hive',
-          expectedResult: {
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24}, identifierChain: [{ name: 'testTable' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 40, last_column: 41},identifierChain: [{ name: 'testTable' }, { name: 'a'}]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 50, last_column: 51},identifierChain: [{ name: 'testTable' }, { name: 'b'}]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 56, last_column: 57},identifierChain: [{ name: 'testTable' }, { name: 'c'}]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 67, last_column: 68},identifierChain: [{ name: 'testTable' }, { name: 'd'}]},
-              { type: 'column', location: { first_line: 2, last_line: 2, first_column: 11, last_column: 14},identifierChain: [{ name: 'testTable2' }, { name: 'bla'}]},
-              { type: 'table', location: { first_line: 2, last_line: 2, first_column: 20, last_column: 30}, identifierChain: [{ name: 'testTable2' }]},
-              { type: 'table', location: { first_line: 3, last_line: 3, first_column: 15, last_column: 25}, identifierChain: [{ name: 'testTable3' }]},
-              { type: 'table', location: { first_line: 3, last_line: 3, first_column: 30, last_column: 40}, identifierChain: [{ name: 'testTable4' }]}
-            ],
-            suggestFunctions: {},
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable3' }], alias: 't3' }, { identifierChain: [{ name: 'testTable4' }], alias: 't4' }] },
-            suggestIdentifiers: [{ name: 't3.', type: 'alias' }, { name: 't4.', type: 'alias'}],
-            lowerCase: false
-          }
-        });
-      });
-
-      it('should handle "SELECT * FROM testTable ORDER BY a ASC, b, c DESC, d; |"', function () {
-        assertAutoComplete({
-          beforeCursor: 'SELECT * FROM testTable ORDER BY a ASC, b, c DESC, d; ',
-          afterCursor: '',
-          containsKeywords: ['SELECT'],
-          dialect: 'impala',
-          expectedResult: {
-            lowerCase: false,
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24}, identifierChain: [{ name: 'testTable' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 34, last_column: 35},identifierChain: [{ name: 'testTable' }, { name: 'a'}]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 42},identifierChain: [{ name: 'testTable' }, { name: 'b'}]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 44, last_column: 45},identifierChain: [{ name: 'testTable' }, { name: 'c'}]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 52, last_column: 53},identifierChain: [{ name: 'testTable' }, { name: 'd'}]}
-            ]
-          }
-        });
-      });
-
-      it('should handle "SELECT * FROM testTable1 JOIN db1.table2; |"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT * FROM testTable1 JOIN db1.table2; ',
-          afterCursor: '',
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            lowerCase: false,
-            locations: [
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 25 }, identifierChain: [{ name: 'testTable1' }] },
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 41 }, identifierChain: [{ name: 'db1' }, { name: 'table2' }] }
-            ]
-          }
-        });
-      });
-
-      it('should handle "SELECT t1.foo FROM table1 t1 CROSS JOIN table2 LEFT OUTER JOIN table3 JOIN table4 t4 ON (t1.c1 = t2.c2); |"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT t1.foo FROM table1 t1 CROSS JOIN table2 LEFT OUTER JOIN table3 JOIN table4 t4 ON (t1.c1 = t2.c2); ',
-          afterCursor: '',
-          dialect: 'hive',
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            locations: [
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 11, last_column: 14 }, identifierChain: [{ name: 'table1' }, { name: 'foo' }] },
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 20, last_column: 26 }, identifierChain: [{ name: 'table1' }]},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 47 }, identifierChain: [{ name: 'table2' }]},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 64, last_column: 70 }, identifierChain: [{ name: 'table3' }]},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 76, last_column: 82 }, identifierChain: [{ name: 'table4' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 93, last_column: 95 }, identifierChain: [{ name: 'table1' }, { name: 'c1'}]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 101, last_column: 103 }, identifierChain: [{ name: 't2' }, { name: 'c2'}]}
-            ],
-            lowerCase: false
-          }
-        });
-      });
-
       it('should handle 100k rows before and after "SELECT * FROM foo WHERE (bar = \'bla\') AND (ble = 1);|"', function() {
         var beforeCursor = '';
         var afterCursor = ';\n';
@@ -621,88 +292,6 @@ define([
           }
         });
       });
-
-      it('should handle "SELECT * FROM foo WHERE bar IN (SELECT * FROM bla);|"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT * FROM foo WHERE bar IN (SELECT * FROM bla);',
-          afterCursor: '',
-          dialect: 'hive',
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            lowerCase: false,
-            locations: [
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
-              {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 47, last_column: 50 }, identifierChain: [{ name: 'bla' }]}
-            ]
-          }
-        });
-      });
-
-      it('should handle "SELECT * FROM foo WHERE bar IN (1+1, 2+2);|"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT * FROM foo WHERE bar IN (1+1, 2+2);',
-          afterCursor: '',
-          noErrors: true,
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            lowerCase: false,
-            locations: [
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
-              {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]}
-            ]
-          }
-        });
-      });
-
-      it('should handle "SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);|"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);',
-          afterCursor: '',
-          noErrors: true,
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            lowerCase: false,
-            locations: [
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
-              {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
-              {type: 'column', location: { first_line: 1, last_line: 1, first_column: 33, last_column: 35 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]},
-              {type: 'column', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 43 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]}
-            ]
-          }
-        });
-      });
-
-      it('should handle "SELECT CASE cos(boo.a) > baa.boo \\n' +
-        '\\tWHEN baa.b THEN true \\n' +
-        '\\tWHEN boo.c THEN false \\n' +
-        '\\tWHEN baa.blue THEN boo.d \\n' +
-        '\\tELSE baa.e END \\n' +
-        '\\t FROM db1.foo boo, bar baa WHERE baa.bla IN (SELECT ble FROM bla);|"', function() {
-        assertAutoComplete({
-          beforeCursor: 'SELECT CASE cos(boo.a) > baa.boo \n\tWHEN baa.b THEN true \n\tWHEN boo.c THEN false \n\tWHEN baa.blue THEN boo.d \n\tELSE baa.e END \n\t FROM db1.foo boo, bar baa WHERE baa.bla IN (SELECT ble FROM bla);',
-          afterCursor: '',
-          containsKeywords: ['SELECT'],
-          expectedResult: {
-            locations: [
-              {type: 'function', location: { first_line: 1, last_line: 1, first_column: 13, last_column: 15 }, function: 'cos'},
-              {type: 'column', location: { first_line: 1, last_line: 1, first_column: 21, last_column: 22 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'a' }]},
-              {type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 33 }, identifierChain: [{ name: 'bar' }, { name: 'boo' }]},
-              {type: 'column', location: { first_line: 2, last_line: 2, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'b' }]},
-              {type: 'column', location: { first_line: 3, last_line: 3, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'c' }] },
-              {type: 'column', location: { first_line: 4, last_line: 4, first_column: 11, last_column: 15 }, identifierChain: [{ name: 'bar' }, { name: 'blue' }] },
-              {type: 'column', location: { first_line: 4, last_line: 4, first_column: 25, last_column: 26 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'd'}] },
-              {type: 'column', location: { first_line: 5, last_line: 5, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'e' }] },
-              {type: 'table', location: { first_line: 6, last_line: 6, first_column: 12, last_column: 15 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }]},
-              {type: 'table', location: { first_line: 6, last_line: 6, first_column: 21, last_column: 24 }, identifierChain: [{ name: 'bar' }]},
-              {type: 'column', location: { first_line: 6, last_line: 6, first_column: 39, last_column: 42 }, identifierChain: [{ name: 'bar' }, { name: 'bla' }] },
-              {type: 'column', location: { first_line: 6, last_line: 6, first_column: 54, last_column: 57 }, identifierChain: [{ name: 'bla' }, { name: 'ble' }] },
-              {type: 'table', location: { first_line: 6, last_line: 6, first_column: 63, last_column: 66 }, identifierChain: [{ name: 'bla' }]}
-            ],
-            lowerCase: false
-          }
-        });
-      });
     });
 
     describe('Select List Completion', function() {
@@ -2209,7 +1798,7 @@ define([
           { name: 'COVAR_POP', dialect: 'hive' },
           { name: 'COVAR_SAMP', dialect: 'hive' }
         ];
-        
+
         binaryFunctions.forEach(function (binaryFunction) {
           assertAutoComplete({
             beforeCursor: 'SELECT ' + binaryFunction.name + '(',
@@ -3544,7 +3133,8 @@ define([
             lowerCase: false,
             suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }, { name: 'testArray' }] }] },
             locations: [
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 }, identifierChain: [{ name: 'testTable' }, { name: 'testArray'}]},
+              // TODO: Add locations properly before *
+              // { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 }, identifierChain: [{ name: 'testTable' }, { name: 'testArray'}]},
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 18, last_column: 27 }, identifierChain: [{ name: 'testTable' }]},
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 33, last_column: 42 }, identifierChain: [{ name: 'testTable' }, { name: 'testArray'}]}
             ]

+ 19 - 8
desktop/core/src/desktop/static/desktop/spec/autocompleterTestUtils.js

@@ -41,14 +41,6 @@ define([
       toEqualDefinition : function() {
         return {
           compare: function(actualResponse, testDefinition) {
-            if (actualResponse.suggestKeywords) {
-              var weightFreeKeywords = [];
-              actualResponse.suggestKeywords.forEach(function (keyword) {
-                weightFreeKeywords.push(keyword.value);
-              });
-              actualResponse.suggestKeywords = weightFreeKeywords;
-            }
-
             if (typeof testDefinition.noErrors === 'undefined' && actualResponse.errors) {
               var allRecoverable = true;
               actualResponse.errors.forEach(function (error) {
@@ -58,6 +50,25 @@ define([
                 delete actualResponse.errors;
               }
             }
+
+            if (testDefinition.locationsOnly) {
+              return {
+                pass: jasmine.matchersUtil.equals(actualResponse.locations, testDefinition.expectedLocations),
+                message: '\n        Statement: ' + testDefinition.beforeCursor + '|' + testDefinition.afterCursor + '\n' +
+                '          Dialect: ' + testDefinition.dialect + '\n' +
+                'Expected locations: ' + JSON.stringify(testDefinition.expectedLocations).replace(/["]/g, '\'') + '\n' +
+                '  Parser locations: ' + JSON.stringify(actualResponse.locations).replace(/["]/g, '\'') +   '\n'
+              };
+            }
+
+            if (actualResponse.suggestKeywords) {
+              var weightFreeKeywords = [];
+              actualResponse.suggestKeywords.forEach(function (keyword) {
+                weightFreeKeywords.push(keyword.value);
+              });
+              actualResponse.suggestKeywords = weightFreeKeywords;
+            }
+
             if (testDefinition.hasLocations) {
               if (actualResponse.locations.length === 0) {
                 return {

+ 2 - 0
desktop/core/src/desktop/templates/sql_context_popover.mako

@@ -238,8 +238,10 @@ from metadata.conf import has_navigator
       <div class="sql-context-flex-fill">
         <div style="margin: 15px;" data-bind="with: fetchedData">
           <a class="pointer" data-bind="text: name, attr: { title: comment }, click: function() { huePubSub.publish('sql.context.popover.scroll.to.column', name); }"></a> (<span data-bind="text: type.indexOf('<') !== -1 ? type.substring(0, type.indexOf('<')) : type, attr: { title: type }"></span>)
+          <!-- ko if: comment -->
           <div style="margin-top: 10px; font-weight: bold;">${ _("Comment") }</div>
           <div data-bind="text: comment"></div>
+          <!-- /ko -->
         </div>
       </div>
       <div class="sql-context-flex-bottom-links">