Przeglądaj źródła

HUE-5291 [editor] Add complete join suggestions to the autocompleter

Johan Ahlen 9 lat temu
rodzic
commit
fab229f08e

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

@@ -1392,7 +1392,7 @@ var ApiHelper = (function () {
   };
 
   /**
-   * Fetches a navigator entity for the given identifierChain
+   * Fetches the popular joins for the given tables
    *
    * @param {Object} options
    * @param {string} options.sourceType
@@ -1405,7 +1405,7 @@ var ApiHelper = (function () {
    * @param {string} options.tables.identifierChain.name
    * @param {string} [options.defaultDatabase]
    */
-  ApiHelper.prototype.fetchNavOptJoinConditions = function (options) {
+  ApiHelper.prototype.fetchNavOptPopularJoins = function (options) {
     var self = this;
 
     var dbTables = [];

+ 38 - 15
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/sql_main.jison

@@ -1396,7 +1396,10 @@ TableExpression_EDIT
          keywords = createWeightedKeywords($1.suggestKeywords, 3);
        }
        if ($1.tableReferenceList.suggestJoinConditions) {
-         joinConditionsSuggest($1.tableReferenceList.suggestJoinConditions);
+         suggestJoinConditions($1.tableReferenceList.suggestJoinConditions);
+       }
+       if ($1.tableReferenceList.suggestJoins) {
+         suggestJoins($1.tableReferenceList.suggestJoins);
        }
        if (!$1.hasLateralViews && $1.tableReferenceList.suggestKeywords) {
          keywords = keywords.concat(createWeightedKeywords($1.tableReferenceList.suggestKeywords, 3));
@@ -2327,6 +2330,16 @@ TableReference_EDIT
 
 TablePrimaryOrJoinedTable
  : TablePrimary
+   {
+      $$ = $1;
+      var lastTablePrimary = parser.yy.latestTablePrimaries[parser.yy.latestTablePrimaries.length - 1];
+      if (!lastTablePrimary.subQueryAlias) {
+        $$.suggestJoins = {
+          prependJoin: true,
+          tables: [{ identifierChain: lastTablePrimary.identifierChain }]
+        };
+      }
+   }
  | JoinedTable
  ;
 
@@ -2398,6 +2411,16 @@ Join_EDIT
      if (!$2 && isImpala()) {
        suggestKeywords(['[BROADCAST]', '[SHUFFLE]']);
      }
+     if (!$2) {
+       var lastTablePrimary = parser.yy.latestTablePrimaries[parser.yy.latestTablePrimaries.length - 1];
+       if (!lastTablePrimary.subQueryAlias) {
+         suggestJoins({
+           prependJoin: false,
+           joinType: $1,
+           tables: [{ identifierChain: lastTablePrimary.identifierChain }]
+         })
+       }
+     }
      suggestTables();
      suggestDatabases({
        appendDot: true
@@ -2413,19 +2436,19 @@ Joins_EDIT
  ;
 
 JoinType
- : 'JOIN'
- | '<hive>CROSS' 'JOIN'
- | 'INNER' 'JOIN'
- | 'FULL' 'JOIN'
- | 'FULL' 'OUTER' 'JOIN'
- | 'LEFT' 'JOIN'
- | 'LEFT' '<impala>ANTI' 'JOIN'
- | 'LEFT' 'OUTER' 'JOIN'
- | 'LEFT' 'SEMI' 'JOIN'
- | 'RIGHT' 'JOIN'
- | 'RIGHT' '<impala>ANTI' 'JOIN'
- | 'RIGHT' 'OUTER' 'JOIN'
- | 'RIGHT' 'SEMI' 'JOIN'
+ : 'JOIN'                         -> 'JOIN'
+ | '<hive>CROSS' 'JOIN'           -> 'CROSS JOIN'
+ | 'INNER' 'JOIN'                 -> 'INNER JOIN'
+ | 'FULL' 'JOIN'                  -> 'FULL JOIN'
+ | 'FULL' 'OUTER' 'JOIN'          -> 'FULL OUTER JOIN'
+ | 'LEFT' 'JOIN'                  -> 'LEFT JOIN'
+ | 'LEFT' '<impala>ANTI' 'JOIN'   -> 'LEFT ANTI JOIN'
+ | 'LEFT' 'OUTER' 'JOIN'          -> 'LEFT OUTER JOIN'
+ | 'LEFT' 'SEMI' 'JOIN'           -> 'LEFT SEMI JOIN'
+ | 'RIGHT' 'JOIN'                 -> 'RIGHT JOIN'
+ | 'RIGHT' '<impala>ANTI' 'JOIN'  -> 'RIGHT ANTI JOIN'
+ | 'RIGHT' 'OUTER' 'JOIN'         -> 'RIGHT OUTER JOIN'
+ | 'RIGHT' 'SEMI' 'JOIN'          -> 'RIGHT SEMI JOIN'
  ;
 
 JoinType_EDIT
@@ -2505,7 +2528,7 @@ JoinCondition_EDIT
  | 'ON' 'CURSOR'
    {
      valueExpressionSuggest();
-     joinConditionsSuggest({ prependOn: false });
+     suggestJoinConditions({ prependOn: false });
    }
  ;
 

+ 71 - 3
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js

@@ -616,7 +616,10 @@ case 792:
          keywords = createWeightedKeywords($$[$0-3].suggestKeywords, 3);
        }
        if ($$[$0-3].tableReferenceList.suggestJoinConditions) {
-         joinConditionsSuggest($$[$0-3].tableReferenceList.suggestJoinConditions);
+         suggestJoinConditions($$[$0-3].tableReferenceList.suggestJoinConditions);
+       }
+       if ($$[$0-3].tableReferenceList.suggestJoins) {
+         suggestJoins($$[$0-3].tableReferenceList.suggestJoins);
        }
        if (!$$[$0-3].hasLateralViews && $$[$0-3].tableReferenceList.suggestKeywords) {
          keywords = keywords.concat(createWeightedKeywords($$[$0-3].tableReferenceList.suggestKeywords, 3));
@@ -1475,6 +1478,18 @@ case 1152: case 1153:
         identifierChain: $$[$0-2]
       });
     
+break;
+case 1163:
+
+      this.$ = $$[$0];
+      var lastTablePrimary = parser.yy.latestTablePrimaries[parser.yy.latestTablePrimaries.length - 1];
+      if (!lastTablePrimary.subQueryAlias) {
+        this.$.suggestJoins = {
+          prependJoin: true,
+          tables: [{ identifierChain: lastTablePrimary.identifierChain }]
+        };
+      }
+   
 break;
 case 1170:
 
@@ -1519,11 +1534,60 @@ case 1181:
      if (!$$[$0-2] && isImpala()) {
        suggestKeywords(['[BROADCAST]', '[SHUFFLE]']);
      }
+     if (!$$[$0-2]) {
+       var lastTablePrimary = parser.yy.latestTablePrimaries[parser.yy.latestTablePrimaries.length - 1];
+       if (!lastTablePrimary.subQueryAlias) {
+         suggestJoins({
+           prependJoin: false,
+           joinType: $$[$0-3],
+           tables: [{ identifierChain: lastTablePrimary.identifierChain }]
+         })
+       }
+     }
      suggestTables();
      suggestDatabases({
        appendDot: true
      });
    
+break;
+case 1186:
+this.$ = 'JOIN';
+break;
+case 1187:
+this.$ = 'CROSS JOIN';
+break;
+case 1188:
+this.$ = 'INNER JOIN';
+break;
+case 1189:
+this.$ = 'FULL JOIN';
+break;
+case 1190:
+this.$ = 'FULL OUTER JOIN';
+break;
+case 1191:
+this.$ = 'LEFT JOIN';
+break;
+case 1192:
+this.$ = 'LEFT ANTI JOIN';
+break;
+case 1193:
+this.$ = 'LEFT OUTER JOIN';
+break;
+case 1194:
+this.$ = 'LEFT SEMI JOIN';
+break;
+case 1195:
+this.$ = 'RIGHT JOIN';
+break;
+case 1196:
+this.$ = 'RIGHT ANTI JOIN';
+break;
+case 1197:
+this.$ = 'RIGHT OUTER JOIN';
+break;
+case 1198:
+this.$ = 'RIGHT SEMI JOIN';
 break;
 case 1199: case 1200: case 1201: case 1203: case 1204: case 1205: case 1207: case 1208: case 1209:
 
@@ -1592,7 +1656,7 @@ break;
 case 1220:
 
      valueExpressionSuggest();
-     joinConditionsSuggest({ prependOn: false });
+     suggestJoinConditions({ prependOn: false });
    
 break;
 case 1221:
@@ -4246,13 +4310,17 @@ var selectListNoTableSuggest = function (selectListEdit, hasDistinctOrAll) {
   }
 };
 
-var joinConditionsSuggest = function (details) {
+var suggestJoinConditions = function (details) {
   parser.yy.result.suggestJoinConditions = details || {};
   if (parser.yy.latestTablePrimaries && !parser.yy.result.suggestJoinConditions.tablePrimaries) {
     parser.yy.result.suggestJoinConditions.tablePrimaries = parser.yy.latestTablePrimaries.concat();
   }
 };
 
+var suggestJoins = function (details) {
+  parser.yy.result.suggestJoins = details || {};
+};
+
 var valueExpressionSuggest = function (oppositeValueExpression, operator) {
   if (oppositeValueExpression && oppositeValueExpression.columnReference) {
     suggestValues();

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

@@ -207,13 +207,17 @@ var selectListNoTableSuggest = function (selectListEdit, hasDistinctOrAll) {
   }
 };
 
-var joinConditionsSuggest = function (details) {
+var suggestJoinConditions = function (details) {
   parser.yy.result.suggestJoinConditions = details || {};
   if (parser.yy.latestTablePrimaries && !parser.yy.result.suggestJoinConditions.tablePrimaries) {
     parser.yy.result.suggestJoinConditions.tablePrimaries = parser.yy.latestTablePrimaries.concat();
   }
 };
 
+var suggestJoins = function (details) {
+  parser.yy.result.suggestJoins = details || {};
+};
+
 var valueExpressionSuggest = function (oppositeValueExpression, operator) {
   if (oppositeValueExpression && oppositeValueExpression.columnReference) {
     suggestValues();

+ 74 - 11
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter2.js

@@ -32,6 +32,7 @@ var SqlAutocompleter2 = (function () {
 
   // Keyword weights come from the parser
   var DEFAULT_WEIGHTS = {
+    ACTIVE_JOIN: 1200,
     JOIN_CONDITION: 1100,
     COLUMN: 1000,
     SAMPLE: 900,
@@ -42,7 +43,8 @@ var SqlAutocompleter2 = (function () {
     UDF: 400,
     HDFS: 300,
     VIRTUAL_COLUMN: 200,
-    COLREF_KEYWORD: 100
+    COLREF_KEYWORD: 100,
+    JOIN: -1
   };
 
   SqlAutocompleter2.prototype.autocomplete = function (beforeCursor, afterCursor, callback, editor) {
@@ -115,19 +117,38 @@ var SqlAutocompleter2 = (function () {
       colRefDeferral.resolve();
     }
 
-    if (parseResult.suggestJoinConditions) {
-      var joinDeferral = $.Deferred();
-      deferrals.push(joinDeferral);
-      self.snippet.getApiHelper().fetchNavOptJoinConditions({
+    if (parseResult.suggestJoins) {
+      var joinsDeferral = $.Deferred();
+      deferrals.push(joinsDeferral);
+      self.snippet.getApiHelper().fetchNavOptPopularJoins({
         sourceType: self.snippet.type(),
         timeout: self.timeout,
         defaultDatabase: database,
         silenceErrors: true,
-        tables: parseResult.suggestJoinConditions.tables,
+        tables: parseResult.suggestJoins.tables,
         successCallback: function (data) {
+          console.log(data);
           data.values.forEach(function (value) {
-            var suggestionString = parseResult.suggestJoinConditions.prependOn ? (parseResult.lowerCase ? 'on ' : 'ON ') : '';
+            var suggestionString = parseResult.suggestJoins.prependJoin ? (parseResult.lowerCase ? 'join ' : 'JOIN ') : '';
             var first = true;
+
+            var existingTables = {};
+            parseResult.suggestJoins.tables.forEach(function (table) {
+              existingTables[table.identifierChain[table.identifierChain.length - 1].name] = true;
+            });
+
+            var joinRequired = false;
+            value.tables.forEach(function (table) {
+              var tableParts = table.split('.');
+              if (!existingTables[tableParts[tableParts.length - 1]]) {
+                suggestionString += joinRequired ? (parseResult.lowerCase ? ' join ' : ' JOIN ') + table : table;
+                joinRequired = true;
+              }
+            });
+
+            if (value.joinCols.length > 0) {
+              suggestionString += parseResult.lowerCase ? ' on ' : ' ON ';
+            }
             value.joinCols.forEach(function (joinColPair) {
               if (!first) {
                 suggestionString += parseResult.lowerCase ? ' and ' : ' AND ';
@@ -137,13 +158,49 @@ var SqlAutocompleter2 = (function () {
             });
             completions.push({
               value: suggestionString,
-              meta: 'condition',
-              weight: DEFAULT_WEIGHTS.JOIN_CONDITION
+              meta: 'join',
+              weight: parseResult.suggestJoins.prependJoin ? DEFAULT_WEIGHTS.JOIN : DEFAULT_WEIGHTS.ACTIVE_JOIN,
+              docHTML: self.createJoinHtml(suggestionString)
             });
           });
-          joinDeferral.resolve();
+          joinsDeferral.resolve();
         },
-        errorCallback: joinDeferral.resolve
+        errorCallback: joinsDeferral.resolve
+      });
+    }
+
+    if (parseResult.suggestJoinConditions) {
+      var joinConditionsDeferral = $.Deferred();
+      deferrals.push(joinConditionsDeferral);
+      self.snippet.getApiHelper().fetchNavOptPopularJoins({
+        sourceType: self.snippet.type(),
+        timeout: self.timeout,
+        defaultDatabase: database,
+        silenceErrors: true,
+        tables: parseResult.suggestJoinConditions.tables,
+        successCallback: function (data) {
+          data.values.forEach(function (value) {
+            if (value.joinCols.length > 0) {
+              var suggestionString = parseResult.suggestJoinConditions.prependOn ? (parseResult.lowerCase ? 'on ' : 'ON ') : '';
+              var first = true;
+              value.joinCols.forEach(function (joinColPair) {
+                if (!first) {
+                  suggestionString += parseResult.lowerCase ? ' and ' : ' AND ';
+                }
+                suggestionString += joinColPair.columns[0] + ' = ' + joinColPair.columns[1];
+                first = false;
+              });
+              completions.push({
+                value: suggestionString,
+                meta: 'condition',
+                weight: DEFAULT_WEIGHTS.JOIN_CONDITION,
+                docHTML: self.createJoinHtml(suggestionString)
+              });
+            }
+          });
+          joinConditionsDeferral.resolve();
+        },
+        errorCallback: joinConditionsDeferral.resolve
       });
     }
 
@@ -307,6 +364,12 @@ var SqlAutocompleter2 = (function () {
     }
   };
 
+  SqlAutocompleter2.prototype.createJoinHtml = function (value) {
+    var html = '<div style="max-width: 600px; white-space: normal; overflow-y: auto; height: 100%; padding: 8px;"><p><span style="white-space: pre; font-family: monospace;">' + value + '</span></p>';
+    html += '<div>';
+    return html;
+  };
+
   SqlAutocompleter2.prototype.addValues = function (parseResult, columnReference, completions) {
     if (columnReference.sample) {
       var suggestValues = parseResult.suggestValues;

+ 42 - 10
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecSelect.js

@@ -125,6 +125,7 @@
         hasLocations: true,
         expectedResult: {
           lowerCase: false,
+          suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'testTableB' }] }] },
           suggestKeywords: ['AS', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'UNION', 'FULL JOIN', 'FULL OUTER JOIN', 'INNER JOIN', 'JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
         }
       });
@@ -1293,20 +1294,21 @@
       describe('Hive specific', function () {
         it('should suggest keywords for "SELECT count(id) OVER w FROM customers |"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT row_number() OVER w FROM customers ',
+            beforeCursor: 'SELECT count(id) OVER w FROM customers ',
             afterCursor: '',
             hasLocations: true,
             dialect: 'hive',
             containsKeywords: ['WINDOW'],
             expectedResult: {
-              lowerCase: false
+              lowerCase: false,
+              suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'customers' }] }] }
             }
           });
         });
 
         it('should suggest keywords for "SELECT count(id) OVER w FROM customers WINDOW w |"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT row_number() OVER w FROM customers WINDOW w ',
+            beforeCursor: 'SELECT count(id) OVER w FROM customers WINDOW w ',
             afterCursor: '',
             hasLocations: true,
             dialect: 'hive',
@@ -1319,7 +1321,7 @@
 
         it('should suggest keywords for "SELECT count(id) OVER w FROM customers WINDOW w AS (|"', function() {
           assertAutoComplete({
-            beforeCursor: 'SELECT row_number() OVER w FROM customers WINDOW w AS (',
+            beforeCursor: 'SELECT count(id) OVER w FROM customers WINDOW w AS (',
             afterCursor: '',
             hasLocations: true,
             dialect: 'hive',
@@ -2489,6 +2491,7 @@
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'foo' }] }] },
             suggestKeywords: ['TABLESAMPLE', 'AS', 'LATERAL VIEW', 'WHERE', 'GROUP BY', 'HAVING', 'WINDOW', 'ORDER BY', 'CLUSTER BY', 'DISTRIBUTE BY', 'SORT BY', 'LIMIT', 'UNION', 'CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
           }
         });
@@ -2502,6 +2505,7 @@
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'db' }, { name: 'foo' }] }] },
             suggestKeywords: ['LATERAL VIEW', 'WHERE', 'GROUP BY', 'HAVING', 'WINDOW', 'ORDER BY', 'CLUSTER BY', 'DISTRIBUTE BY', 'SORT BY', 'LIMIT', 'UNION', 'CROSS JOIN', 'FULL JOIN', 'FULL OUTER JOIN', 'JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN']
           }
         });
