瀏覽代碼

HUE-5157 [editor] Add asterisk locations and expansion back to the editor

Johan Ahlen 9 年之前
父節點
當前提交
4ed34b24d3

+ 2 - 2
desktop/core/src/desktop/static/desktop/js/aceSqlWorker.js

@@ -14,8 +14,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-importScripts('/static/desktop/js/autocomplete/sql.js?version=4');
-importScripts('/static/desktop/js/sqlFunctions.js?version=4');
+importScripts('/static/desktop/js/autocomplete/sql.js?version=8');
+importScripts('/static/desktop/js/sqlFunctions.js?version=8');
 
 (function () {
 

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

@@ -863,6 +863,9 @@ ColumnReference
      parser.yy.locations[parser.yy.locations.length - 1].type = 'column';
    }
  | BasicIdentifierChain AnyDot '*'
+   {
+     addAsteriskLocation(@3, $1.concat({ asterisk: true }));
+   }
  ;
 
 ColumnReference_EDIT
@@ -2188,6 +2191,7 @@ SelectSpecification
    }
  | '*'
    {
+     addAsteriskLocation(@1, [{ asterisk: true }]);
      $$ = { asterisk: true }
    }
  ;

+ 50 - 3
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js

@@ -280,6 +280,11 @@ case 659:
 
      parser.yy.locations[parser.yy.locations.length - 1].type = 'column';
    
+break;
+case 660:
+
+     addAsteriskLocation(_$[$0], $$[$0-2].concat({ asterisk: true }));
+   
 break;
 case 662:
 
@@ -1408,6 +1413,7 @@ case 1128:
 break;
 case 1129:
 
+     addAsteriskLocation(_$[$0], [{ asterisk: true }]);
      this.$ = { asterisk: true }
    
 break;
@@ -4320,6 +4326,19 @@ var commitLocations = function () {
       }
     }
 
+    if (location.type === 'asterisk' && !location.linked) {
+      if (parser.yy.latestTablePrimaries && parser.yy.latestTablePrimaries.length > 0) {
+        location.tables = [];
+        location.linked = false;
+        expandIdentifierChain(location, true);
+        if (location.tables.length === 0) {
+          parser.yy.locations.splice(i, 1);
+        }
+      } else {
+        parser.yy.locations.splice(i, 1);
+      }
+    }
+
     if (location.type === 'unknown') {
       location.type = 'column';
     }
@@ -4535,6 +4554,14 @@ parser.expandLateralViews = function (lateralViews, originalIdentifierChain, col
   return identifierChain;
 };
 
