ソースを参照

HUE-4176 [editor] Autocomplete field values

This commit also fixes a bunch of other cases that worked in the previous autocompleter.
Johan Ahlen 9 年 前
コミット
41a8c2582e

+ 1 - 1
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -690,7 +690,7 @@
       })
       .done(function (data) {
         if (! self.successResponseIsError(data)) {
-          if (data.rows.length > 0) {
+          if ((typeof data.rows !== 'undefined' && data.rows.length > 0) || typeof data.sample !== 'undefined') {
             storeInCache(data);
           }
           options.successCallback(data);

+ 60 - 41
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.jison

@@ -93,7 +93,7 @@
 <hdfs><<EOF>>                       { return 'EOF'; }
 
 [-+&~|^/%*(),.;!]                   { return yytext; }
-[=<>]                               { return yytext; }
+[=<>]+                              { return yytext; }
 
 
 \[                                  { return '['; }
@@ -127,11 +127,16 @@ AnyCursor
  | 'PARTIAL_CURSOR'
  ;
 
-NoOrPartialRegularIdentifier
+PartialIdentifierOrCursor
  : 'REGULAR_IDENTIFIER' 'PARTIAL_CURSOR'
  | 'CURSOR'
  ;
 
+PartialIdentifierOrPartialCursor
+ : 'REGULAR_IDENTIFIER' 'PARTIAL_CURSOR'
+ | 'PARTIAL_CURSOR'
+ ;
+
 InitResults
  : /* empty */
    {
@@ -214,20 +219,20 @@ DataManipulation
 
 LoadStatement
  : HiveOrImpalaLoad HiveOrImpalaData HiveOrImpalaInpath HdfsPath 'INTO' 'TABLE' 'REGULAR_IDENTIFIER'
- | HiveOrImpalaLoad HiveOrImpalaData HiveOrImpalaInpath HdfsPath 'INTO' NoOrPartialRegularIdentifier
+ | HiveOrImpalaLoad HiveOrImpalaData HiveOrImpalaInpath HdfsPath 'INTO' PartialIdentifierOrCursor
    {
      suggestKeywords([ 'TABLE' ]);
    }
- | HiveOrImpalaLoad HiveOrImpalaData HiveOrImpalaInpath HdfsPath NoOrPartialRegularIdentifier
+ | HiveOrImpalaLoad HiveOrImpalaData HiveOrImpalaInpath HdfsPath PartialIdentifierOrCursor
    {
      suggestKeywords([ 'INTO' ]);
    }
  | HiveOrImpalaLoad HiveOrImpalaData HiveOrImpalaInpath HdfsPath
- | HiveOrImpalaLoad HiveOrImpalaData NoOrPartialRegularIdentifier
+ | HiveOrImpalaLoad HiveOrImpalaData PartialIdentifierOrCursor
    {
      suggestKeywords([ 'INPATH' ]);
    }
- | HiveOrImpalaLoad NoOrPartialRegularIdentifier
+ | HiveOrImpalaLoad PartialIdentifierOrCursor
    {
      suggestKeywords([ 'DATA' ]);
    }
@@ -266,7 +271,7 @@ UpdateStatement
      suggestKeywords([ 'SET' ]);
    }
  | 'UPDATE' TargetTable
- | 'UPDATE' NoOrPartialRegularIdentifier
+ | 'UPDATE' PartialIdentifierOrCursor
    {
      suggestTables();
      suggestDatabases({ appendDot: true });
@@ -302,7 +307,7 @@ SetClause
    {
      suggestKeywords([ '=' ]);
    }
- | NoOrPartialRegularIdentifier
+ | PartialIdentifierOrCursor
    {
      suggestColumns();
    }
@@ -322,32 +327,32 @@ ValueExpression
 
 TableDefinition
  : 'CREATE' TableScope 'TABLE' 'REGULAR_IDENTIFIER' TableElementList TableLocation
- | 'CREATE' NoOrPartialRegularIdentifier 'TABLE' 'REGULAR_IDENTIFIER' TableElementList
+ | 'CREATE' PartialIdentifierOrCursor 'TABLE' 'REGULAR_IDENTIFIER' TableElementList
     {
       if (parser.yy.dialect === 'hive' || parser.yy.dialect === 'impala') {
         suggestKeywords(['EXTERNAL'])
       }
     }
- | 'CREATE' NoOrPartialRegularIdentifier 'TABLE' 'REGULAR_IDENTIFIER'
+ | 'CREATE' PartialIdentifierOrCursor 'TABLE' 'REGULAR_IDENTIFIER'
     {
       if (parser.yy.dialect === 'hive' || parser.yy.dialect === 'impala') {
         suggestKeywords(['EXTERNAL'])
       }
     }
- | 'CREATE' NoOrPartialRegularIdentifier 'TABLE'
+ | 'CREATE' PartialIdentifierOrCursor 'TABLE'
     {
       if (parser.yy.dialect === 'hive' || parser.yy.dialect === 'impala') {
         suggestKeywords(['EXTERNAL'])
       }
     }
- | 'CREATE' TableScope 'TABLE' 'REGULAR_IDENTIFIER' TableElementList NoOrPartialRegularIdentifier
+ | 'CREATE' TableScope 'TABLE' 'REGULAR_IDENTIFIER' TableElementList PartialIdentifierOrCursor
    {
      if (parser.yy.dialect === 'hive' || parser.yy.dialect === 'impala') {
        suggestKeywords(['LOCATION'])
      }
    }
  | 'CREATE' 'TABLE' 'REGULAR_IDENTIFIER' TableElementList
- | 'CREATE' NoOrPartialRegularIdentifier
+ | 'CREATE' PartialIdentifierOrCursor
     {
       if (parser.yy.dialect === 'hive' || parser.yy.dialect === 'impala') {
         suggestKeywords(['EXTERNAL', 'TABLE'])
@@ -377,7 +382,7 @@ TableElement
 
 ColumnDefinition
  : 'REGULAR_IDENTIFIER' PrimitiveType
- | 'REGULAR_IDENTIFIER' NoOrPartialRegularIdentifier
+ | 'REGULAR_IDENTIFIER' PartialIdentifierOrCursor
    {
      if (parser.yy.dialect == 'hive') {
        suggestKeywords(['BIGINT', 'BINARY', 'BOOLEAN', 'CHAR', 'DATE', 'DECIMAL', 'DOUBLE', 'FLOAT', 'INT', 'SMALLINT', 'TIMESTAMP', 'STRING', 'TINYINT', 'VARCHAR']);
@@ -385,7 +390,7 @@ ColumnDefinition
        suggestKeywords(['BIGINT', 'BOOLEAN', 'CHAR', 'DECIMAL', 'DOUBLE', 'FLOAT', 'INT', 'SMALLINT', 'TIMESTAMP', 'STRING', 'TINYINT', 'VARCHAR']);
      }
    }
- | 'REGULAR_IDENTIFIER' NoOrPartialRegularIdentifier ColumnDefinitionError error
+ | 'REGULAR_IDENTIFIER' PartialIdentifierOrCursor ColumnDefinitionError error
    // error here is because it expects closing ')'
  ;
 
@@ -482,7 +487,7 @@ CleanUpSelectConditions
 
 FromClause
  : 'FROM' TableReferenceList
- | 'FROM' NoOrPartialRegularIdentifier
+ | 'FROM' PartialIdentifierOrCursor
    {
      suggestTables();
      suggestDatabases({ appendDot: true });
@@ -520,8 +525,11 @@ SelectCondition
      if (!parser.yy.afterGroupBy) {
        keywords.push('GROUP BY');
      }
-     if (parser.yy.dialect === 'hive' && !parser.yy.afterGroupBy && !parser.yy.afterWhere && !parser.yy.afterOrderBy && !parser.yy.afterLimit) {
-       keywords.push('LATERAL');
+     if (!parser.yy.afterGroupBy && !parser.yy.afterWhere && !parser.yy.afterOrderBy && !parser.yy.afterLimit) {
+       keywords.push('JOIN');
+       if (parser.yy.dialect === 'hive') {
+         keywords.push('LATERAL');
+       }
      }
      if (!parser.yy.afterLimit) {
        keywords.push('LIMIT');
@@ -594,10 +602,10 @@ Predicate
 CompOp
  : '='
  | '<>'
- | '<'
- | '>'
  | '<='
  | '>='
+ | '<'
+ | '>'
  ;
 
 ParenthesizedBooleanValueExpression
@@ -661,9 +669,13 @@ IdentifierChain
  ;
 
 Identifier
- : 'REGULAR_IDENTIFIER'
+ : ColumnIdentifier
+   {
+     parser.yy.identifierChain.push($1);
+   }
+ | ColumnIdentifier 'PARTIAL_CURSOR'
    {
-     parser.yy.identifierChain.push({ name: $1 });
+     suggestColumns();
    }
  | '"' 'REGULAR_IDENTIFIER' '"'
    {
@@ -673,7 +685,7 @@ Identifier
 
 GroupByClause
  : 'GROUP' 'BY' ColumnList
- | 'GROUP' NoOrPartialRegularIdentifier
+ | 'GROUP' PartialIdentifierOrCursor
    {
      suggestKeywords(['BY']);
    }
@@ -681,7 +693,7 @@ GroupByClause
 
 OrderByClause
  : 'ORDER' 'BY' ColumnList
- | 'ORDER' NoOrPartialRegularIdentifier
+ | 'ORDER' PartialIdentifierOrCursor
    {
      suggestKeywords(['BY']);
    }
@@ -689,7 +701,7 @@ OrderByClause
 
 LimitClause
  : 'LIMIT' 'UNSIGNED_INTEGER'
- | 'LIMIT' NoOrPartialRegularIdentifier
+ | 'LIMIT' PartialIdentifierOrCursor
    {
      suggestNumbers([1, 5, 10]);
    }
@@ -697,12 +709,12 @@ LimitClause
 
 SelectList
  : ColumnList
- | ColumnList NoOrPartialRegularIdentifier
+ | ColumnList PartialIdentifierOrCursor
    {
-      suggestTables({ prependFrom: true });
-      suggestDatabases({ prependFrom: true, appendDot: true });
-    }
- | '*' NoOrPartialRegularIdentifier
+     suggestTables({ prependFrom: true });
+     suggestDatabases({ prependFrom: true, appendDot: true });
+   }
+ | '*' PartialIdentifierOrCursor
    {
      suggestTables({ prependFrom: true });
      suggestDatabases({ prependFrom: true, appendDot: true });
@@ -736,7 +748,7 @@ ColumnIdentifier
 
 DerivedColumn
  : ColumnIdentifier
- | ColumnIdentifier AnyDot 'PARTIAL_CURSOR'
+ | ColumnIdentifier AnyDot PartialIdentifierOrPartialCursor
    {
      // TODO: Check if valid: SELECT testMap["key"].* FROM foo
      if (typeof $1.key === 'undefined') {
@@ -773,7 +785,7 @@ DerivedColumn
       });
       delete parser.yy.derivedColumnChain;
     }
- | 'CURSOR'
+ | AnyCursor
    {
      parser.yy.result.suggestStar = true;
      suggestColumns();
@@ -872,7 +884,7 @@ RegularOrBackTickedSchemaQualifiedName
    {
      $$ = { partial: true, identifierChain: [ { name: $2 } ] };
    }
- | 'REGULAR_IDENTIFIER' AnyDot 'REGULAR_IDENTIFIER' 'PARTIAL_CURSOR'
+ | 'REGULAR_IDENTIFIER' AnyDot PartialIdentifierOrPartialCursor
    {
      $$ = { partial: true, identifierChain: [ { name: $1 } ] };
    }
@@ -884,10 +896,6 @@ RegularOrBackTickedSchemaQualifiedName
    {
      $$ = { partial: true, identifierChain: [ ] };
    }
- | 'REGULAR_IDENTIFIER' AnyDot 'PARTIAL_CURSOR'
-   {
-     $$ = { partial: true, identifierChain: [ { name: $1 } ] };
-   }
  | 'BACKTICK' 'VALUE' 'BACKTICK'
    {
      $$ = { identifierChain: [ { name: $2 } ] }
@@ -910,17 +918,20 @@ LocalOrSchemaQualifiedName
    }
  ;
 
+// TODO: '<hive>[pos]explode' '(' 'CURSOR' possible?
 userDefinedTableGeneratingFunction
  : '<hive>explode' '(' DerivedColumnChain ')'
    {
      delete parser.yy.derivedColumnChain;
      $$ = { function: $1, expression: $3 }
    }
+ | '<hive>explode' '(' PartialIdentifierOrPartialCursor error
  | '<hive>posexplode' '(' DerivedColumnChain ')'
     {
       delete parser.yy.derivedColumnChain;
       $$ = { function: $1, expression: $3 }
     }
+ | '<hive>posexplode' '(' PartialIdentifierOrPartialCursor error
  ;
 
 LateralViews
@@ -948,15 +959,19 @@ LateralView
     {
       $$ = { udtf: $3, columnAliases: $4 }
     }
- | '<hive>LATERAL' 'VIEW' userDefinedTableGeneratingFunction NoOrPartialRegularIdentifier
+ | '<hive>LATERAL' 'VIEW' userDefinedTableGeneratingFunction 'REGULAR_IDENTIFIER' PartialIdentifierOrCursor
    {
      suggestKeywords(['AS']);
    }
- | '<hive>LATERAL' 'VIEW' NoOrPartialRegularIdentifier
+ | '<hive>LATERAL' 'VIEW' userDefinedTableGeneratingFunction PartialIdentifierOrCursor
+   {
+     suggestKeywords(['AS']);
+   }
+ | '<hive>LATERAL' 'VIEW' PartialIdentifierOrCursor
    {
      suggestKeywords(['explode', 'posexplode']);
    }
- | '<hive>LATERAL' NoOrPartialRegularIdentifier
+ | '<hive>LATERAL' PartialIdentifierOrCursor
    {
      suggestKeywords(['VIEW']);
    }
@@ -975,6 +990,10 @@ LateralViewColumnAliases
 
 JoinedTable
  : TableReference 'JOIN' TableReference JoinSpecification
+ | TableReference 'JOIN' TableReference 'CURSOR'
+   {
+     suggestKeywords(['ON']);
+   }
  | TableReference 'JOIN' 'CURSOR'
    {
      suggestTables({});
@@ -1290,7 +1309,7 @@ parser.parseSql = function(beforeCursor, afterCursor, dialect) {
   parser.yy.dialect = dialect;
   try {
     // Add |CURSOR| or |PARTIAL_CURSOR| to represent the different cursor states in the lexer
-    result = parser.parse(beforeCursor + (beforeCursor.length == 0 || beforeCursor.indexOf(' ', beforeCursor.length - 1) !== -1 ? ' |CURSOR| ' : '|PARTIAL_CURSOR|') + afterCursor);
+    result = parser.parse(beforeCursor + (beforeCursor.length == 0 || /.*\s+$/.test(beforeCursor) ? ' |CURSOR| ' : '|PARTIAL_CURSOR|') + afterCursor);
   } catch (err) {
     // On any error try to at least return any existing result
     if (typeof parser.yy.result === 'undefined') {

ファイルの差分が大きいため隠しています
+ 1 - 1
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js


+ 87 - 3
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter2.js

@@ -107,14 +107,46 @@
           fields: fields,
           editor: editor,
           successCallback: function (data) {
-            if (data.columns) {
+            if (data.extended_columns) {
+              data.extended_columns.forEach(function (column) {
+                if (column.type.indexOf('map') === 0 && self.snippet.type() === 'hive') {
+                  completions.push({value: column.name + '[]', meta: 'map'})
+                } else if (column.type.indexOf('map') === 0) {
+                  completions.push({value: column.name, meta: 'map'})
+                } else if (column.type.indexOf('struct') === 0) {
+                  completions.push({ value: column.name, meta: 'struct' })
+                } else if (column.type.indexOf('array') === 0 && self.snippet.type() === 'hive') {
+                  completions.push({ value: column.name + '[]', meta: 'array' })
+                } else if (column.type.indexOf('array') === 0) {
+                  completions.push({ value: column.name, meta: 'array' })
+                } else {
+                  completions.push({ value: column.name, meta: column.type })
+                }
+              });
+            } else if (data.columns) {
               data.columns.forEach(function (column) {
                 completions.push({ value: column, meta: 'column' })
               });
             }
+            if (data.type === 'map' && self.snippet.type() === 'impala') {
+              completions.push({ value: 'key', meta: 'key' });
+              completions.push({ value: 'value', meta: 'value' });
+            }
             if (data.type === 'struct') {
               data.fields.forEach(function (field) {
-                completions.push({ value: field.name  , meta: 'struct' })
+                completions.push({ value: field.name, meta: 'struct' })
+              });
+            } else if (data.type === 'map' && (data.value && data.value.fields)) {
+              data.value.fields.forEach(function (field) {
+                completions.push({ value: field.name, meta: field.type });
+              });
+            } else if (data.type === 'array' && (data.item && data.item.fields)) {
+              data.item.fields.forEach(function (field) {
+                if ((field.type === 'array' || field.type === 'map') && self.snippet.type() === 'hive') {
+                  completions.push({ value: field.name + '[]', meta: field.type });
+                } else {
+                  completions.push({ value: field.name, meta: field.type });
+                }
               });
             }
             columnsDeferred.resolve();
@@ -124,6 +156,58 @@
         });
       }
 
+      if (parseResult.suggestValues) {
+        var valuesDeferred = $.Deferred();
+        var impalaValuesDeferred = $.Deferred();
+        deferrals.push(valuesDeferred);
+        deferrals.push(impalaValuesDeferred);
+
+        self.snippet.getApiHelper().fetchTableSample({
+          sourceType: self.snippet.type(),
+          databaseName: parseResult.suggestValues.database || database,
+          tableName: parseResult.suggestValues.table,
+          columnName: parseResult.suggestValues.identifierChain[0].name,
+          editor: editor,
+          successCallback: function (data) {
+            if (data.status === 0 && data.headers.length === 1) {
+              data.rows.forEach(function (row) {
+                completions.push({ value: typeof row[0] === 'string' ? "'" + row[0] + "'" :  '' + row[0], meta: 'value' });
+              });
+            }
+            valuesDeferred.resolve();
+          },
+          silenceErrors: true,
+          errorCallback: valuesDeferred.resolve
+        });
+
+        if (self.snippet.type() === 'impala') {
+          // TODO: Fetch for each identifier in the chain, we need to add key or value for impala
+          //       select a.key from customers c, c.addresses a WHERE a.zip_code = |
+          // Same goes for Hive
+          //       SELECT orders[].items[].| FROM customers
+          self.snippet.getApiHelper().fetchFields({
+            sourceType: self.snippet.type(),
+            databaseName: parseResult.suggestValues.database || database,
+            tableName: parseResult.suggestValues.table,
+            fields: $.map(parseResult.suggestValues.identifierChain, function (value) { return value.name }),
+            editor: editor,
+            successCallback: function (data) {
+              if (data.sample) {
+                var isString = data.type === "string";
+                data.sample.forEach(function (sample) {
+                  completions.push({ meta: 'value', value: isString ? "'" + sample + "'" : new String(sample) })
+                });
+              }
+              impalaValuesDeferred.resolve();
+            },
+            silenceErrors: true,
+            errorCallback: impalaValuesDeferred.resolve
+          });
+        } else {
+          impalaValuesDeferred.resolve();
+        }
+      }
+
       $.when.apply($, deferrals).done(function () {
         self.finalizeCompletions(completions, callback, editor);
       });
@@ -149,7 +233,7 @@
     callback(completions);
   };
 
-  var typeOrder = { 'star': 1, 'alias': 2, 'table': 3, 'identifier': 4, 'keyword': 5 };
+  var typeOrder = { 'star': 1, 'alias': 2, 'table': 3, 'identifier': 4, 'key' : 5, 'value' : 6, 'keyword': 7 };
 
   SqlAutocompleter2.prototype.sortCompletions = function (completions) {
     completions.sort(function (a, b) {

+ 208 - 2
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpec.js

@@ -159,7 +159,7 @@ define([
           afterCursor: '',
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['GROUP BY', 'LIMIT', 'ORDER BY', 'WHERE']
+            suggestKeywords: ['GROUP BY', 'JOIN', 'LIMIT', 'ORDER BY', 'WHERE']
           }
         });
       });
@@ -330,7 +330,31 @@ define([
             dialect: 'hive',
             expectedResult: {
               lowerCase: false,
-              suggestKeywords: ['GROUP BY', 'LATERAL', 'LIMIT', 'ORDER BY', 'WHERE']
+              suggestKeywords: ['GROUP BY', 'JOIN', 'LATERAL', 'LIMIT', 'ORDER BY', 'WHERE']
+            }
+          });
+        });
+
+        it('should suggest keywords after SELECT SelectList FROM TablePrimary JOIN TablePrimary ', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT bar FROM foo JOIN baz ',
+            afterCursor: '',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['ON']
+            }
+          });
+        });
+
+        it('should suggest keywords after SELECT SelectList FROM TablePrimary with linebreaks', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT bar\nFROM foo\n',
+            afterCursor: '',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['GROUP BY', 'JOIN', 'LATERAL', 'LIMIT', 'ORDER BY', 'WHERE']
             }
           });
         });
@@ -382,6 +406,18 @@ define([
             }
           });
         });
+
+        it('should suggest keywords after SELECT SelectList FROM TablePrimary LATERAL VIEW udtf ', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT bar FROM foo LATERAL VIEW explode(bar) b ',
+            afterCursor: '',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['AS']
+            }
+          });
+        });
       });
     });
 
