Skip to content

Commit ae37fd2

Browse files
authored
fix(parser): parse INSERT ON CONFLICT after a join's trailing ON (#2551)
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 8b3d35a commit ae37fd2

2 files changed

Lines changed: 172 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: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,118 @@ 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+
assertSqlCanBeParsedAndDeparsed(
725+
"INSERT INTO conf.supply_info (id, deal_id)\n"
726+
+ "SELECT uuid_generate_v4(), rep.deal_id\n"
727+
+ "FROM conf.reports rep\n"
728+
+ "JOIN conf.orders o ON rep.id = o.report_id\n"
729+
+ "ON CONFLICT DO UPDATE SET deal_id = excluded.deal_id",
730+
true);
731+
732+
assertSqlCanBeParsedAndDeparsed(
733+
"INSERT INTO conf.supply_info (id, deal_id)\n"
734+
+ "SELECT uuid_generate_v4(), rep.deal_id\n"
735+
+ "FROM conf.reports rep\n"
736+
+ "JOIN conf.orders o ON rep.id = o.report_id\n"
737+
+ "ON CONFLICT (id, deal_id) DO NOTHING",
738+
true);
739+
740+
assertSqlCanBeParsedAndDeparsed(
741+
"INSERT INTO supply_info (id, deal_id)\n"
742+
+ "SELECT rep.deal_id, o.total\n"
743+
+ "FROM reports rep\n"
744+
+ "JOIN orders o ON rep.id = o.report_id\n"
745+
+ "JOIN regions r ON o.region = r.id\n"
746+
+ "ON CONFLICT DO NOTHING",
747+
true);
748+
749+
assertSqlCanBeParsedAndDeparsed(
750+
"INSERT INTO conf.supply_info (id, deal_id)\n"
751+
+ "SELECT uuid_generate_v4(), rep.deal_id\n"
752+
+ "FROM conf.reports rep\n"
753+
+ "JOIN conf.orders o ON rep.id = o.report_id\n"
754+
+ "ON CONFLICT (id) WHERE (deal_id IN (1, 2)) DO NOTHING",
755+
true);
756+
}
757+
758+
@Test
759+
public void testInsertSelectWithTrailingOnExpressionsIssue2358() throws JSQLParserException {
760+
String sqlStr = "INSERT INTO supply_info (id, deal_id)\n"
761+
+ "SELECT rep.deal_id, o.total\n"
762+
+ "FROM reports rep\n"
763+
+ "JOIN orders o ON rep.id = o.report_id\n"
764+
+ "ON o.total > 100";
765+
Insert insert = (Insert) assertSqlCanBeParsedAndDeparsed(sqlStr, true);
766+
PlainSelect plainSelect = insert.getSelect().getPlainSelect();
767+
assertEquals(1, plainSelect.getJoins().size());
768+
assertEquals(2, plainSelect.getJoins().get(0).getOnExpressions().size());
769+
assertNull(insert.getConflictAction());
770+
assertNull(insert.getConflictTarget());
771+
772+
insert = (Insert) assertSqlCanBeParsedAndDeparsed(
773+
"INSERT INTO supply_info (id, deal_id)\n"
774+
+ "SELECT rep.deal_id, o.total\n"
775+
+ "FROM reports rep\n"
776+
+ "JOIN orders o ON rep.id = o.report_id\n"
777+
+ "ON conflict",
778+
true);
779+
assertEquals(2,
780+
insert.getSelect().getPlainSelect().getJoins().get(0).getOnExpressions().size());
781+
782+
insert = (Insert) assertSqlCanBeParsedAndDeparsed(
783+
"INSERT INTO t (a)\n"
784+
+ "SELECT x FROM u\n"
785+
+ "JOIN v USING (id)\n"
786+
+ "ON CONFLICT DO NOTHING",
787+
true);
788+
assertEquals(ConflictActionType.DO_NOTHING,
789+
insert.getConflictAction().getConflictActionType());
790+
}
791+
680792
@Test
681793
public void insertOnConflictObjectsTest() throws JSQLParserException {
682794
String sqlStr = "WITH a ( a, b , c ) \n" + "AS (SELECT 1 , 2 , 3 )\n"

0 commit comments

Comments
 (0)