+var addCleanTablePrimary = function (tables, tablePrimary) {
+  if (tablePrimary.alias) {
+    tables.push({ alias: tablePrimary.alias, identifierChain: tablePrimary.identifierChain });
+  } else {
+    tables.push({ identifierChain: tablePrimary.identifierChain });
+  }
+};
+
 var expandIdentifierChain = function (wrapper, anyOwner) {
   if (typeof wrapper.identifierChain === 'undefined' || typeof parser.yy.latestTablePrimaries === 'undefined') {
     return;
@@ -4551,10 +4578,22 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
   if (identifierChain.length > 0 && identifierChain[identifierChain.length - 1].asterisk) {
     var tables = [];
     tablePrimaries.forEach(function (tablePrimary) {
-      if (tablePrimary.subQueryAlias) {
-        tables.push({ identifierChain: [{ subQuery: tablePrimary.subQueryAlias }]});
+      if (identifierChain.length > 1) {
+        if (identifierChain.length === 2 && tablePrimary.alias === identifierChain[0].name) {
+          addCleanTablePrimary(tables, tablePrimary);
+        } else if (identifierChain.length === 2 && tablePrimary.identifierChain[0].name === identifierChain[0].name) {
+          addCleanTablePrimary(tables, tablePrimary);
+        } else if (identifierChain.length === 3 && tablePrimary.identifierChain.length > 1 &&
+            tablePrimary.identifierChain[0].name === identifierChain[0].name &&
+            tablePrimary.identifierChain[1].name === identifierChain[1].name) {
+          addCleanTablePrimary(tables, tablePrimary);
+        }
       } else {
-        tables.push({ identifierChain: tablePrimary.identifierChain });
+        if (tablePrimary.subQueryAlias) {
+          tables.push({ identifierChain: [{ subQuery: tablePrimary.subQueryAlias }]});
+        } else {
+          addCleanTablePrimary(tables, tablePrimary);
+        }
       }
     });
     // Possible Joins
@@ -5046,6 +5085,14 @@ var addTableLocation = function (location, identifierChain) {
   });
 };
 
+var addAsteriskLocation = function (location, identifierChain) {
+  parser.yy.locations.push({
+    type: 'asterisk',
+    location: adjustLocationForCursor(location),
+    identifierChain: identifierChain
+  });
+};
+
 var addColumnLocation = function (location, identifierChain) {
   parser.yy.locations.push({
     type: 'column',

+ 44 - 3
desktop/core/src/desktop/static/desktop/js/autocomplete/sql_support.js

@@ -322,6 +322,19 @@ var commitLocations = function () {
       }
     }
 
+    if (location.type === 'asterisk' && !location.linked) {
+      if (parser.yy.latestTablePrimaries && parser.yy.latestTablePrimaries.length > 0) {
+        location.tables = [];
+        location.linked = false;
+        expandIdentifierChain(location, true);
+        if (location.tables.length === 0) {
+          parser.yy.locations.splice(i, 1);
+        }
+      } else {
+        parser.yy.locations.splice(i, 1);
+      }
+    }
+
     if (location.type === 'unknown') {
       location.type = 'column';
     }
@@ -537,6 +550,14 @@ parser.expandLateralViews = function (lateralViews, originalIdentifierChain, col
   return identifierChain;
 };
 
+var addCleanTablePrimary = function (tables, tablePrimary) {
+  if (tablePrimary.alias) {
+    tables.push({ alias: tablePrimary.alias, identifierChain: tablePrimary.identifierChain });
+  } else {
+    tables.push({ identifierChain: tablePrimary.identifierChain });
+  }
+};
+
 var expandIdentifierChain = function (wrapper, anyOwner) {
   if (typeof wrapper.identifierChain === 'undefined' || typeof parser.yy.latestTablePrimaries === 'undefined') {
     return;
@@ -553,10 +574,22 @@ var expandIdentifierChain = function (wrapper, anyOwner) {
   if (identifierChain.length > 0 && identifierChain[identifierChain.length - 1].asterisk) {
     var tables = [];
     tablePrimaries.forEach(function (tablePrimary) {
-      if (tablePrimary.subQueryAlias) {
-        tables.push({ identifierChain: [{ subQuery: tablePrimary.subQueryAlias }]});
+      if (identifierChain.length > 1) {
+        if (identifierChain.length === 2 && tablePrimary.alias === identifierChain[0].name) {
+          addCleanTablePrimary(tables, tablePrimary);
+        } else if (identifierChain.length === 2 && tablePrimary.identifierChain[0].name === identifierChain[0].name) {
+          addCleanTablePrimary(tables, tablePrimary);
+        } else if (identifierChain.length === 3 && tablePrimary.identifierChain.length > 1 &&
+            tablePrimary.identifierChain[0].name === identifierChain[0].name &&
+            tablePrimary.identifierChain[1].name === identifierChain[1].name) {
+          addCleanTablePrimary(tables, tablePrimary);
+        }
       } else {
-        tables.push({ identifierChain: tablePrimary.identifierChain });
+        if (tablePrimary.subQueryAlias) {
+          tables.push({ identifierChain: [{ subQuery: tablePrimary.subQueryAlias }]});
+        } else {
+          addCleanTablePrimary(tables, tablePrimary);
+        }
       }
     });
     // Possible Joins
@@ -1048,6 +1081,14 @@ var addTableLocation = function (location, identifierChain) {
   });
 };
 
+var addAsteriskLocation = function (location, identifierChain) {
+  parser.yy.locations.push({
+    type: 'asterisk',
+    location: adjustLocationForCursor(location),
+    identifierChain: identifierChain
+  });
+};
+
 var addColumnLocation = function (location, identifierChain) {
   parser.yy.locations.push({
     type: 'column',

+ 10 - 4
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpec.js

@@ -439,8 +439,11 @@
         afterCursor: '\n-- other line comment',
         containsKeywords: ['SELECT'],
         expectedResult: {
-          lowerCase: false,
-          locations: [{ type: 'table', location: { first_line:2, last_line:2, first_column:15, last_column:25 }, identifierChain: [{ name: 'testTable1' }] }]
+          locations: [
+            { type: 'asterisk', location: { first_line: 2, last_line: 2, first_column: 8, last_column: 9 }, tables: [{ identifierChain: [{ name: 'testTable1' }] }] },
+            { type: 'table', location: { first_line:2, last_line:2, first_column:15, last_column:25 }, identifierChain: [{ name: 'testTable1' }] }
+          ],
+          lowerCase: false
         }
       });
     });
@@ -451,8 +454,11 @@
         afterCursor: '',
         containsKeywords: ['SELECT'],
         expectedResult: {
-          lowerCase: false,
-          locations: [{ type: 'table', location: { first_line:4, last_line:4, first_column:15, last_column:25 }, identifierChain: [{ name: 'testTable1' }] }]
+          locations: [
+            { type: 'asterisk', location: { first_line: 4, last_line: 4, first_column: 8, last_column: 9 }, tables: [{ identifierChain: [{ name: 'testTable1' }] }] },
+            { type: 'table', location: { first_line:4, last_line:4, first_column:15, last_column:25 }, identifierChain: [{ name: 'testTable1' }] }
+          ],
+          lowerCase: false
         }
       });
     });

+ 16 - 7
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecLocations.js

@@ -39,6 +39,7 @@
       assertLocations({
         beforeCursor: 'SELECT * FROM testTable1 JOIN db1.table2; ',
         expectedLocations: [
+          { type: 'asterisk', location: { first_line:1, last_line:1, first_column: 8, last_column: 9 }, tables: [{ identifierChain: [{ name: 'testTable1' }] }, { identifierChain: [{ name: 'db1' }, { name: 'table2' }] }] },
           { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 25 }, identifierChain: [{ name: 'testTable1' }] },
           { type: 'database', location: { first_line: 1, last_line: 1, first_column: 31, last_column: 34}, identifierChain: [{ name: 'db1' }]},
           { type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 41 }, identifierChain: [{ name: 'db1' }, { name: 'table2' }] }
@@ -79,6 +80,7 @@
       assertLocations({
         beforeCursor: 'SELECT * FROM foo WHERE bar IN (1+1, 2+2);',
         expectedLocations: [
+          { type: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }, tables: [{ identifierChain: [{ name: 'foo' }] }] },
           { 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'}]}
         ]
@@ -89,6 +91,7 @@
       assertLocations({
         beforeCursor: 'SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);',
         expectedLocations: [
+          { type: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }, tables: [{ identifierChain: [{ name: 'foo' }] }] },
           { 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'}]},
@@ -137,6 +140,7 @@
         beforeCursor: 'SELECT tta.* FROM testTableA tta, testTableB; ',
         expectedLocations: [
           { type: 'table', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'testTableA' }]},
+          { type: 'asterisk', location:{ first_line: 1, last_line: 1, first_column: 12, last_column: 13 }, tables: [{ alias: 'tta', identifierChain: [{ name: 'testTableA' }] }] },
           { 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' }]}
         ]
@@ -154,7 +158,7 @@
     });
 
     describe('HDFS paths', function () {
-      fit('should report locations for "LOAD DATA LOCAL INPATH \'/some/path/file.ble\' OVERWRITE INTO TABLE bla; |"', function () {
+      it('should report locations for "LOAD DATA LOCAL INPATH \'/some/path/file.ble\' OVERWRITE INTO TABLE bla; |"', function () {
         assertLocations({
           dialect: 'hive',
           beforeCursor: 'LOAD DATA LOCAL INPATH \'/some/path/file.ble\' OVERWRITE INTO TABLE bla;',
@@ -165,7 +169,7 @@
         });
       });
 
-      fit('should report locations for "CREATE TABLE bla (id INT) LOCATION \'/bla/bla/\'; |"', function () {
+      it('should report locations for "CREATE TABLE bla (id INT) LOCATION \'/bla/bla/\'; |"', function () {
         assertLocations({
           dialect: 'impala',
           beforeCursor: 'CREATE TABLE bla (id INT) LOCATION \'/bla/bla/\';',
@@ -182,6 +186,7 @@
           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: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }, tables: [{ alias: 't1', identifierChain: [{ name: 'testTable' }] }] },
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24 }, identifierChain: [{ name: 'testTable' }]},
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 37, last_column: 39 }, identifierChain: [{ name: 'testTable' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 40, last_column: 41 }, identifierChain: [{ name: 'testTable' }, { name: 'a' }]},
@@ -194,6 +199,7 @@
             { type: 'table', location: { first_line: 2, last_line: 2, first_column: 8, last_column: 10 }, identifierChain: [{ name: 'testTable2' }]},
             { 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: 'asterisk', location: { first_line: 3, last_line: 3, first_column: 8, last_column: 9 }, tables: [{ alias: 't3', identifierChain: [{ name: 'testTable3' }]}, { alias: 't4', identifierChain: [{ name: 'testTable4' }] }] },
             { 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' }]}
           ]
@@ -224,8 +230,10 @@
           dialect: 'hive',
           beforeCursor: 'SELECT * FROM foo WHERE bar IN (SELECT * FROM bla);',
           expectedLocations: [
+            { type: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }, tables: [{ identifierChain: [{ name: 'foo' }] }] },
             { 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: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 40, last_column: 41 }, tables: [{ identifierChain: [{ name: 'bla' }] }] },
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 47, last_column: 50 }, identifierChain: [{ name: 'bla' }]}
           ]
         });
@@ -401,11 +409,12 @@
           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'}]}
+            { type: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }, tables: [{ identifierChain: [{ name: 'testTable' }] }] },
+            { 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'}]}
           ]
         });
       });

