HHH-14483 Split insert-values statement if dialect doesn't support values lists

This commit is contained in:
Christian Beikov 2023-06-20 18:35:50 +02:00
parent 88e07652c9
commit 98e028c51d
13 changed files with 97 additions and 77 deletions

View File

@ -225,26 +225,7 @@ public class OracleLegacySqlAstTranslator<T extends JdbcOperation> extends Abstr
@Override
protected void visitValuesList(List<Values> valuesList) {
if ( valuesList.size() < 2 ) {
super.visitValuesList( valuesList );
}
else {
// Oracle doesn't support a multi-values insert
// So we render a select union emulation instead
String separator = "";
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.VALUES );
for ( Values values : valuesList ) {
appendSql( separator );
renderExpressionsAsSubquery( values.getExpressions() );
separator = " union all ";
}
}
finally {
clauseStack.pop();
}
}
visitValuesListEmulateSelectUnion( valuesList );
}
@Override

View File

@ -546,11 +546,6 @@ public class SybaseASELegacyDialect extends SybaseLegacyDialect {
return 255;
}
@Override
public boolean supportsValuesListForInsert() {
return false;
}
@Override
public boolean supportsLockTimeouts() {
return false;

View File

@ -33,6 +33,7 @@ import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
@ -253,6 +254,11 @@ public class SybaseASELegacySqlAstTranslator<T extends JdbcOperation> extends Ab
}
}
@Override
protected void visitValuesList(List<Values> valuesList) {
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );

View File

@ -1135,11 +1135,6 @@ public abstract class AbstractHANADialect extends Dialect {
return false;
}
@Override
public boolean supportsValuesListForInsert() {
return false;
}
@Override
public boolean supportsOrderByInSubquery() {
// Seems to work, though I don't know as of which version

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.dialect;
import java.util.List;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
@ -20,6 +22,7 @@ import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.FunctionTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
@ -157,4 +160,9 @@ public class HANASqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
)
);
}
@Override
protected void visitValuesList(List<Values> valuesList) {
visitValuesListEmulateSelectUnion( valuesList );
}
}

View File

@ -10,7 +10,6 @@ import java.util.ArrayList;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -170,26 +169,7 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTrans
@Override
protected void visitValuesList(List<Values> valuesList) {
if ( valuesList.size() < 2 ) {
super.visitValuesList( valuesList );
}
else {
// Oracle doesn't support a multi-values insert
// So we render a select union emulation instead
String separator = "";
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.VALUES );
for ( Values values : valuesList ) {
appendSql( separator );
renderExpressionsAsSubquery( values.getExpressions() );
separator = " union all ";
}
}
finally {
clauseStack.pop();
}
}
visitValuesListEmulateSelectUnion( valuesList );
}
@Override

View File

