Skip to content

Commit d18c59b

Browse files
Implement Joins with multiple trailing ON Expressions (#1303)
* Implement Joins with multiple trailing ON Expressions Fixes #1302 Fixes SpecialOracleTest JOIN17, now 190/273 tests pass * Fixes #1229 * Merge MASTER Refactor the appendTo() method in favour of the traditional toString() * Remove unused imports
1 parent 2e87613 commit d18c59b

File tree

11 files changed

+115
-68
lines changed

11 files changed

+115
-68
lines changed

src/main/java/net/sf/jsqlparser/statement/select/Join.java

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@
99
*/
1010
package net.sf.jsqlparser.statement.select;
1111

12-
import java.util.ArrayList;
13-
import java.util.Collection;
14-
import java.util.Collections;
15-
import java.util.List;
16-
import java.util.Optional;
12+
import java.util.*;
13+
1714
import net.sf.jsqlparser.expression.Expression;
1815
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1916
import net.sf.jsqlparser.schema.Column;
@@ -32,8 +29,8 @@ public class Join extends ASTNodeAccessImpl {
3229
private boolean straight = false;
3330
private boolean apply = false;
3431
private FromItem rightItem;
35-
private Expression onExpression;
36-
private List<Column> usingColumns;
32+
private final LinkedList<Expression> onExpressions = new LinkedList<>();
33+
private final LinkedList<Column> usingColumns = new LinkedList<>();
3734
private KSQLJoinWindow joinWindow;
3835

3936
public boolean isSimple() {
@@ -228,17 +225,35 @@ public void setRightItem(FromItem item) {
228225
/**
229226
* Returns the "ON" expression (if any)
230227
*/
228+
@Deprecated
231229
public Expression getOnExpression() {
232-
return onExpression;
230+
return onExpressions.get(0);
231+
}
232+
233+
public Collection<Expression> getOnExpressions() {
234+
return onExpressions;
233235
}
234236

237+
@Deprecated
235238
public Join withOnExpression(Expression expression) {
236239
this.setOnExpression(expression);
237240
return this;
238241
}
239242

243+
@Deprecated
240244
public void setOnExpression(Expression expression) {
241-
onExpression = expression;
245+
onExpressions.add(0, expression);
246+
}
247+
248+
public Join addOnExpression(Expression expression) {
249+
onExpressions.add(expression);
250+
return this;
251+
}
252+
253+
public Join setOnExpressions(Collection<Expression> expressions) {
254+
onExpressions.clear();
255+
onExpressions.addAll(expressions);
256+
return this;
242257
}
243258

244259
/**
@@ -254,7 +269,8 @@ public Join withUsingColumns(List<Column> list) {
254269
}
255270

256271
public void setUsingColumns(List<Column> list) {
257-
usingColumns = list;
272+
usingColumns.clear();
273+
usingColumns.addAll(list);
258274
}
259275

260276
public boolean isWindowJoin() {
@@ -281,46 +297,52 @@ public void setJoinWindow(KSQLJoinWindow joinWindow) {
281297
@Override
282298
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
283299
public String toString() {
300+
StringBuilder builder = new StringBuilder();
301+
284302
if (isSimple() && isOuter()) {
285-
return "OUTER " + rightItem;
303+
builder.append("OUTER ").append(rightItem);
286304
} else if (isSimple()) {
287-
return "" + rightItem;
305+
builder.append(rightItem);
288306
} else {
289-
String type = "";
290-
291307
if (isRight()) {
292-
type += "RIGHT ";
308+
builder.append("RIGHT ");
293309
} else if (isNatural()) {
294-
type += "NATURAL ";
310+
builder.append("NATURAL ");
295311
} else if (isFull()) {
296-
type += "FULL ";
312+
builder.append("FULL ");
297313
} else if (isLeft()) {
298-
type += "LEFT ";
314+
builder.append("LEFT ");
299315
} else if (isCross()) {
300-
type += "CROSS ";
316+
builder.append("CROSS ");
301317
}
302318

303319
if (isOuter()) {
304-
type += "OUTER ";
320+
builder.append("OUTER ");
305321
} else if (isInner()) {
306-
type += "INNER ";
322+
builder.append("INNER ");
307323
} else if (isSemi()) {
308-
type += "SEMI ";
324+
builder.append("SEMI ");
309325
}
310326

311327
if (isStraight()) {
312-
type = "STRAIGHT_JOIN ";
328+
builder.append("STRAIGHT_JOIN ");
313329
} else if (isApply()) {
314-
type += "APPLY ";
330+
builder.append("APPLY ");
315331
} else {
316-
type += "JOIN ";
332+
builder.append("JOIN ");
317333
}
318334

319-
return type + rightItem + ((joinWindow != null) ? " WITHIN " + joinWindow : "")
320-
+ ((onExpression != null) ? " ON " + onExpression + "" : "")
321-
+ PlainSelect.getFormatedList(usingColumns, "USING", true, true);
335+
builder.append(rightItem).append((joinWindow != null) ? " WITHIN " + joinWindow : "");
336+
}
337+
338+
for (Expression onExpression: onExpressions) {
339+
builder.append(" ON ").append(onExpression);
340+
}
341+
if (usingColumns.size()>0) {
342+
builder.append(PlainSelect.getFormatedList(usingColumns, "USING", true, true));
322343
}
323344

345+
return builder.toString();
324346
}
325347

326348
public Join addUsingColumns(Column... usingColumns) {
@@ -334,12 +356,4 @@ public Join addUsingColumns(Collection<? extends Column> usingColumns) {
334356
collection.addAll(usingColumns);
335357
return this.withUsingColumns(collection);
336358
}
337-
338-
public <E extends FromItem> E getRightItem(Class<E> type) {
339-
return type.cast(getRightItem());
340-
}
341-
342-
public <E extends Expression> E getOnExpression(Class<E> type) {
343-
return type.cast(getOnExpression());
344-
}
345359
}

src/main/java/net/sf/jsqlparser/util/SelectUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static void addExpression(Select select, final Expression expr) {
8383
*/
8484
public static Join addJoin(Select select, final Table table, final Expression onExpression) {
8585
if (select.getSelectBody() instanceof PlainSelect) {
86-
Join join = new Join().withRightItem(table).withOnExpression(onExpression);
86+
Join join = new Join().withRightItem(table).addOnExpression(onExpression);
8787
select.getSelectBody(PlainSelect.class).addJoins(join);
8888
return join;
8989
} else {

src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@
1212
import java.util.Iterator;
1313
import java.util.List;
1414

15-
import net.sf.jsqlparser.expression.Alias;
16-
import net.sf.jsqlparser.expression.ExpressionVisitor;
17-
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
18-
import net.sf.jsqlparser.expression.MySQLIndexHint;
19-
import net.sf.jsqlparser.expression.OracleHint;
20-
import net.sf.jsqlparser.expression.SQLServerHints;
15+
import net.sf.jsqlparser.expression.*;
2116
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
2217
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
2318
import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitor;
@@ -429,11 +424,11 @@ public void deparseJoin(Join join) {
429424
buffer.append(" WITHIN ");
430425
buffer.append(join.getJoinWindow().toString());
431426
}
432-
if (join.getOnExpression() != null) {
427+
for (Expression onExpression : join.getOnExpressions()) {
433428
buffer.append(" ON ");
434-
join.getOnExpression().accept(expressionVisitor);
429+
onExpression.accept(expressionVisitor);
435430
}
436-
if (join.getUsingColumns() != null) {
431+
if (join.getUsingColumns().size()>0) {
437432
buffer.append(" USING (");
438433
for (Iterator<Column> iterator = join.getUsingColumns().iterator(); iterator.hasNext();) {
439434
Column column = iterator.next();

src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
package net.sf.jsqlparser.util.validation.validator;
1111

1212
import java.util.List;
13+
14+
import net.sf.jsqlparser.expression.Expression;
1315
import net.sf.jsqlparser.expression.MySQLIndexHint;
1416
import net.sf.jsqlparser.expression.SQLServerHints;
1517
import net.sf.jsqlparser.parser.feature.Feature;
@@ -241,7 +243,9 @@ public void validateOptionalJoin(Join join) {
241243
}
242244

243245
validateOptionalFromItem(join.getRightItem());
244-
validateOptionalExpression(join.getOnExpression());
246+
for (Expression onExpression: join.getOnExpressions()) {
247+
validateOptionalExpression(onExpression);
248+
}
245249
validateOptionalExpressions(join.getUsingColumns());
246250
}
247251

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,7 +1641,7 @@ String RelObjectName() :
16411641
{
16421642
(result = RelObjectNameWithoutValue()
16431643
| tk=<K_GROUP> | tk=<K_INTERVAL> | tk=<K_ON> | tk=<K_ORDER> | tk=<K_START> | tk=<K_TOP> | tk=<K_VALUE>
1644-
| tk=<K_VALUES> | tk=<K_CREATE> | tk=<K_TABLES> )
1644+
| tk=<K_VALUES> | tk=<K_CREATE> | tk=<K_TABLES> )
16451645

16461646
{
16471647
if (tk!=null) result=tk.image;
@@ -1653,7 +1653,7 @@ String RelObjectNameWithoutStart() :
16531653
{ Token tk = null; String result = null; }
16541654
{
16551655
(result = RelObjectNameWithoutValue() | tk=<K_TOP> | tk=<K_VALUE> | tk=<K_VALUES>
1656-
| tk=<K_INTERVAL>)
1656+
| tk=<K_INTERVAL> )
16571657

16581658
{
16591659
if (tk!=null) result=tk.image;
@@ -1672,7 +1672,7 @@ String RelObjectNameExt():
16721672
( result=RelObjectName() | tk=<K_ALL> | tk=<K_ANY> | tk=<K_SOME> | tk=<K_LEFT> | tk=<K_RIGHT> | tk=<K_SET>
16731673
| tk=<K_DOUBLE> | tk=<K_IF> | tk=<K_IIF> | tk=<K_OPTIMIZE> | tk=<K_LIMIT>
16741674
| tk=<K_OFFSET> | tk=<K_PROCEDURE> | tk=<K_PUBLIC>
1675-
| tk=<K_CASEWHEN> | tk=<K_IN> | tk=<K_GROUPING> )
1675+
| tk=<K_CASEWHEN> | tk=<K_IN> | tk=<K_GROUPING> )
16761676
{
16771677
if (tk!=null) result=tk.image;
16781678
return result;
@@ -2483,13 +2483,19 @@ Join JoinerExpression() #JoinerExpression:
24832483

24842484

24852485
[
2486-
LOOKAHEAD(2) ([ <K_WITHIN> "(" joinWindow = JoinWindow() ")" {join.setJoinWindow(joinWindow);}]
2487-
( <K_ON> onExpression=Expression() { join.setOnExpression(onExpression); })
2488-
|
2489-
( <K_USING> "(" tableColumn=Column() { columns = new ArrayList<Column>(); columns.add(tableColumn); }
2490-
("," tableColumn=Column() { columns.add(tableColumn); } )* ")"
2491-
{ join.setUsingColumns(columns); } ))
2492-
]
2486+
LOOKAHEAD(2) (
2487+
[ <K_WITHIN> "(" joinWindow = JoinWindow() ")" {join.setJoinWindow(joinWindow);} ]
2488+
( <K_ON> onExpression=Expression() { join.addOnExpression(onExpression); }
2489+
( LOOKAHEAD(2) <K_ON> onExpression=Expression() { join.addOnExpression(onExpression); } )*
2490+
)
2491+
|
2492+
(
2493+
<K_USING> "(" tableColumn=Column() { columns = new ArrayList<Column>(); columns.add(tableColumn); }
2494+
( "," tableColumn=Column() { columns.add(tableColumn); } ) *
2495+
")" { join.setUsingColumns(columns); }
2496+
)
2497+
)
2498+
]
24932499
{
24942500
linkAST(join,jjtThis);
24952501
join.setRightItem(right);
@@ -5786,7 +5792,7 @@ AlterSystemStatement AlterSystemStatement():
57865792
"QUIESCE" "RESTRICTED" { operation = AlterSystemOperation.QUIESCE; }
57875793
)
57885794
|
5789-
(
5795+
(
57905796
"UNQUIESCE" { operation = AlterSystemOperation.UNQUIESCE; }
57915797
)
57925798
|

src/test/java/net/sf/jsqlparser/statement/alter/RenameTableStatementTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public void testTableNamesFinder() throws JSQLParserException {
8989
public void testValidator() throws JSQLParserException {
9090
String sqlStr = "RENAME oldTableName TO newTableName";
9191

92-
ValidationTestAsserts.validateNoErrors(sqlStr, 1, DatabaseType.ORACLE);
92+
ValidationTestAsserts.validateNoErrors(sqlStr, 1, DatabaseType.POSTGRESQL);
9393
}
9494

9595
@Test

src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4716,7 +4716,36 @@ public void testCollisionWithSpecialStringFunctionsIssue1284() throws JSQLParser
47164716
"recv_time >= toDateTime('2021-07-20 00:00:00')\n" +
47174717
"and recv_time < toDateTime('2021-07-21 00:00:00')", true);
47184718
}
4719-
4719+
4720+
@Test
4721+
public void testJoinWithTrailingOnExpressionIssue1302() throws JSQLParserException {
4722+
assertSqlCanBeParsedAndDeparsed(
4723+
"SELECT * FROM TABLE1 tb1\n" +
4724+
"INNER JOIN TABLE2 tb2\n" +
4725+
"INNER JOIN TABLE3 tb3\n" +
4726+
"INNER JOIN TABLE4 tb4\n" +
4727+
"ON (tb3.aaa = tb4.aaa)\n" +
4728+
"ON (tb2.aaa = tb3.aaa)\n" +
4729+
"ON (tb1.aaa = tb2.aaa)", true);
4730+
4731+
assertSqlCanBeParsedAndDeparsed(
4732+
"SELECT *\n" +
4733+
"FROM\n" +
4734+
"TABLE1 tbl1\n" +
4735+
" INNER JOIN TABLE2 tbl2\n" +
4736+
" INNER JOIN TABLE3 tbl3\n" +
4737+
" ON (tbl2.column1 = tbl3.column1)\n" +
4738+
" ON (tbl1.column2 = tbl2.column2)\n" +
4739+
"WHERE\n" +
4740+
"tbl1.column1 = 123", true);
4741+
}
4742+
4743+
@Test
4744+
public void testSimpleJoinOnExpressionIssue1229() throws JSQLParserException {
4745+
assertSqlCanBeParsedAndDeparsed(
4746+
"select t1.column1,t1.column2,t2.field1,t2.field2 from T_DT_ytb_01 t1 , T_DT_ytb_02 t2 on t1.column1 = t2.field1", true);
4747+
}
4748+
47204749
@Test
47214750
public void testNestedCaseComplexExpressionIssue1306() throws JSQLParserException {
47224751
// with extra brackets
@@ -4736,7 +4765,7 @@ public void testNestedCaseComplexExpressionIssue1306() throws JSQLParserExceptio
47364765
"END AS \"column1\"\n" +
47374766
"FROM test_schema.table_name\n" +
47384767
"", true);
4739-
4768+
47404769
// without brackets
47414770
assertSqlCanBeParsedAndDeparsed(
47424771
"SELECT CASE\n" +

src/test/java/net/sf/jsqlparser/statement/select/SpecialOracleTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,12 @@ public class SpecialOracleTest {
115115
"condition14.sql",
116116
"condition19.sql",
117117
"condition20.sql",
118+
"connect_by01.sql",
118119
"connect_by02.sql",
119120
"connect_by03.sql",
120121
"connect_by04.sql",
121122
"connect_by05.sql",
123+
"connect_by06.sql",
122124
"connect_by07.sql",
123125
"datetime01.sql",
124126
"datetime02.sql",
@@ -175,6 +177,7 @@ public class SpecialOracleTest {
175177
"join14.sql",
176178
"join15.sql",
177179
"join16.sql",
180+
"join17.sql",
178181
"join18.sql",
179182
"join19.sql",
180183
"join20.sql",

src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/connect_by01.sql

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,4 @@ from o
2727
connect by nocycle obj=prior link
2828
start with obj='a'
2929

30-
31-
--@FAILURE: Encountered unexpected token: "root" <S_IDENTIFIER> recorded first on Aug 3, 2021, 7:20:08 AM
32-
--@SUCCESSFULLY_PARSED_AND_DEPARSED first on 25.08.2021 23:11:02
30+
--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 14, 2021 9:00:57 PM

src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/connect_by06.sql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@ select last_name "Employee", connect_by_root last_name "Manager",
1515
order by "Employee", "Manager", "Pathlen", "Path"
1616

1717

18-
--@FAILURE: Encountered unexpected token: "\"Manager\"" <S_QUOTED_IDENTIFIER> recorded first on Aug 3, 2021, 7:20:08 AM
19-
--@SUCCESSFULLY_PARSED_AND_DEPARSED first on 25.08.2021 23:11:02
18+
--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 14, 2021 9:00:57 PM

src/test/resources/net/sf/jsqlparser/statement/select/oracle-tests/join17.sql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ inner join ca c
1414
on c.id = s.id
1515
on a.va = s.va
1616

17-
18-
--@FAILURE: Encountered unexpected token: "on" "ON" recorded first on Aug 3, 2021, 7:20:08 AM
17+
--@SUCCESSFULLY_PARSED_AND_DEPARSED first on Aug 14, 2021 9:00:57 PM

0 commit comments

Comments
 (0)