Skip to content

Commit e82e8b4

Browse files
committed
fix(parser): parse INSERT ON CONFLICT after a join's trailing ON
The trailing-ON loop of a join (introduced for issue #1302) claimed ON CONFLICT with its lookahead and parsed conflict as a column, so the ON CONFLICT clause of INSERT ... SELECT ... JOIN ... ON ... never reached the INSERT production and DO raised an unexpected-token error. Gate the loop with a semantic predicate that recognizes the ON CONFLICT clause shapes (DO, ON CONSTRAINT, parenthesized target with optional WHERE) and leaves the ON token to the INSERT production. Fixes #2358 Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent 54a7499 commit e82e8b4

2 files changed

Lines changed: 120 additions & 1 deletion

File tree

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

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,65 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
12411241
}
12421242
}
12431243

1244+
/**
1245+
* True when the pending {@code ON} opens the ON CONFLICT clause of the enclosing
1246+
* INSERT rather than another trailing join condition: after a join, the trailing
1247+
* ON expressions of Issue #1302 and the INSERT clause claim the same ON token.
1248+
* The clause shapes are ON CONFLICT DO, ON CONFLICT ON CONSTRAINT and
1249+
* ON CONFLICT ( target ) [ WHERE ] DO, so the parenthesized target has to be
1250+
* separated from an expression by scanning past its balanced brackets.
1251+
*/
1252+
private boolean isInsertOnConflictAhead() {
1253+
if (getToken(1).kind != K_ON || getToken(2).kind != K_CONFLICT) {
1254+
return false;
1255+
}
1256+
Token t = getToken(3);
1257+
if (t.kind == K_DO) {
1258+
return true;
1259+
}
1260+
if (t.kind == K_ON) {
1261+
return getToken(4).kind == K_CONSTRAINT;
1262+
}
1263+
if (!"(".equals(t.image)) {
1264+
return false;
1265+
}
1266+
int depth = 1;
1267+
int i = 4;
1268+
while (depth > 0) {
1269+
t = getToken(i++);
1270+
if (t == null || t.kind == EOF) {
1271+
return false;
1272+
}
1273+
if ("(".equals(t.image)) {
1274+
depth++;
1275+
} else if (")".equals(t.image)) {
1276+
depth--;
1277+
}
1278+
}
1279+
t = getToken(i);
1280+
if (t.kind == K_DO) {
1281+
return true;
1282+
}
1283+
if (t.kind != K_WHERE) {
1284+
return false;
1285+
}
1286+
// the index predicate ends where the conflict action's DO begins
1287+
depth = 0;
1288+
for (i++; ; i++) {
1289+
t = getToken(i);
1290+
if (t == null || t.kind == EOF) {
1291+
return false;
1292+
}
1293+
if ("(".equals(t.image)) {
1294+
depth++;
1295+
} else if (")".equals(t.image)) {
1296+
depth--;
1297+
} else if (t.kind == K_DO && depth == 0) {
1298+
return true;
1299+
}
1300+
}
1301+
}
1302+
12441303
private boolean isMySqlDialect() {
12451304
String dialect = getAsString(Feature.dialect);
12461305
return Dialect.MYSQL.name().equals(dialect) || Dialect.MARIADB.name().equals(dialect);
@@ -6925,7 +6984,7 @@ Join JoinerExpression() #JoinerExpression:
69256984
LOOKAHEAD(2) (
69266985
[ <K_WITHIN> "(" joinWindow = JoinWindow() ")" {join.setJoinWindow(joinWindow);} ]
69276986
( <K_ON> onExpression=Expression() { join.addOnExpression(onExpression); }
6928-
( LOOKAHEAD(2) <K_ON> onExpression=Expression() { join.addOnExpression(onExpression); } )*
6987+
( LOOKAHEAD({ getToken(1).kind == K_ON && !isInsertOnConflictAhead() }) <K_ON> onExpression=Expression() { join.addOnExpression(onExpression); } )*
69296988
)
69306989
|
69316990
(

src/test/java/net/sf/jsqlparser/statement/insert/InsertTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,66 @@ public void testInsertOnConflictIssue1551() throws JSQLParserException {
677677
true);
678678
}
679679

680+
@Test
681+
public void testInsertOnConflictAfterJoinIssue2358() throws JSQLParserException {
682+
String sqlStr = "INSERT INTO conf.supply_info (id, deal_id)\n"
683+
+ "SELECT uuid_generate_v4(), rep.deal_id\n"
684+
+ "FROM conf.reports rep\n"
685+
+ "JOIN conf.orders o ON rep.id = o.report_id\n"
686+
+ "ON CONFLICT DO NOTHING";
687+
Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true);
688+
PlainSelect plainSelect = insert.getSelect().getPlainSelect();
689+
assertEquals(1, plainSelect.getJoins().size());
690+
assertEquals(1, plainSelect.getJoins().get(0).getOnExpressions().size());
691+
assertEquals(ConflictActionType.DO_NOTHING,
692+
insert.getConflictAction().getConflictActionType());
693+
assertNull(insert.getConflictTarget());
694+
695+
assertSqlCanBeParsedAndDeparsed(
696+
"INSERT INTO conf.supply_info (id, deal_id)\n"
697+
+ "SELECT uuid_generate_v4(), rep.deal_id\n"
698+
+ "FROM conf.reports rep\n"
699+
+ "JOIN conf.orders o ON rep.id = o.report_id\n"
700+
+ "ON CONFLICT (id) DO NOTHING",
701+
true);
702+
703+
assertSqlCanBeParsedAndDeparsed(
704+
"INSERT INTO distributors (did, dname)\n"
705+
+ "SELECT did, dname FROM staging\n"
706+
+ "JOIN suppliers s ON staging.did = s.did\n"
707+
+ "ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING",
708+
true);
709+
710+
assertSqlCanBeParsedAndDeparsed(
711+
"INSERT INTO distributors (did, dname)\n"
712+
+ "SELECT did, dname FROM staging\n"
713+
+ "JOIN suppliers s ON staging.did = s.did\n"
714+
+ "ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname",
715+
true);
716+
717+
assertSqlCanBeParsedAndDeparsed(
718+
"INSERT INTO distributors (did, dname)\n"
719+
+ "SELECT did, dname FROM staging\n"
720+
+ "JOIN suppliers s ON staging.did = s.did\n"
721+
+ "ON CONFLICT (did) WHERE is_active DO NOTHING",
722+
true);
723+
}
724+
725+
@Test
726+
public void testInsertSelectWithTrailingOnExpressionsIssue2358() throws JSQLParserException {
727+
String sqlStr = "INSERT INTO supply_info (id, deal_id)\n"
728+
+ "SELECT rep.deal_id, o.total\n"
729+
+ "FROM reports rep\n"
730+
+ "JOIN orders o ON rep.id = o.report_id\n"
731+
+ "ON o.total > 100";
732+
Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true);
733+
PlainSelect plainSelect = insert.getSelect().getPlainSelect();
734+
assertEquals(1, plainSelect.getJoins().size());
735+
assertEquals(2, plainSelect.getJoins().get(0).getOnExpressions().size());
736+
assertNull(insert.getConflictAction());
737+
assertNull(insert.getConflictTarget());
738+
}
739+
680740
@Test
681741
public void insertOnConflictObjectsTest() throws JSQLParserException {
682742
String sqlStr = "WITH a ( a, b , c ) \n" + "AS (SELECT 1 , 2 , 3 )\n"

0 commit comments

Comments
 (0)