Przeglądaj źródła

HUE-8856 [autocomplete] Add initial set of tests for the generic SQL autocompleter

Johan Ahlen 6 lat temu
rodzic
commit
34b68a36d5

Plik diff jest za duży
+ 98 - 101
desktop/core/src/desktop/js/parse/genericAutocompleteParser.js


Plik diff jest za duży
+ 98 - 101
desktop/core/src/desktop/js/parse/genericSyntaxParser.js


+ 1 - 0
desktop/core/src/desktop/js/parse/jison/sql/generic/sql.jisonlex

@@ -88,6 +88,7 @@
 'RIGHT'                                    { return 'RIGHT'; }
 'RLIKE'                                    { return 'RLIKE'; }
 'ROW'                                      { return 'ROW'; }
+'ROLE'                                     { return 'ROLE'; }
 'ROWS'                                     { return 'ROWS'; }
 'SCHEMA'                                   { return 'SCHEMA'; }
 'SELECT'                                   { parser.determineCase(yytext); parser.addStatementTypeLocation('SELECT', yylloc); return 'SELECT'; }

+ 2 - 2
desktop/core/src/desktop/js/parse/jison/sql/generic/sql_create.jison

@@ -35,7 +35,7 @@ CreateStatement_EDIT
  | ViewDefinition_EDIT
  | 'CREATE' 'CURSOR'
    {
-     parser.suggestKeywords(['DATABASE', 'SCHEMA', 'TABLE', 'VIEW']);
+     parser.suggestKeywords(['DATABASE', 'ROLE', 'SCHEMA', 'TABLE', 'VIEW']);
    }
  ;
 
@@ -76,7 +76,7 @@ DatabaseDefinitionOptionals
  : OptionalComment
    {
      if (!$1) {
-       parser.suggestKeywords(['COMMENT');
+       parser.suggestKeywords(['COMMENT']);
      }
    }
  ;

+ 4 - 4
desktop/core/src/desktop/js/parse/jison/sql/generic/sql_main.jison

@@ -1206,7 +1206,7 @@ OptionalSelectConditions_EDIT
    }
  | OptionalWhereClause OptionalGroupByClause OptionalHavingClause OptionalOrderByClause LimitClause 'CURSOR'
    {
-     $$ = { suggestKeywords: [], true };
+     $$ = { suggestKeywords: [], cursorAtEnd: true };
      $$.whereClauseLocation = $1 ? @1 : undefined;
      $$.limitClausePreceding = parser.firstDefined($4, @4, $3, @3, $2, @2, $1, @1);
      $$.limitClauseLocation = @5;
@@ -1859,7 +1859,7 @@ Joins
        $$.suggestJoinConditions = { prependOn: true, tablePrimaries: parser.yy.latestTablePrimaries.concat() }
      }
      if ($3.suggestKeywords) {
-       $$.suggestKeywords = $4.suggestKeywords;
+       $$.suggestKeywords = $3.suggestKeywords;
      }
      if (parser.yy.latestTablePrimaries.length > 0) {
         parser.yy.latestTablePrimaries[parser.yy.latestTablePrimaries.length - 1].join = true;
@@ -1877,7 +1877,7 @@ Joins
        $$.suggestJoinConditions = { prependOn: true, tablePrimaries: parser.yy.latestTablePrimaries.concat() }
      }
      if ($4.suggestKeywords) {
-       $$.suggestKeywords = $5.suggestKeywords;
+       $$.suggestKeywords = $4.suggestKeywords;
      }
      if (parser.yy.latestTablePrimaries.length > 0) {
        parser.yy.latestTablePrimaries[parser.yy.latestTablePrimaries.length - 1].join = true;
@@ -1976,7 +1976,7 @@ JoinType_EDIT
  ;
 
 OptionalJoinCondition
- :                                       -> { noJoinCondition: true, ['ON'] }
+ :                                       -> { noJoinCondition: true, suggestKeywords: ['ON'] }
  | 'ON' ValueExpression                  -> { valueExpression: $2 }
  ;
 

+ 157 - 0
desktop/core/src/desktop/js/parse/spec/generic/genericAutocompleteParser_Alter_Spec.js

@@ -0,0 +1,157 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SqlTestUtils from '../sqlTestUtils';
+import genericAutocompleteParser from '../../genericAutocompleteParser';
+
+describe('genericAutocompleteParser.js ALTER statements', () => {
+  beforeAll(() => {
+    genericAutocompleteParser.yy.parseError = function(msg) {
+      throw Error(msg);
+    };
+    jasmine.addMatchers(SqlTestUtils.testDefinitionMatcher);
+  });
+
+  const assertAutoComplete = testDefinition => {
+    const debug = false;
+    expect(
+      genericAutocompleteParser.parseSql(
+        testDefinition.beforeCursor,
+        testDefinition.afterCursor,
+        undefined,
+        debug
+      )
+    ).toEqualDefinition(testDefinition);
+  };
+
+  describe('ALTER TABLE', () => {
+    it('should suggest keywords for "ALTER |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER ',
+        afterCursor: '',
+        containsKeywords: ['TABLE'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest tables for "ALTER TABLE |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER TABLE ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { onlyTables: true },
+          suggestDatabases: { appendDot: true }
+        }
+      });
+    });
+
+    it('should suggest tables for "ALTER TABLE foo.|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER TABLE foo.',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { identifierChain: [{ name: 'foo' }], onlyTables: true }
+        }
+      });
+    });
+  });
+
+  describe('ALTER VIEW', () => {
+    it('should handle "ALTER VIEW baa.boo AS SELECT * FROM bla;|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER VIEW baa.boo AS SELECT * FROM bla;',
+        afterCursor: '',
+        noErrors: true,
+        containsKeywords: ['SELECT'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "ALTER |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER ',
+        afterCursor: '',
+        containsKeywords: ['VIEW'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest views for "ALTER VIEW |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER VIEW ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { onlyViews: true },
+          suggestDatabases: { appendDot: true }
+        }
+      });
+    });
+
+    it('should suggest views for "ALTER VIEW boo.|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER VIEW boo.',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { identifierChain: [{ name: 'boo' }], onlyViews: true }
+        }
+      });
+    });
+
+    it('should suggest keywords for "ALTER VIEW boo |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER VIEW boo ',
+        afterCursor: '',
+        containsKeywords: ['AS'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "ALTER VIEW baa.boo AS |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER VIEW baa.boo AS ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['SELECT']
+        }
+      });
+    });
+
+    it('should suggest databases for "ALTER VIEW baa.boo AS SELECT * FROM |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'ALTER VIEW baa.boo AS SELECT * FROM ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: {},
+          suggestDatabases: { appendDot: true }
+        }
+      });
+    });
+  });
+});