+ 11 - 31
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecSelect.js

@@ -3028,13 +3028,10 @@
           beforeCursor: 'SELECT * FROM testTableA tta, testTableB ',
           afterCursor: '',
           dialect: 'impala',
+          hasLocations: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['AS', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', 'UNION', 'FULL JOIN', 'FULL OUTER JOIN', 'INNER JOIN', 'JOIN', 'LEFT ANTI JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT ANTI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'RIGHT SEMI JOIN'],
-            locations: [
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 25}, identifierChain: [{ name: 'testTableA' }]},
-              {type: 'table', location: { first_line: 1, last_line: 1, first_column: 31, last_column: 41}, identifierChain: [{ name: 'testTableB' }]}
-            ]
+            suggestKeywords: ['AS', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', 'UNION', 'FULL JOIN', 'FULL OUTER JOIN', 'INNER JOIN', 'JOIN', 'LEFT ANTI JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT ANTI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'RIGHT SEMI JOIN']
           }
         });
       });
@@ -3139,14 +3136,8 @@
           beforeCursor: 'SELECT ta.* FROM testTable t, t.testArray ta WHERE ta.',
           afterCursor: '',
           dialect: 'impala',
+          hasLocations: true,
           expectedResult: {
-            locations: [
-              { 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: 'table', location: { first_line: 1, last_line: 1, first_column: 31, last_column: 32 }, identifierChain: [{ name: 'testTable' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 33, last_column: 42 }, identifierChain: [{ name: 'testTable' },{ name: 'testArray' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 52, last_column: 54 }, identifierChain: [{ name: 'testTable' },{ name: 'testArray' }]}
-            ],
             suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }, { name: 'testArray' }] }] },
             lowerCase: false
           }
