瀏覽代碼

HUE-4551 [editor] The new autocompleter should support CTEs (WITH) and UNION

This also adds support for HAVING, CLUSTER BY, DISTRIBUTE BY and SORT BY.
Johan Ahlen 9 年之前
父節點
當前提交
a4fe3af

+ 7 - 1
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/sql.jisonlex

@@ -58,6 +58,7 @@
 <hive>'BUCKETS'                            { return '<hive>BUCKETS'; }
 <hive>'CASCADE'                            { return '<hive>CASCADE'; }
 <hive>'CHANGE'                             { return '<hive>CHANGE'; }
+<hive>'CLUSTER'                            { return '<hive>CLUSTER'; }
 <hive>'CLUSTERED'                          { return '<hive>CLUSTERED'; }
 <hive>'COLLECTION'                         { return '<hive>COLLECTION'; }
 <hive>'COLUMNS'                            { return '<hive>COLUMNS'; }
@@ -72,6 +73,7 @@
 <hive>'DELIMITED'                          { return '<hive>DELIMITED'; }
 <hive>'DESC'                               { return '<hive>DESC'; }
 <hive>'DISABLE'                            { return '<hive>DISABLE'; }
+<hive>'DISTRIBUTE'                         { return '<hive>DISTRIBUTE'; }
 <hive>'ESCAPED'                            { return '<hive>ESCAPED'; }
 <hive>'ENABLE'                             { return '<hive>ENABLE'; }
 <hive>'EXCHANGE'                           { return '<hive>EXCHANGE'; }
@@ -120,6 +122,7 @@
 <hive>'SHOW'                               { determineCase(yytext); return '<hive>SHOW'; }
 <hive>'SKEWED'                             { return '<hive>SKEWED'; }
 <hive>'SKEWED LOCATION'                    { return '<hive>SKEWED_LOCATION'; } // Hack to prevent hdfs lexer state
+<hive>'SORT'                               { return '<hive>SORT'; }
 <hive>'SORTED'                             { return '<hive>SORTED'; }
 <hive>'STORED'                             { return '<hive>STORED'; }
 <hive>STORED[ \t\n]+AS[ \t\n]+DIRECTORIES  { return '<hive>STORED_AS_DIRECTORIES'; }
@@ -179,6 +182,7 @@
 <impala>'LOCATION'                         { this.begin('hdfs'); return '<impala>LOCATION'; }
 <impala>'MERGE_FN'                         { return '<impala>MERGE_FN'; }
 <impala>'NULLS'                            { return '<impala>NULLS'; }
+<impala>'OFFSET'                           { return '<impala>OFFSET'; }
 <impala>'PARQUET'                          { return '<impala>PARQUET'; }
 <impala>'PARTITIONED'                      { return '<impala>PARTITIONED'; }
 <impala>'PARTITIONS'                       { return '<impala>PARTITIONS'; }
@@ -251,6 +255,7 @@
 'FULL'                                     { return 'FULL'; }
 'GROUP'                                    { return 'GROUP'; }
 'GROUPING'                                 { return 'GROUPING'; } // Not in Impala?
+'HAVING'                                   { return 'HAVING'; }
 'IF'                                       { return 'IF'; }
 'IN'                                       { return 'IN'; }
 'INNER'                                    { return 'INNER'; }
@@ -291,11 +296,12 @@
 'UNBOUNDED'                                { return 'UNBOUNDED'; }
 'UPDATE'                                   { determineCase(yytext); return 'UPDATE'; }
 'USE'                                      { determineCase(yytext); return 'USE'; }
+'UNION'                                    { return 'UNION'; }
 'VIEW'                                     { return 'VIEW'; }
 'VARCHAR'                                  { return 'VARCHAR'; } // Not in Impala
 'WHEN'                                     { return 'WHEN'; }
 'WHERE'                                    { return 'WHERE'; }
-'WITH'                                     { return 'WITH'; }
+'WITH'                                     { determineCase(yytext); return 'WITH'; }
 
 // Non-reserved Keywords
 'OVER'                                     { return 'OVER'; }

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

@@ -162,18 +162,34 @@ SqlStatements
  | SqlStatements ';' NewStatement SqlStatements
  ;
 
+SqlStatements_EDIT
+ : SqlStatement_EDIT
+ | SqlStatement_EDIT ';' NewStatement SqlStatements
+ | SqlStatements ';' NewStatement SqlStatement_EDIT
+ | SqlStatements ';' NewStatement SqlStatement_EDIT ';' NewStatement SqlStatements
+ ;
 
 ErrorStatement
  : error
  | NonStartingToken error // Having just ': error' does not work for some reason, jison bug?
  ;
 
+SqlStatement_EDIT
+ : AnyCursor
+   {
+     suggestDdlAndDmlKeywords();
+   }
+ | DataDefinition_EDIT
+ | DataManipulation_EDIT
+ | QuerySpecification_EDIT
+ ;
+
 // This is a work-around for error handling when a statement starts with some token that the parser can understand but
 // it's not a valid statement (see ErrorStatement). It contains everything except valid starting tokens ('SELECT', 'USE' etc.)
 NonStartingToken
  : '<hive>ALL' | '<hive>ARRAY' | '<hive>AVRO' | '<hive>BINARY' | '<hive>BUCKETS' | '<hive>AS' | '<hive>CLUSTERED' | '<hive>COLLECTION' | '<hive>CONF' | '<hive>CROSS' | '<hive>CURRENT' | '<hive>DATE' | '<hive>DEFERRED' | '<hive>DELIMITED' | '<hive>ESCAPED' | '<hive>EXTENDED' | '<hive>EXTERNAL' | '<hive>FIELDS' | '<hive>FILE' | '<hive>FORMAT' | '<hive>FUNCTION' | '<hive>GRANT' | '<hive>IDXPROPERTIES' | '<hive>LATERAL' | '<hive>MACRO' | '<hive>PARTITION' | '<hive>REBUILD' | '<hive>TABLE' | '<hive>USER' | '<hive>ASC' | '<hive>COLUMNS' | '<hive>COMMENT' | '<hive>COMPACTIONS' | '<hive>DATA' | '<hive>DATABASES' | '<hive>DEFINED' | '<hive>DESC' | '<hive>STORED_AS_DIRECTORIES' | '<hive>FORMATTED' | '<hive>FUNCTIONS' | '<hive>INDEX' | '<hive>INDEXES' | '<hive>INPATH' | '<hive>INPUTFORMAT' | '<hive>ITEMS' | '<hive>JAR' | '<hive>LIMIT' | '<hive>KEYS' | '<hive>LINES' | '<hive>LOCATION' | '<hive>LOCKS' | '<hive>MAP' | '<hive>ORC' | '<hive>OUTPUTFORMAT' | '<hive>PARQUET' | '<hive>PARTITIONED' | '<hive>PARTITIONS' | '<hive>RCFILE' | '<hive>ROLE' | '<hive>ROLES' | '<hive>SCHEMA' | '<hive>SCHEMAS' | '<hive>SEQUENCEFILE' | '<hive>SERDE' | '<hive>SERDEPROPERTIES' | '<hive>SKEWED' | '<hive>SORTED' | '<hive>STORED' | '<hive>STRING' | '<hive>STRUCT' | '<hive>TABLES' | '<hive>TBLPROPERTIES' | '<hive>TEMPORARY' | '<hive>TERMINATED' | '<hive>TEXTFILE' | '<hive>TINYINT' | '<hive>TRANSACTIONS' | '<hive>UNIONTYPE' | '<hive>USING' | '<hive>VIEW' | '<hive>WINDOW' | '<hive>.' | '<hive>[' | '<hive>]'
  | '<impala>AGGREGATE' | '<impala>AVRO' | '<impala>CACHED' | '<impala>CLOSE_FN' | '<impala>COLUMN' | '<impala>COMMENT' | '<impala>DATA' | '<impala>DATABASES' | '<impala>DELIMITED' | '<impala>ESCAPED' | '<impala>EXTERNAL' | '<impala>FIELDS' | '<impala>FINALIZE_FN' | '<impala>FIRST' | '<impala>FORMAT' | '<impala>FORMATTED' | '<impala>FUNCTION' | '<impala>FUNCTIONS' | '<impala>GROUP' | '<impala>INCREMENTAL' | '<impala>INIT_FN' | '<impala>INPATH' | '<impala>LAST' | '<impala>LINES' | '<impala>LOCATION' | '<impala>MERGE_FN' | '<impala>NULLS' | '<impala>PARTITIONS' | '<impala>PREPARE_FN' | '<impala>REAL' | '<impala>RETURNS' | '<impala>SCHEMAS' | '<impala>SERIALIZE_FN' | '<impala>STATS' | '<impala>SYMBOL' | '<impala>TABLE' | '<impala>TABLES' | '<impala>USING' | '<impala>ANALYTIC' | '<impala>ANTI' | '<impala>CURRENT' | '<impala>GRANT' | '<impala>PARQUET' | '<impala>PARTITIONED' | '<impala>RCFILE' | '<impala>ROLE' | '<impala>ROLES' | '<impala>SEQUENCEFILE' | '<impala>SERDEPROPERTIES' | '<impala>SHUFFLE' | '<impala>STORED' | '<impala>TBLPROPERTIES' | '<impala>TERMINATED' | '<impala>TEXTFILE' | '<impala>UPDATE_FN' | '<impala>BROADCAST' | '<impala>...' | '<impala>.' | '<impala>[' | '<impala>]'