+ 277 - 0
desktop/core/src/desktop/js/parse/spec/generic/genericAutocompleteParser_Create_Spec.js

@@ -0,0 +1,277 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SqlTestUtils from '../sqlTestUtils';
+import genericAutocompleteParser from '../../genericAutocompleteParser';
+
+describe('genericAutocompleteParser.js CREATE statements', () => {
+  beforeAll(() => {
+    genericAutocompleteParser.yy.parseError = function(msg) {
+      throw Error(msg);
+    };
+    jasmine.addMatchers(SqlTestUtils.testDefinitionMatcher);
+  });
+
+  const assertAutoComplete = testDefinition => {
+    const debug = false;
+    expect(
+      genericAutocompleteParser.parseSql(
+        testDefinition.beforeCursor,
+        testDefinition.afterCursor,
+        undefined,
+        debug
+      )
+    ).toEqualDefinition(testDefinition);
+  };
+
+  it('should suggest keywords for "|"', () => {
+    assertAutoComplete({
+      beforeCursor: '',
+      afterCursor: '',
+      containsKeywords: ['CREATE'],
+      expectedResult: {
+        lowerCase: false
+      }
+    });
+  });
+
+  it('should suggest keywords for "CREATE |"', () => {
+    assertAutoComplete({
+      beforeCursor: 'CREATE ',
+      afterCursor: '',
+      containsKeywords: ['DATABASE', 'ROLE', 'SCHEMA', 'TABLE', 'VIEW'],
+      expectedResult: {
+        lowerCase: false
+      }
+    });
+  });
+
+  describe('CREATE DATABASE', () => {
+    it('should suggest keywords for "CREATE DATABASE |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE DATABASE ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['IF NOT EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE DATABASE IF |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE DATABASE IF ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['NOT EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE SCHEMA |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE SCHEMA ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['IF NOT EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE DATABASE | bla;"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE DATABASE ',
+        afterCursor: ' bla;',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['IF NOT EXISTS']
+        }
+      });
+    });
+  });
+
+  describe('CREATE ROLE', () => {
+    it('should handle "CREATE ROLE boo; |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE ROLE boo; ',
+        afterCursor: '',
+        noErrors: true,
+        containsKeywords: ['SELECT'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+  });
+
+  describe('CREATE TABLE', () => {
+    it('should suggest keywords for "CREATE TABLE |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE TABLE ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['IF NOT EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE TABLE IF |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE TABLE IF ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['NOT EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE TABLE IF NOT |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE TABLE IF NOT ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['EXISTS']
+        }
+      });
+    });
+
+    it('should handle for "CREATE TABLE foo (id INT);|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE TABLE foo (id INT);',
+        afterCursor: '',
+        containsKeywords: ['SELECT'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE TABLE foo (id |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE TABLE foo (id ',
+        afterCursor: '',
+        containsKeywords: ['BOOLEAN'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE TABLE foo (id INT, `some` FLOAT, bar |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE TABLE foo (id INT, `some` FLOAT, bar ',
+        afterCursor: '',
+        containsKeywords: ['BOOLEAN'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+  });
+
+  describe('CREATE VIEW', () => {
+    it('should handle "CREATE VIEW foo AS SELECT a, | FROM tableOne"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE VIEW foo AS SELECT a, ',
+        afterCursor: ' FROM tableOne',
+        hasLocations: true,
+        containsKeywords: ['*', 'CASE'],
+        expectedResult: {
+          lowerCase: false,
+          suggestAggregateFunctions: { tables: [{ identifierChain: [{ name: 'tableOne' }] }] },
+          suggestAnalyticFunctions: true,
+          suggestFunctions: {},
+          suggestColumns: {
+            source: 'select',
+            tables: [{ identifierChain: [{ name: 'tableOne' }] }]
+          }
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE VIEW |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE VIEW ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['IF NOT EXISTS'],
+          suggestDatabases: { appendDot: true }
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE VIEW | boo AS select * from baa;"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE VIEW ',
+        afterCursor: ' boo AS SELECT * FROM baa;',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['IF NOT EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE VIEW IF |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE VIEW IF ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['NOT EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE VIEW IF NOT |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE VIEW IF NOT ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE VIEW boo AS |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE VIEW boo AS ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['SELECT']
+        }
+      });
+    });
+
+    it('should suggest keywords for "CREATE VIEW IF NOT EXISTS boo AS |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'CREATE VIEW IF NOT EXISTS boo AS ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['SELECT']
+        }
+      });
+    });
+  });
+});

+ 290 - 0
desktop/core/src/desktop/js/parse/spec/generic/genericAutocompleteParser_Drop_Spec.js

