HHH-16103 - MERGE for optional table updates on SQL Server
This commit is contained in:
parent
5525b8d9b7
commit
e27dc5bc47
|
@ -4705,9 +4705,9 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link MutationOperation} for an "upsert".
|
* Create a {@link MutationOperation} for a updating an optional table
|
||||||
*/
|
*/
|
||||||
public MutationOperation createUpsertOperation(
|
public MutationOperation createOptionalTableUpdateOperation(
|
||||||
EntityMutationTarget mutationTarget,
|
EntityMutationTarget mutationTarget,
|
||||||
OptionalTableUpdate optionalTableUpdate,
|
OptionalTableUpdate optionalTableUpdate,
|
||||||
SessionFactoryImplementor factory) {
|
SessionFactoryImplementor factory) {
|
||||||
|
|
|
@ -872,11 +872,11 @@ public class H2Dialect extends Dialect {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MutationOperation createUpsertOperation(
|
public MutationOperation createOptionalTableUpdateOperation(
|
||||||
EntityMutationTarget mutationTarget,
|
EntityMutationTarget mutationTarget,
|
||||||
OptionalTableUpdate optionalTableUpdate,
|
OptionalTableUpdate optionalTableUpdate,
|
||||||
SessionFactoryImplementor factory) {
|
SessionFactoryImplementor factory) {
|
||||||
final H2SqlAstTranslator<?> translator = new H2SqlAstTranslator<>( factory, optionalTableUpdate );
|
final H2SqlAstTranslator<?> translator = new H2SqlAstTranslator<>( factory, optionalTableUpdate );
|
||||||
return translator.visitUpsert( optionalTableUpdate );
|
return translator.createMergeOperation( optionalTableUpdate );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.query.sqm.ComparisonOperator;
|
import org.hibernate.query.sqm.ComparisonOperator;
|
||||||
import org.hibernate.sql.ast.Clause;
|
import org.hibernate.sql.ast.Clause;
|
||||||
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
|
||||||
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
|
||||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||||
import org.hibernate.sql.ast.tree.Statement;
|
import org.hibernate.sql.ast.tree.Statement;
|
||||||
import org.hibernate.sql.ast.tree.cte.CteContainer;
|
import org.hibernate.sql.ast.tree.cte.CteContainer;
|
||||||
|
@ -35,11 +34,7 @@ import org.hibernate.sql.ast.tree.predicate.LikePredicate;
|
||||||
import org.hibernate.sql.ast.tree.select.QueryPart;
|
import org.hibernate.sql.ast.tree.select.QueryPart;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectClause;
|
import org.hibernate.sql.ast.tree.select.SelectClause;
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperation;
|
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||||
import org.hibernate.sql.model.MutationOperation;
|
|
||||||
import org.hibernate.sql.model.ast.ColumnValueBinding;
|
|
||||||
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
|
||||||
import org.hibernate.sql.model.internal.TableInsertStandard;
|
import org.hibernate.sql.model.internal.TableInsertStandard;
|
||||||
import org.hibernate.sql.model.jdbc.UpsertOperation;
|
|
||||||
|
|
||||||
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
|
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
|
||||||
|
|
||||||
|
@ -48,7 +43,7 @@ import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpt
|
||||||
*
|
*
|
||||||
* @author Christian Beikov
|
* @author Christian Beikov
|
||||||
*/
|
*/
|
||||||
public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
|
public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslatorWithMerge<T> {
|
||||||
|
|
||||||
private boolean renderAsArray;
|
private boolean renderAsArray;
|
||||||
|
|
||||||
|
@ -311,148 +306,4 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstT
|
||||||
// Introduction of PERCENT support https://github.com/h2database/h2database/commit/f45913302e5f6ad149155a73763c0c59d8205849
|
// Introduction of PERCENT support https://github.com/h2database/h2database/commit/f45913302e5f6ad149155a73763c0c59d8205849
|
||||||
return getDialect().getVersion().isSameOrAfter( 1, 4, 198 );
|
return getDialect().getVersion().isSameOrAfter( 1, 4, 198 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutationOperation visitUpsert(OptionalTableUpdate optionalTableUpdate) {
|
|
||||||
// template:
|
|
||||||
//
|
|
||||||
// merge into [table] as t
|
|
||||||
// using values([bindings]) as s ([column-names])
|
|
||||||
// on t.[key] = s.[key]
|
|
||||||
// when not matched
|
|
||||||
// then insert ...
|
|
||||||
// when matched
|
|
||||||
// and s.[columns] is null
|
|
||||||
// then delete
|
|
||||||
// when matched
|
|
||||||
// then update set ...
|
|
||||||
|
|
||||||
renderMergeInto( optionalTableUpdate );
|
|
||||||
appendSql( " " );
|
|
||||||
renderMergeUsing( optionalTableUpdate );
|
|
||||||
appendSql( " " );
|
|
||||||
renderMergeOn( optionalTableUpdate );
|
|
||||||
appendSql( " " );
|
|
||||||
renderMergeInsert( optionalTableUpdate );
|
|
||||||
appendSql( " " );
|
|
||||||
renderMergeDelete( optionalTableUpdate );
|
|
||||||
appendSql( " " );
|
|
||||||
renderMergeUpdate( optionalTableUpdate );
|
|
||||||
|
|
||||||
return new UpsertOperation(
|
|
||||||
optionalTableUpdate.getMutatingTable().getTableMapping(),
|
|
||||||
optionalTableUpdate.getMutationTarget(),
|
|
||||||
getSql(),
|
|
||||||
getParameterBinders()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
|
|
||||||
appendSql( "merge into " );
|
|
||||||
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
|
|
||||||
appendSql( " as t" );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderMergeUsing(OptionalTableUpdate optionalTableUpdate) {
|
|
||||||
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
|
||||||
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
|
||||||
|
|
||||||
final StringBuilder columnList = new StringBuilder();
|
|
||||||
|
|
||||||
appendSql( "using values (" );
|
|
||||||
|
|
||||||
for ( int i = 0; i < keyBindings.size(); i++ ) {
|
|
||||||
final ColumnValueBinding keyBinding = keyBindings.get( i );
|
|
||||||
if ( i > 0 ) {
|
|
||||||
appendSql( ", " );
|
|
||||||
columnList.append( ", " );
|
|
||||||
}
|
|
||||||
columnList.append( keyBinding.getColumnReference().getColumnExpression() );
|
|
||||||
renderCasted( keyBinding.getValueExpression() );
|
|
||||||
}
|
|
||||||
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
|
||||||
appendSql( ", " );
|
|
||||||
columnList.append( ", " );
|
|
||||||
final ColumnValueBinding valueBinding = valueBindings.get( i );
|
|
||||||
columnList.append( valueBinding.getColumnReference().getColumnExpression() );
|
|
||||||
renderCasted( valueBinding.getValueExpression() );
|
|
||||||
}
|
|
||||||
|
|
||||||
appendSql( ") as s(" );
|
|
||||||
appendSql( columnList.toString() );
|
|
||||||
appendSql( ")" );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderMergeOn(OptionalTableUpdate optionalTableUpdate) {
|
|
||||||
appendSql( "on " );
|
|
||||||
|
|
||||||
// todo : optimistic locks?
|
|
||||||
|
|
||||||
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
|
||||||
for ( int i = 0; i < keyBindings.size(); i++ ) {
|
|
||||||
final ColumnValueBinding keyBinding = keyBindings.get( i );
|
|
||||||
if ( i > 0 ) {
|
|
||||||
appendSql( " and " );
|
|
||||||
}
|
|
||||||
keyBinding.getColumnReference().appendReadExpression( this, "t" );
|
|
||||||
appendSql( "=" );
|
|
||||||
keyBinding.getColumnReference().appendReadExpression( this, "s" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderMergeInsert(OptionalTableUpdate optionalTableUpdate) {
|
|
||||||
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
|
||||||
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
|
||||||
|
|
||||||
final StringBuilder valuesList = new StringBuilder();
|
|
||||||
|
|
||||||
appendSql( "when not matched then insert (" );
|
|
||||||
for ( int i = 0; i < keyBindings.size(); i++ ) {
|
|
||||||
if ( i > 0 ) {
|
|
||||||
appendSql( ", " );
|
|
||||||
valuesList.append( ", " );
|
|
||||||
}
|
|
||||||
final ColumnValueBinding keyBinding = keyBindings.get( i );
|
|
||||||
appendSql( keyBinding.getColumnReference().getColumnExpression() );
|
|
||||||
keyBinding.getColumnReference().appendReadExpression( "s", valuesList::append );
|
|
||||||
}
|
|
||||||
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
|
||||||
appendSql( ", " );
|
|
||||||
valuesList.append( ", " );
|
|
||||||
final ColumnValueBinding valueBinding = valueBindings.get( i );
|
|
||||||
appendSql( valueBinding.getColumnReference().getColumnExpression() );
|
|
||||||
valueBinding.getColumnReference().appendReadExpression( "s", valuesList::append );
|
|
||||||
}
|
|
||||||
|
|
||||||
appendSql( ") values (" );
|
|
||||||
appendSql( valuesList.toString() );
|
|
||||||
appendSql( ")" );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderMergeDelete(OptionalTableUpdate optionalTableUpdate) {
|
|
||||||
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
|
||||||
|
|
||||||
appendSql( " when matched " );
|
|
||||||
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
|
||||||
final ColumnValueBinding binding = valueBindings.get( i );
|
|
||||||
appendSql( " and " );
|
|
||||||
binding.getColumnReference().appendReadExpression( this, "s" );
|
|
||||||
appendSql( " is null" );
|
|
||||||
}
|
|
||||||
appendSql( " then delete" );
|
|
||||||
}
|
|
||||||
|
|
||||||
private void renderMergeUpdate(OptionalTableUpdate optionalTableUpdate) {
|
|
||||||
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
|
||||||
|
|
||||||
appendSql( " when matched then update set " );
|
|
||||||
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
|
||||||
final ColumnValueBinding binding = valueBindings.get( i );
|
|
||||||
if ( i > 0 ) {
|
|
||||||
appendSql( ", " );
|
|
||||||
}
|
|
||||||
binding.getColumnReference().appendColumnForWrite( this, "t" );
|
|
||||||
appendSql( "=" );
|
|
||||||
binding.getColumnReference().appendColumnForWrite( this, "s" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.time.temporal.ChronoField;
|
||||||
import java.time.temporal.TemporalAccessor;
|
import java.time.temporal.TemporalAccessor;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
|
@ -48,6 +49,7 @@ import org.hibernate.exception.LockTimeoutException;
|
||||||
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
|
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
|
||||||
import org.hibernate.internal.util.JdbcExceptionHelper;
|
import org.hibernate.internal.util.JdbcExceptionHelper;
|
||||||
import org.hibernate.mapping.Column;
|
import org.hibernate.mapping.Column;
|
||||||
|
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
|
||||||
import org.hibernate.query.sqm.CastType;
|
import org.hibernate.query.sqm.CastType;
|
||||||
import org.hibernate.query.sqm.FetchClauseType;
|
import org.hibernate.query.sqm.FetchClauseType;
|
||||||
import org.hibernate.query.sqm.IntervalType;
|
import org.hibernate.query.sqm.IntervalType;
|
||||||
|
@ -61,6 +63,8 @@ import org.hibernate.sql.ast.spi.SqlAppender;
|
||||||
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
|
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
|
||||||
import org.hibernate.sql.ast.tree.Statement;
|
import org.hibernate.sql.ast.tree.Statement;
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperation;
|
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||||
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
|
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
||||||
import org.hibernate.tool.schema.internal.StandardSequenceExporter;
|
import org.hibernate.tool.schema.internal.StandardSequenceExporter;
|
||||||
import org.hibernate.tool.schema.spi.Exporter;
|
import org.hibernate.tool.schema.spi.Exporter;
|
||||||
import org.hibernate.type.BasicType;
|
import org.hibernate.type.BasicType;
|
||||||
|
@ -75,8 +79,6 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
|
||||||
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
|
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
|
||||||
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
|
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jakarta.persistence.TemporalType;
|
import jakarta.persistence.TemporalType;
|
||||||
|
|
||||||
import static org.hibernate.query.sqm.TemporalUnit.NANOSECOND;
|
import static org.hibernate.query.sqm.TemporalUnit.NANOSECOND;
|
||||||
|
@ -1076,4 +1078,14 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
||||||
// public String getEnableConstraintStatement(String tableName, String name) {
|
// public String getEnableConstraintStatement(String tableName, String name) {
|
||||||
// return "alter table " + tableName + " with check check constraint " + name;
|
// return "alter table " + tableName + " with check check constraint " + name;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutationOperation createOptionalTableUpdateOperation(
|
||||||
|
EntityMutationTarget mutationTarget,
|
||||||
|
OptionalTableUpdate optionalTableUpdate,
|
||||||
|
SessionFactoryImplementor factory) {
|
||||||
|
final SQLServerSqlAstTranslator<JdbcOperation> translator = new SQLServerSqlAstTranslator<>( factory, optionalTableUpdate );
|
||||||
|
return translator.createMergeOperation( optionalTableUpdate );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import org.hibernate.query.sqm.ComparisonOperator;
|
||||||
import org.hibernate.query.sqm.FetchClauseType;
|
import org.hibernate.query.sqm.FetchClauseType;
|
||||||
import org.hibernate.sql.ast.Clause;
|
import org.hibernate.sql.ast.Clause;
|
||||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||||
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
|
||||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||||
import org.hibernate.sql.ast.tree.Statement;
|
import org.hibernate.sql.ast.tree.Statement;
|
||||||
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
|
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
|
||||||
|
@ -36,6 +35,7 @@ import org.hibernate.sql.ast.tree.select.QuerySpec;
|
||||||
import org.hibernate.sql.ast.tree.select.SelectClause;
|
import org.hibernate.sql.ast.tree.select.SelectClause;
|
||||||
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||||
import org.hibernate.sql.exec.spi.JdbcOperation;
|
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||||
|
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
||||||
import org.hibernate.type.SqlTypes;
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,7 +43,7 @@ import org.hibernate.type.SqlTypes;
|
||||||
*
|
*
|
||||||
* @author Christian Beikov
|
* @author Christian Beikov
|
||||||
*/
|
*/
|
||||||
public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
|
public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslatorWithMerge<T> {
|
||||||
|
|
||||||
private static final String UNION_ALL = " union all ";
|
private static final String UNION_ALL = " union all ";
|
||||||
|
|
||||||
|
@ -440,4 +440,9 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends Abstract
|
||||||
TOP_ONLY,
|
TOP_ONLY,
|
||||||
EMULATED;
|
EMULATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void renderMergeStatement(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
super.renderMergeStatement( optionalTableUpdate );
|
||||||
|
appendSql( ";" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.tree.Statement;
|
||||||
|
import org.hibernate.sql.exec.spi.JdbcOperation;
|
||||||
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
|
import org.hibernate.sql.model.ast.ColumnValueBinding;
|
||||||
|
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
||||||
|
import org.hibernate.sql.model.jdbc.MergeOperation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base SqlAstTranslator for translators which support a full insert/update/delete MERGE statement
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public abstract class SqlAstTranslatorWithMerge<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
|
||||||
|
public SqlAstTranslatorWithMerge(SessionFactoryImplementor sessionFactory, Statement statement) {
|
||||||
|
super( sessionFactory, statement );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the MutationOperation for performing a MERGE
|
||||||
|
*/
|
||||||
|
public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
renderMergeStatement( optionalTableUpdate );
|
||||||
|
|
||||||
|
return new MergeOperation(
|
||||||
|
optionalTableUpdate.getMutatingTable().getTableMapping(),
|
||||||
|
optionalTableUpdate.getMutationTarget(),
|
||||||
|
getSql(),
|
||||||
|
getParameterBinders()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeStatement(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
// template:
|
||||||
|
//
|
||||||
|
// merge into [table] as t
|
||||||
|
// using values([bindings]) as s ([column-names])
|
||||||
|
// on t.[key] = s.[key]
|
||||||
|
// when not matched
|
||||||
|
// then insert ...
|
||||||
|
// when matched
|
||||||
|
// and s.[columns] is null
|
||||||
|
// then delete
|
||||||
|
// when matched
|
||||||
|
// then update set ...
|
||||||
|
|
||||||
|
renderMergeInto( optionalTableUpdate );
|
||||||
|
appendSql( " " );
|
||||||
|
renderMergeUsing( optionalTableUpdate );
|
||||||
|
appendSql( " " );
|
||||||
|
renderMergeOn( optionalTableUpdate );
|
||||||
|
appendSql( " " );
|
||||||
|
renderMergeInsert( optionalTableUpdate );
|
||||||
|
appendSql( " " );
|
||||||
|
renderMergeDelete( optionalTableUpdate );
|
||||||
|
appendSql( " " );
|
||||||
|
renderMergeUpdate( optionalTableUpdate );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
appendSql( "merge into " );
|
||||||
|
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
|
||||||
|
renderMergeTargetAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeTargetAlias() {
|
||||||
|
appendSql( " as t" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeUsing(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
appendSql( "using " );
|
||||||
|
|
||||||
|
renderMergeSource( optionalTableUpdate );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean wrapMergeSourceExpression() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderMergeSource(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
if ( wrapMergeSourceExpression() ) {
|
||||||
|
appendSql( " (" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
||||||
|
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
||||||
|
|
||||||
|
final StringBuilder columnList = new StringBuilder();
|
||||||
|
|
||||||
|
appendSql( " values (" );
|
||||||
|
|
||||||
|
for ( int i = 0; i < keyBindings.size(); i++ ) {
|
||||||
|
final ColumnValueBinding keyBinding = keyBindings.get( i );
|
||||||
|
if ( i > 0 ) {
|
||||||
|
appendSql( ", " );
|
||||||
|
columnList.append( ", " );
|
||||||
|
}
|
||||||
|
columnList.append( keyBinding.getColumnReference().getColumnExpression() );
|
||||||
|
renderCasted( keyBinding.getValueExpression() );
|
||||||
|
}
|
||||||
|
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
||||||
|
appendSql( ", " );
|
||||||
|
columnList.append( ", " );
|
||||||
|
final ColumnValueBinding valueBinding = valueBindings.get( i );
|
||||||
|
columnList.append( valueBinding.getColumnReference().getColumnExpression() );
|
||||||
|
renderCasted( valueBinding.getValueExpression() );
|
||||||
|
}
|
||||||
|
|
||||||
|
appendSql( ") " );
|
||||||
|
|
||||||
|
if ( wrapMergeSourceExpression() ) {
|
||||||
|
appendSql( ") " );
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMergeSourceAlias();
|
||||||
|
|
||||||
|
appendSql( "(" );
|
||||||
|
appendSql( columnList.toString() );
|
||||||
|
appendSql( ")" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeSourceAlias() {
|
||||||
|
appendSql( " as s" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeOn(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
appendSql( "on (" );
|
||||||
|
|
||||||
|
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
||||||
|
for ( int i = 0; i < keyBindings.size(); i++ ) {
|
||||||
|
final ColumnValueBinding keyBinding = keyBindings.get( i );
|
||||||
|
if ( i > 0 ) {
|
||||||
|
appendSql( " and " );
|
||||||
|
}
|
||||||
|
keyBinding.getColumnReference().appendReadExpression( this, "t" );
|
||||||
|
appendSql( "=" );
|
||||||
|
keyBinding.getColumnReference().appendReadExpression( this, "s" );
|
||||||
|
}
|
||||||
|
// todo : optimistic locks?
|
||||||
|
|
||||||
|
appendSql( ")" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeInsert(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
||||||
|
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
||||||
|
|
||||||
|
final StringBuilder valuesList = new StringBuilder();
|
||||||
|
|
||||||
|
appendSql( "when not matched then insert (" );
|
||||||
|
for ( int i = 0; i < keyBindings.size(); i++ ) {
|
||||||
|
if ( i > 0 ) {
|
||||||
|
appendSql( ", " );
|
||||||
|
valuesList.append( ", " );
|
||||||
|
}
|
||||||
|
final ColumnValueBinding keyBinding = keyBindings.get( i );
|
||||||
|
appendSql( keyBinding.getColumnReference().getColumnExpression() );
|
||||||
|
keyBinding.getColumnReference().appendReadExpression( "s", valuesList::append );
|
||||||
|
}
|
||||||
|
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
||||||
|
appendSql( ", " );
|
||||||
|
valuesList.append( ", " );
|
||||||
|
final ColumnValueBinding valueBinding = valueBindings.get( i );
|
||||||
|
appendSql( valueBinding.getColumnReference().getColumnExpression() );
|
||||||
|
valueBinding.getColumnReference().appendReadExpression( "s", valuesList::append );
|
||||||
|
}
|
||||||
|
|
||||||
|
appendSql( ") values (" );
|
||||||
|
appendSql( valuesList.toString() );
|
||||||
|
appendSql( ")" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeDelete(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
||||||
|
|
||||||
|
appendSql( " when matched " );
|
||||||
|
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
||||||
|
final ColumnValueBinding binding = valueBindings.get( i );
|
||||||
|
appendSql( " and " );
|
||||||
|
binding.getColumnReference().appendReadExpression( this, "s" );
|
||||||
|
appendSql( " is null" );
|
||||||
|
}
|
||||||
|
appendSql( " then delete" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeUpdate(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
||||||
|
|
||||||
|
appendSql( " when matched then update set " );
|
||||||
|
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
||||||
|
final ColumnValueBinding binding = valueBindings.get( i );
|
||||||
|
if ( i > 0 ) {
|
||||||
|
appendSql( ", " );
|
||||||
|
}
|
||||||
|
binding.getColumnReference().appendColumnForWrite( this, "t" );
|
||||||
|
appendSql( "=" );
|
||||||
|
binding.getColumnReference().appendColumnForWrite( this, "s" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -131,7 +131,7 @@ public class OptionalTableUpdate
|
||||||
// Fallback to the optional table mutation operation because we have to execute user specified SQL
|
// Fallback to the optional table mutation operation because we have to execute user specified SQL
|
||||||
return new OptionalTableUpdateOperation( getMutationTarget(), this, factory );
|
return new OptionalTableUpdateOperation( getMutationTarget(), this, factory );
|
||||||
}
|
}
|
||||||
return factory.getJdbcServices().getDialect().createUpsertOperation(
|
return factory.getJdbcServices().getDialect().createOptionalTableUpdateOperation(
|
||||||
getMutationTarget(),
|
getMutationTarget(),
|
||||||
this,
|
this,
|
||||||
factory
|
factory
|
||||||
|
|
|
@ -15,12 +15,12 @@ import org.hibernate.sql.model.MutationType;
|
||||||
import org.hibernate.sql.model.TableMapping;
|
import org.hibernate.sql.model.TableMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JdbcMutation implementation for MERGE/UPSERT handling
|
* JdbcMutation implementation for MERGE handling
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class UpsertOperation extends AbstractJdbcMutation {
|
public class MergeOperation extends AbstractJdbcMutation {
|
||||||
public UpsertOperation(
|
public MergeOperation(
|
||||||
TableMapping tableDetails,
|
TableMapping tableDetails,
|
||||||
MutationTarget<?> mutationTarget,
|
MutationTarget<?> mutationTarget,
|
||||||
String sql,
|
String sql,
|
|
@ -24,9 +24,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
@DomainModel(annotatedClasses = UpsertTests.TheEntity.class)
|
@DomainModel(annotatedClasses = OptionalTableUpdateTests.TheEntity.class)
|
||||||
@SessionFactory
|
@SessionFactory
|
||||||
public class UpsertTests {
|
public class OptionalTableUpdateTests {
|
||||||
@Test
|
@Test
|
||||||
void testUpsertInsert(SessionFactoryScope scope) {
|
void testUpsertInsert(SessionFactoryScope scope) {
|
||||||
scope.inTransaction( (session) -> {
|
scope.inTransaction( (session) -> {
|
Loading…
Reference in New Issue