- | 'ALL' | 'AS' | 'ASC' | 'BETWEEN' | 'BIGINT' | 'BOOLEAN' | 'BY' | 'CASE' | 'CHAR' | 'CURRENT' | 'DATABASE' | 'DECIMAL' | 'DISTINCT' | 'DOUBLE' | 'DESC' | 'ELSE' | 'END' | 'EXISTS' | 'FALSE' | 'FLOAT' | 'FOLLOWING' | 'FROM' | 'FULL' | 'GROUP' | 'GROUPING' | 'IF' | 'IN' | 'INNER' | 'INT' | 'INTO' | 'IS' | 'JOIN' | 'LEFT' | 'LIKE' | 'LIMIT' | 'NOT' | 'NULL' | 'ON' | 'ORDER' | 'OUTER' | 'OVER' | 'PARTITION' | 'PRECEDING' | 'RANGE' | 'REGEXP' | 'RIGHT' | 'RLIKE' | 'ROW' | 'ROWS' | 'SCHEMA' | 'SEMI' | 'SET' | 'SMALLINT' | 'STRING' | 'TABLE' | 'THEN' | 'TIMESTAMP' | 'TINYINT' | 'TRUE' | 'VARCHAR' | 'WHEN' | 'WHERE' | 'WITH' | 'ROLE'
+ | 'ALL' | 'AS' | 'ASC' | 'BETWEEN' | 'BIGINT' | 'BOOLEAN' | 'BY' | 'CASE' | 'CHAR' | 'CURRENT' | 'DATABASE' | 'DECIMAL' | 'DISTINCT' | 'DOUBLE' | 'DESC' | 'ELSE' | 'END' | 'EXISTS' | 'FALSE' | 'FLOAT' | 'FOLLOWING' | 'FROM' | 'FULL' | 'GROUP' | 'GROUPING' | 'IF' | 'IN' | 'INNER' | 'INT' | 'INTO' | 'IS' | 'JOIN' | 'LEFT' | 'LIKE' | 'LIMIT' | 'NOT' | 'NULL' | 'ON' | 'ORDER' | 'OUTER' | 'OVER' | 'PARTITION' | 'PRECEDING' | 'RANGE' | 'REGEXP' | 'RIGHT' | 'RLIKE' | 'ROW' | 'ROWS' | 'SCHEMA' | 'SEMI' | 'SET' | 'SMALLINT' | 'STRING' | 'TABLE' | 'THEN' | 'TIMESTAMP' | 'TINYINT' | 'TRUE' | 'UNION' | 'VARCHAR' | 'WHEN' | 'WHERE' | 'WITH' | 'ROLE'
  | 'AVG' | 'CAST' | 'COUNT' | 'MAX' | 'MIN' | 'STDDEV_POP' | 'STDDEV_SAMP' | 'SUM' | 'VARIANCE' | 'VAR_POP' | 'VAR_SAMP'
  | '<hive>COLLECT_SET' | '<hive>COLLECT_LIST' | '<hive>CORR' | '<hive>COVAR_POP' | '<hive>COVAR_SAMP' | '<hive>HISTOGRAM_NUMERIC' | '<hive>NTILE' | '<hive>PERCENTILE' | '<hive>PERCENTILE_APPROX'
  | '<impala>APPX_MEDIAN' | '<impala>EXTRACT' | '<impala>GROUP_CONCAT' | '<impala>STDDEV' | '<impala>VARIANCE_POP' | '<impala>VARIANCE_SAMP'
@@ -181,23 +197,6 @@ NonStartingToken
  | 'UNSIGNED_INTEGER' | 'UNSIGNED_INTEGER_E' | 'REGULAR_IDENTIFIER' | 'HDFS_START_QUOTE' | 'AND' | 'OR' | '=' | '<' | '>' | 'COMPARISON_OPERATOR' | '-' | '*' | 'ARITHMETIC_OPERATOR' | ',' | '.' | '~' | '!' | '(' | ')' | '[' | ']' | 'VARIABLE_REFERENCE' | 'BACKTICK' | 'SINGLE_QUOTE' | 'DOUBLE_QUOTE'
  ;
 
-SqlStatements_EDIT
- : SqlStatement_EDIT
- | SqlStatement_EDIT ';' NewStatement SqlStatements
- | SqlStatements ';' NewStatement SqlStatement_EDIT
- | SqlStatements ';' NewStatement SqlStatement_EDIT ';' NewStatement SqlStatements
- ;
-
-SqlStatement_EDIT
- : AnyCursor
-   {
-     suggestDdlAndDmlKeywords();
-   }
- | DataDefinition_EDIT
- | DataManipulation_EDIT
- | QuerySpecification_EDIT
- ;
-
 DataDefinition
  : DescribeStatement
  ;
@@ -1040,11 +1039,88 @@ LoadStatement_EDIT
 // ===================================== SELECT statement =====================================
 
 QuerySpecification
+ : SelectStatement OptionalUnions                                   -> $1
+ | CommonTableExpression SelectStatement OptionalUnions
+ | CommonTableExpression '(' QuerySpecification ')' OptionalUnions  -> $3
+ ;
+
+QuerySpecification_EDIT
+ : SelectStatement_EDIT OptionalUnions
+ | SelectStatement OptionalUnions_EDIT
+ | CommonTableExpression 'CURSOR'
+   {
+     suggestKeywords(['SELECT']); // TODO: Move up for more statement types
+   }
+ | CommonTableExpression '(' QuerySpecification_EDIT ')'
+   {
+     addCommonTableExpressions($1);
+   }
+ | CommonTableExpression SelectStatement_EDIT OptionalUnions
+   {
+     addCommonTableExpressions($1);
+   }
+ | CommonTableExpression SelectStatement OptionalUnions_EDIT
+   {
+     addCommonTableExpressions($1);
+   }
+ | CommonTableExpression_EDIT
+ | CommonTableExpression_EDIT '(' QuerySpecification ')'
+ | CommonTableExpression_EDIT SelectStatement OptionalUnions
+ ;
+
+OptionallyParenthesizedSelectStatement
+ : SelectStatement
+ | '(' SelectStatement ')' // Impala specific
+ ;
+
+OptionallyParenthesizedSelectStatement_EDIT
+ : SelectStatement_EDIT
+ | '(' SelectStatement_EDIT RightParenthesisOrError
+ ;
+
+SelectStatement
  : 'SELECT' OptionalAllOrDistinct SelectList                  -> { selectList: $3 }
  | 'SELECT' OptionalAllOrDistinct SelectList TableExpression  -> { selectList: $3, tableExpression: $4 }
  ;
 