@@ -3234,15 +3225,9 @@
           beforeCursor: 'SELECT * FROM testTable t, t.testMap m WHERE m.field = ',
           afterCursor: '',
           dialect: 'impala',
+          hasLocations: true,
           containsKeywords: ['CASE'],
           expectedResult: {
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24}, identifierChain: [{ name: 'testTable' }]},
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 29}, identifierChain: [{ name: 'testTable' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 37}, identifierChain: [{ name: 'testTable' }, { name: 'testMap' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 46, last_column: 47}, identifierChain: [{ name: 'testTable' },{ name: 'testMap' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 48, last_column: 53}, identifierChain: [{ name: 'testTable' }, { name: 'testMap' },{ name: 'field' }]}
-            ],
             lowerCase: false,
             suggestFunctions: { types: ['COLREF'] },
             suggestValues: {},
@@ -6052,6 +6037,7 @@
           expectedResult: {
             locations: [
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 18 }, identifierChain: [{ name: 'testTable1' }]},
+              { type: 'asterisk', location: { first_line: 1, last_line: 1, first_column: 19, last_column: 20 }, tables: [{ identifierChain: [{ name: 'testTable1' }] }] },
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 26, last_column: 36}, identifierChain: [{ name: 'testTable1' }]},
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 42, last_column: 52}, identifierChain: [{ name: 'testTable2' }]},
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 62, last_column: 72 }, identifierChain: [{ name: 'testTable1' }]},
@@ -6697,6 +6683,7 @@
           beforeCursor: 'SELECT * FROM foo WHERE bar IN (SELECT ',
           afterCursor: '',
           containsKeywords: ['*', 'ALL', 'DISTINCT'],