@@ -707,6 +743,36 @@ define([
       });
     });
 
+    describe('functions', function () {
+      xit('should suggest fields in functions', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT id, SUM(',
+          afterCursor: ' FROM testTable',
+          ignoreErrors: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: {
+              table: 'testTable'
+            }
+          }
+        });
+      });
+
+      xit('should suggest fields in functions after operators', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT id, SUM(a * ',
+          afterCursor: ' FROM testTable',
+          ignoreErrors: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: {
+              table: 'testTable'
+            }
+          }
+        });
+      })
+    });
+
     describe('update statements', function () {
       it('should suggest tables after UPDATE', function() {
         assertAutoComplete({
@@ -1009,6 +1075,21 @@ define([
         });
       });
 
+      it('should suggest struct from map values without a given key after where', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable WHERE testMap[].',
+          afterCursor: '',
+          dialect: 'hive',
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns : {
+              table: 'testTable',
+              identifierChain: [{ name: 'testMap', key: null }]
+            }
+          }
+        });
+      });
+
       // Lateral view === only hive?
       describe('lateral views', function() {
 
@@ -1206,6 +1287,46 @@ define([
           });
         });
 
+        xit('should suggest columns in explode', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT * FROM testTable LATERAL VIEW explode(',
+            afterCursor: '',
+            dialect: 'hive',
+            expectedResult: {
+              suggestColumns: {
+                table: 'testTable'
+              }
+            }
+          });
+        });
+
+        xit('should suggest columns in explode for structs', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT * FROM testTable LATERAL VIEW explode(a.b.',
+            afterCursor: '',
+            dialect: 'hive',
+            expectedResult: {
+              suggestColumns: {
+                table: 'testTable',
+                identifierChain: [ { name: 'a' }, { name: 'b' }]
+              }
+            }
+          });
+        });
+
+        xit('should suggest columns in posexplode', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT * FROM testTable LATERAL VIEW posexplode(',
+            afterCursor: '',
+            dialect: 'hive',
+            expectedResult: {
+              suggestColumns: {
+                table: 'testTable'
+              }
+            }
+          });
+        });
+
         it('should suggest lateral view aliases with multiple column aliases', function () {
           assertAutoComplete({
             beforeCursor: 'SELECT ',
@@ -1462,6 +1583,22 @@ define([
         });
       });
 
+      it('should suggest fields from nested structs with table alias', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT tt.columnA.fieldC.',
+          afterCursor: ' FROM testTable tt',
+          dialect: 'impala',
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: {
+              table: 'testTable',
+              identifierChain: [{ name: 'columnA' }, { name: 'fieldC' }]
+            }
+          }
+        });
+      });
+
+
       // TODO: Result should have 'key', 'key' is only possible after call to see column type but as it's
       //       after FROM perhaps only maps are allowed there?
       //       If the map has a scalar value type (int etc.) it should also suggest 'value'