-QuerySpecification_EDIT
+OptionalUnions
+ :
+ | Unions
+ ;
+
+OptionalUnions_EDIT
+ : Unions_EDIT
+ ;
+
+Unions
+ : UnionClause
+ | Unions UnionClause
+ ;
+
+Unions_EDIT
+ : UnionClause_EDIT
+ | Unions UnionClause_EDIT
+ | UnionClause_EDIT Unions
+ | Unions UnionClause_EDIT Unions
+ ;
+
+UnionClause
+ : 'UNION' NewStatement OptionalAllOrDistinct SelectStatement
+ ;
+
+UnionClause_EDIT
+ : 'UNION' NewStatement 'CURSOR'
+   {
+     suggestKeywords(['ALL', 'DISTINCT', 'SELECT']);
+   }
+ | 'UNION' NewStatement 'CURSOR' SelectStatement
+   {
+     suggestKeywords(['ALL', 'DISTINCT']);
+   }
+ | 'UNION' NewStatement OptionalAllOrDistinct SelectStatement_EDIT
+ ;
+
+SelectStatement_EDIT
  : 'SELECT' OptionalAllOrDistinct SelectList_EDIT
    {
      if ($3.cursorAtStart) {
@@ -1141,6 +1217,42 @@ QuerySpecification_EDIT
    }
  ;
 
+CommonTableExpression
+ : 'WITH' WithQueries  -> $2
+ ;
+
+CommonTableExpression_EDIT
+ : 'WITH' WithQueries_EDIT
+ ;
+
+WithQueries
+ : WithQuery                   -> [$1]
+ | WithQueries ',' WithQuery   -> $1.concat([$3]);
+ ;
+
+WithQueries_EDIT
+ : WithQuery_EDIT
+ | WithQueries ',' WithQuery_EDIT
+ | WithQuery_EDIT ',' WithQueries
+ | WithQueries ',' WithQuery_EDIT ',' WithQueries
+ ;
+
+WithQuery
+ : RegularOrBacktickedIdentifier AnyAs '(' TableSubQueryInner ')'
+ ;
+
+WithQuery_EDIT
+ : RegularOrBacktickedIdentifier 'CURSOR'
+   {
+     suggestKeywords(['AS']);
+   }
+ | RegularOrBacktickedIdentifier AnyAs '(' AnyCursor RightParenthesisOrError
+   {
+     suggestKeywords(['SELECT']);
+   }
+ | RegularOrBacktickedIdentifier AnyAs '(' TableSubQueryInner_EDIT RightParenthesisOrError
+ ;
+
 OptionalAllOrDistinct
  :
  | '<hive>ALL'
@@ -1166,45 +1278,42 @@ TableExpression_EDIT
      // The reason for the join mess is because for "SELECT * FROM foo | JOIN bar" the parts surrounding the
      // cursor are complete and not in _EDIT rules.
 
-     if (!$2) {
-       var keywords = [];
-       if (typeof $1.hasJoinCondition !== 'undefined' && ! $1.hasJoinCondition) {
-         keywords.push({ value: 'ON', weight: 8 });
-         if (isImpala()) {
-           keywords.push({ value: 'USING', weight: 8 });
-         }
-       }
+     var keywords = [];
+
+     if ($4 && $4.joinType.toUpperCase() === 'JOIN') {
+       keywords = ['FULL', 'FULL OUTER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER'];
        if (isHive()) {
-         if ($4 && $4.joinType.toUpperCase() === 'JOIN') {
-           keywords = keywords.concat(['CROSS', 'FULL', 'FULL OUTER', 'LEFT', 'LEFT OUTER', 'LEFT SEMI', 'RIGHT', 'RIGHT OUTER']);
-         } else {
-           keywords = keywords.concat([{ value: 'LATERAL VIEW', weight: 1 }, { value: 'CROSS JOIN', weight: 1 }, { value: 'FULL JOIN', weight: 1 }, { value: 'FULL OUTER JOIN', weight: 1 }, { value: 'JOIN', weight: 1 }, { value: 'LEFT JOIN', weight: 1 }, { value: 'LEFT OUTER JOIN', weight: 1 }, { value: 'LEFT SEMI JOIN', weight: 1 }, { value: 'RIGHT JOIN', weight: 1 }, { value: 'RIGHT OUTER JOIN', weight: 1 }, { value: 'WHERE', weight: 6 }, { value: 'GROUP BY', weight: 5 }, { value: 'WINDOW', weight: 4 }, { value: 'ORDER BY', weight: 3 }, { value: 'LIMIT', weight: 2 }]);
-         }
+         keywords = keywords.concat(['CROSS', 'LEFT SEMI']);
        } else if (isImpala()) {
-         if ($4 && $4.joinType.toUpperCase() === 'JOIN') {
-           keywords = keywords.concat(['FULL', 'FULL OUTER', 'INNER', 'LEFT ANTI', 'LEFT', 'LEFT OUTER', 'LEFT SEMI', 'RIGHT ANTI', 'RIGHT', 'RIGHT OUTER', 'RIGHT SEMI']);
-         } else {
-           keywords = keywords.concat([{ value: 'FULL JOIN', weight: 1 }, { value: 'FULL OUTER JOIN', weight: 1 }, { value: 'INNER JOIN', weight: 1 }, { value: 'JOIN', weight: 1 }, { value: 'LEFT ANTI JOIN', weight: 1 }, { value: 'LEFT JOIN', weight: 1 }, { value: 'LEFT OUTER JOIN', weight: 1 }, { value: 'LEFT SEMI JOIN', weight: 1 }, { value: 'RIGHT ANTI JOIN', weight: 1 }, { value: 'RIGHT JOIN', weight: 1 }, { value: 'RIGHT OUTER JOIN', weight: 1 }, { value: 'RIGHT SEMI JOIN', weight: 1 }, { value: 'WHERE', weight: 5 }, { value: 'GROUP BY', weight: 4 }, { value: 'ORDER BY', weight: 3 }, { value: 'LIMIT', weight: 2 }]);
-         }
+         keywords = keywords.concat(['INNER', 'LEFT ANTI', 'LEFT SEMI', 'RIGHT ANTI', 'RIGHT SEMI']);
        } else {
-         if ($4 && $4.joinType.toUpperCase() === 'JOIN') {
-           keywords = keywords.concat(['FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER']);
-         } else {
-           keywords = keywords.concat([{ value: 'FULL JOIN', weight: 1 }, { value: 'FULL OUTER JOIN', weight: 1 }, { value: 'INNER JOIN', weight: 1 }, { value: 'JOIN', weight: 1 }, { value: 'LEFT JOIN', weight: 1 }, { value: 'LEFT OUTER JOIN', weight: 1 }, { value: 'RIGHT JOIN', weight: 1 }, { value: 'RIGHT OUTER JOIN', weight: 1 }, { value: 'WHERE', weight: 5 }, { value: 'GROUP BY', weight: 4 }, { value: 'ORDER BY', weight: 3 }, { value: 'LIMIT', weight: 2 }]);
-         }
+         keywords.push('INNER');
        }
-       if ($1.suggestKeywords) {
-         keywords = keywords.concat($1.suggestKeywords);
-         suggestKeywords(keywords);
+     } else {
+        if ($2 && $2.suggestKeywords) {
+          keywords = createWeightedKeywords($2.suggestKeywords, 2);
+        }
+        if ($2 && $2.suggestColRefKeywords) {
+          suggestColRefKeywords($2.suggestColRefKeywords);
+          addColRefIfExists($2);
+        }
+        keywords.push({ value: 'UNION', weight: 2.19 });
+     }
+
+     if ($2 && $2.empty) {
+       if (typeof $1.hasJoinCondition !== 'undefined' && ! $1.hasJoinCondition) {
+         keywords.push({ value: 'ON', weight: 3 });
+         if (isImpala()) {
+           keywords.push({ value: 'USING', weight: 3 });
+         }
+       } else if ($1.suggestKeywords) {
+         keywords = keywords.concat(createWeightedKeywords($1.suggestKeywords, 3));
        } else if ($1.types) {
-        // Checks if valueExpression could happen when there's no OptionalJoinCondition
          suggestValueExpressionKeywords($1, keywords);
-       } else {
-         suggestKeywords(keywords);
+         return;
        }
-     } else {
-       checkForKeywords($2);
      }
+     suggestKeywords(keywords);
    }
  ;
 
@@ -1228,41 +1337,76 @@ FromClause_EDIT
  ;
 
 OptionalSelectConditions
- : OptionalWhereClause OptionalGroupByClause OptionalWindowClause OptionalOrderByClause OptionalLimitClause
+ : OptionalWhereClause OptionalGroupByClause OptionalHavingClause OptionalWindowClause OptionalOrderByClause OptionalClusterOrDistributeBy OptionalLimitClause OptionalOffsetClause
    {
-     if ($1 && !$2 && !$3 && !$4 && !$5) {
+     var keywords = [];
+
+     if (isImpala() && !$8) {
+       keywords.push({ value: 'OFFSET', weight: 2 });
+     }
+
+     if (!$7 && !$8) {
+       keywords.push({ value: 'LIMIT', weight: 3 });
+     }
+
+     if (isHive() && !$6 && !$7 && !$8) {
+       keywords = keywords.concat([{ value: 'CLUSTER BY', weight: 4 }, { value: 'DISTRIBUTE BY', weight: 4 }, { value: 'SORT BY', weight: 4 }]);
+     } else if (isHive() && $6 && $6.suggestKeywords && !$7 && !$8) {
+       keywords = keywords.concat(createWeightedKeywords($6.suggestKeywords, 4));
+     }
+
+     if (!$5 && !$6 && !$7 && !$8) {
+       keywords.push({ value: 'ORDER BY', weight: 5 });
+     } else if ($5 && $5.suggestKeywords && !$6 && !$7) {
+       keywords = keywords.concat(createWeightedKeywords($5.suggestKeywords, 5));
+     }
+
+     if (isHive() && !$4 && !$5 && !$6 && !$7 && !$8) {
+       keywords.push({ value: 'WINDOW', weight: 6 });
+     }
+
+     if (!$3 && !$4 && !$5 && !$6 && !$7 && !$8) {
+       keywords.push({ value: 'HAVING', weight: 7 });
+     }
+
+     if (!$2 && !$3 && !$4 && !$5 && !$6 && !$7 && !$8) {
+       keywords.push({ value: 'GROUP BY', weight: 8 });
+     }
+
+     if (!$1 && !$2 && !$3 && !$4 && !$5 && !$6 && !$7 && !$8) {
+       keywords = keywords.concat([{ value: 'WHERE', weight: 9 }, { value: 'FULL JOIN', weight: 1 }, { value: 'FULL OUTER JOIN', weight: 1 }, { value: 'JOIN', weight: 1 }, { value: 'LEFT JOIN', weight: 1 }, { value: 'LEFT OUTER JOIN', weight: 1 }, { value: 'RIGHT JOIN', weight: 1 }, { value: 'RIGHT OUTER JOIN', weight: 1 }]);
        if (isHive()) {
-         $$ = getValueExpressionKeywords($1, [{ value: 'GROUP BY', weight: 4 }, { value: 'WINDOW', weight: 3 }, { value: 'ORDER BY', weight: 2 }, { value: 'LIMIT', weight: 1 }]);
+         keywords = keywords.concat([{ value: 'LATERAL VIEW', weight: 1 }, { value: 'CROSS JOIN', weight: 1 }, { value: 'LEFT SEMI JOIN', weight: 1 }]);
+       } else if (isImpala()) {
+         keywords = keywords.concat([{ value: 'INNER JOIN', weight: 1 },  { value: 'LEFT ANTI JOIN', weight: 1 }, { value: 'LEFT SEMI JOIN', weight: 1 }, { value: 'RIGHT ANTI JOIN', weight: 1 }, { value: 'RIGHT SEMI JOIN', weight: 1 }]);
        } else {
-         $$ = getValueExpressionKeywords($1, [{ value: 'GROUP BY', weight: 3 }, { value: 'ORDER BY', weight: 2 }, { value: 'LIMIT', weight: 1 }]);
+         keywords.push({ value: 'INNER JOIN', weight: 1 });
        }
+     }
+
+     if ($1 && !$2 && !$3 && !$4 && !$5 && !$6 && !$7 && !$8) {
+       $$ = getValueExpressionKeywords($1, keywords);
        if ($1.columnReference) {
-         $$.columnReference = $1.columnReference
-       }
-     } else if ($2 && !$3 && !$4 && !$5) {
-       if (isHive()) {
-         $$ = { suggestKeywords: [{ value: 'WINDOW', weight: 3 }, { value: 'ORDER BY', weight: 2 }, { value: 'LIMIT', weight: 1 }] };
-       } else {
-         $$ = { suggestKeywords: [{ value: 'ORDER BY', weight: 2 }, { value: 'LIMIT', weight: 1 }] };
-       }
-     } else if ($3 && !$4 && !$5) {
-       $$ = { suggestKeywords: [{ value: 'ORDER BY', weight: 2 }, { value: 'LIMIT', weight: 1 }] };
-     } else if ($4 && !$5) {
-       if ($4.suggestKeywords) {
-         $$ = { suggestKeywords: $4.suggestKeywords.concat([{ value: 'LIMIT', weight: -2 }]) };
-       } else {
-         $$ = { suggestKeywords: ['LIMIT'] };
+         $$.columnReference = $1.columnReference;
        }
+     } else {
+       $$ = { suggestKeywords: keywords };
+     }
+     if (!$1 && !$2 && !$3 && !$4 && !$5 && !$6 && !$7 && !$8) {
+       $$.empty = true;
      }
    }
  ;
 
 OptionalSelectConditions_EDIT
- : OptionalWhereClause_EDIT OptionalGroupByClause OptionalWindowClause OptionalOrderByClause OptionalLimitClause
- | OptionalWhereClause OptionalGroupByClause_EDIT OptionalWindowClause OptionalOrderByClause OptionalLimitClause
- | OptionalWhereClause OptionalGroupByClause OptionalWindowClause_EDIT OptionalOrderByClause OptionalLimitClause
- | OptionalWhereClause OptionalGroupByClause OptionalWindowClause OptionalOrderByClause_EDIT OptionalLimitClause
- | OptionalWhereClause OptionalGroupByClause OptionalWindowClauseOptionalOrderByClause OptionalLimitClause_EDIT
+ : OptionalWhereClause_EDIT OptionalGroupByClause OptionalHavingClause OptionalWindowClause OptionalOrderByClause OptionalClusterOrDistributeBy OptionalLimitClause OptionalOffsetClause
+ | OptionalWhereClause OptionalGroupByClause_EDIT OptionalHavingClause OptionalWindowClause OptionalOrderByClause OptionalClusterOrDistributeBy OptionalLimitClause OptionalOffsetClause
+ | OptionalWhereClause OptionalGroupByClause OptionalHavingClause_EDIT OptionalWindowClause OptionalOrderByClause OptionalClusterOrDistributeBy OptionalLimitClause OptionalOffsetClause
+ | OptionalWhereClause OptionalGroupByClause OptionalHavingClause OptionalWindowClause_EDIT OptionalOrderByClause OptionalClusterOrDistributeBy OptionalLimitClause OptionalOffsetClause
+ | OptionalWhereClause OptionalGroupByClause OptionalHavingClause OptionalWindowClause OptionalOrderByClause_EDIT OptionalClusterOrDistributeBy OptionalLimitClause OptionalOffsetClause
+ | OptionalWhereClause OptionalGroupByClause OptionalHavingClause OptionalWindowClause OptionalOrderByClause OptionalClusterOrDistributeBy_EDIT OptionalLimitClause OptionalOffsetClause
+ | OptionalWhereClause OptionalGroupByClause OptionalHavingClause OptionalWindowClause OptionalOrderByClause OptionalClusterOrDistributeBy OptionalLimitClause_EDIT OptionalOffsetClause
+ | OptionalWhereClause OptionalGroupByClause OptionalHavingClause OptionalWindowClause OptionalOrderByClause OptionalClusterOrDistributeBy OptionalLimitClause OptionalOffsetClause_EDIT
  ;
 
 OptionalWhereClause
@@ -1415,6 +1559,90 @@ OptionalImpalaNullsFirstOrLast_EDIT
    }
  ;
 
+OptionalClusterOrDistributeBy
+ :
+ | ClusterByClause
+ | DistributeByClause               -> { suggestKeywords: ['SORT BY'] }
+ | DistributeByClause SortByClause
+ | SortByClause
+ ;
+
+OptionalClusterOrDistributeBy_EDIT
+ : ClusterByClause_EDIT
+ | DistributeByClause_EDIT
+ | DistributeByClause SortByClause_EDIT
+ | DistributeByClause_EDIT SortByClause
+ | SortByClause_EDIT
+ ;
+
+ClusterByClause
+ : '<hive>CLUSTER' 'BY' ColumnList
+ ;
+
+ClusterByClause_EDIT
+ : '<hive>CLUSTER' 'CURSOR'
+   {
+     suggestKeywords: ['BY'];
+   }
+ | '<hive>CLUSTER' 'BY' 'CURSOR'
+   {
+     suggestColumns();
+   }
+ | '<hive>CLUSTER' 'BY' ColumnList_EDIT
+ ;
+
+DistributeByClause
+ : '<hive>DISTRIBUTE' 'BY' ColumnList
+ ;
+
+DistributeByClause_EDIT
+ : '<hive>DISTRIBUTE' 'CURSOR'
+   {
+     suggestKeywords: ['BY'];
+   }
+ | '<hive>DISTRIBUTE' 'BY' 'CURSOR'
+   {
+     suggestColumns();
+   }
+ | '<hive>DISTRIBUTE' 'BY' ColumnList_EDIT
+ ;
+
+SortByClause
+ : '<hive>SORT' 'BY' SortByList  -> $3
+ ;
+
+SortByClause_EDIT
+ : '<hive>SORT' 'CURSOR'
+   {
+     suggestKeywords: ['BY'];
+   }
+ | '<hive>SORT' 'BY' SortByList_EDIT
+ ;
+
+SortByList
+ : SortByIdentifier
+ | SortByList ',' SortByIdentifier  -> $3
+ ;
+
+SortByList_EDIT
+ : SortByIdentifier_EDIT
+ | SortByIdentifier_EDIT ',' SortByList
+ | SortByList ',' SortByIdentifier_EDIT
+ | SortByList ',' SortByIdentifier_EDIT ',' SortByList
+ ;
+
+SortByIdentifier
+ : ColumnIdentifier OptionalAscOrDesc  -> $2
+ ;
+
+SortByIdentifier_EDIT
+ : ColumnIdentifier_EDIT OptionalAscOrDesc
+ | AnyCursor OptionalAscOrDesc
+   {
+     suggestColumns();
+   }
+ ;
+
 AnyLimit
  : 'LIMIT'
  | '<hive>LIMIT'
@@ -1422,14 +1650,30 @@ AnyLimit
 
 OptionalLimitClause
  :
- | AnyLimit 'UNSIGNED_INTEGER'
+ | AnyLimit ValueExpression
  ;
 
 OptionalLimitClause_EDIT
  : AnyLimit 'CURSOR'
    {
-     suggestNumbers([1, 5, 10]);
+     valueExpressionSuggest();
+     applyTypeToSuggestions(['INT']);
    }
+ | AnyLimit ValueExpression_EDIT
+ ;
+
+OptionalOffsetClause
+ :
+ | '<impala>OFFSET' ValueExpression
+ ;
+
+OptionalOffsetClause_EDIT
+ : '<impala>OFFSET' 'CURSOR'
+   {
+     valueExpressionSuggest();
+     applyTypeToSuggestions(['INT']);
+   }
+ | '<impala>OFFSET' ValueExpression_EDIT
  ;
 
 SearchCondition
@@ -2394,7 +2638,23 @@ IntegerOrUnbounded
  | 'UNBOUNDED'
  ;
 
-// Group by, window, order by, limit
+OptionalHavingClause
+ :
+ | 'HAVING' ValueExpression
+ ;
+
+OptionalHavingClause
+ : 'HAVING' 'CURSOR'
+   {
+     valueExpressionSuggest();
+     suggestAggregateFunctions();
+   }
+ | 'HAVING' ValueExpression_EDIT
+   {
+     suggestAggregateFunctions();
+   }
+ ;
+
 OptionalWindowClause
  :
  | '<hive>WINDOW' RegularOrBacktickedIdentifier '<hive>AS' WindowExpression

文件差異過大導致無法顯示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js


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

@@ -19,6 +19,7 @@ var prepareNewStatement = function () {
   commitLocations();
 
   delete parser.yy.latestTablePrimaries;
+  delete parser.yy.latestCommonTableExpressions;
   delete parser.yy.correlatedSubQuery;
   parser.yy.subQueries = [];
 
@@ -28,6 +29,10 @@ var prepareNewStatement = function () {
   };
 };
 
+var addCommonTableExpressions = function (identifiers) {
+  parser.yy.latestCommonTableExpressions = identifiers;
+};
+
 var popQueryState = function (subQuery) {
   linkTablePrimaries();
   commitLocations();
@@ -264,6 +269,23 @@ var prioritizeSuggestions = function () {
   } else {
     delete parser.yy.result.subQueries;
   }
+
+  if (typeof parser.yy.result.suggestTables !== 'undefined' && typeof parser.yy.latestCommonTableExpressions !== 'undefined') {
+    var ctes = [];
+    parser.yy.latestCommonTableExpressions.forEach(function (identifier) {
+      var cte = { name: identifier };
+      if (parser.yy.result.suggestTables.prependFrom) {
+        cte.prependFrom = true
+      }
+      if (parser.yy.result.suggestTables.prependQuestionMark) {
+        cte.prependQuestionMark = true;
+      }
+      ctes.push(cte);
+    });
+    if (ctes.length > 0) {
+      parser.yy.result.suggestCommonTableExpressions = ctes;
+    }
+  }
 };
 
 /**
@@ -587,7 +609,7 @@ var suggestFileFormats = function () {
 };
 
 var suggestDdlAndDmlKeywords = function () {
-  var keywords = ['ALTER', 'CREATE', 'DELETE', 'DESCRIBE', 'DROP', 'EXPLAIN', 'INSERT', 'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE'];
+  var keywords = ['ALTER', 'CREATE', 'DELETE', 'DESCRIBE', 'DROP', 'EXPLAIN', 'INSERT', 'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE', 'WITH'];
 
   if (isHive()) {
     keywords = keywords.concat(['ANALYZE', 'EXPORT', 'IMPORT', 'LOAD', 'MSCK', 'RESET']);

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

@@ -39,9 +39,10 @@
 
   // Keyword weights come from the parser
   var DEFAULT_WEIGHTS = {
-    COLUMN: 500,
-    SAMPLE: 400,
-    IDENTIFIER: 300,
+    COLUMN: 600,
+    SAMPLE: 500,
+    IDENTIFIER: 400,
+    CTE: 300,
     TABLE: 200,
     DATABASE: 100,
     HDFS: 1,
@@ -71,6 +72,16 @@
       });
     }
 
+    if (parseResult.suggestCommonTableExpressions) {
+      parseResult.suggestCommonTableExpressions.forEach(function (expression) {
+        var prefix = expression.prependQuestionMark ? '? ' : '';
+        if (expression.prependFrom) {
+          prefix += parseResult.lowerCase ? 'from ' : 'FROM ';
+        }
+        completions.push({value: prefix + expression.name, meta: 'CTE', weight: DEFAULT_WEIGHTS.CTE });
+      });
+    }
+
     var database = parseResult.useDatabase || self.snippet.database();
 
     var colRefDeferral = $.Deferred();

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

@@ -43,7 +43,7 @@ define([
       assertAutoComplete({
         beforeCursor: ';;',
         afterCursor: '',
-        containsKeywords: ['SELECT'],
+        containsKeywords: ['SELECT', 'WITH'],
         expectedResult: {
           lowerCase: false
         }
@@ -205,7 +205,7 @@ define([
             lowerCase: false,
             suggestKeywords: ['ALTER', 'COMPUTE', 'CREATE', 'DELETE', 'DESCRIBE',
               'DROP', 'EXPLAIN', 'INSERT', 'INVALIDATE', 'LOAD', 'REFRESH',
-              'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE']
+              'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE', 'WITH']
           }
         });
       });
@@ -221,7 +221,7 @@ define([
             lowerCase: false,
             suggestKeywords: ['ALTER', 'ANALYZE', 'CREATE', 'DELETE', 'DESCRIBE',
               'DROP', 'EXPLAIN', 'EXPORT', 'IMPORT', 'INSERT', 'LOAD', 'MSCK',
-              'RESET', 'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE']
+              'RESET', 'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE', 'WITH']
           }
         });
       });

+ 14 - 1
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecCreate.js

@@ -1377,7 +1377,7 @@ define([
       });
 
       describe('Hive specific', function () {
-        it('should handle "CREATE VIEW IF NOT EXISTS db.foo (baa COMMENT \'foo bar\', ble) COMMENT \'baa\' TBLPROPERTIES ("boo.baa"="buu") AS SELECT a, | FROM tableOne"', function () {
+        it('should suggest columns for "CREATE VIEW IF NOT EXISTS db.foo (baa COMMENT \'foo bar\', ble) COMMENT \'baa\' TBLPROPERTIES ("boo.baa"="buu") AS SELECT a, | FROM tableOne"', function () {
           assertAutoComplete({
             beforeCursor: 'CREATE VIEW IF NOT EXISTS db.foo (baa COMMENT \'foo bar\', ble) COMMENT \'baa\' TBLPROPERTIES ("boo.baa"="buu") AS SELECT a, ',
             afterCursor: ' FROM tableOne',
@@ -1394,6 +1394,19 @@ define([
           });
         });
 
+        it('should handle "CREATE VIEW v1 AS WITH q1 AS ( SELECT key FROM src WHERE key = \'5\') SELECT * FROM q1;|', function () {
+          assertAutoComplete({
+            beforeCursor: 'CREATE VIEW v1 AS WITH q1 AS ( SELECT key FROM src WHERE key = \'5\') SELECT * FROM q1;',
+            afterCursor: '',
+            dialect: 'hive',
+            hasLocations:true,
+            containsKeywords: ['SELECT', 'WITH'],
+            expectedResult: {
+              lowerCase: false
+            }
+          });
+        });
+
         it('should suggest keywords for "CREATE VIEW IF NOT EXISTS boo |"', function () {
           assertAutoComplete({
             beforeCursor: 'CREATE VIEW IF NOT EXISTS boo ',

+ 537 - 197
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecSelect.js

@@ -102,7 +102,7 @@ define([
         hasLocations: true,
         expectedResult: {
           lowerCase: false,
-          suggestKeywords: ['WHERE', 'GROUP BY', 'ORDER BY', 'LIMIT', 'FULL JOIN', 'FULL OUTER JOIN', 'INNER JOIN', 'JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
+          suggestKeywords: ['WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'UNION', 'FULL JOIN', 'FULL OUTER JOIN', 'INNER JOIN', 'JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
         }
       });
     });
@@ -2551,7 +2551,7 @@ define([
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['WHERE', 'GROUP BY', 'WINDOW', 'ORDER BY', 'LIMIT', 'CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
+            suggestKeywords: ['WHERE', 'GROUP BY', 'HAVING', 'WINDOW', 'ORDER BY', 'CLUSTER BY', 'DISTRIBUTE BY', 'SORT BY', 'LIMIT', 'UNION', 'CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
           }
         });
       });
@@ -2564,7 +2564,7 @@ define([
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['WHERE', 'GROUP BY', 'WINDOW', 'ORDER BY', 'LIMIT', 'CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
+            suggestKeywords: ['WHERE', 'GROUP BY', 'HAVING', 'WINDOW', 'ORDER BY', 'CLUSTER BY', 'DISTRIBUTE BY', 'SORT BY', 'LIMIT', 'UNION', 'CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
           }
         });
       });
@@ -2601,9 +2601,9 @@ define([
           afterCursor: '',
           dialect: 'hive',
           hasLocations: true,
+          containsKeywords: ['WHERE', 'HAVING'],
           expectedResult: {
-            lowerCase: false,
-            suggestKeywords: ['WHERE', 'GROUP BY', 'WINDOW', 'ORDER BY', 'LIMIT', 'CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
+            lowerCase: false
           }
         });
       });
@@ -3134,7 +3134,7 @@ define([
           dialect: 'impala',
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['WHERE', 'GROUP BY', 'ORDER BY', 'LIMIT', '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'],
+            suggestKeywords: ['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}, table: 'testTableA'},
               {type: 'table', location: { first_line: 1, last_line: 1, first_column: 31, last_column: 41}, table: 'testTableB'}
@@ -4238,7 +4238,7 @@ define([
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['GROUP BY', 'ORDER BY', 'LIMIT', '<', '<=', '<>', '=', '>', '>=', 'AND', 'BETWEEN', 'IN', 'IS NOT NULL', 'IS NULL', 'NOT BETWEEN', 'NOT IN', 'OR']
+            suggestKeywords: ['GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'UNION', '<', '<=', '<>', '=', '>', '>=', 'AND', 'BETWEEN', 'IN', 'IS NOT NULL', 'IS NULL', 'NOT BETWEEN', 'NOT IN', 'OR']
           }
         });
       });
@@ -4251,7 +4251,7 @@ define([
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['GROUP BY', 'ORDER BY', 'LIMIT', '<', '<=', '<>', '=', '>', '>=', 'AND', 'BETWEEN', 'IN', 'IS NOT NULL', 'IS NULL', 'NOT BETWEEN', 'NOT IN', 'OR']
+            suggestKeywords: ['GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'UNION', '<', '<=', '<>', '=', '>', '>=', 'AND', 'BETWEEN', 'IN', 'IS NOT NULL', 'IS NULL', 'NOT BETWEEN', 'NOT IN', 'OR']
           }
         });
       });
@@ -4451,7 +4451,7 @@ define([
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
-              suggestKeywords: ['GROUP BY', 'WINDOW', 'ORDER BY', 'LIMIT', '<', '<=', '<=>', '<>', '=', '>', '>=', 'AND', 'BETWEEN',  'IN', 'IS NOT NULL', 'IS NULL',  'NOT BETWEEN', 'NOT IN', 'OR']
+              suggestKeywords: ['GROUP BY', 'HAVING', 'WINDOW', 'ORDER BY', 'CLUSTER BY', 'DISTRIBUTE BY', 'SORT BY', 'LIMIT', 'UNION', '<', '<=', '<=>', '<>', '=', '>', '>=', 'AND', 'BETWEEN',  'IN', 'IS NOT NULL', 'IS NULL',  'NOT BETWEEN', 'NOT IN', 'OR']
             }
           });
         });
@@ -4666,80 +4666,259 @@ define([
         });
       });
 
-      describe('ORDER BY Clause', function () {
-        it('should suggest keywords for "SELECT * FROM testTable ORDER |"', function () {
-          assertAutoComplete({
-            beforeCursor: 'SELECT * FROM testTable ORDER ',
-            afterCursor: '',
-            hasLocations: true,
-            expectedResult: {
-              lowerCase: false,
-              suggestKeywords: ['BY']
-            }
-          });
+      it('should suggest columns for "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.|"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: { table: 'testTable1' }
+          }
         });
+      });
 
-        it('should suggest columns for "SELECT * FROM testTable ORDER BY |"', function() {
-          assertAutoComplete({
-            beforeCursor: 'SELECT * FROM testTable ORDER BY ',
-            afterCursor: '',
-            hasLocations: true,
-            expectedResult: {
-              lowerCase: false,
-              suggestAnalyticFunctions: true,
-              suggestColumns: { table: 'testTable' }
-            }
-          });
+      it('should suggest columns for "SELECT b.| FROM dbOne.foo f JOIN dbOne.bar b"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT b.',
+          afterCursor: ' FROM dbOne.foo f JOIN dbOne.bar b',
+          hasLocations: true,
+          dialect: 'impala',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['*'],
+            suggestColumns: { table: 'bar', database: 'dbOne' }
+          }
         });
+      });
 
-        it('should suggest columns for "SELECT * FROM database_two.testTable ORDER BY |"', function() {
-          assertAutoComplete({
-            beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY ',
-            afterCursor: '',
-            hasLocations: true,
-            expectedResult: {
-              lowerCase: false,
-              suggestAnalyticFunctions: true,
-              suggestColumns: { database: 'database_two', table: 'testTable' }
-            }
-          });
+      it('should suggest columns for "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM database_two.testTable1 t1 JOIN testTable2 t2 ON t1.|"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM database_two.testTable1 t1 JOIN testTable2 t2 ON t1.',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: { table: 'testTable1', database: 'database_two' }
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT testTable.| FROM testTable"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT testTable.',
+          afterCursor: ' FROM testTable',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['*'],
+            suggestColumns: { table: 'testTable' }
+          }
+        });
+      });
+
+      it('should suggest columns "SELECT tt.| FROM testTable tt"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT tt.',
+          afterCursor: ' FROM testTable tt',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['*'],
+            suggestColumns: { table: 'testTable' }
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT tt.| FROM database_two.testTable tt"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT tt.',
+          afterCursor: ' FROM database_two.testTable tt',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['*'],
+            suggestColumns: { table: 'testTable', database: 'database_two' }
+          }
         });
+      });
 
+      it('should suggest columns for "SELECT tta.| FROM testTableA tta, testTableB ttb"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT tta.',
+          afterCursor: ' FROM testTableA tta, testTableB ttb',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['*'],
+            suggestColumns: { table: 'testTableA' }
+          }
+        });
+        assertAutoComplete({
+          beforeCursor: 'SELECT ttb.',
+          afterCursor: ' FROM testTableA tta, testTableB ttb',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['*'],
+            suggestColumns: { table: 'testTableB' }
+          }
+        });
+      });
+    });
+
+    describe('ORDER BY Clause', function () {
+      it('should suggest keywords for "SELECT * FROM testTable ORDER |"', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable ORDER ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['BY']
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT * FROM testTable ORDER BY |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable ORDER BY ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestAnalyticFunctions: true,
+            suggestColumns: { table: 'testTable' }
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT * FROM database_two.testTable ORDER BY |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestAnalyticFunctions: true,
+            suggestColumns: { database: 'database_two', table: 'testTable' }
+          }
+        });
+      });
+
+      it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo ',
+          afterCursor: '',
+          dialect: 'generic',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['ASC', 'DESC', 'LIMIT', 'UNION']
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT * FROM database_two.testTable ORDER BY foo, |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo, ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestAnalyticFunctions: true,
+            suggestColumns: { database: 'database_two', table: 'testTable' }
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT * FROM database_two.testTable ORDER BY foo ASC, |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo ASC, ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestAnalyticFunctions: true,
+            suggestColumns: { database: 'database_two', table: 'testTable' }
+          }
+        });
+      });
+
+      it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ',
+          afterCursor: '',
+          dialect: 'generic',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['ASC', 'DESC', 'LIMIT', 'UNION']
+          }
+        });
+      });
+
+      it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar |, bla"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ',
+          afterCursor: ', bla',
+          containsKeywords: ['ASC', 'DESC'],
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false
+          }
+        });
+      });
+
+      it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo LIMIT 10 |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo LIMIT 10 ',
+          afterCursor: '',
+          hasLocations: true,
+          dialect: 'generic',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['UNION']
+          }
+        });
+      });
+
+      describe('Impala specific', function () {
         it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo |"', function() {
           assertAutoComplete({
             beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo ',
             afterCursor: '',
-            dialect: 'generic',
+            dialect: 'impala',
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
-              suggestKeywords: ['ASC', 'DESC', 'LIMIT']
+              suggestKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST', 'LIMIT', 'OFFSET', 'UNION']
             }
           });
         });
 
-        it('should suggest columns for "SELECT * FROM database_two.testTable ORDER BY foo, |"', function() {
+        it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY 1 |"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo, ',
+            beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY 1 ',
             afterCursor: '',
+            dialect: 'impala',
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
-              suggestAnalyticFunctions: true,
-              suggestColumns: { database: 'database_two', table: 'testTable' }
+              suggestKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST', 'LIMIT', 'OFFSET', 'UNION']
             }
           });
         });
 
-        it('should suggest columns for "SELECT * FROM database_two.testTable ORDER BY foo ASC, |"', function() {
+        it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo NULLS |"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo ASC, ',
+            beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo NULLS ',
             afterCursor: '',
+            dialect: 'impala',
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
-              suggestAnalyticFunctions: true,
-              suggestColumns: { database: 'database_two', table: 'testTable' }
+              suggestKeywords: ['FIRST', 'LAST']
             }
           });
         });
@@ -4748,282 +4927,443 @@ define([
           assertAutoComplete({
             beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ',
             afterCursor: '',
-            dialect: 'generic',
+            dialect: 'impala',
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
-              suggestKeywords: ['ASC', 'DESC', 'LIMIT']
+              suggestKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST', 'LIMIT', 'OFFSET', 'UNION']
             }
           });
-        });
 
-        it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar |, bla"', function() {
-          assertAutoComplete({
-            beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ',
-            afterCursor: ', bla',
-            containsKeywords: ['ASC', 'DESC'],
-            hasLocations: true,
-            expectedResult: {
-              lowerCase: false
-            }
-          });
-        });
-
-        describe('Impala specific', function () {
-          it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo |"', function() {
+          it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar |, bla"', function() {
             assertAutoComplete({
-              beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo ',
-              afterCursor: '',
+              beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ',
+              afterCursor: ', bla',
               dialect: 'impala',
+              containsKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST'],
               hasLocations: true,
               expectedResult: {
-                lowerCase: false,
-                suggestKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST', 'LIMIT']
+                lowerCase: false
               }
             });
           });
 
-          it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY 1 |"', function() {
+          it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ASC NULLS |, bla"', function() {
             assertAutoComplete({
-              beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY 1 ',
-              afterCursor: '',
+              beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ASC NULLS ',
+              afterCursor: ', bla',
               dialect: 'impala',
+              containsKeywords: ['FIRST', 'LAST'],
               hasLocations: true,
               expectedResult: {
-                lowerCase: false,
-                suggestKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST', 'LIMIT']
+                lowerCase: false
               }
             });
           });
+        });
+      });
+    });
 
-          it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo NULLS |"', function() {
-            assertAutoComplete({
-              beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo NULLS ',
-              afterCursor: '',
-              dialect: 'impala',
-              hasLocations: true,
-              expectedResult: {
-                lowerCase: false,
-                suggestKeywords: ['FIRST', 'LAST']
-              }
-            });
+    describe('CLUSTER BY, DISTRIBUTE BY and SORT BY', function () {
+      describe('Hive specific', function () {
+        it('should suggest keywords for "SELECT * FROM t1 ORDER BY foo ASC |', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT * FROM t1 ORDER BY foo ASC ',
+            afterCursor: '',
+            dialect: 'hive',
+            containsKeywords: ['CLUSTER BY', 'DISTRIBUTE BY', 'SORT BY', 'LIMIT'],
+            hasLocations: true,
+            expectedResult: {
+              lowerCase: false
+            }
           });
+        });
 
-          it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar |"', function() {
-            assertAutoComplete({
-              beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ',
-              afterCursor: '',
-              dialect: 'impala',
-              hasLocations: true,
-              expectedResult: {
-                lowerCase: false,
-                suggestKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST', 'LIMIT']
-              }
-            });
-
-            it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar |, bla"', function() {
-              assertAutoComplete({
-                beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ',
-                afterCursor: ', bla',
-                dialect: 'impala',
-                containsKeywords: ['ASC', 'DESC', 'NULLS FIRST', 'NULLS LAST'],
-                hasLocations: true,
-                expectedResult: {
-                  lowerCase: false
-                }
-              });
-            });
-
-            it('should suggest keywords for "SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ASC NULLS |, bla"', function() {
-              assertAutoComplete({
-                beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY foo DESC, bar ASC NULLS ',
-                afterCursor: ', bla',
-                dialect: 'impala',
-                containsKeywords: ['FIRST', 'LAST'],
-                hasLocations: true,
-                expectedResult: {
-                  lowerCase: false
-                }
-              });
-            });
+        it('should suggest keywords for "SELECT * FROM t1 ORDER BY foo ASC CLUSTER BY a |', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT * FROM t1 ORDER BY foo ASC CLUSTER BY a ',
+            afterCursor: '',
+            dialect: 'hive',
+            containsKeywords: ['LIMIT'],
+            doesNotContainKeywords: ['DISTRIBUTE BY', 'SORT BY'],
+            hasLocations: true,
+            expectedResult: {
+              lowerCase: false
+            }
           });
         });
-      });
-
 
-      describe('GROUP BY Clause', function () {
-        it('should suggest keywords for "SELECT * FROM testTable GROUP |"', function () {
+        it('should suggest keywords for "SELECT * FROM t1 ORDER BY foo ASC DISTRIBUTE BY a |', function () {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM testTable GROUP ',
+            beforeCursor: 'SELECT * FROM t1 ORDER BY foo ASC DISTRIBUTE BY a ',
             afterCursor: '',
+            dialect: 'hive',
+            containsKeywords: ['SORT BY', 'LIMIT'],
+            doesNotContainKeywords: ['CLUSTER BY', 'DISTRIBUTE BY'],
             hasLocations: true,
             expectedResult: {
-              lowerCase: false,
-              suggestKeywords: ['BY']
+              lowerCase: false
             }
           });
         });
 
-        it('should suggest identifiers for "SELECT * FROM testTableA tta, testTableB GROUP BY |"', function() {
+        it('should suggest keywords for "SELECT * FROM t1 ORDER BY foo ASC SORT BY a DESC |', function () {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY ',
+            beforeCursor: 'SELECT * FROM t1 ORDER BY foo ASC SORT BY a DESC ',
             afterCursor: '',
-            dialect: 'generic',
+            dialect: 'hive',
+            containsKeywords: ['LIMIT'],
+            doesNotContainKeywords: ['CLUSTER BY', 'DISTRIBUTE BY', 'NULLS FIRST', 'SORT BY'],
             hasLocations: true,
-            expectedResult : {
-              lowerCase: false,
-              suggestIdentifiers: [{ name: 'tta.', type: 'alias' }, { name: 'testTableB.', type: 'table' }]
+            expectedResult: {
+              lowerCase: false
             }
           });
         });
+      });
+    });
+
+    describe('GROUP BY Clause', function () {
+      it('should suggest keywords for "SELECT * FROM testTable GROUP |"', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable GROUP ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['BY']
+          }
+        });
+      });
 
-        it('should suggest identifier for "SELECT * FROM testTableA tta, testTableB GROUP BY bla, |"', function() {
+      it('should suggest identifiers for "SELECT * FROM testTableA tta, testTableB GROUP BY |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY ',
+          afterCursor: '',
+          dialect: 'generic',
+          hasLocations: true,
+          expectedResult : {
+            lowerCase: false,
+            suggestIdentifiers: [{ name: 'tta.', type: 'alias' }, { name: 'testTableB.', type: 'table' }]
+          }
+        });
+      });
+
+      it('should suggest identifier for "SELECT * FROM testTableA tta, testTableB GROUP BY bla, |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY bla, ',
+          afterCursor: '',
+          dialect: 'generic',
+          hasLocations: true,
+          expectedResult : {
+            lowerCase: false,
+            suggestIdentifiers: [{ name: 'tta.', type: 'alias' }, { name: 'testTableB.', type: 'table' }]
+          }
+        });
+      });
+
+      it('should suggest identifier for "SELECT * FROM testTableA tta, testTableB GROUP BY bla, |, foo"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY bla, ',
+          afterCursor: ', foo',
+          dialect: 'generic',
+          hasLocations: true,
+          expectedResult : {
+            lowerCase: false,
+            suggestIdentifiers: [{ name: 'tta.', type: 'alias' }, { name: 'testTableB.', type: 'table' }]
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT * FROM testTable GROUP BY |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable GROUP BY ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: { table: 'testTable' }
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT * FROM database_two.testTable GROUP BY |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM database_two.testTable GROUP BY ',
+          afterCursor: '',
+          hasLocations: true,
+          expectedResult: {
+            lowerCase: false,
+            suggestColumns: { database: 'database_two', table: 'testTable' }
+          }
+        });
+      });
+    });
+
+    describe('HAVING clause', function () {
+      describe('Hive specific', function () {
+        it('should suggest identifiers for "SELECT COUNT(*) AS boo FROM testTable GROUP BY baa HAVING |"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY bla, ',
+            beforeCursor: 'SELECT COUNT(*) AS boo FROM testTable GROUP BY baa HAVING ',
             afterCursor: '',
-            dialect: 'generic',
+            dialect: 'hive',
             hasLocations: true,
-            expectedResult : {
+            expectedResult: {
               lowerCase: false,
-              suggestIdentifiers: [{ name: 'tta.', type: 'alias' }, { name: 'testTableB.', type: 'table' }]
+              suggestFunctions: {},
+              suggestAggregateFunctions: true,
+              suggestColumns: { table: 'testTable' }
             }
           });
         });
+      });
 
-        it('should suggest identifier for "SELECT * FROM testTableA tta, testTableB GROUP BY bla, |, foo"', function() {
+      describe('Impala specific', function () {
+        it('should suggest identifiers for "SELECT COUNT(*) AS boo FROM testTable GROUP BY baa HAVING |"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY bla, ',
-            afterCursor: ', foo',
-            dialect: 'generic',
+            beforeCursor: 'SELECT COUNT(*) AS boo FROM testTable GROUP BY baa HAVING ',
+            afterCursor: '',
+            dialect: 'impala',
             hasLocations: true,
-            expectedResult : {
+            expectedResult: {
               lowerCase: false,
-              suggestIdentifiers: [{ name: 'tta.', type: 'alias' }, { name: 'testTableB.', type: 'table' }]
+              suggestFunctions: {},
+              suggestAggregateFunctions: true,
+              suggestColumns: { table: 'testTable' }
             }
           });
         });
+      });
+    });
 
-        it('should suggest columns for "SELECT * FROM testTable GROUP BY |"', function() {
+    describe('OFFSET clause', function () {
+      describe('Impala specific', function () {
+        it('should handle "SELECT COUNT(*) AS boo FROM testTable GROUP BY baa OFFSET 1;|"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM testTable GROUP BY ',
+            beforeCursor: 'SELECT COUNT(*) AS boo FROM testTable GROUP BY baa OFFSET 1; ',
             afterCursor: '',
+            dialect: 'impala',
             hasLocations: true,
+            noErrors: true,
+            containsKeywords: ['SELECT'],
             expectedResult: {
-              lowerCase: false,
-              suggestColumns: { table: 'testTable' }
+              lowerCase: false
             }
           });
         });
 
-        it('should suggest columns for "SELECT * FROM database_two.testTable GROUP BY |"', function() {
+        it('should suggest keywords for "SELECT COUNT(*) AS boo FROM testTable GROUP BY baa LIMIT 10 |"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT * FROM database_two.testTable GROUP BY ',
+            beforeCursor: 'SELECT COUNT(*) AS boo FROM testTable GROUP BY baa LIMIT 10 ',
             afterCursor: '',
+            dialect: 'impala',
             hasLocations: true,
+            containsKeywords: ['OFFSET'],
             expectedResult: {
-              lowerCase: false,
-              suggestColumns: { database: 'database_two', table: 'testTable' }
+              lowerCase: false
             }
           });
         });
       });
+    });
 
-      it('should suggest columns for "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.|"', function() {
+    describe('UNION clause', function () {
+      // TODO: Fix locations
+      xit('should handle "SELECT * FROM (SELECT x FROM few_ints UNION ALL SELECT x FROM few_ints) AS t1 ORDER BY x;|', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.',
+          beforeCursor: 'SELECT * FROM (SELECT x FROM few_ints UNION ALL SELECT x FROM few_ints) AS t1 ORDER BY x;',
           afterCursor: '',
           hasLocations: true,
+          noErrors: true,
+          containsKeywords: ['SELECT'],
           expectedResult: {
-            lowerCase: false,
-            suggestColumns: { table: 'testTable1' }
+            lowerCase: false
           }
         });
       });
 
-      it('should suggest columns for "SELECT b.| FROM dbOne.foo f JOIN dbOne.bar b"', function() {
+      it('should handle "SELECT key FROM (SELECT key FROM src ORDER BY key LIMIT 10)subq1 UNION SELECT key FROM (SELECT key FROM src1 ORDER BY key LIMIT 10)subq2;|', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT b.',
-          afterCursor: ' FROM dbOne.foo f JOIN dbOne.bar b',
+          beforeCursor: 'SELECT key FROM (SELECT key FROM src ORDER BY key LIMIT 10)subq1 UNION SELECT key FROM (SELECT key FROM src1 ORDER BY key LIMIT 10)subq2;',
+          afterCursor: '',
           hasLocations: true,
-          dialect: 'impala',
+          noErrors: true,
+          containsKeywords: ['SELECT'],
           expectedResult: {
-            lowerCase: false,
-            suggestKeywords: ['*'],
-            suggestColumns: { table: 'bar', database: 'dbOne' }
+            lowerCase: false
           }
         });
       });
 
-      it('should suggest columns for "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM database_two.testTable1 t1 JOIN testTable2 t2 ON t1.|"', function() {
+      it('should handle "SELECT * FROM t1 UNION DISTINCT SELECT * FROM t2;|', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM database_two.testTable1 t1 JOIN testTable2 t2 ON t1.',
+          beforeCursor: 'SELECT * FROM t1 UNION DISTINCT SELECT * FROM t2;',
+          afterCursor: '',
+          hasLocations: true,
+          noErrors: true,
+          containsKeywords: ['SELECT'],
+          expectedResult: {
+            lowerCase: false
+          }
+        });
+      });
+
+      it('should handle "SELECT * FROM t1 UNION SELECT * FROM t2;|', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM t1 UNION SELECT * FROM t2;',
+          afterCursor: '',
+          hasLocations: true,
+          noErrors: true,
+          containsKeywords: ['SELECT'],
+          expectedResult: {
+            lowerCase: false
+          }
+        });
+      });
+
+      it('should suggest keywords for "SELECT * FROM t1 UNION |', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM t1 UNION ',
           afterCursor: '',
           hasLocations: true,
+          noErrors: true,
           expectedResult: {
             lowerCase: false,
-            suggestColumns: { table: 'testTable1', database: 'database_two' }
+            suggestKeywords: ['ALL', 'DISTINCT', 'SELECT']
           }
         });
       });
 
-      it('should suggest columns for "SELECT testTable.| FROM testTable"', function() {
+      it('should suggest tables for "SELECT * FROM t1 UNION ALL SELECT |', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT testTable.',
-          afterCursor: ' FROM testTable',
+          beforeCursor: 'SELECT * FROM t1 UNION ALL SELECT ',
+          afterCursor: '',
           hasLocations: true,
+          noErrors: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['*'],
-            suggestColumns: { table: 'testTable' }
+            suggestKeywords: ['*', 'ALL', 'DISTINCT'],
+            suggestAggregateFunctions: true,
+            suggestAnalyticFunctions: true,
+            suggestFunctions: {},
+            suggestTables: {
+              prependQuestionMark: true,
+              prependFrom: true
+            },
+            suggestDatabases: {
+              prependQuestionMark: true,
+              prependFrom: true,
+              appendDot: true
+            }
           }
         });
       });
+    });
 
-      it('should suggest columns "SELECT tt.| FROM testTable tt"', function() {
+    describe('WITH clause', function () {
+      it('should handle "WITH q1 AS ( SELECT key FROM src WHERE something) SELECT * FROM q1;|', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT tt.',
-          afterCursor: ' FROM testTable tt',
+          beforeCursor: 'WITH q1 AS ( SELECT key FROM src WHERE something) SELECT * FROM q1;',
+          afterCursor: '',
           hasLocations: true,
+          noErrors: true,
+          containsKeywords: ['SELECT'],
+          expectedResult: {
+            lowerCase: false
+          }
+        });
+      });
+
+      it('should handle "WITH q1 AS (SELECT * FROM src WHERE something), q2 AS (SELECT * FROM src s2 WHERE something) SELECT * FROM q1 UNION ALL SELECT * FROM q2;|', function () {
+        assertAutoComplete({
+          beforeCursor: 'WITH q1 AS (SELECT * FROM src WHERE something), q2 AS (SELECT * FROM src s2 WHERE something) SELECT * FROM q1 UNION ALL SELECT * FROM q2;',
+          afterCursor: '',
+          hasLocations: true,
+          noErrors: true,
+          containsKeywords: ['SELECT'],
+          expectedResult: {
+            lowerCase: false
+          }
+        });
+      });
+
+      it('should handle "WITH t1 AS (SELECT 1) (WITH t2 AS (SELECT 2) SELECT * FROM t2) UNION ALL SELECT * FROM t1;|', function () {
+        assertAutoComplete({
+          beforeCursor: 'WITH t1 AS (SELECT 1) (WITH t2 AS (SELECT 2) SELECT * FROM t2) UNION ALL SELECT * FROM t1;',
+          afterCursor: '',
+          hasLocations: true,
+          noErrors: true,
+          containsKeywords: ['SELECT'],
+          expectedResult: {
+            lowerCase: false
+          }
+        });
+      });
+
+      it('should suggest keywords for "WITH t1 |', function () {
+        assertAutoComplete({
+          beforeCursor: 'WITH t1 ',
+          afterCursor: '',
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['*'],
-            suggestColumns: { table: 'testTable' }
+            suggestKeywords: ['AS']
           }
         });
       });
 
-      it('should suggest columns for "SELECT tt.| FROM database_two.testTable tt"', function() {
+      it('should suggest keywords for "WITH t1 AS (|', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT tt.',
-          afterCursor: ' FROM database_two.testTable tt',
+          beforeCursor: 'WITH t1 AS (',
+          afterCursor: '',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['SELECT']
+          }
+        });
+      });
+
+      it('should suggest keywords for "WITH t1 AS (SELECT * FROM boo) |', function () {
+        assertAutoComplete({
+          beforeCursor: 'WITH t1 AS (SELECT * FROM boo) ',
+          afterCursor: '',
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['*'],
-            suggestColumns: { table: 'testTable', database: 'database_two' }
+            suggestKeywords: ['SELECT']
           }
         });
       });
 
-      it('should suggest columns for "SELECT tta.| FROM testTableA tta, testTableB ttb"', function() {
+      it('should suggest identifiers for "WITH t1 AS (SELECT * FROM FOO) SELECT |', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT tta.',
-          afterCursor: ' FROM testTableA tta, testTableB ttb',
+          beforeCursor: 'WITH t1 AS (SELECT * FROM FOO) SELECT ',
+          afterCursor: '',
           hasLocations: true,
+          noErrors: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['*'],
-            suggestColumns: { table: 'testTableA' }
+            suggestKeywords: ['*', 'ALL', 'DISTINCT'],
+            suggestAggregateFunctions: true,
+            suggestAnalyticFunctions: true,
+            suggestFunctions: {},
+            suggestTables: { prependQuestionMark: true, prependFrom: true },
+            suggestDatabases: { prependQuestionMark: true, prependFrom: true, appendDot:true },
+            suggestCommonTableExpressions: [{ name: 't1', prependFrom: true, prependQuestionMark:true }]
           }
         });
+      });
+
+      it('should suggest identifiers for "WITH t1 AS (SELECT * FROM FOO) SELECT * FROM |', function () {
         assertAutoComplete({
-          beforeCursor: 'SELECT ttb.',
-          afterCursor: ' FROM testTableA tta, testTableB ttb',
+          beforeCursor: 'WITH t1 AS (SELECT * FROM FOO) SELECT * FROM ',
+          afterCursor: '',
           hasLocations: true,
+          noErrors: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['*'],
-            suggestColumns: { table: 'testTableB' }
+            suggestTables: { },
+            suggestDatabases: { appendDot: true },
+            suggestCommonTableExpressions: [{ name: 't1' }]
           }
         });
       });
@@ -5329,7 +5669,7 @@ define([
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT OUTER', 'ON', 'RIGHT', 'RIGHT OUTER']
+            suggestKeywords: ['ON', 'FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER']
           }
         });
       });

+ 1 - 1
desktop/core/src/desktop/static/desktop/spec/autocompleterTestUtils.js

@@ -49,7 +49,7 @@ define([
               actualResponse.suggestKeywords = weightFreeKeywords;
             }
 
-            if (actualResponse.errors) {
+            if (typeof testDefinition.noErrors === 'undefined' && actualResponse.errors) {
               var allRecoverable = true;
               actualResponse.errors.forEach(function (error) {
                 allRecoverable = allRecoverable && error.recoverable;

+ 1 - 1
tools/jison/hue-jison.sh

@@ -17,7 +17,7 @@
 
 echo "Make sure you install jison first (npm install jison -g)"
 echo ""
-echo "Note: There's supposed to be one conflict when generating for token 'error' (reduce by rule: QuerySpecification_EDIT -> SELECT OptionalAllOrDistinct SelectList_EDIT)"
+echo "Note: There's supposed to be one conflict when generating for token 'error' (reduce by rule: SelectStatement_EDIT -> SELECT OptionalAllOrDistinct SelectList_EDIT)"
 echo ""
 echo "Generating parser..."
 

部分文件因文件數量過多而無法顯示