+          hasLocations: true,
           expectedResult: {
             lowerCase: false,
             suggestAggregateFunctions: true,
@@ -6710,11 +6697,7 @@
               prependQuestionMark: true,
               prependFrom: true,
               appendDot: true
-            },
-            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'}] }
-            ]
+            }
           }
         });
       });
@@ -6725,6 +6708,7 @@
           beforeCursor: 'SELECT * FROM bar WHERE foo NOT IN (SELECT ',
           afterCursor: ')',
           containsKeywords: ['*', 'ALL', 'DISTINCT'],
+          hasLocations: true,
           expectedResult: {
             lowerCase: false,
             suggestAggregateFunctions: true,
@@ -6738,11 +6722,7 @@
               prependQuestionMark: true,
               prependFrom: true,
               appendDot: true
-            },
-            locations: [
-              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18}, identifierChain: [{ name: 'bar' }] },
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28}, identifierChain: [{ name: 'bar' }, { name: 'foo'}] }
-            ]
+            }
           }
         });
       });
@@ -6972,14 +6952,14 @@
               { identifierChain: [{ name: 'tableThree' }] },
               { identifierChain: [{ subQuery: 'subQueryTwo'}] }
             ]},
-            suggestIdentifiers: [{ name: 'subQueryOne.', type: 'sub-query' }, { name: 'tAlias.', type: 'alias' }, { name: 'tableThree.', type: 'table' }, { name: 'subQueryTwo.', type: 'sub-query' }],
             subQueries: [{
               columns: [{ tables: [{ identifierChain: [{ name: 'tableOne' }] }] }],
               alias: 'subQueryOne'
             }, {
-              columns: [{ tables: [{ identifierChain: [{ name: 't3' }]}, { identifierChain: [{ name: 'table4' }] }] }],
+              columns: [{ tables: [{ identifierChain: [{ name: 't3' }]}, { alias:'t4', identifierChain: [{ name: 'table4' }] }] }],
               alias: 'subQueryTwo'
             }],
+            suggestIdentifiers: [{ name: 'subQueryOne.', type: 'sub-query' }, { name: 'tAlias.', type: 'alias' }, { name: 'tableThree.', type: 'table' }, { name: 'subQueryTwo.', type: 'sub-query' }],
             lowerCase: false
           }
         });

+ 152 - 3
desktop/core/src/desktop/templates/sql_context_popover.mako

@@ -280,6 +280,16 @@ from metadata.conf import has_navigator
         <a class="inactive-action pointer" data-bind="visible: isTable, click: function() { huePubSub.publish('sql.context.popover.open.in.metastore') }"><i style="font-size: 11px;" title="${ _("Open in Metastore...") }" class="fa fa-external-link"></i> ${ _("Metastore") }</a>
         <a class="inactive-action pointer" data-bind="visible: isHdfs, click: function() { huePubSub.publish('sql.context.popover.replace.in.editor') }"><i style="font-size: 11px;" title="${ _("Replace the editor content...") }" class="fa fa-pencil"></i> ${ _("Insert in the editor") }</a>
         <a class="inactive-action pointer" data-bind="visible: isHdfs, click: function() { huePubSub.publish('sql.context.popover.open.in.file.browser') }"><i style="font-size: 11px;" title="${ _("Open in File Browser...") }" class="fa fa-external-link"></i> ${ _("File Browser") }</a>
+        <!-- ko if: isAsterisk -->
+        <!-- ko with: contents.data -->
+        <!-- ko if: selectedColumns().length > 0 -->
+        <a class="inactive-action pointer" data-bind="click: expand">${ _("Expand to selected columns") }</a>
+        <!-- /ko -->
+        <!-- ko if: selectedColumns().length === 0 -->
+        <a class="inactive-action pointer" data-bind="click: expand">${ _("Expand to all columns") }</a>
+        <!-- /ko -->
+        <!-- /ko -->
+        <!-- /ko -->
       </div>
     </div>
   </script>
@@ -394,6 +404,12 @@ from metadata.conf import has_navigator
     </div>
   </script>
 
