فهرست منبع

HUE-4732 [editor] Autocomplete should work inside parenthesized value expressions

Johan Ahlen 9 سال پیش
والد
کامیت
4f19b85

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

@@ -3066,13 +3066,13 @@ CountFunction
 CountFunction_EDIT
  : 'COUNT' '(' OptionalAllOrDistinct AnyCursor RightParenthesisOrError
    {
-     suggestColumns();
+     valueExpressionSuggest();
      if (!$3) {
-       if (isImpala()) {
-         suggestKeywords([{ value: '*', weight: 1000 }, 'ALL', 'DISTINCT']);
-       } else {
-         suggestKeywords([{ value: '*', weight: 1000 }, 'DISTINCT']);
+       var keywords = isImpala() ? [{ value: '*', weight: 1000 }, 'ALL', 'DISTINCT'] : [{ value: '*', weight: 1000 }, 'DISTINCT'];
+       if (parser.yy.result.suggestKeywords) {
+         keywords = parser.yy.result.suggestKeywords.concat(keywords);
        }
+       suggestKeywords(keywords);
      }
      $$ = { types: findReturnTypes($1) };
    }
@@ -3102,16 +3102,20 @@ OtherAggregateFunction
 OtherAggregateFunction_EDIT
  : OtherAggregateFunction_Type '(' OptionalAllOrDistinct AnyCursor RightParenthesisOrError
    {
-     suggestFunctions();
-     suggestColumns();
+     valueExpressionSuggest();
      if (!$3) {
+       var keywords = [];
        if ($1.toLowerCase() === 'group_concat') {
-         suggestKeywords(['ALL' ]);
+         keywords = ['ALL'];
        } else if (isImpala()) {
-         suggestKeywords(['ALL', 'DISTINCT']);
+         keywords = ['ALL', 'DISTINCT'];
        } else {
-         suggestKeywords(['DISTINCT']);
+         keywords = ['DISTINCT'];
+       }
+       if (parser.yy.result.suggestKeywords) {
+         keywords = parser.yy.result.suggestKeywords.concat(keywords);
        }
+       suggestKeywords(keywords);
      }
      applyArgumentTypesToSuggestions($1, 1);
      $$ = { types: findReturnTypes($1) };
@@ -3124,13 +3128,18 @@ OtherAggregateFunction_EDIT
  | OtherAggregateFunction_Type '(' OptionalAllOrDistinct ValueExpressionList_EDIT RightParenthesisOrError
    {
      if ($4.cursorAtStart && !$3) {
+       var keywords = [];
        if ($1.toLowerCase() === 'group_concat') {
-         suggestKeywords(['ALL' ]);
+         keywords = ['ALL'];
        } else if (isImpala()) {
-         suggestKeywords(['ALL', 'DISTINCT']);
+         keywords = ['ALL', 'DISTINCT'];
        } else {
-         suggestKeywords(['DISTINCT']);
+         keywords = ['DISTINCT'];
+       }
+       if (parser.yy.result.suggestKeywords) {
+         keywords = parser.yy.result.suggestKeywords.concat(keywords);
        }
+       suggestKeywords(keywords);
      }
      if (parser.yy.result.suggestFunctions && !parser.yy.result.suggestFunctions.types) {
        applyArgumentTypesToSuggestions($1, $4.position);
@@ -3261,11 +3270,11 @@ SumFunction_EDIT
      valueExpressionSuggest();
      applyArgumentTypesToSuggestions($1, 1);
      if (!$3) {
-       if (isImpala()) {
-         suggestKeywords(['ALL', 'DISTINCT']);
-       } else {
-         suggestKeywords(['DISTINCT']);
+       var keywords = isImpala() ? ['ALL', 'DISTINCT'] : ['DISTINCT'];
+       if (parser.yy.result.suggestKeywords) {
+         keywords = parser.yy.result.suggestKeywords.concat(keywords);
        }
+       suggestKeywords(keywords);
      }
      $$ = { types: findReturnTypes($1) };
    }

+ 101 - 68
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/sql_valueExpression.jison

@@ -60,7 +60,9 @@ ValueExpression_EDIT
    }
  | '-' ValueExpression_EDIT %prec NEGATION
    {
-     applyTypeToSuggestions('NUMBER')
+     if (!$2.typeSet) {
+       applyTypeToSuggestions('NUMBER');
+     }
      $$ = { types: [ 'NUMBER' ] };
    }
  | '-' 'PARTIAL_CURSOR' %prec NEGATION
@@ -99,11 +101,14 @@ ValueExpression
 
 ValueExpression_EDIT
  : 'EXISTS' TableSubQuery_EDIT                               -> { types: [ 'BOOLEAN' ] }
- | '(' ValueExpression_EDIT RightParenthesisOrError          -> $2
- | '(' AnyCursor RightParenthesisOrError
+ | '(' ValueExpression_EDIT RightParenthesisOrError
+   {
+     $$ = $2;
+   }
+ | '(' 'CURSOR' RightParenthesisOrError
    {
      valueExpressionSuggest();
-     $$ = { types: ['T'] };
+     $$ = { types: ['T'], typeSet: true };
    }
  ;
 
@@ -121,96 +126,112 @@ ValueExpression_EDIT
    {
      valueExpressionSuggest($3, $2);
      applyTypeToSuggestions($3.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true };
    }
  | 'CURSOR' '<' ValueExpression
    {
      valueExpressionSuggest($3, $2);
      applyTypeToSuggestions($3.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | 'CURSOR' '>' ValueExpression
    {
      valueExpressionSuggest($3, $2);
      applyTypeToSuggestions($3.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | 'CURSOR' 'COMPARISON_OPERATOR' ValueExpression
    {
      valueExpressionSuggest($3, $2);
      applyTypeToSuggestions($3.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression_EDIT '=' ValueExpression
    {
-     applyTypeToSuggestions($3.types);
-     addColRefIfExists($3);
+     if (!$1.typeSet) {
+       applyTypeToSuggestions($3.types);
+       addColRefIfExists($3);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  | ValueExpression_EDIT '<' ValueExpression
    {
-     applyTypeToSuggestions($3.types);
-     addColRefIfExists($3);
+     if (!$1.typeSet) {
+       applyTypeToSuggestions($3.types);
+       addColRefIfExists($3);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  | ValueExpression_EDIT '>' ValueExpression
    {
-     applyTypeToSuggestions($3.types);
-     addColRefIfExists($3);
+     if (!$1.typeSet) {
+       applyTypeToSuggestions($3.types);
+       addColRefIfExists($3);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  | ValueExpression_EDIT 'COMPARISON_OPERATOR' ValueExpression
    {
-     applyTypeToSuggestions($3.types);
-     addColRefIfExists($3);
+     if (!$1.typeSet) {
+       applyTypeToSuggestions($3.types);
+       addColRefIfExists($3);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  | ValueExpression '=' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest($1, $2);
      applyTypeToSuggestions($1.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression '<' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest($1, $2);
      applyTypeToSuggestions($1.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ] , typeSet: true };
    }
  | ValueExpression '>' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest($1, $2);
      applyTypeToSuggestions($1.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression 'COMPARISON_OPERATOR' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest($1, $2);
      applyTypeToSuggestions($1.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression '=' ValueExpression_EDIT
    {
-     applyTypeToSuggestions($1.types);
-     addColRefIfExists($1);
+     if (!$3.typeSet) {
+       applyTypeToSuggestions($1.types);
+       addColRefIfExists($1);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  | ValueExpression '<' ValueExpression_EDIT
    {
-     applyTypeToSuggestions($1.types);
-     addColRefIfExists($1);
+     if (!$3.typeSet) {
+       applyTypeToSuggestions($1.types);
+       addColRefIfExists($1);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  | ValueExpression '>' ValueExpression_EDIT
    {
-     applyTypeToSuggestions($1.types);
-     addColRefIfExists($1);
+     if (!$3.typeSet) {
+       applyTypeToSuggestions($1.types);
+       addColRefIfExists($1);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  | ValueExpression 'COMPARISON_OPERATOR' ValueExpression_EDIT
    {
-     applyTypeToSuggestions($1.types);
-     addColRefIfExists($1);
+     if (!$3.typeSet) {
+       applyTypeToSuggestions($1.types);
+       addColRefIfExists($1);
+     }
      $$ = { types: [ 'BOOLEAN' ] }
    }
  ;
@@ -235,7 +256,7 @@ ValueExpression_EDIT
      if ($4.cursorAtStart) {
        suggestKeywords(['SELECT']);
      }
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression 'IN' ValueExpressionInSecondPart_EDIT
    {
@@ -246,7 +267,7 @@ ValueExpression_EDIT
      if ($3.cursorAtStart) {
        suggestKeywords(['SELECT']);
      }
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression_EDIT 'NOT' 'IN' '(' ValueExpressionList RightParenthesisOrError  -> { types: [ 'BOOLEAN' ] }
  | ValueExpression_EDIT 'NOT' 'IN' '(' TableSubQueryInner RightParenthesisOrError   -> { types: [ 'BOOLEAN' ] }
@@ -270,21 +291,21 @@ ValueExpression
 ValueExpression_EDIT
  : ValueExpression_EDIT 'NOT' 'BETWEEN' ValueExpression 'BETWEEN_AND' ValueExpression
    {
-     if ($4.types[0] === $6.types[0]) {
+     if ($4.types[0] === $6.types[0] && !$1.typeSet) {
        applyTypeToSuggestions($4.types);
      }
      $$ = { types: [ 'BOOLEAN' ] };
    }
  | ValueExpression 'NOT' 'BETWEEN' ValueExpression_EDIT 'BETWEEN_AND' ValueExpression
    {
-     if ($1.types[0] === $6.types[0]) {
+     if ($1.types[0] === $6.types[0] && !$4.typeSet) {
        applyTypeToSuggestions($1.types);
      }
      $$ = { types: [ 'BOOLEAN' ] };
    }
  | ValueExpression 'NOT' 'BETWEEN' ValueExpression 'BETWEEN_AND' ValueExpression_EDIT
    {
-     if ($1.types[0] === $4.types[0]) {
+     if ($1.types[0] === $4.types[0] && !$6.typeSet) {
        applyTypeToSuggestions($1.types);
      }
      $$ = { types: [ 'BOOLEAN' ] };
@@ -292,7 +313,7 @@ ValueExpression_EDIT
  | ValueExpression 'NOT' 'BETWEEN' ValueExpression 'BETWEEN_AND' 'CURSOR'
    {
      valueExpressionSuggest($1, $5);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression 'NOT' 'BETWEEN' ValueExpression 'CURSOR'
    {
@@ -302,25 +323,25 @@ ValueExpression_EDIT
  | ValueExpression 'NOT' 'BETWEEN' 'CURSOR'
    {
      valueExpressionSuggest($1, $2 + ' ' + $3);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression_EDIT 'BETWEEN' ValueExpression 'BETWEEN_AND' ValueExpression
    {
-     if ($1.types[0] === $3.types[0]) {
+     if ($1.types[0] === $3.types[0] && !$1.typeSet) {
        applyTypeToSuggestions($1.types)
      }
      $$ = { types: [ 'BOOLEAN' ] };
    }
  | ValueExpression 'BETWEEN' ValueExpression_EDIT 'BETWEEN_AND' ValueExpression
    {
-     if ($1.types[0] === $3.types[0]) {
+     if ($1.types[0] === $3.types[0] && !$3.typeSet) {
        applyTypeToSuggestions($1.types)
      }
      $$ = { types: [ 'BOOLEAN' ] };
    }
  | ValueExpression 'BETWEEN' ValueExpression 'BETWEEN_AND' ValueExpression_EDIT
    {
-     if ($1.types[0] === $3.types[0]) {
+     if ($1.types[0] === $3.types[0] && !$5.typeSet) {
        applyTypeToSuggestions($1.types)
      }
      $$ = { types: [ 'BOOLEAN' ] };
@@ -329,7 +350,7 @@ ValueExpression_EDIT
    {
      valueExpressionSuggest($1, $4);
      applyTypeToSuggestions($1.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression 'BETWEEN' ValueExpression 'CURSOR'
    {
@@ -340,7 +361,7 @@ ValueExpression_EDIT
    {
      valueExpressionSuggest($1, $2);
      applyTypeToSuggestions($1.types);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true };
    }
  ;
 
@@ -365,7 +386,7 @@ ValueExpression_EDIT
  : 'CURSOR' 'OR' ValueExpression
    {
      valueExpressionSuggest(undefined, $2);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression_EDIT 'OR' ValueExpression
    {
@@ -375,7 +396,7 @@ ValueExpression_EDIT
  | ValueExpression 'OR' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest(undefined, $2);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression 'OR' ValueExpression_EDIT
    {
@@ -385,7 +406,7 @@ ValueExpression_EDIT
  | 'CURSOR' 'AND' ValueExpression
    {
      valueExpressionSuggest(undefined, $2);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true };
    }
  | ValueExpression_EDIT 'AND' ValueExpression
    {
@@ -395,7 +416,7 @@ ValueExpression_EDIT
  | ValueExpression 'AND' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest(undefined, $2);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true  };
    }
  | ValueExpression 'AND' ValueExpression_EDIT
    {
@@ -432,66 +453,78 @@ ValueExpression_EDIT
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions([ 'NUMBER' ]);
-     $$ = { types: [ 'NUMBER' ] };
+     $$ = { types: [ 'NUMBER' ], typeSet: true };
    }
  | 'CURSOR' 'ARITHMETIC_OPERATOR' ValueExpression
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions([ 'NUMBER' ]);
-     $$ = { types: [ 'NUMBER' ] };
+     $$ = { types: [ 'NUMBER' ], typeSet: true };
    }
  | ValueExpression_EDIT '-' ValueExpression
    {
-     applyTypeToSuggestions(['NUMBER']);
-     addColRefIfExists($3);
+     if (!$1.typeSet) {
+       applyTypeToSuggestions(['NUMBER']);
+       addColRefIfExists($3);
+     }
      $$ = { types: [ 'NUMBER' ] }
    }
  | ValueExpression_EDIT '*' ValueExpression
    {
-     applyTypeToSuggestions(['NUMBER']);
-     addColRefIfExists($3);
+     if (!$1.typeSet) {
+       applyTypeToSuggestions(['NUMBER']);
+       addColRefIfExists($3);
+     }
      $$ = { types: [ 'NUMBER' ] }
    }
  | ValueExpression_EDIT 'ARITHMETIC_OPERATOR' ValueExpression
    {
-     applyTypeToSuggestions(['NUMBER']);
-     addColRefIfExists($3);
+     if (!$1.typeSet) {
+       applyTypeToSuggestions(['NUMBER']);
+       addColRefIfExists($3);
+     }
      $$ = { types: [ 'NUMBER' ] }
    }
  | ValueExpression '-' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions(['NUMBER']);
-     $$ = { types: [ 'NUMBER' ] };
+     $$ = { types: [ 'NUMBER' ], typeSet: true };
    }
  | ValueExpression '*' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions(['NUMBER']);
-     $$ = { types: [ 'NUMBER' ] };
+     $$ = { types: [ 'NUMBER' ], typeSet: true };
    }
  | ValueExpression 'ARITHMETIC_OPERATOR' PartialBacktickedOrAnyCursor
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions(['NUMBER']);
-     $$ = { types: [ 'NUMBER' ] };
+     $$ = { types: [ 'NUMBER' ], typeSet: true };
    }
  | ValueExpression '-' ValueExpression_EDIT
    {
-     applyTypeToSuggestions(['NUMBER']);
-     addColRefIfExists($1);
+     if (!$3.typeSet) {
+       applyTypeToSuggestions(['NUMBER']);
+       addColRefIfExists($1);
+     }
      $$ = { types: [ 'NUMBER' ] };
    }
  | ValueExpression '*' ValueExpression_EDIT
    {
-     applyTypeToSuggestions(['NUMBER']);
-     addColRefIfExists($1);
+     if (!$3.typeSet) {
+       applyTypeToSuggestions(['NUMBER']);
+       addColRefIfExists($1);
+     }
      $$ = { types: [ 'NUMBER' ] };
    }
  | ValueExpression 'ARITHMETIC_OPERATOR' ValueExpression_EDIT
    {
-     applyTypeToSuggestions(['NUMBER']);
-     addColRefIfExists($1);
+     if (!$3.typeSet) {
+       applyTypeToSuggestions(['NUMBER']);
+       addColRefIfExists($1);
+     }
      $$ = { types: [ 'NUMBER' ] };
    }
  ;
@@ -534,25 +567,25 @@ ValueExpression_EDIT
    {
      valueExpressionSuggest(undefined, $2 + ' ' + $3);
      applyTypeToSuggestions([ 'STRING' ]);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true };
    }
  | 'CURSOR' 'LIKE' ValueExpression
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions([ 'STRING' ]);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true };
    }
  | 'CURSOR' 'RLIKE' ValueExpression
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions([ 'STRING' ]);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true };
    }
  | 'CURSOR' 'REGEXP' ValueExpression
    {
      valueExpressionSuggest(undefined, $2);
      applyTypeToSuggestions([ 'STRING' ]);
-     $$ = { types: [ 'BOOLEAN' ] };
+     $$ = { types: [ 'BOOLEAN' ], typeSet: true };
    }
  | ValueExpression 'LIKE' PartialBacktickedOrCursor
    {
@@ -587,13 +620,13 @@ ValueExpression_EDIT
    {
      valueExpressionSuggest();
      suggestKeywords(['WHEN']);
-     $$ = { types: [ 'T' ] };
+     $$ = { types: [ 'T' ], typeSet: true };
    }
  | 'CASE' ValueExpression CaseRightPart_EDIT         -> $3
  | 'CASE' ValueExpression 'CURSOR' EndOrError
    {
      suggestValueExpressionKeywords($2, ['WHEN']);
-     $$ = { types: [ 'T' ] };
+     $$ = { types: [ 'T' ], typeSet: true };
    }
  | 'CASE' ValueExpression_EDIT CaseRightPart         -> $3
  | 'CASE' ValueExpression_EDIT EndOrError            -> { types: [ 'T' ] }
@@ -654,7 +687,7 @@ CaseRightPart_EDIT
  | 'ELSE' 'CURSOR' EndOrError
    {
      valueExpressionSuggest();
-     $$ = { types: [ 'T' ] };
+     $$ = { types: [ 'T' ], typeSet: true };
    }
  | 'CURSOR' 'ELSE' ValueExpression EndOrError
    {

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 131 - 51
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js


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

@@ -1012,7 +1012,7 @@ parser.parseSql = function (beforeCursor, afterCursor, dialect, sqlFunctions, de
   var result;
   try {
     // Add |CURSOR| or |PARTIAL_CURSOR| to represent the different cursor states in the lexer
-    result = parser.parse(beforeCursor + (beforeCursor.length == 0 || /.*\s+$/.test(beforeCursor) ? ' \u2020 ' : '\u2021') + afterCursor);
+    result = parser.parse(beforeCursor + (beforeCursor.length == 0 || /[\s\(]$$/.test(beforeCursor) ? ' \u2020 ' : '\u2021') + afterCursor);
   } catch (err) {
     // On any error try to at least return any existing result
     if (typeof parser.yy.result === 'undefined') {

+ 93 - 51
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecSelect.js

@@ -798,8 +798,6 @@ define([
         });
       });
 
-
-
       it('should suggest tables for "SELECT | FROM tableA;"', function() {
         assertAutoComplete({
           beforeCursor: 'SELECT ',
@@ -832,6 +830,48 @@ define([
         });
       });
 
+      it('should suggest columns for "SELECT (bl|a AND boo FROM testWHERE"', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT (bl',
+          afterCursor: ' AND boo FROM testWHERE',
+          hasLocations: true,
+          containsKeywords: ['CASE'],
+          expectedResult: {
+            lowerCase: false,
+            suggestFunctions: {},
+            suggestColumns: { tables: [{ identifierChain: [{ name: 'testWHERE' }] }] }
+          }
+        });
+      });
+
+      // TODO: Parser can't handle multiple errors in a row, in this case 2 missing ')')
+      xit('should suggest columns for "SELECT ((| FROM testWHERE"', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT ((',
+          afterCursor: ' FROM testWHERE',
+          containsKeywords: ['CASE'],
+          expectedResult: {
+            lowerCase: false,
+            suggestFunctions: {},
+            suggestColumns: { tables: [{ identifierChain: [{ name: 'testWHERE' }] }] }
+          }
+        });
+      });
+
+      it('should suggest columns for "SELECT (bla| AND boo FROM testWHERE"', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT (bla',
+          afterCursor: ' AND boo FROM testWHERE',
+          hasLocations: true,
+          containsKeywords: ['CASE'],
+          expectedResult: {
+            lowerCase: false,
+            suggestFunctions: {},
+            suggestColumns: { tables: [{ identifierChain: [{ name: 'testWHERE' }] }] }
+          }
+        });
+      });
+
       it('should suggest columns for "SELECT | FROM testON"', function () {
         assertAutoComplete({
           beforeCursor: 'SELECT ',
@@ -1929,41 +1969,41 @@ define([
 
       it('should suggest columns for "SELECT <GeneralSetFunction>(|) FROM testTable"', function () {
         var aggregateFunctions = [
-          { name: 'APPX_MEDIAN', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'AVG', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'AVG', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'AVG', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'collect_set', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'COLLECT_LIST', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'COUNT', dialect: 'generic', suggestKeywords: ['*', 'DISTINCT'] },
-          { name: 'COUNT', dialect: 'hive', suggestKeywords: ['*', 'DISTINCT'] },
-          { name: 'COUNT', dialect: 'impala', suggestKeywords: ['*', 'ALL', 'DISTINCT'] },
-          { name: 'GROUP_CONCAT', dialect: 'impala', suggestKeywords: ['ALL'], types: ['STRING'] },
-          { name: 'stddev', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'STDDEV_POP', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'STDDEV_POP', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'STDDEV_POP', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'STDDEV_SAMP', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'STDDEV_SAMP', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'STDDEV_SAMP', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'SUM', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'sum', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'SUM', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'MAX', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'MAX', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'max', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'MIN', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'MIN', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'MIN', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'VARIANCE', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'variance_pop', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'VARIANCE_SAMP', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'VAR_POP', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'VAR_POP', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'VAR_POP', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] },
-          { name: 'var_samp', dialect: 'generic', suggestKeywords: ['DISTINCT'] },
-          { name: 'VAR_SAMP', dialect: 'hive', suggestKeywords: ['DISTINCT'] },
-          { name: 'VAR_SAMP', dialect: 'impala', suggestKeywords: ['ALL', 'DISTINCT'] }
+          { name: 'APPX_MEDIAN', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'AVG', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'AVG', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'AVG', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'collect_set', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'COLLECT_LIST', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'COUNT', dialect: 'generic', containsKeywords: ['*', 'DISTINCT'] },
+          { name: 'COUNT', dialect: 'hive', containsKeywords: ['*', 'DISTINCT'] },
+          { name: 'COUNT', dialect: 'impala', containsKeywords: ['*', 'ALL', 'DISTINCT'] },
+          { name: 'GROUP_CONCAT', dialect: 'impala', containsKeywords: ['ALL'], types: ['STRING'] },
+          { name: 'stddev', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'STDDEV_POP', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'STDDEV_POP', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'STDDEV_POP', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'STDDEV_SAMP', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'STDDEV_SAMP', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'STDDEV_SAMP', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'SUM', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'sum', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'SUM', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'MAX', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'MAX', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'max', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'MIN', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'MIN', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'MIN', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'VARIANCE', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'variance_pop', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'VARIANCE_SAMP', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'VAR_POP', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'VAR_POP', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'VAR_POP', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] },
+          { name: 'var_samp', dialect: 'generic', containsKeywords: ['DISTINCT'] },
+          { name: 'VAR_SAMP', dialect: 'hive', containsKeywords: ['DISTINCT'] },
+          { name: 'VAR_SAMP', dialect: 'impala', containsKeywords: ['ALL', 'DISTINCT'] }
         ];
         aggregateFunctions.forEach(function (aggregateFunction) {
           if (aggregateFunction.name === 'COUNT') {
@@ -1972,10 +2012,11 @@ define([
               afterCursor: ') FROM testTable',
               dialect: aggregateFunction.dialect,
               hasLocations: true,
+              containsKeywords: aggregateFunction.containsKeywords.concat(['*', 'CASE']),
               expectedResult: {
                 lowerCase: false,
-                suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }] }] },
-                suggestKeywords: aggregateFunction.suggestKeywords || ['*']
+                suggestFunctions: {},
+                suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }] }] }
               }
             });
           } else {
@@ -1984,14 +2025,12 @@ define([
               suggestFunctions: { types: aggregateFunction.types || ['T'] },
               suggestColumns: { types: aggregateFunction.types || ['T'], tables: [{ identifierChain: [{ name: 'testTable' }] }] }
             };
-            if (aggregateFunction.suggestKeywords) {
-              expectedResult.suggestKeywords = aggregateFunction.suggestKeywords
-            }
             assertAutoComplete({
               beforeCursor: 'SELECT ' + aggregateFunction.name + '(',
               afterCursor: ') FROM testTable',
               dialect: aggregateFunction.dialect,
               hasLocations: true,
+              containsKeywords: aggregateFunction.containsKeywords.concat(['CASE']),
               expectedResult: expectedResult
             });
           }
@@ -2011,11 +2050,12 @@ define([
             afterCursor: ',col) FROM testTable',
             dialect: binaryFunction.dialect,
             hasLocations: true,
+            containsKeywords: ['DISTINCT', 'CASE'],
+            doesNotContainKeywords: ['ALL'],
             expectedResult: {
               lowerCase: false,
               suggestFunctions: { types: ['T'] },
-              suggestColumns: { types: ['T'], tables: [{ identifierChain: [{ name: 'testTable' }] }] },
-              suggestKeywords: ['DISTINCT']
+              suggestColumns: { types: ['T'], tables: [{ identifierChain: [{ name: 'testTable' }] }] }
             }
           });
         })
@@ -2057,11 +2097,12 @@ define([
             afterCursor: ' FROM testTable',
             dialect: binaryFunction.dialect,
             hasLocations: true,
+            containsKeywords: ['CASE', 'DISTINCT'],
+            doesNotContainKeywords: ['ALL'],
             expectedResult: {
               lowerCase: false,
               suggestFunctions: { types: ['T'] },
-              suggestColumns: { types: ['T'], tables: [{ identifierChain: [{ name: 'testTable' }] }] },
-              suggestKeywords: ['DISTINCT']
+              suggestColumns: { types: ['T'], tables: [{ identifierChain: [{ name: 'testTable' }] }] }
             }
           });
         })
@@ -6201,18 +6242,19 @@ define([
           afterCursor: ' AND testTable1.testColumn1 = testTable2.testColumn3',
           containsKeywords: ['CASE'],
           expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable1' }] }, { identifierChain: [{ name: 'testTable2' }] }] },
-            suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'testTable2.', type: 'table' }],
-            suggestFunctions: {},
             locations: [
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 26, last_column: 36}, identifierChain: [{ name: 'testTable1' }]},
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 42, last_column: 52}, identifierChain: [{ name: 'testTable2' }]},
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 62, last_column: 84}, identifierChain: [{ name: 'testTable1' }, { name: 'testColumn1'}] },
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 87, last_column: 109}, identifierChain: [{ name: 'testTable2' }, { name: 'testColumn3'}] }
-            ]
+            ],
+            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable1' }] }, { identifierChain: [{ name: 'testTable2' }] }] },
+            suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'testTable2.', type: 'table' }],
+            suggestFunctions: {},
+            lowerCase: false
           }
         });
+
       });
 
     it('should suggest columns for "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable2.|"', function() {

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است