Consider CONFLICT clause on INSERT in HQL.
Closes: #3689 Original Pull Request: #3691
This commit is contained in:
committed by
Christoph Strobl
parent
7afb8d4747
commit
34dc005af1
@@ -147,7 +147,7 @@ deleteStatement
|
||||
|
||||
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-insert
|
||||
insertStatement
|
||||
: INSERT INTO? targetEntity targetFields (queryExpression | valuesList)
|
||||
: INSERT INTO? targetEntity targetFields (queryExpression | valuesList) conflictClause?
|
||||
;
|
||||
|
||||
// Already defined underneath updateStatement
|
||||
@@ -167,6 +167,23 @@ values
|
||||
: '(' expression (',' expression)* ')'
|
||||
;
|
||||
|
||||
/**
|
||||
* a 'conflict' clause in an 'insert' statement
|
||||
*/
|
||||
conflictClause
|
||||
: ON CONFLICT conflictTarget? DO conflictAction
|
||||
;
|
||||
|
||||
conflictTarget
|
||||
: ON CONSTRAINT identifier
|
||||
| '(' simplePath (',' simplePath)* ')'
|
||||
;
|
||||
|
||||
conflictAction
|
||||
: NOTHING
|
||||
| UPDATE setClause whereClause?
|
||||
;
|
||||
|
||||
instantiation
|
||||
: NEW instantiationTarget '(' instantiationArguments ')'
|
||||
;
|
||||
@@ -921,7 +938,11 @@ CURRENT_DATE : C U R R E N T '_' D A T E;
|
||||
CURRENT_INSTANT : C U R R E N T '_' I N S T A N T;
|
||||
CURRENT_TIME : C U R R E N T '_' T I M E;
|
||||
CURRENT_TIMESTAMP : C U R R E N T '_' T I M E S T A M P;
|
||||
CONFLICT : C O N F L I C T;
|
||||
CONSTRAINT : C O N S T R A I N T;
|
||||
COLUMN : C O L U M N;
|
||||
CYCLE : C Y C L E;
|
||||
DO : D O;
|
||||
DATE : D A T E;
|
||||
DATETIME : D A T E T I M E ;
|
||||
DAY : D A Y;
|
||||
@@ -1009,6 +1030,7 @@ NEW : N E W;
|
||||
NEXT : N E X T;
|
||||
NO : N O;
|
||||
NOT : N O T;
|
||||
NOTHING : N O T H I N G;
|
||||
NULL : N U L L;
|
||||
NULLS : N U L L S;
|
||||
OBJECT : O B J E C T;
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an HQL query without making any changes.
|
||||
@@ -545,6 +546,10 @@ class HqlQueryRenderer extends HqlBaseVisitor<QueryTokenStream> {
|
||||
builder.appendExpression(visit(ctx.valuesList()));
|
||||
}
|
||||
|
||||
if (ctx.conflictClause() != null) {
|
||||
builder.appendExpression(visit(ctx.conflictClause()));
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -583,6 +588,66 @@ class HqlQueryRenderer extends HqlBaseVisitor<QueryTokenStream> {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryTokenStream visitConflictClause(HqlParser.ConflictClauseContext ctx) {
|
||||
|
||||
QueryRendererBuilder builder = QueryRenderer.builder();
|
||||
|
||||
builder.append(QueryTokens.expression(ctx.ON()));
|
||||
builder.append(QueryTokens.expression(ctx.CONFLICT()));
|
||||
|
||||
if (ctx.conflictTarget() != null) {
|
||||
builder.appendExpression(visit(ctx.conflictTarget()));
|
||||
}
|
||||
|
||||
builder.append(QueryTokens.expression(ctx.DO()));
|
||||
builder.appendExpression(visit(ctx.conflictAction()));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryTokenStream visitConflictTarget(HqlParser.ConflictTargetContext ctx) {
|
||||
|
||||
QueryRendererBuilder builder = QueryRenderer.builder();
|
||||
|
||||
if (ctx.identifier() != null) {
|
||||
|
||||
builder.append(QueryTokens.expression(ctx.ON()));
|
||||
builder.append(QueryTokens.expression(ctx.CONSTRAINT()));
|
||||
builder.appendExpression(visit(ctx.identifier()));
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(ctx.simplePath())) {
|
||||
|
||||
builder.append(TOKEN_OPEN_PAREN);
|
||||
builder.append(QueryTokenStream.concat(ctx.simplePath(), this::visit, TOKEN_COMMA));
|
||||
|
||||
builder.append(TOKEN_CLOSE_PAREN);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryTokenStream visitConflictAction(HqlParser.ConflictActionContext ctx) {
|
||||
|
||||
QueryRendererBuilder builder = QueryRenderer.builder();
|
||||
|
||||
if (ctx.NOTHING() != null) {
|
||||
builder.append(QueryTokens.expression(ctx.NOTHING()));
|
||||
} else {
|
||||
builder.append(QueryTokens.expression(ctx.UPDATE()));
|
||||
builder.appendExpression(visit(ctx.setClause()));
|
||||
|
||||
if (ctx.whereClause() != null) {
|
||||
builder.appendExpression(visit(ctx.whereClause()));
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryTokenStream visitInstantiation(HqlParser.InstantiationContext ctx) {
|
||||
|
||||
|
||||
@@ -983,6 +983,29 @@ class HqlSpecificationTests {
|
||||
""");
|
||||
}
|
||||
|
||||
@Test // GH-3689
|
||||
void insertQueries() {
|
||||
|
||||
assertQuery("insert Person (id, name) values (100L, 'Jane Doe')");
|
||||
|
||||
assertQuery("insert Person (id, name) values " + //
|
||||
"(101L, 'J A Doe III'), " + //
|
||||
"(102L, 'J X Doe'), " + //
|
||||
"(103L, 'John Doe, Jr')");
|
||||
|
||||
assertQuery("insert into Partner (id, name) " + //
|
||||
"select p.id, p.name from Person p ");
|
||||
|
||||
assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) "
|
||||
+ "ON CONFLICT (range) DO UPDATE SET price = :price, type = :priceType");
|
||||
|
||||
assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) "
|
||||
+ "ON CONFLICT ON CONSTRAINT foo DO UPDATE SET price = :price, type = :priceType");
|
||||
|
||||
assertQuery("INSERT INTO AggregationPrice (range, price, type) " + "VALUES (:range, :price, :priceType) "
|
||||
+ "ON CONFLICT ON CONSTRAINT foo DO NOTHING");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hqlQueries() {
|
||||
|
||||
@@ -1000,15 +1023,7 @@ class HqlSpecificationTests {
|
||||
assertQuery("update versioned Person " + //
|
||||
"set name = :newName " + //
|
||||
"where name = :oldName");
|
||||
assertQuery("insert Person (id, name) " + //
|
||||
"values (100L, 'Jane Doe')");
|
||||
assertQuery("insert Person (id, name) " + //
|
||||
"values (101L, 'J A Doe III'), " + //
|
||||
"(102L, 'J X Doe'), " + //
|
||||
"(103L, 'John Doe, Jr')");
|
||||
assertQuery("insert into Partner (id, name) " + //
|
||||
"select p.id, p.name " + //
|
||||
"from Person p ");
|
||||
|
||||
assertQuery("select p " + //
|
||||
"from Person p " + //
|
||||
"where p.name like 'Joe'");
|
||||
|
||||
Reference in New Issue
Block a user