+  <script type="text/html" id="sql-context-asterisk-details">
+    <div class="sql-context-flex-fill">
+      <!-- ko component: { name: 'sql-columns-table', params: { columns: columns, scrollToColumns: false } } --><!-- /ko -->
+    </div>
+  </script>
+
   <script type="text/html" id="sql-context-function-details">
     <div class="sql-context-flex-fill" data-bind="with: details, niceScroll">
       <div style="padding: 8px">
@@ -823,6 +839,111 @@ from metadata.conf import has_navigator
         self.activeTab = ko.observable('tags');
       }
 
+
+      function AsteriskData(data, sourceType, defaultDatabase) {
+        var self = this;
+        self.loading = ko.observable(true);
+        self.hasErrors = ko.observable(false);
+        self.columns = [];
+
+        self.selectedColumns = ko.pureComputed(function () {
+          return self.columns.filter(function (column) {
+            return column.selected();
+          });
+        });
+
+        self.expand = function () {
+          var colsToExpand = self.selectedColumns().length === 0 ? self.columns : self.selectedColumns();
+          var colIndex = {};
+          colsToExpand.forEach(function (col) {
+            if (colIndex[col.name]) {
+              colIndex[col.name]++;
+            } else {
+              colIndex[col.name] = 1;
+            }
+          });
+          Object.keys(colIndex).forEach(function (name) {
+            if (colIndex[name] === 1) {
+              delete colIndex[name];
+            }
+          });
+          var sqlAutocompleter = new SqlAutocompleter2({
+            snippet: {
+              type: function () {
+                return sourceType;
+              }
+            }
+          });
+          huePubSub.publish('ace.replace', {
+            location: data.location,
+            text: $.map(colsToExpand, function (column) {
+              if (column.tableAlias) {
+                return sqlAutocompleter.backTickIfNeeded(column.tableAlias) + '.' + sqlAutocompleter.backTickIfNeeded(column.name);
+              }
+              if (colIndex[column.name]) {
+                return sqlAutocompleter.backTickIfNeeded(column.table) + '.' + sqlAutocompleter.backTickIfNeeded(column.name);
+              }
+              return sqlAutocompleter.backTickIfNeeded(column.name)
+            }).join(', ')
+          });
+          huePubSub.publish('sql.context.popover.hide');
+        };
+
+        var apiHelper = ApiHelper.getInstance();
+        var deferrals = [];
+        data.tables.forEach(function (table) {
+          if (table.identifierChain) {
+            var fetchDeferred = $.Deferred();
+            deferrals.push(fetchDeferred);
+            apiHelper.fetchAutocomplete({
+              sourceType: sourceType,
+              defaultDatabase: defaultDatabase,
+              identifierChain: table.identifierChain,
+              successCallback: function (data) {
+                if (typeof data.extended_columns !== 'undefined') {
+                  data.extended_columns.forEach(function (column) {
+                    column.extendedType = column.type.replace(/</g, '&lt;').replace(/>/g, '&lt;');
+                    if (column.type.indexOf('<') !== -1) {
+                      column.type = column.type.substring(0, column.type.indexOf('<'));
+                    }
+                    column.selected = ko.observable(false);
+                    column.table = table.identifierChain[table.identifierChain.length - 1].name;
+                    if (table.alias) {
+                      column.tableAlias = table.alias
+                    }
+                  });
+                }
+                self.columns = self.columns.concat(data.extended_columns);
+                fetchDeferred.resolve();
+              },
+              silenceErrors: true,
+              errorCallback: fetchDeferred.reject
+            })
+          }
+        });
+
+        if (deferrals.length === 0) {
+          self.loading(false);
+        }
+        $.when.apply($, deferrals).done(function () {
+          self.loading(false);
+        }, function () {
+          if (self.columns.length === 0) {
+            self.hasErrors(true);
+          }
+        });
+      }
+
+      function AsteriskContextTabs(data, sourceType, defaultDatabase) {
+        var self = this;
+        self.data = new AsteriskData(data, sourceType, defaultDatabase);
+
+        self.tabs = [
+          { id: 'details', label: '${ _("Details") }', template: 'sql-context-asterisk-details', templateData: self.data }
+        ];
+        self.activeTab = ko.observable('details');
+      }
+
       function HdfsContextTabs(data) {
         var self = this;
 
@@ -896,7 +1017,7 @@ from metadata.conf import has_navigator
             width: $('.sql-context-popover').width(),
             height: $('.sql-context-popover').height()
           });