@@ -1482,6 +1619,22 @@ define([
         });
       });
 
+      it('should suggest fields from map values of type structs with partial identifier', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT tm.a',
+          afterCursor: ' FROM testTable t, t.testMap tm;',
+          dialect: 'impala',
+          expectedResult: {
+            lowerCase: false,
+            suggestStar: true,
+            suggestColumns : {
+              table: 'testTable',
+              identifierChain: [{ name: 'testMap' }]
+            }
+          }
+        });
+      });
+
       // Same as above, 'items' or 'value' for scalar
       it('should suggest items from arrays if complex in from clause', function() {
         assertAutoComplete({
@@ -1653,6 +1806,20 @@ define([
           }
         });
       });
+
+      it('should suggest values for columns in conditions', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT a, b, c FROM testTable WHERE d >= ',
+          afterCursor: '',
+          expectedResult: {
+            lowerCase: false,
+            suggestValues: {
+              table: 'testTable',
+              identifierChain: [{ name: 'd' }]
+            }
+          }
+        });
+      });
     });
 
     describe('field completion', function() {
@@ -1680,6 +1847,18 @@ define([
         });
       });
 
+      it('should suggest multiple columns for table without space', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT a,',
+          afterCursor: ' FROM testTable',
+          expectedResult: {
+            lowerCase: false,
+            suggestStar: true, // TODO: Correct?
+            suggestColumns: { table: 'testTable' }
+          }
+        });
+      });
+
       it('should suggest columns for tables with where keyword in name', function () {
         assertAutoComplete({
           beforeCursor: 'SELECT ',
@@ -1739,6 +1918,17 @@ define([
         });
       });
 
+      it('should suggest columns for table after WHERE with partial column', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable WHERE a',
+          afterCursor: '',
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: { table: 'testTable' }
+          }
+        });
+      });
+
       it('should suggest BY after ORDER', function () {
         assertAutoComplete({
           beforeCursor: 'SELECT * FROM testTable ORDER ',
@@ -1950,6 +2140,22 @@ define([
         });
       });
 
+      xit('should suggest identifiers or values in join condition if table reference is present from multiple tables', function() {
+        assertAutoComplete({
+          beforeCursor: 'select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = ',
+          afterCursor: '',
+          ignoreErrors: true,
+          expectedResult: {
+            lowerCase: true,
+            suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'testTable2.', type: 'table' }],
+            suggestValues: {
+              table: 'testTable1',
+              identifierChain: [{ name: 'testColumn1' }]
+            }
+          }
+        });
+      });
+
       it('should suggest field references in join condition if table reference is present from multiple tables', function() {
         assertAutoComplete({
           beforeCursor: 'select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = testTable2.',

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません