浏览代码

HUE-4234 [editor] Improved JOIN autocompletion

This adds support for all different Hive and Impala join types and fixes a couple of issues:

- Removed partial words for less verbose parser, we don't care about them anyway
- Correct order of WHERE, GROUP BY, ORDER BY and LIMIT
Johan Ahlen 9 年之前
父节点
当前提交
0bbd5ca0d9

文件差异内容过多而无法显示
+ 329 - 242
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.jison


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js


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

@@ -52,6 +52,20 @@ define([
           }
         });
       });
+
+      it('should suggest keywords for partial statement', function() {
+        assertAutoComplete({
+          beforeCursor: 'foo',
+          afterCursor: 'bar',
+          dialect: 'impala',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['ALTER', 'COMPUTE', 'CREATE', 'DELETE', 'DESCRIBE',
+              'DROP', 'EXPLAIN', 'INSERT', 'INVALIDATE', 'LOAD', 'REFRESH',
+              'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE']
+          }
+        });
+      });
     });
 
     describe('Hive specific', function () {
@@ -68,15 +82,19 @@ define([
           }
         });
       });
-    });
 
-    it('should return empty suggestions for bogus statement', function() {
-      assertAutoComplete({
-        beforeCursor: 'foo',
-        afterCursor: 'bar',
-        expectedResult: {
-          lowerCase: false
-        }
+      it('should suggest keywords for partial statement', function() {
+        assertAutoComplete({
+          beforeCursor: 'foo',
+          afterCursor: 'bar',
+          dialect: 'hive',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['ALTER', 'ANALYZE', 'CREATE', 'DELETE', 'DESCRIBE',
+              'DROP', 'EXPLAIN', 'EXPORT', 'IMPORT', 'INSERT', 'LOAD', 'MSCK',
+              'REVOKE', 'SELECT', 'SET', 'SHOW', 'TRUNCATE', 'UPDATE', 'USE']
+          }
+        });
       });
     });
 
@@ -108,6 +126,27 @@ define([
       });
     });
 
+    describe('partial removal', function () {
+      it('should identify part lengths', function () {
+        var limitChars = [' ', '\n', '\t', '&', '~', '%', '!', '.', ',', '+', '-', '*', '/', '=', '<', '>', '(', ')', '[', ']', ';'];
+        expect(sql.identifyPartials('', '')).toEqual({left: 0, right: 0});
+        expect(sql.identifyPartials('foo', '')).toEqual({left: 3, right: 0});
+        expect(sql.identifyPartials(' foo', '')).toEqual({left: 3, right: 0});
+        expect(sql.identifyPartials('foo', 'bar')).toEqual({left: 3, right: 3});
+        expect(sql.identifyPartials('foo ', '')).toEqual({left: 0, right: 0});
+        expect(sql.identifyPartials('foo \'', '\'')).toEqual({left: 0, right: 0});
+        expect(sql.identifyPartials('foo "', '"')).toEqual({left: 0, right: 0});
+        limitChars.forEach(function (char) {
+          expect(sql.identifyPartials('bar foo' + char, '')).toEqual({left: 0, right: 0});
+          expect(sql.identifyPartials('bar foo' + char + 'foofoo', '')).toEqual({left: 6, right: 0});
+          expect(sql.identifyPartials('bar foo' + char + 'foofoo ', '')).toEqual({left: 0, right: 0});
+          expect(sql.identifyPartials('', char + 'foo bar')).toEqual({left: 0, right: 0});
+          expect(sql.identifyPartials('', 'foofoo' + char)).toEqual({left: 0, right: 6});
+          expect(sql.identifyPartials('', ' foofoo' + char)).toEqual({left: 0, right: 0});
+        });
+      });
+    });
+
     describe('identifierChain expansion', function () {
       it('should expand 1', function () {
         var tablePrimaries = [{

+ 342 - 12
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecSelect.js

@@ -83,7 +83,20 @@ define([
         dialect: 'generic',
         expectedResult: {
           lowerCase: false,
-          suggestKeywords: ['GROUP BY', 'JOIN', 'LIMIT', 'ORDER BY', 'WHERE']
+          suggestKeywords: ['FULL JOIN', 'FULL OUTER JOIN', 'GROUP BY', 'INNER JOIN', 'JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LIMIT', 'ORDER BY', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'WHERE']
+        }
+      });
+    });
+
+    it('should suggest keywords after SELECT SelectList FROM TablePrimary ', function() {
+      assertAutoComplete({
+        serverResponses: {},
+        beforeCursor: 'SELECT * FROM testTableA tta, testTableB ',
+        afterCursor: '',
+        dialect: 'impala',
+        expectedResult: {
+          lowerCase: false,
+          suggestKeywords: ['FULL JOIN', 'FULL OUTER JOIN', 'GROUP BY', 'INNER JOIN', 'JOIN', 'LEFT ANTI JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'LIMIT', 'ORDER BY', 'RIGHT ANTI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'RIGHT SEMI JOIN', 'WHERE']
         }
       });
     });
@@ -107,7 +120,19 @@ define([
           dialect: 'hive',
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['GROUP BY', 'JOIN', 'LATERAL', 'LIMIT', 'ORDER BY', 'WHERE']
+            suggestKeywords: ['CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'GROUP BY', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'LIMIT', 'ORDER BY', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'WHERE']
+          }
+        });
+      });
+
+      it('should suggest keywords after SELECT SelectList FROM TablePrimary ', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT bar FROM db.foo f ',
+          afterCursor: '',
+          dialect: 'hive',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'GROUP BY', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'LIMIT', 'ORDER BY', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'WHERE']
           }
         });
       });