@@ -2543,7 +2547,8 @@
           hasLocations: true,
           containsKeywords: ['WHERE', 'HAVING'],
           expectedResult: {
-            lowerCase: false
+            lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'foo' }] }] }
           }
         });
       });
@@ -2608,7 +2613,8 @@
           hasLocations: true,
           containsKeywords: ['AS', 'WHERE'],
           expectedResult: {
-            lowerCase: false
+            lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'foo' }] }] }
           }
         });
       });
@@ -3054,6 +3060,7 @@
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'testTableB' }] }] },
             suggestKeywords: ['AS', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', 'UNION', 'FULL JOIN', 'FULL OUTER JOIN', 'INNER JOIN', 'JOIN', 'LEFT ANTI JOIN', 'LEFT JOIN', 'LEFT OUTER JOIN', 'LEFT SEMI JOIN', 'RIGHT ANTI JOIN', 'RIGHT JOIN', 'RIGHT OUTER JOIN', 'RIGHT SEMI JOIN']
           }
         });
@@ -5590,7 +5597,8 @@
           noErrors: true,
           containsKeywords: ['TABLESAMPLE', 'AS'],
           expectedResult: {
-            lowerCase: false
+            lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'boo' }] }] }
           }
         });
       });
@@ -5603,7 +5611,8 @@
           hasLocations: true,
           containsKeywords: ['TABLESAMPLE', 'AS'],
           expectedResult: {
-            lowerCase: false
+            lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'boo' }] }] }
           }
         });
       });
