Skip to content

Commit 34dc005

Browse files
mp911dechristophstrobl
authored andcommitted
Consider CONFLICT clause on INSERT in HQL.
Closes: #3689 Original Pull Request: #3691
1 parent 7afb8d4 commit 34dc005

File tree

3 files changed

+112
-10
lines changed

3 files changed

+112
-10
lines changed

spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4

+23-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ deleteStatement
147147

148148
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-insert
149149
insertStatement
150-
: INSERT INTO? targetEntity targetFields (queryExpression | valuesList)
150+
: INSERT INTO? targetEntity targetFields (queryExpression | valuesList) conflictClause?
151151
;
152152

153153
// Already defined underneath updateStatement
@@ -167,6 +167,23 @@ values
167167
: '(' expression (',' expression)* ')'
168168
;
169169

170+
/**
171+
* a 'conflict' clause in an 'insert' statement
172+
*/
173+
conflictClause
174+
: ON CONFLICT conflictTarget? DO conflictAction
175+
;
176+
177+
conflictTarget
178+
: ON CONSTRAINT identifier
179+
| '(' simplePath (',' simplePath)* ')'
180+
;
181+
182+
conflictAction
183+
: NOTHING
184+
| UPDATE setClause whereClause?
185+
;
186+
170187
instantiation
171188
: NEW instantiationTarget '(' instantiationArguments ')'
172189
;
@@ -921,7 +938,11 @@ CURRENT_DATE : C U R R E N T '_' D A T E;
921938
CURRENT_INSTANT : C U R R E N T '_' I N S T A N T;
922939
CURRENT_TIME : C U R R E N T '_' T I M E;
923940
CURRENT_TIMESTAMP : C U R R E N T '_' T I M E S T A M P;
941+
CONFLICT : C O N F L I C T;
942+
CONSTRAINT : C O N S T R A I N T;
943+
COLUMN : C O L U M N;
924944
CYCLE : C Y C L E;
945+
DO : D O;
925946
DATE : D A T E;
926947
DATETIME : D A T E T I M E ;
927948
DAY : D A Y;
@@ -1009,6 +1030,7 @@ NEW : N E W;
10091030
NEXT : N E X T;
10101031
NO : N O;
10111032
NOT : N O T;
1033+
NOTHING : N O T H I N G;
10121034
NULL : N U L L;
10131035
NULLS : N U L L S;
10141036
OBJECT : O B J E C T;

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java

+65
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.antlr.v4.runtime.tree.ParseTree;
2525

2626
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
27+
import org.springframework.util.ObjectUtils;
2728

2829
/**
2930
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an HQL query without making any changes.
@@ -545,6 +546,10 @@ public QueryTokenStream visitInsertStatement(HqlParser.InsertStatementContext ct
545546
builder.appendExpression(visit(ctx.valuesList()));
546547
}
547548

549+
if (ctx.conflictClause() != null) {
550+
builder.appendExpression(visit(ctx.conflictClause()));
551+
}
552+
548553
return builder;
549554
}
550555

@@ -583,6 +588,66 @@ public QueryTokenStream visitValues(HqlParser.ValuesContext ctx) {
583588
return builder;
584589
}
585590

591+
@Override
592+
public QueryTokenStream visitConflictClause(HqlParser.ConflictClauseContext ctx) {
593+
594+
QueryRendererBuilder builder = QueryRenderer.builder();
595+
596+
builder.append(QueryTokens.expression(ctx.ON()));
597+
builder.append(QueryTokens.expression(ctx.CONFLICT()));
598+
599+
if (ctx.conflictTarget() != null) {
600+
builder.appendExpression(visit(ctx.conflictTarget()));
601+
}
602+
603+
builder.append(QueryTokens.expression(ctx.DO()));
604+
builder.appendExpression(visit(ctx.conflictAction()));
605+
606+
return builder;
607+
}
608+
609+
@Override
610+
public QueryTokenStream visitConflictTarget(HqlParser.ConflictTargetContext ctx) {
611+
612+
QueryRendererBuilder builder = QueryRenderer.builder();
613+
614+
if (ctx.identifier() != null) {
615+
616+
builder.append(QueryTokens.expression(ctx.ON()));
617+
builder.append(QueryTokens.expression(ctx.CONSTRAINT()));
618+
builder.appendExpression(visit(ctx.identifier()));
619+
}
620+
621+
if (!ObjectUtils.isEmpty(ctx.simplePath())) {
622+
623+
builder.append(TOKEN_OPEN_PAREN);
624+
builder.append(QueryTokenStream.concat(ctx.simplePath(), this::visit, TOKEN_COMMA));
625+
626+
builder.append(TOKEN_CLOSE_PAREN);
627+
}
628+
629+
return builder;
630+
}
631+
632+
@Override
633+
public QueryTokenStream visitConflictAction(HqlParser.ConflictActionContext ctx) {
634+
635+
QueryRendererBuilder builder = QueryRenderer.builder();
636+
637+
if (ctx.NOTHING() != null) {
638+
builder.append(QueryTokens.expression(ctx.NOTHING()));
639+
} else {
640+
builder.append(QueryTokens.expression(ctx.UPDATE()));
641+
builder.appendExpression(visit(ctx.setClause()));
642+
643+
if (ctx.whereClause() != null) {
644+
builder.appendExpression(visit(ctx.whereClause()));
645+
}
646+
}
647+
648+
return builder;
649+
}
650+
586651
@Override
587652
public QueryTokenStream visitInstantiation(HqlParser.InstantiationContext ctx) {
588653

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlSpecificationTests.java

+24-9
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,29 @@ void theRest38() {
983983
""");
984984
}
985985

986+
@Test // GH-3689
987+
void insertQueries() {
988+
989+
assertQuery("insert Person (id, name) values (100L, 'Jane Doe')");
990+
991+
assertQuery("insert Person (id, name) values " + //
992+
"(101L, 'J A Doe III'), " + //
993+
"(102L, 'J X Doe'), " + //
994+
"(103L, 'John Doe, Jr')");
995+
996+
assertQuery("insert into Partner (id, name) " + //
997+
"select p.id, p.name from Person p ");
998+
999+
assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) "
1000+
+ "ON CONFLICT (range) DO UPDATE SET price = :price, type = :priceType");
1001+
1002+
assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) "
1003+
+ "ON CONFLICT ON CONSTRAINT foo DO UPDATE SET price = :price, type = :priceType");
1004+
1005+
assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) "
1006+
+ "ON CONFLICT ON CONSTRAINT foo DO NOTHING");
1007+
}
1008+
9861009
@Test
9871010
void hqlQueries() {
9881011

@@ -1000,15 +1023,7 @@ void hqlQueries() {
10001023
assertQuery("update versioned Person " + //
10011024
"set name = :newName " + //
10021025
"where name = :oldName");
1003-
assertQuery("insert Person (id, name) " + //
1004-
"values (100L, 'Jane Doe')");
1005-
assertQuery("insert Person (id, name) " + //
1006-
"values (101L, 'J A Doe III'), " + //
1007-
"(102L, 'J X Doe'), " + //
1008-
"(103L, 'John Doe, Jr')");
1009-
assertQuery("insert into Partner (id, name) " + //
1010-
"select p.id, p.name " + //
1011-
"from Person p ");
1026+
10121027
assertQuery("select p " + //
10131028
"from Person p " + //
10141029
"where p.name like 'Joe'");

0 commit comments

Comments
 (0)