@ -554,11 +554,6 @@ public class SybaseASEDialect extends SybaseDialect {
return 255;
}
@Override
public boolean supportsValuesListForInsert() {
return false;
}
@Override
public boolean supportsLockTimeouts() {
return false;

View File

@ -33,6 +33,7 @@ import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.UnionTableReference;
import org.hibernate.sql.ast.tree.insert.Values;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
@ -251,6 +252,11 @@ public class SybaseASESqlAstTranslator<T extends JdbcOperation> extends Abstract
}
}
@Override
protected void visitValuesList(List<Values> valuesList) {
visitValuesListEmulateSelectUnion( valuesList );
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
assertRowsOnlyFetchClauseType( queryPart );

View File

@ -817,9 +817,30 @@ public class QuerySqmImpl<R>
}
}
}
return !useMultiTableInsert
? new SimpleInsertQueryPlan( sqmInsert, domainParameterXref )
: new MultiTableInsertQueryPlan( sqmInsert, domainParameterXref, persister.getSqmMultiTableInsertStrategy() );
if ( useMultiTableInsert ) {
return new MultiTableInsertQueryPlan(
sqmInsert,
domainParameterXref,
persister.getSqmMultiTableInsertStrategy()
);
}
else if ( sqmInsert instanceof SqmInsertValuesStatement<?>
&& ( (SqmInsertValuesStatement<R>) sqmInsert ).getValuesList().size() != 1
&& !getSessionFactory().getJdbcServices().getDialect().supportsValuesListForInsert() ) {
// Split insert-values queries if the dialect doesn't support values lists
final SqmInsertValuesStatement<R> insertValues = (SqmInsertValuesStatement<R>) sqmInsert;
final List<SqmValues> valuesList = insertValues.getValuesList();
final NonSelectQueryPlan[] planParts = new NonSelectQueryPlan[valuesList.size()];
for ( int i = 0; i < valuesList.size(); i++ ) {
final SqmInsertValuesStatement<?> subInsert = insertValues.copyWithoutValues( SqmCopyContext.simpleContext() );
subInsert.getValuesList().add( valuesList.get( i ) );
planParts[i] = new SimpleInsertQueryPlan( subInsert, domainParameterXref );
}
return new AggregatedNonSelectQueryPlanImpl( planParts );
}
return new SimpleInsertQueryPlan( sqmInsert, domainParameterXref );
}
protected boolean hasIdentifierAssigned(SqmInsertStatement<?> sqmInsert, EntityPersister entityDescriptor) {

View File

@ -69,6 +69,21 @@ public class SqmInsertValuesStatement<T> extends AbstractSqmInsertStatement<T> {
);
}
public SqmInsertValuesStatement<T> copyWithoutValues(SqmCopyContext context) {
return context.registerCopy(
this,
new SqmInsertValuesStatement<>(
nodeBuilder(),
getQuerySource(),
copyParameters( context ),
copyCteStatements( context ),
getTarget().copy( context ),
copyInsertionTargetPaths( context ),
new ArrayList<>()
)
);
}
public List<SqmValues> getValuesList() {
return valuesList;
}

View File

@ -1169,28 +1169,28 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
protected void visitValuesList(List<Values> valuesList) {
visitValuesListStandard( valuesList );
}
protected final void visitValuesListStandard(List<Values> valuesList) {
if ( valuesList.size() != 1 && !dialect.supportsValuesListForInsert() ) {
throw new IllegalQueryOperationException( "Dialect does not support values lists for insert statements" );
}
appendSql("values");
boolean firstTuple = true;
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.VALUES );
for ( Values values : valuesList ) {
if ( firstTuple ) {
firstTuple = false;
}
else {
for ( int i = 0; i < valuesList.size(); i++ ) {
if ( i != 0 ) {
appendSql( COMMA_SEPARATOR_CHAR );
}
appendSql( " (" );
boolean firstExpr = true;
for ( Expression expression : values.getExpressions() ) {
if ( firstExpr ) {
firstExpr = false;
}
else {
final List<Expression> expressions = valuesList.get( i ).getExpressions();
for ( int j = 0; j < expressions.size(); j++ ) {
if ( j != 0 ) {
appendSql( COMMA_SEPARATOR_CHAR );
}
expression.accept( this );
expressions.get( j ).accept( this );
}
appendSql( ')' );
}
@ -1200,6 +1200,29 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
protected void visitValuesListEmulateSelectUnion(List<Values> valuesList) {
if ( valuesList.size() < 2 ) {
visitValuesListStandard( valuesList );
}
else {
// Oracle doesn't support a multi-values insert
// So we render a select union emulation instead
String separator = "";
final Stack<Clause> clauseStack = getClauseStack();
try {
clauseStack.push( Clause.VALUES );
for ( int i = 0; i < valuesList.size(); i++ ) {
appendSql( separator );
renderExpressionsAsSubquery( valuesList.get( i ).getExpressions() );
separator = " union all ";
}
}
finally {
clauseStack.pop();
}
}
}
protected void visitForUpdateClause(QuerySpec querySpec) {
if ( querySpec.isRoot() ) {
if ( forUpdate != null ) {

View File

@ -51,7 +51,6 @@ import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -252,7 +251,6 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
}
@Test
@RequiresDialectFeature(DialectChecks.SupportsValuesListForInsert.class)
public void hql_multi_insert_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-insert-example[]

View File

@ -9,9 +9,7 @@ package org.hibernate.orm.test.query.hql;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.contacts.Contact;
import org.hibernate.testing.orm.domain.contacts.Contact.Name;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@ -91,7 +89,6 @@ public class InsertUpdateTests {
}
@Test
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsValuesListForInsert.class)
public void testInsertMultipleValues(SessionFactoryScope scope) {
scope.inTransaction(
session -> {