@@ -5887,6 +5896,7 @@
           dialect: 'generic',
           expectedResult: {
             lowerCase: false,
+            suggestJoins: { prependJoin: false, joinType: 'JOIN', tables: [{ identifierChain: [{ name: 'testTable1' }] }] },
             suggestTables: {},
             suggestDatabases: { appendDot: true }
           }
@@ -6183,6 +6193,7 @@
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'table1' }] }] },
             suggestKeywords: ['FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER']
           }
         });
@@ -6196,6 +6207,7 @@
           hasLocations: true,
           expectedResult: {
             lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'table1' }] }] },
             suggestKeywords: ['FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT OUTER', 'RIGHT', 'RIGHT OUTER']
           }
         });
@@ -6282,6 +6294,19 @@
         });
       });
 
+      it('should suggest joins for "SELECT * FROM testTable1 |"', function() {
+        assertAutoComplete({
+          beforeCursor: 'SELECT * FROM testTable1 ',
+          afterCursor: '',
+          hasLocations: true,
+          containsKeywords: ['JOIN'],
+          expectedResult: {
+            lowerCase: false,
+            suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'testTable1' }] }] }
+          }
+        });
+      });
+
       describe('Hive specific', function () {
         it('should suggest keywords for "SELECT t1.* FROM table1 t1 |"', function () {
           assertAutoComplete({
@@ -6291,7 +6316,8 @@
             hasLocations: true,
             containsKeywords: ['LEFT SEMI JOIN', 'CROSS JOIN'], // Tested in full above
             expectedResult: {
-              lowerCase: false
+              lowerCase: false,
+              suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'table1' }] }] }
             }
           });
         });