@@ -0,0 +1,290 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SqlTestUtils from '../sqlTestUtils';
+import genericAutocompleteParser from '../../genericAutocompleteParser';
+
+describe('genericAutocompleteParser.js DROP statements', () => {
+  beforeAll(() => {
+    genericAutocompleteParser.yy.parseError = function(msg) {
+      throw Error(msg);
+    };
+    jasmine.addMatchers(SqlTestUtils.testDefinitionMatcher);
+  });
+
+  const assertAutoComplete = testDefinition => {
+    const debug = false;
+    expect(
+      genericAutocompleteParser.parseSql(
+        testDefinition.beforeCursor,
+        testDefinition.afterCursor,
+        undefined,
+        debug
+      )
+    ).toEqualDefinition(testDefinition);
+  };
+
+  it('should suggest keywords for "DROP |"', () => {
+    assertAutoComplete({
+      beforeCursor: 'DROP ',
+      afterCursor: '',
+      containsKeywords: ['DATABASE', 'ROLE', 'SCHEMA', 'TABLE', 'VIEW'],
+      expectedResult: {
+        lowerCase: false
+      }
+    });
+  });
+
+  describe('DROP DATABASE', () => {
+    it('should suggest databases for "DROP DATABASE |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP DATABASE ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestDatabases: {},
+          suggestKeywords: ['IF EXISTS']
+        }
+      });
+    });
+
+    it('should suggest databases for "DROP SCHEMA |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP SCHEMA ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestDatabases: {},
+          suggestKeywords: ['IF EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "DROP DATABASE IF |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP DATABASE IF ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['EXISTS']
+        }
+      });
+    });
+
+    it('should suggest databases for "DROP DATABASE IF EXISTS |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP DATABASE IF EXISTS ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestDatabases: {}
+        }
+      });
+    });
+
+    it('should suggest keywords for "DROP DATABASE foo |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP DATABASE foo ',
+        afterCursor: '',
+        containsKeywords: ['CASCADE'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+  });
+
+  describe('DROP ROLE', () => {
+    it('should handle "DROP ROLE boo;|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP ROLE boo;',
+        afterCursor: '',
+        noErrors: true,
+        containsKeywords: ['SELECT'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+  });
+
+  describe('DROP TABLE', () => {
+    it('should handle "DROP TABLE db.tbl PURGE;"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP TABLE db.tbl PURGE;',
+        afterCursor: '',
+        containsKeywords: ['SELECT'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest tables for "DROP TABLE |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP TABLE ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { onlyTables: true },
+          suggestKeywords: ['IF EXISTS'],
+          suggestDatabases: {
+            appendDot: true
+          }
+        }
+      });
+    });
+
+    it('should suggest tables for "DROP TABLE db.|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP TABLE db.',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { identifierChain: [{ name: 'db' }], onlyTables: true }
+        }
+      });
+    });
+
+    it('should suggest keywords for "DROP TABLE IF |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP TABLE IF ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['EXISTS']
+        }
+      });
+    });
+
+    it('should suggest tables for "DROP TABLE IF EXISTS |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP TABLE IF EXISTS ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { onlyTables: true },
+          suggestDatabases: {
+            appendDot: true
+          }
+        }
+      });
+    });
+  });
+
+  describe('DROP VIEW', () => {
+    it('should handle "DROP VIEW boo;|', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP VIEW boo;',
+        afterCursor: '',
+        containsKeywords: ['SELECT'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should handle "DROP VIEW IF EXISTS baa.boo;|', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP VIEW IF EXISTS baa.boo;',
+        afterCursor: '',
+        containsKeywords: ['SELECT'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest views for "DROP VIEW |', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP VIEW ',
+        afterCursor: '',
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { onlyViews: true },
+          suggestDatabases: { appendDot: true },
+          suggestKeywords: ['IF EXISTS']
+        }
+      });
+    });
+
+    it('should suggest keywords for "DROP VIEW IF |', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP VIEW IF ',
+        afterCursor: '',
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['EXISTS']
+        }
+      });
+    });
+
+    it('should suggest views for "DROP VIEW boo.|', () => {
+      assertAutoComplete({
+        beforeCursor: 'DROP VIEW boo.',
+        afterCursor: '',
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { identifierChain: [{ name: 'boo' }], onlyViews: true }
+        }
+      });
+    });
+  });
+
+  describe('TRUNCATE TABLE', () => {
+    it('should handle "TRUNCATE TABLE baa.boo;"', () => {
+      assertAutoComplete({
+        beforeCursor: 'TRUNCATE TABLE baa.boo;',
+        afterCursor: '',
+        containsKeywords: ['SELECT'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "TRUNCATE |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'truncate ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: true,
+          suggestKeywords: ['TABLE']
+        }
+      });
+    });
+
+    it('should suggest tables for "TRUNCATE TABLE |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'TRUNCATE TABLE ',
+        afterCursor: '',
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: {},
+          suggestDatabases: { appendDot: true },
+          suggestKeywords: ['IF EXISTS']
+        }
+      });
+    });
+  });
+});

+ 138 - 0
desktop/core/src/desktop/js/parse/spec/generic/genericAutocompleteParser_Error_Spec.js

@@ -0,0 +1,138 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SqlTestUtils from '../sqlTestUtils';
+import genericAutocompleteParser from '../../genericAutocompleteParser';
+
+describe('genericAutocompleteParser.js Error statements', () => {
+  beforeAll(() => {
+    genericAutocompleteParser.yy.parseError = function(msg) {
+      throw Error(msg);
+    };
+    jasmine.addMatchers(SqlTestUtils.testDefinitionMatcher);
+  });
+
+  const assertAutoComplete = testDefinition => {
+    const debug = false;
+    expect(
+      genericAutocompleteParser.parseSql(
+        testDefinition.beforeCursor,
+        testDefinition.afterCursor,
+        undefined,
+        debug
+      )
+    ).toEqualDefinition(testDefinition);
+  };
+
+  it('should suggest columns for "SELECT BAABO BOOAA BLARGH, | FROM testTable"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT BAABO BOOAA BLARGH, ',
+      afterCursor: ' FROM testTable',
+      expectedResult: {
+        lowerCase: false,
+        suggestFunctions: {},
+        suggestAggregateFunctions: { tables: [{ identifierChain: [{ name: 'testTable' }] }] },
+        suggestAnalyticFunctions: true,
+        suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }] }] }
+      }
+    });
+  });
+
+  it('should suggest columns for "SELECT foo, bar, SELECT, | FROM testTable"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT foo, bar, SELECT, ',
+      afterCursor: ' FROM testTable',
+      expectedResult: {
+        lowerCase: false,
+        suggestFunctions: {},
+        suggestAggregateFunctions: { tables: [{ identifierChain: [{ name: 'testTable' }] }] },
+        suggestAnalyticFunctions: true,
+        suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }] }] }
+      }
+    });
+  });
+
+  it('should suggest columns for "SELECT foo, baa baa baa baa, SELECT, | FROM testTable"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT foo, baa baa baa baa, SELECT, ',
+      afterCursor: ' FROM testTable',
+      expectedResult: {
+        lowerCase: false,
+        suggestFunctions: {},
+        suggestAggregateFunctions: { tables: [{ identifierChain: [{ name: 'testTable' }] }] },
+        suggestAnalyticFunctions: true,
+        suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }] }] }
+      }
+    });
+  });
+
+  it('should suggest columns for "SELECT * FROM testTable WHERE baa baaa booo |"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT * FROM testTable WHERE baa baaa boo',
+      afterCursor: '',
+      containsKeywords: ['GROUP BY', 'ORDER BY'],
+      expectedResult: {
+        lowerCase: false
+      }
+    });
+  });
+
+  it('should suggest columns for "SELECT * FROM testTable WHERE baa baaa booo GROUP |"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT * FROM testTable WHERE baa baaa boo GROUP ',
+      afterCursor: '',
+      expectedResult: {
+        lowerCase: false,
+        suggestKeywords: ['BY'],
+        suggestGroupBys: { prefix: 'BY', tables: [{ identifierChain: [{ name: 'testTable' }] }] }
+      }
+    });
+  });
+
+  it('should suggest columns for "SELECT * FROM testTable WHERE baa baaa booo ORDER |"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT * FROM testTable WHERE baa baaa boo ORDER ',
+      afterCursor: '',
+      expectedResult: {
+        lowerCase: false,
+        suggestKeywords: ['BY'],
+        suggestOrderBys: { prefix: 'BY', tables: [{ identifierChain: [{ name: 'testTable' }] }] }
+      }
+    });
+  });
+
+  it('should suggest columns for "SELECT * FROM testTable ORDER BY bla bla bla boo |"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT * FROM testTable ORDER BY bla bla bla boo ',
+      afterCursor: '',
+      containsKeywords: ['LIMIT', 'UNION'],
+      expectedResult: {
+        lowerCase: false
+      }
+    });
+  });
+
+  it('should suggest columns for "SELECT * FROM testTable GROUP BY boo hoo hoo ORDER BY bla bla bla boo |"', () => {
+    assertAutoComplete({
+      beforeCursor: 'SELECT * FROM testTable ORDER BY bla bla bla boo ',
+      afterCursor: '',
+      containsKeywords: ['LIMIT', 'UNION'],
+      expectedResult: {
+        lowerCase: false
+      }
+    });
+  });
+});