@@ -131,7 +156,7 @@ define([
           dialect: 'hive',
           expectedResult: {
             lowerCase: false,
-            suggestKeywords: ['GROUP BY', 'JOIN', 'LATERAL', 'LIMIT', 'ORDER BY', 'WHERE']
+            suggestKeywords: ['CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'GROUP BY', 'JOIN', 'LATERAL VIEW', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'LIMIT', 'ORDER BY', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'WHERE']
           }
         });
       });
@@ -160,6 +185,30 @@ define([
         });
       });
 
+      it('should suggest keywords after SELECT SelectList FROM TablePrimary LATERAL ', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT bar FROM db.foo f LATERAL ',
+          afterCursor: '',
+          dialect: 'hive',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['VIEW']
+          }
+        });
+      });
+
+      it('should suggest keywords after SELECT SelectList FROM TablePrimary LATERAL ', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT bar FROM db.foo AS f LATERAL ',
+          afterCursor: '',
+          dialect: 'hive',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['VIEW']
+          }
+        });
+      });
+
       it('should suggest keywords after SELECT SelectList FROM TablePrimary LATERAL VIEW ', function () {
         assertAutoComplete({
           beforeCursor: 'SELECT bar FROM foo LATERAL VIEW ',
@@ -1311,6 +1360,17 @@ define([
     });
 
     describe('joins', function() {
+      it('should handle complete JOIN statement', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable1 JOIN db1.table2; ',
+          afterCursor: '',
+          containsKeywords: ['SELECT'],
+          expectedResult: {
+            lowerCase: false
+          }
+        });
+      });
+
       it('should suggest tables to join with', function() {
         assertAutoComplete({
           beforeCursor: 'SELECT * FROM testTable1 JOIN ',
@@ -1323,7 +1383,40 @@ define([
         });
       });
 
+      it('should suggest tables to join with from database', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable1 JOIN db1.',
+          afterCursor: '',
+          expectedResult: {
+            lowerCase: false,
+            suggestTables: { database: 'db1' }
+          }
+        });
+      });
+
+      it('should suggest tables to join with from database before other join', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable1 JOIN db1.',
+          afterCursor: ' JOIN foo',
+          expectedResult: {
+            lowerCase: false,
+            suggestTables: { database: 'db1' }
+          }
+        });
+      });
+
       it('should suggest table references in join condition if not already there', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON ',
+          afterCursor: '',
+          expectedResult: {
+            lowerCase: false,
+            suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'testTable2.', type: 'table' }]
+          }
+        });
+      });
+
+      it('should suggest table references in join condition if not already there with parenthesis', function() {
         assertAutoComplete({
           beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (',
           afterCursor: '',
@@ -1338,7 +1431,6 @@ define([
         assertAutoComplete({
           beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND ',
           afterCursor: '',
-          ignoreErrors: true, // Here the right parenthesis is missing
           expectedResult: {
             lowerCase: false,
             suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'testTable2.', type: 'table' }]
@@ -1361,7 +1453,6 @@ define([
         assertAutoComplete({
           beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable2.',
           afterCursor: '',
-          ignoreErrors: true,
           expectedResult: {
             lowerCase: false,
             suggestColumns: { table: 'testTable2'}
@@ -1369,18 +1460,35 @@ define([
         });
       });
 
-      xit('should suggest identifiers or values in join condition if table reference is present from multiple tables', function() {
+      it('should suggest field references in join condition if table reference is present', function() {
+        assertAutoComplete({
+          beforeCursor: 'select * from testTable1 cross join testTable2 on testTable1.',
+          afterCursor: '',
+          expectedResult: {
+            lowerCase: true,
+            suggestColumns: { table: 'testTable1'}
+          }
+        });
+      });
+
+      it('should suggest correct identifier in join condition if database reference is present', function() {
+        assertAutoComplete({
+          beforeCursor: 'select * from testTable1 join db.testTable2 on ',
+          afterCursor: '',
+          expectedResult: {
+            lowerCase: true,
+            suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'db.testTable2.', type: 'table' }]
+          }
+        });
+      });
+
+      it('should suggest identifiers or values in join condition if table reference is present from multiple tables', function() {
         assertAutoComplete({
           beforeCursor: 'select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = ',
           afterCursor: '',
-          ignoreErrors: true,
           expectedResult: {
             lowerCase: true,
-            suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'testTable2.', type: 'table' }],
-            suggestValues: {
-              table: 'testTable1',
-              identifierChain: [{ name: 'testColumn1' }]
-            }
+            suggestIdentifiers: [{ name: 'testTable1.', type: 'table' }, { name: 'testTable2.', type: 'table' }]
           }
         });
       });
@@ -1408,6 +1516,228 @@ define([
           }
         });
       });
+
+      xit('should suggest join types before JOIN', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT t1.* FROM table1 t1 ',
+          afterCursor: ' JOIN',
+          dialect: 'generic',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER']
+          }
+        });
+      });
+
+      it('should suggest join types before JOIN and after FULL', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT t1.* FROM table1 t1 FULL ',
+          afterCursor: ' JOIN',
+          dialect: 'generic',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['OUTER']
+          }
+        });
+      });
+
+      it('should suggest join types before JOIN and after LEFT', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT t1.* FROM table1 t1 LEFT ',
+          afterCursor: ' JOIN',
+          dialect: 'impala',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['ANTI', 'SEMI', 'OUTER']
+          }
+        });
+      });
+
+      it('should suggest join types before JOIN and after RIGHT', function () {
+        assertAutoComplete({
+          beforeCursor: 'SELECT t1.* FROM table1 t1 RIGHT ',
+          afterCursor: ' JOIN',
+          dialect: 'generic',
+          expectedResult: {
+            lowerCase: false,
+            suggestKeywords: ['OUTER']
+          }
+        });
+      });
+
+      describe('Hive specific', function () {
+        it('should suggest join types', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 ',
+            afterCursor: '',
+            dialect: 'hive',
+            containsKeywords: ['LEFT SEMI JOIN', 'CROSS JOIN'], // Tested in full above
+            expectedResult: {
+              lowerCase: false
+            }
+          });
+        });
+
+        xit('should suggest join types before JOIN', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 ',
+            afterCursor: ' JOIN',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['CROSS', 'FULL', 'FULL OUTER', 'LEFT', 'LEFT OUTER', 'LEFT SEMI', 'RIGHT', 'RIGHT OUTER']
+            }
+          });
+        });
+
+        it('should suggest join types before JOIN and after FULL', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 FULL ',
+            afterCursor: ' JOIN',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['OUTER']
+            }
+          });
+        });
+
+        it('should suggest join types before JOIN and after LEFT', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 LEFT ',
+            afterCursor: ' JOIN',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['SEMI', 'OUTER']
+            }
+          });
+        });
+
+        it('should suggest join types before JOIN and after RIGHT', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 RIGHT ',
+            afterCursor: ' JOIN',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['OUTER']
+            }
+          });
+        });
+
+        it('should suggest table references in join conditions for multiple joins', function() {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 CROSS JOIN table2 LEFT OUTER JOIN table3 JOIN table4 t4 ON (',
+            afterCursor: ' AND t1.c1 = t2.c2',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestIdentifiers: [{ name: 't1.', type: 'alias' }, { name: 'table2.', type: 'table' }, { name: 'table3.', type: 'table' }, { name: 't4.', type: 'alias' }]
+            }
+          });
+        });
+
+        it('should suggest tables in partial join conditions for multiple joins', function() {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 LEFT OUTER JOIN tab',
+            afterCursor: ' CROSS JOIN table3 JOIN table4 t4 ON (t1.c1 = t2.c2',
+            dialect: 'hive',
+            expectedResult: {
+              lowerCase: false,
+              suggestTables: {},
+              suggestDatabases: { appendDot: true }
+            }
+          });
+        });
+      });
+
+      describe('Impala specific', function () {
+        it('should suggest join types', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 ',
+            afterCursor: '',
+            dialect: 'impala',
+            containsKeywords: ['LEFT ANTI JOIN', 'RIGHT ANTI JOIN'], // Tested in full above
+            expectedResult: {
+              lowerCase: false
+            }
+          });
+        });
+
+        xit('should suggest join types before JOIN', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 ',
+            afterCursor: ' JOIN',
+            dialect: 'impala',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT ANTI', 'LEFT OUTER', 'LEFT SEMI', 'RIGHT', 'RIGHT ANTI', 'RIGHT OUTER', 'RIGHT SEMI']
+            }
+          });
+        });
+
+        it('should suggest join types before JOIN and after FULL', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 FULL ',
+            afterCursor: ' JOIN',
+            dialect: 'impala',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['OUTER']
+            }
+          });
+        });
+
+        it('should suggest join types before JOIN and after LEFT', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 LEFT ',
+            afterCursor: ' JOIN',
+            dialect: 'impala',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['ANTI', 'SEMI', 'OUTER']
+            }
+          });
+        });
+
+        it('should suggest join types before JOIN and after RIGHT', function () {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 RIGHT ',
+            afterCursor: ' JOIN',
+            dialect: 'impala',
+            expectedResult: {
+              lowerCase: false,
+              suggestKeywords: ['ANTI', 'SEMI', 'OUTER']
+            }
+          });
+        });
+
+        it('should suggest table references in join conditions for multiple joins', function() {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 LEFT OUTER JOIN table2 INNER JOIN table3 JOIN table4 t4 ON (',
+            afterCursor: ' AND t1.c1 = t2.c2',
+            dialect: 'impala',
+            expectedResult: {
+              lowerCase: false,
+              suggestIdentifiers: [{ name: 't1.', type: 'alias' }, { name: 'table2.', type: 'table' }, { name: 'table3.', type: 'table' }, { name: 't4.', type: 'alias' }]
+            }
+          });
+        });
+
+        it('should suggest tables in partial join conditions for multiple joins', function() {
+          assertAutoComplete({
+            beforeCursor: 'SELECT t1.* FROM table1 t1 LEFT OUTER JOIN tab',
+            afterCursor: ' INNER JOIN table3 JOIN table4 t4 ON (t1.c1 = t2.c2',
+            dialect: 'impala',
+            expectedResult: {
+              lowerCase: false,
+              suggestTables: {},
+              suggestDatabases: { appendDot: true }
+            }
+          });
+        });
+      })
     })
   });
 });

部分文件因为文件数量过多而无法显示