-        }
+        };
 
         self.resizeStart = function (event, ui) {
           preventHide = true;
@@ -1103,6 +1224,7 @@ from metadata.conf import has_navigator
         self.isColumn = params.data.type === 'column';
         self.isFunction = params.data.type === 'function';
         self.isHdfs = params.data.type === 'hdfs';
+        self.isAsterisk = params.data.type === 'asterisk';
 
         if (self.isDatabase) {
           self.contents = new DatabaseContextTabs(self.data, self.sourceType);
@@ -1124,6 +1246,10 @@ from metadata.conf import has_navigator
           self.contents = new HdfsContextTabs(self.data);
           self.title = self.data.path;
           self.iconClass = 'fa-folder-o'
+        } else if (self.isAsterisk) {
+          self.contents = new AsteriskContextTabs(self.data, self.sourceType, self.defaultDatabase);
+          self.title = '*';
+          self.iconClass = 'fa-table';
         } else {
           self.title = '';
           self.iconClass = 'fa-info'
@@ -1218,17 +1344,39 @@ from metadata.conf import has_navigator
           <thead>
           <tr data-bind="visible: filteredColumns().length !== 0">
             <th width="6%">&nbsp;</th>
+            <!-- ko if: typeof filteredColumns()[0].table === 'undefined' -->
             <th width="60%">${_('Name')}</th>
+            <!-- /ko -->
+            <!-- ko if: typeof filteredColumns()[0].table !== 'undefined' -->
+            <th width="40%">${_('Name')}</th>
+            <th width="20%">${_('Table')}</th>
+            <!-- /ko -->
             <th width="34%">${_('Type')}</th>
             <th width="6%">&nbsp;</th>
           </tr>
           </thead>
           <tbody data-bind="foreachVisible: { data: filteredColumns, minHeight: 29, container: '.sql-columns-table', pubSubDispose: 'sql.context.popover.dispose' }">
           <tr>
+            <!-- ko if: typeof selected === 'undefined' -->
             <td data-bind="text: $index()+$indexOffset()+1"></td>
+            <!-- /ko -->
+            <!-- ko if: typeof selected !== 'undefined' -->
+            <td data-bind="toggle: selected" class="center" style="cursor: default;">
+              <div class="hueCheckbox fa" data-bind="css: {'fa-check': selected }"></div>
+            </td>
+
+            <!-- /ko -->
             <td style="overflow: hidden;">
+              <!-- ko if: $parent.scrollToColumns -->
               <a href="javascript:void(0)" class="column-selector" data-bind="text: name, click: function() { huePubSub.publish('sql.context.popover.scroll.to.column', name); }" title="${ _("Show sample") }"></a>
+              <!-- /ko -->
+              <!-- ko ifnot: $parent.scrollToColumns -->
+              <span data-bind="text: name"></span>
+              <!-- /ko -->
             </td>
+            <!-- ko if: typeof table !== 'undefined' -->
+            <td><span data-bind="text: table"></span></td>
+            <!-- /ko -->
             <td><span data-bind="text: type, attr: { 'title': extendedType }, tooltip: { placement: 'bottom' }"></span></td>
             <td><i class="snippet-icon fa fa-question-circle" data-bind="visible: comment, attr: { 'title': comment }, tooltip: { placement: 'bottom' }"></i></td>
           </tr>
@@ -1245,7 +1393,7 @@ from metadata.conf import has_navigator
       function SqlColumnsTable(params) {
         var self = this;
         var columns = params.columns;
-
+        self.scrollToColumns = typeof params.scrollToColumns !== 'undefined' ?  params.scrollToColumns : true;
         self.searchInput = ko.observable('');
         self.searchVisible = ko.observable(false);
         self.searchFocus = ko.observable(false);
@@ -1264,7 +1412,8 @@ from metadata.conf import has_navigator
           return columns.filter(function (column) {
             return column.name.toLowerCase().indexOf(query) != -1
                 || column.type.toLowerCase().indexOf(query) != -1
-                || column.comment.toLowerCase().indexOf(query) != -1;
+                || column.comment.toLowerCase().indexOf(query) != -1
+                || (typeof column.table !== 'undefined' && column.table.toLowerCase().indexOf(query) !== -1);
           })
         });
       }