+ 139 - 0
desktop/core/src/desktop/js/parse/spec/generic/genericAutocompleteParser_Insert_Spec.js

@@ -0,0 +1,139 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SqlTestUtils from '../sqlTestUtils';
+import genericAutocompleteParser from '../../genericAutocompleteParser';
+
+describe('genericAutocompleteParser.js INSERT statements', () => {
+  beforeAll(() => {
+    genericAutocompleteParser.yy.parseError = function(msg) {
+      throw Error(msg);
+    };
+    jasmine.addMatchers(SqlTestUtils.testDefinitionMatcher);
+  });
+
+  const assertAutoComplete = testDefinition => {
+    const debug = false;
+    expect(
+      genericAutocompleteParser.parseSql(
+        testDefinition.beforeCursor,
+        testDefinition.afterCursor,
+        undefined,
+        debug
+      )
+    ).toEqualDefinition(testDefinition);
+  };
+
+  describe('INSERT', () => {
+    it('should handle "INSERT INTO bla.boo VALUES (1, 2, \'a\', 3); |"', () => {
+      assertAutoComplete({
+        beforeCursor: "INSERT INTO bla.boo VALUES (1, 2, 'a', 3); ",
+        afterCursor: '',
+        noErrors: true,
+        containsKeywords: ['SELECT'],
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "|"', () => {
+      assertAutoComplete({
+        beforeCursor: '',
+        afterCursor: '',
+        containsKeywords: ['INSERT'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "INSERT |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'INSERT ',
+        afterCursor: '',
+        containsKeywords: ['INTO'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest tables for "INSERT INTO |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'INSERT INTO ',
+        afterCursor: '',
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: {},
+          suggestDatabases: { appendDot: true },
+          suggestKeywords: ['TABLE']
+        }
+      });
+    });
+
+    it('should suggest tables for "INSERT INTO baa.|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'INSERT INTO baa.',
+        afterCursor: '',
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { identifierChain: [{ name: 'baa' }] }
+        }
+      });
+    });
+
+    it('should suggest tables for "INSERT INTO TABLE baa.|"', () => {
+      assertAutoComplete({
+        beforeCursor: 'INSERT INTO TABLE baa.',
+        afterCursor: '',
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false,
+          suggestTables: { identifierChain: [{ name: 'baa' }] }
+        }
+      });
+    });
+
+    it('should suggest keywords for "INSERT INTO baa |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'INSERT INTO baa ',
+        afterCursor: '',
+        containsKeywords: ['VALUES'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+
+    it('should suggest keywords for "INSERT INTO TABLE baa |"', () => {
+      assertAutoComplete({
+        beforeCursor: 'INSERT INTO TABLE baa ',
+        afterCursor: '',
+        containsKeywords: ['VALUES'],
+        noErrors: true,
+        expectedResult: {
+          lowerCase: false
+        }
+      });
+    });
+  });
+});

+ 1303 - 0
desktop/core/src/desktop/js/parse/spec/generic/genericAutocompleteParser_Locations_Spec.js

