From 98e028c51d5dd8341626e05ff6433e078dd2f958 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 20 Jun 2023 18:35:50 +0200 Subject: [PATCH] HHH-14483 Split insert-values statement if dialect doesn't support values lists --- .../dialect/OracleLegacySqlAstTranslator.java | 21 +------- .../dialect/SybaseASELegacyDialect.java | 5 -- .../SybaseASELegacySqlAstTranslator.java | 6 +++ .../dialect/AbstractHANADialect.java | 5 -- .../dialect/HANASqlAstTranslator.java | 8 +++ .../dialect/OracleSqlAstTranslator.java | 22 +-------- .../hibernate/dialect/SybaseASEDialect.java | 5 -- .../dialect/SybaseASESqlAstTranslator.java | 6 +++ .../query/sqm/internal/QuerySqmImpl.java | 27 ++++++++-- .../tree/insert/SqmInsertValuesStatement.java | 15 ++++++ .../sql/ast/spi/AbstractSqlAstTranslator.java | 49 ++++++++++++++----- .../org/hibernate/orm/test/hql/HQLTest.java | 2 - .../orm/test/query/hql/InsertUpdateTests.java | 3 -- 13 files changed, 97 insertions(+), 77 deletions(-) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacySqlAstTranslator.java index 887ee2f140..a7b44376ae 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacySqlAstTranslator.java @@ -225,26 +225,7 @@ public class OracleLegacySqlAstTranslator extends Abstr @Override protected void visitValuesList(List 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 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 diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacyDialect.java index bf1b99965c..9a8a9b9ed0 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacyDialect.java @@ -546,11 +546,6 @@ public class SybaseASELegacyDialect extends SybaseLegacyDialect { return 255; } - @Override - public boolean supportsValuesListForInsert() { - return false; - } - @Override public boolean supportsLockTimeouts() { return false; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacySqlAstTranslator.java index 17b1e501f7..8f933a3d11 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseASELegacySqlAstTranslator.java @@ -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 extends Ab } } + @Override + protected void visitValuesList(List valuesList) { + visitValuesListEmulateSelectUnion( valuesList ); + } + @Override public void visitOffsetFetchClause(QueryPart queryPart) { assertRowsOnlyFetchClauseType( queryPart ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index af15cd8e9c..7b0bcd7af1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -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 diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java index 550af48704..4a0cc3af5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HANASqlAstTranslator.java @@ -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 extends AbstractSqlAs ) ); } + + @Override + protected void visitValuesList(List valuesList) { + visitValuesListEmulateSelectUnion( valuesList ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java index f69eff7022..460db6cc4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java @@ -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 extends SqlAstTrans @Override protected void visitValuesList(List 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 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 diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java index 5b634f62b3..8b3dc6d751 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java @@ -554,11 +554,6 @@ public class SybaseASEDialect extends SybaseDialect { return 255; } - @Override - public boolean supportsValuesListForInsert() { - return false; - } - @Override public boolean supportsLockTimeouts() { return false; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java index 5782f7399c..7c0ea62041 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASESqlAstTranslator.java @@ -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 extends Abstract } } + @Override + protected void visitValuesList(List valuesList) { + visitValuesListEmulateSelectUnion( valuesList ); + } + @Override public void visitOffsetFetchClause(QueryPart queryPart) { assertRowsOnlyFetchClauseType( queryPart ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index 25207131f2..64b0fae537 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -817,9 +817,30 @@ public class QuerySqmImpl } } } - 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) sqmInsert ).getValuesList().size() != 1 + && !getSessionFactory().getJdbcServices().getDialect().supportsValuesListForInsert() ) { + // Split insert-values queries if the dialect doesn't support values lists + final SqmInsertValuesStatement insertValues = (SqmInsertValuesStatement) sqmInsert; + final List 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) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java index 5adbf4d7ad..f450c8b0be 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/insert/SqmInsertValuesStatement.java @@ -69,6 +69,21 @@ public class SqmInsertValuesStatement extends AbstractSqmInsertStatement { ); } + public SqmInsertValuesStatement copyWithoutValues(SqmCopyContext context) { + return context.registerCopy( + this, + new SqmInsertValuesStatement<>( + nodeBuilder(), + getQuerySource(), + copyParameters( context ), + copyCteStatements( context ), + getTarget().copy( context ), + copyInsertionTargetPaths( context ), + new ArrayList<>() + ) + ); + } + public List getValuesList() { return valuesList; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index daf450a596..69bf601f16 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -1169,28 +1169,28 @@ public abstract class AbstractSqlAstTranslator implemen } protected void visitValuesList(List valuesList) { + visitValuesListStandard( valuesList ); + } + + protected final void visitValuesListStandard(List 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 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 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 implemen } } + protected void visitValuesListEmulateSelectUnion(List 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 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 ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java index 45969f7028..19516462d6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/HQLTest.java @@ -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[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java index f9ce58a1f0..3a7ce9d935 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertUpdateTests.java @@ -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 -> {