@@ -6369,6 +6395,7 @@
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
+              suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'table1' }] }] },
               suggestKeywords: ['LATERAL VIEW', 'CROSS', 'FULL', 'FULL OUTER', 'LEFT', 'LEFT OUTER', 'LEFT SEMI', 'RIGHT', 'RIGHT OUTER']
             }
           });
@@ -6437,6 +6464,7 @@
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
+              suggestJoins: { prependJoin: false, joinType: 'LEFT OUTER JOIN', tables: [{ identifierChain: [{ name: 'table1' }] }] },
               suggestTables: {},
               suggestDatabases: { appendDot: true }
             }
@@ -6469,7 +6497,8 @@
             containsKeywords: ['LEFT ANTI JOIN', 'RIGHT ANTI JOIN'], // Tested in full above
             hasLocations: true,
             expectedResult: {
-              lowerCase: false
+              lowerCase: false,
+              suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'table1' }] }] }
             }
           });
         });
@@ -6536,6 +6565,7 @@
               lowerCase: false,
               suggestTables: {},
               suggestDatabases: { appendDot: true },
+              suggestJoins: { prependJoin: false, joinType: 'JOIN', tables: [{ identifierChain: [{ name: 'testTable1' }] }] },
               suggestKeywords: ['[BROADCAST]', '[SHUFFLE]']
             }
           });
@@ -6563,6 +6593,7 @@
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
+              suggestJoins: { prependJoin: true, tables: [{ identifierChain: [{ name: 'table1' }] }] },
               suggestKeywords: ['FULL', 'FULL OUTER', 'INNER', 'LEFT', 'LEFT ANTI', 'LEFT OUTER', 'LEFT SEMI', 'RIGHT', 'RIGHT ANTI', 'RIGHT OUTER', 'RIGHT SEMI']
             }
           });
@@ -6631,6 +6662,7 @@
             hasLocations: true,
             expectedResult: {
               lowerCase: false,
+              suggestJoins: { prependJoin: false, joinType: 'LEFT OUTER JOIN', tables: [{ identifierChain: [{ name: 'table1' }] }] },
               suggestTables: {},
               suggestDatabases: { appendDot: true },
               suggestKeywords: ['[BROADCAST]', '[SHUFFLE]']