@@ -0,0 +1,1303 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SqlTestUtils from '../sqlTestUtils';
+import genericAutocompleteParser from '../../genericAutocompleteParser';
+
+describe('sqlAutocompleteParser.js locations', () => {
+  beforeAll(() => {
+    genericAutocompleteParser.yy.parseError = function(msg) {
+      throw Error(msg);
+    };
+    jasmine.addMatchers(SqlTestUtils.testDefinitionMatcher);
+  });
+
+  const assertAutoComplete = testDefinition => {
+    const debug = true;
+    expect(
+      genericAutocompleteParser.parseSql(
+        testDefinition.beforeCursor,
+        testDefinition.afterCursor,
+        undefined,
+        debug
+      )
+    ).toEqualDefinition(testDefinition);
+  };
+
+  const assertLocations = function(options) {
+    assertAutoComplete({
+      beforeCursor: options.beforeCursor,
+      afterCursor: options.afterCursor || '',
+      locationsOnly: true,
+      noErrors: true,
+      expectedLocations: options.expectedLocations,
+      expectedDefinitions: options.expectedDefinitions
+    });
+  };
+
+  it('should report locations for "select cos(1) as foo from customers order by foo;"', () => {
+    assertLocations({
+      beforeCursor: 'select cos(1) as foo from customers order by foo; ',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 49 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 21 }
+        },
+        {
+          type: 'function',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 },
+          function: 'cos'
+        },
+        {
+          type: 'alias',
+          source: 'column',
+          alias: 'foo',
+          location: { first_line: 1, last_line: 1, first_column: 18, last_column: 21 },
+          parentLocation: { first_line: 1, last_line: 1, first_column: 8, last_column: 14 }
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 27, last_column: 36 },
+          identifierChain: [{ name: 'customers' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 36, last_column: 36 }
+        },
+        {
+          type: 'alias',
+          location: { first_line: 1, last_line: 1, first_column: 46, last_column: 49 },
+          alias: 'foo',
+          source: 'column',
+          parentLocation: { first_line: 1, last_line: 1, first_column: 8, last_column: 14 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 49, last_column: 49 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "WITH boo AS (SELECT * FROM tbl) SELECT * FROM boo; |"', () => {
+    assertLocations({
+      beforeCursor: 'WITH boo AS (SELECT * FROM tbl) SELECT * FROM boo; ',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 50 }
+        },
+        {
+          type: 'alias',
+          source: 'cte',
+          alias: 'boo',
+          location: { first_line: 1, last_line: 1, first_column: 6, last_column: 9 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 21, last_column: 22 },
+          subquery: true
+        },
+        {
+          type: 'asterisk',
+          location: { first_line: 1, last_line: 1, first_column: 21, last_column: 22 },
+          tables: [{ identifierChain: [{ name: 'tbl' }] }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 28, last_column: 31 },
+          identifierChain: [{ name: 'tbl' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 31, last_column: 31 },
+          subquery: true
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 31, last_column: 31 },
+          subquery: true
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 40, last_column: 41 }
+        },
+        {
+          type: 'asterisk',
+          location: { first_line: 1, last_line: 1, first_column: 40, last_column: 41 },
+          tables: [{ identifierChain: [{ name: 'boo' }] }]
+        },
+        {
+          type: 'alias',
+          target: 'cte',
+          alias: 'boo',
+          location: { first_line: 1, last_line: 1, first_column: 47, last_column: 50 }
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 50, last_column: 50 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 50, last_column: 50 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "SELECT * FROM testTable1 JOIN db1.table2; |"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT * FROM testTable1 JOIN db1.table2; ',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 41 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'asterisk',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 },
+          tables: [
+            { identifierChain: [{ name: 'testTable1' }] },
+            { identifierChain: [{ name: 'db1' }, { name: 'table2' }] }
+          ]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 15, last_column: 25 },
+          identifierChain: [{ name: 'testTable1' }]
+        },
+        {
+          type: 'database',
+          location: { first_line: 1, last_line: 1, first_column: 31, last_column: 34 },
+          identifierChain: [{ name: 'db1' }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 35, last_column: 41 },
+          identifierChain: [{ name: 'db1' }, { name: 'table2' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 41, last_column: 41 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 41, last_column: 41 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "SELECT a.col FROM db.tbl a; |"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT a.col FROM db.tbl a; ',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 27 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 13 }
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 },
+          identifierChain: [{ name: 'db' }, { name: 'tbl' }]
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 10, last_column: 13 },
+          identifierChain: [{ name: 'col' }],
+          tables: [{ identifierChain: [{ name: 'db' }, { name: 'tbl' }], alias: 'a' }],
+          qualified: true
+        },
+        {
+          type: 'database',
+          location: { first_line: 1, last_line: 1, first_column: 19, last_column: 21 },
+          identifierChain: [{ name: 'db' }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 22, last_column: 25 },
+          identifierChain: [{ name: 'db' }, { name: 'tbl' }]
+        },
+        {
+          type: 'alias',
+          source: 'table',
+          alias: 'a',
+          location: { first_line: 1, last_line: 1, first_column: 26, last_column: 27 },
+          identifierChain: [{ name: 'db' }, { name: 'tbl' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 27, last_column: 27 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 27, last_column: 27 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "SELECT tbl.col FROM db.tbl a; |"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT tbl.col FROM db.tbl a; ',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 29 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 15 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11 },
+          identifierChain: [{ name: 'tbl' }],
+          tables: [{ identifierChain: [{ name: 'db' }, { name: 'tbl' }], alias: 'a' }],
+          qualified: false
+        },
+        {
+          type: 'complex',
+          location: { first_line: 1, last_line: 1, first_column: 12, last_column: 15 },
+          identifierChain: [{ name: 'tbl' }, { name: 'col' }],
+          tables: [{ identifierChain: [{ name: 'db' }, { name: 'tbl' }], alias: 'a' }],
+          qualified: true
+        },
+        {
+          type: 'database',
+          location: { first_line: 1, last_line: 1, first_column: 21, last_column: 23 },
+          identifierChain: [{ name: 'db' }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 24, last_column: 27 },
+          identifierChain: [{ name: 'db' }, { name: 'tbl' }]
+        },
+        {
+          type: 'alias',
+          source: 'table',
+          alias: 'a',
+          location: { first_line: 1, last_line: 1, first_column: 28, last_column: 29 },
+          identifierChain: [{ name: 'db' }, { name: 'tbl' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 29, last_column: 29 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 29, last_column: 29 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "select x from x;"', () => {
+    assertLocations({
+      beforeCursor: 'select x from x;',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 16 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 },
+          identifierChain: [{ name: 'x' }],
+          tables: [{ identifierChain: [{ name: 'x' }] }],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 15, last_column: 16 },
+          identifierChain: [{ name: 'x' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 16, last_column: 16 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 16, last_column: 16 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "select x from x;select y from y;"', () => {
+    assertLocations({
+      beforeCursor: 'select x from x;select y from y;',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 16 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 },
+          identifierChain: [{ name: 'x' }],
+          tables: [{ identifierChain: [{ name: 'x' }] }],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 15, last_column: 16 },
+          identifierChain: [{ name: 'x' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 16, last_column: 16 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 16, last_column: 16 }
+        },
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 17, last_column: 32 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 24, last_column: 25 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 24, last_column: 25 },
+          identifierChain: [{ name: 'y' }],
+          tables: [{ identifierChain: [{ name: 'y' }] }],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 31, last_column: 32 },
+          identifierChain: [{ name: 'y' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 32, last_column: 32 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 32, last_column: 32 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "-- comment\nselect x from x;\n\n\nselect y from y;"', () => {
+    assertLocations({
+      beforeCursor: '-- comment\nselect x from x;\n\n\nselect y from y;',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 2, first_column: 1, last_column: 16 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 2, last_line: 2, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 2, last_line: 2, first_column: 8, last_column: 9 },
+          identifierChain: [{ name: 'x' }],
+          tables: [{ identifierChain: [{ name: 'x' }] }],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 2, last_line: 2, first_column: 15, last_column: 16 },
+          identifierChain: [{ name: 'x' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 2, last_line: 2, first_column: 16, last_column: 16 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 2, last_line: 2, first_column: 16, last_column: 16 }
+        },
+        {
+          type: 'statement',
+          location: { first_line: 2, last_line: 5, first_column: 17, last_column: 16 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 5, last_line: 5, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 5, last_line: 5, first_column: 8, last_column: 9 },
+          identifierChain: [{ name: 'y' }],
+          tables: [{ identifierChain: [{ name: 'y' }] }],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 5, last_line: 5, first_column: 15, last_column: 16 },
+          identifierChain: [{ name: 'y' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 5, last_line: 5, first_column: 16, last_column: 16 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 5, last_line: 5, first_column: 16, last_column: 16 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "select x from x, y;"', () => {
+    assertLocations({
+      beforeCursor: 'select x from x, y;',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 19 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 },
+          identifierChain: [{ name: 'x' }],
+          tables: [{ identifierChain: [{ name: 'x' }] }, { identifierChain: [{ name: 'y' }] }],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 15, last_column: 16 },
+          identifierChain: [{ name: 'x' }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 18, last_column: 19 },
+          identifierChain: [{ name: 'y' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 19, last_column: 19 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 19, last_column: 19 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "SELECT t3.id, id FROM testTable1, db.testTable2, testTable3 t3;|"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT t3.id, id FROM testTable1, db.testTable2, testTable3 t3;',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 63 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 17 }
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 },
+          identifierChain: [{ name: 'testTable3' }]
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 11, last_column: 13 },
+          identifierChain: [{ name: 'id' }],
+          tables: [{ identifierChain: [{ name: 'testTable3' }], alias: 't3' }],
+          qualified: true
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 15, last_column: 17 },
+          identifierChain: [{ name: 'id' }],
+          tables: [
+            { identifierChain: [{ name: 'testTable1' }] },
+            { identifierChain: [{ name: 'db' }, { name: 'testTable2' }] },
+            { identifierChain: [{ name: 'testTable3' }], alias: 't3' }
+          ],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 23, last_column: 33 },
+          identifierChain: [{ name: 'testTable1' }]
+        },
+        {
+          type: 'database',
+          location: { first_line: 1, last_line: 1, first_column: 35, last_column: 37 },
+          identifierChain: [{ name: 'db' }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 38, last_column: 48 },
+          identifierChain: [{ name: 'db' }, { name: 'testTable2' }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 50, last_column: 60 },
+          identifierChain: [{ name: 'testTable3' }]
+        },
+        {
+          type: 'alias',
+          source: 'table',
+          alias: 't3',
+          location: { first_line: 1, last_line: 1, first_column: 61, last_column: 63 },
+          identifierChain: [{ name: 'testTable3' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 63, last_column: 63 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 63, last_column: 63 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "SELECT * FROM foo WHERE bar IN (1+1, 2+2);|"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT * FROM foo WHERE bar IN (1+1, 2+2);',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 42 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'asterisk',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 },
+          tables: [{ identifierChain: [{ name: 'foo' }] }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 },
+          identifierChain: [{ name: 'foo' }]
+        },
+        {
+          type: 'whereClause',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 19, last_column: 42 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 },
+          identifierChain: [{ name: 'bar' }],
+          tables: [{ identifierChain: [{ name: 'foo' }] }],
+          qualified: false
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 42, last_column: 42 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);|"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 48 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 }
+        },
+        {
+          type: 'asterisk',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 9 },
+          tables: [{ identifierChain: [{ name: 'foo' }] }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 },
+          identifierChain: [{ name: 'foo' }]
+        },
+        {
+          type: 'whereClause',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 19, last_column: 48 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 },
+          identifierChain: [{ name: 'bar' }],
+          tables: [{ identifierChain: [{ name: 'foo' }] }],
+          qualified: false
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 33, last_column: 35 },
+          identifierChain: [{ name: 'id' }],
+          tables: [{ identifierChain: [{ name: 'foo' }] }],
+          qualified: false
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 41, last_column: 43 },
+          identifierChain: [{ name: 'id' }],
+          tables: [{ identifierChain: [{ name: 'foo' }] }],
+          qualified: false
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 48, last_column: 48 }
+        }
+      ]
+    });
+  });
+
+  it(
+    'should report locations for "SELECT s07.description, s07.salary, s08.salary,\\r\\n' +
+      '  s08.salary - s07.salary\\r\\n' +
+      'FROM\\r\\n' +
+      '  sample_07 s07 JOIN sample_08 s08\\r\\n' +
+      'ON ( s07.code = s08.code)\\r\\n' +
+      'WHERE\\r\\n' +
+      '  s07.salary < s08.salary\\r\\n' +
+      'ORDER BY s08.salary-s07.salary DESC\\r\\n' +
+      'LIMIT 1000;|"',
+    () => {
+      assertLocations({
+        beforeCursor:
+          'SELECT s07.description, s07.salary, s08.salary,\r\n' +
+          '  s08.salary - s07.salary\r\n' +
+          'FROM\r\n' +
+          '  sample_07 s07 JOIN sample_08 s08\r\n' +
+          'ON ( s07.code = s08.code)\r\n' +
+          'WHERE\r\n' +
+          '  s07.salary < s08.salary\r\n' +
+          'ORDER BY s08.salary-s07.salary DESC\r\n' +
+          'LIMIT 1000;',
+        expectedLocations: [
+          {
+            type: 'statement',
+            location: { first_line: 1, last_line: 9, first_column: 1, last_column: 11 }
+          },
+          {
+            type: 'selectList',
+            missing: false,
+            location: { first_line: 1, last_line: 2, first_column: 8, last_column: 26 }
+          },
+          {
+            type: 'table',
+            location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 1, last_line: 1, first_column: 12, last_column: 23 },
+            identifierChain: [{ name: 'description' }],
+            tables: [{ identifierChain: [{ name: 'sample_07' }], alias: 's07' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 1, last_line: 1, first_column: 29, last_column: 35 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_07' }], alias: 's07' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 1, last_line: 1, first_column: 37, last_column: 40 },
+            identifierChain: [{ name: 'sample_08' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 1, last_line: 1, first_column: 41, last_column: 47 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_08' }], alias: 's08' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 2, last_line: 2, first_column: 3, last_column: 6 },
+            identifierChain: [{ name: 'sample_08' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 2, last_line: 2, first_column: 7, last_column: 13 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_08' }], alias: 's08' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 2, last_line: 2, first_column: 16, last_column: 19 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 2, last_line: 2, first_column: 20, last_column: 26 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_07' }], alias: 's07' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 4, last_line: 4, first_column: 3, last_column: 12 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'alias',
+            source: 'table',
+            alias: 's07',
+            location: { first_line: 4, last_line: 4, first_column: 13, last_column: 16 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'table',
+            location: { first_line: 4, last_line: 4, first_column: 22, last_column: 31 },
+            identifierChain: [{ name: 'sample_08' }]
+          },
+          {
+            type: 'alias',
+            source: 'table',
+            alias: 's08',
+            location: { first_line: 4, last_line: 4, first_column: 32, last_column: 35 },
+            identifierChain: [{ name: 'sample_08' }]
+          },
+          {
+            type: 'table',
+            location: { first_line: 5, last_line: 5, first_column: 6, last_column: 9 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 5, last_line: 5, first_column: 10, last_column: 14 },
+            identifierChain: [{ name: 'code' }],
+            tables: [{ identifierChain: [{ name: 'sample_07' }], alias: 's07' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 5, last_line: 5, first_column: 17, last_column: 20 },
+            identifierChain: [{ name: 'sample_08' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 5, last_line: 5, first_column: 21, last_column: 25 },
+            identifierChain: [{ name: 'code' }],
+            tables: [{ identifierChain: [{ name: 'sample_08' }], alias: 's08' }],
+            qualified: true
+          },
+          {
+            type: 'whereClause',
+            missing: false,
+            location: { first_line: 6, last_line: 7, first_column: 1, last_column: 26 }
+          },
+          {
+            type: 'table',
+            location: { first_line: 7, last_line: 7, first_column: 3, last_column: 6 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 7, last_line: 7, first_column: 7, last_column: 13 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_07' }], alias: 's07' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 7, last_line: 7, first_column: 16, last_column: 19 },
+            identifierChain: [{ name: 'sample_08' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 7, last_line: 7, first_column: 20, last_column: 26 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_08' }], alias: 's08' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 8, last_line: 8, first_column: 10, last_column: 13 },
+            identifierChain: [{ name: 'sample_08' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 8, last_line: 8, first_column: 14, last_column: 20 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_08' }], alias: 's08' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 8, last_line: 8, first_column: 21, last_column: 24 },
+            identifierChain: [{ name: 'sample_07' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 8, last_line: 8, first_column: 25, last_column: 31 },
+            identifierChain: [{ name: 'salary' }],
+            tables: [{ identifierChain: [{ name: 'sample_07' }], alias: 's07' }],
+            qualified: true
+          },
+          {
+            type: 'limitClause',
+            missing: false,
+            location: { first_line: 9, last_line: 9, first_column: 1, last_column: 11 }
+          }
+        ]
+      });
+    }
+  );
+
+  it('should handle "select bl from blablabla join (select * from blablabla) s1;', () => {
+    assertLocations({
+      beforeCursor: 'select bl from blablabla join (select * from blablabla) s1;',
+      afterCursor: '',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 59 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 }
+        },
+        {
+          type: 'column',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 },
+          identifierChain: [{ name: 'bl' }],
+          tables: [{ identifierChain: [{ name: 'blablabla' }] }, { subQuery: 's1' }],
+          qualified: false
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 16, last_column: 25 },
+          identifierChain: [{ name: 'blablabla' }]
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 39, last_column: 40 },
+          subquery: true
+        },
+        {
+          type: 'asterisk',
+          location: { first_line: 1, last_line: 1, first_column: 39, last_column: 40 },
+          tables: [{ identifierChain: [{ name: 'blablabla' }] }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 46, last_column: 55 },
+          identifierChain: [{ name: 'blablabla' }]
+        },
+        {
+          type: 'whereClause',
+          subquery: true,
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 55, last_column: 55 }
+        },
+        {
+          type: 'limitClause',
+          subquery: true,
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 55, last_column: 55 }
+        },
+        {
+          type: 'alias',
+          source: 'subquery',
+          alias: 's1',
+          location: { first_line: 1, last_line: 1, first_column: 57, last_column: 59 }
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 59, last_column: 59 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 59, last_column: 59 }
+        }
+      ]
+    });
+  });
+
+  it(
+    'should report locations for "SELECT CASE cos(boo.a) > baa.boo \\n' +
+      '\\tWHEN baa.b THEN true \\n' +
+      '\\tWHEN boo.c THEN false \\n' +
+      '\\tWHEN baa.blue THEN boo.d \\n' +
+      '\\tELSE baa.e END \\n' +
+      '\\t FROM db1.foo boo, bar baa WHERE baa.bla IN (SELECT ble FROM bla);|"',
+    () => {
+      assertLocations({
+        beforeCursor:
+          'SELECT CASE cos(boo.a) > baa.boo \n\tWHEN baa.b THEN true \n\tWHEN boo.c THEN false \n\tWHEN baa.blue THEN boo.d \n\tELSE baa.e END \n\t FROM db1.foo boo, bar baa WHERE baa.bla IN (SELECT ble FROM bla);',
+        expectedLocations: [
+          {
+            type: 'statement',
+            location: { first_line: 1, last_line: 6, first_column: 1, last_column: 67 }
+          },
+          {
+            type: 'selectList',
+            missing: false,
+            location: { first_line: 1, last_line: 5, first_column: 8, last_column: 16 }
+          },
+          {
+            type: 'function',
+            location: { first_line: 1, last_line: 1, first_column: 13, last_column: 15 },
+            function: 'cos'
+          },
+          {
+            type: 'table',
+            location: { first_line: 1, last_line: 1, first_column: 17, last_column: 20 },
+            identifierChain: [{ name: 'db1' }, { name: 'foo' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 1, last_line: 1, first_column: 21, last_column: 22 },
+            identifierChain: [{ name: 'a' }],
+            tables: [{ identifierChain: [{ name: 'db1' }, { name: 'foo' }], alias: 'boo' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 1, last_line: 1, first_column: 26, last_column: 29 },
+            identifierChain: [{ name: 'bar' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 1, last_line: 1, first_column: 30, last_column: 33 },
+            identifierChain: [{ name: 'boo' }],
+            tables: [{ identifierChain: [{ name: 'bar' }], alias: 'baa' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 2, last_line: 2, first_column: 7, last_column: 10 },
+            identifierChain: [{ name: 'bar' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 2, last_line: 2, first_column: 11, last_column: 12 },
+            identifierChain: [{ name: 'b' }],
+            tables: [{ identifierChain: [{ name: 'bar' }], alias: 'baa' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 3, last_line: 3, first_column: 7, last_column: 10 },
+            identifierChain: [{ name: 'db1' }, { name: 'foo' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 3, last_line: 3, first_column: 11, last_column: 12 },
+            identifierChain: [{ name: 'c' }],
+            tables: [{ identifierChain: [{ name: 'db1' }, { name: 'foo' }], alias: 'boo' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 4, last_line: 4, first_column: 7, last_column: 10 },
+            identifierChain: [{ name: 'bar' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 4, last_line: 4, first_column: 11, last_column: 15 },
+            identifierChain: [{ name: 'blue' }],
+            tables: [{ identifierChain: [{ name: 'bar' }], alias: 'baa' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 4, last_line: 4, first_column: 21, last_column: 24 },
+            identifierChain: [{ name: 'db1' }, { name: 'foo' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 4, last_line: 4, first_column: 25, last_column: 26 },
+            identifierChain: [{ name: 'd' }],
+            tables: [{ identifierChain: [{ name: 'db1' }, { name: 'foo' }], alias: 'boo' }],
+            qualified: true
+          },
+          {
+            type: 'table',
+            location: { first_line: 5, last_line: 5, first_column: 7, last_column: 10 },
+            identifierChain: [{ name: 'bar' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 5, last_line: 5, first_column: 11, last_column: 12 },
+            identifierChain: [{ name: 'e' }],
+            tables: [{ identifierChain: [{ name: 'bar' }], alias: 'baa' }],
+            qualified: true
+          },
+          {
+            type: 'database',
+            location: { first_line: 6, last_line: 6, first_column: 8, last_column: 11 },
+            identifierChain: [{ name: 'db1' }]
+          },
+          {
+            type: 'table',
+            location: { first_line: 6, last_line: 6, first_column: 12, last_column: 15 },
+            identifierChain: [{ name: 'db1' }, { name: 'foo' }]
+          },
+          {
+            type: 'alias',
+            source: 'table',
+            alias: 'boo',
+            location: { first_line: 6, last_line: 6, first_column: 16, last_column: 19 },
+            identifierChain: [{ name: 'db1' }, { name: 'foo' }]
+          },
+          {
+            type: 'table',
+            location: { first_line: 6, last_line: 6, first_column: 21, last_column: 24 },
+            identifierChain: [{ name: 'bar' }]
+          },
+          {
+            type: 'alias',
+            source: 'table',
+            alias: 'baa',
+            location: { first_line: 6, last_line: 6, first_column: 25, last_column: 28 },
+            identifierChain: [{ name: 'bar' }]
+          },
+          {
+            type: 'whereClause',
+            missing: false,
+            location: { first_line: 6, last_line: 6, first_column: 29, last_column: 67 }
+          },
+          {
+            type: 'table',
+            location: { first_line: 6, last_line: 6, first_column: 35, last_column: 38 },
+            identifierChain: [{ name: 'bar' }]
+          },
+          {
+            type: 'column',
+            location: { first_line: 6, last_line: 6, first_column: 39, last_column: 42 },
+            identifierChain: [{ name: 'bla' }],
+            tables: [{ identifierChain: [{ name: 'bar' }], alias: 'baa' }],
+            qualified: true
+          },
+          {
+            type: 'selectList',
+            missing: false,
+            location: { first_line: 6, last_line: 6, first_column: 54, last_column: 57 },
+            subquery: true
+          },
+          {
+            type: 'column',
+            location: { first_line: 6, last_line: 6, first_column: 54, last_column: 57 },
+            identifierChain: [{ name: 'ble' }],
+            tables: [{ identifierChain: [{ name: 'bla' }] }],
+            qualified: false
+          },
+          {
+            type: 'table',
+            location: { first_line: 6, last_line: 6, first_column: 63, last_column: 66 },
+            identifierChain: [{ name: 'bla' }]
+          },
+          {
+            type: 'whereClause',
+            subquery: true,
+            missing: true,
+            location: { first_line: 6, last_line: 6, first_column: 66, last_column: 66 }
+          },
+          {
+            type: 'limitClause',
+            subquery: true,
+            missing: true,
+            location: { first_line: 6, last_line: 6, first_column: 66, last_column: 66 }
+          },
+          {
+            type: 'limitClause',
+            missing: true,
+            location: { first_line: 6, last_line: 6, first_column: 67, last_column: 67 }
+          }
+        ]
+      });
+    }
+  );
+
+  it('should report locations for "SELECT tta.* FROM testTableA tta, testTableB; |"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT tta.* FROM testTableA tta, testTableB; ',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 45 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 13 }
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11 },
+          identifierChain: [{ name: 'testTableA' }]
+        },
+        {
+          type: 'asterisk',
+          location: { first_line: 1, last_line: 1, first_column: 12, last_column: 13 },
+          tables: [{ alias: 'tta', identifierChain: [{ name: 'testTableA' }] }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 19, last_column: 29 },
+          identifierChain: [{ name: 'testTableA' }]
+        },
+        {
+          type: 'alias',
+          source: 'table',
+          alias: 'tta',
+          location: { first_line: 1, last_line: 1, first_column: 30, last_column: 33 },
+          identifierChain: [{ name: 'testTableA' }]
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 35, last_column: 45 },
+          identifierChain: [{ name: 'testTableB' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 45, last_column: 45 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 45, last_column: 45 }
+        }
+      ]
+    });
+  });
+
+  it('should report locations for "SELECT COUNT(*) FROM testTable; |"', () => {
+    assertLocations({
+      beforeCursor: 'SELECT COUNT(*) FROM testTable;',
+      expectedLocations: [
+        {
+          type: 'statement',
+          location: { first_line: 1, last_line: 1, first_column: 1, last_column: 31 }
+        },
+        {
+          type: 'selectList',
+          missing: false,
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 16 }
+        },
+        {
+          type: 'function',
+          location: { first_line: 1, last_line: 1, first_column: 8, last_column: 12 },
+          function: 'count'
+        },
+        {
+          type: 'table',
+          location: { first_line: 1, last_line: 1, first_column: 22, last_column: 31 },
+          identifierChain: [{ name: 'testTable' }]
+        },
+        {
+          type: 'whereClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 31, last_column: 31 }
+        },
+        {
+          type: 'limitClause',
+          missing: true,
+          location: { first_line: 1, last_line: 1, first_column: 31, last_column: 31 }
+        }
+      ]
+    });
+  });
+});

+ 10 - 1
tools/jison/generateParsers.js

@@ -286,7 +286,16 @@ identifySqlParsers().then(() => {
       } else if (parserDefinitions[arg]) {
         parsersToGenerate.push(arg);
       } else {
-        invalid.push(arg);
+        let prefixFound = false;
+        Object.keys(parserDefinitions).forEach(key => {
+          if (key.indexOf(arg) === 0) {
+            prefixFound = true;
+            parsersToGenerate.push(key);
+          }
+        });
+        if (!prefixFound) {
+          invalid.push(arg);
+        }
       }
     } else if (arg.indexOf('generateParsers.js') !== -1) {
       appFound = true;

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików