HHH-16101 - MERGE for optional table updates on Oracle

This commit is contained in:
Steve Ebersole 2023-01-25 23:06:38 -06:00
parent ee8d80a8bd
commit 1d62d2d66e
7 changed files with 504 additions and 10 deletions

View File

@ -50,6 +50,7 @@ import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.procedure.internal.StandardCallableStatementSupport; import org.hibernate.procedure.internal.StandardCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.procedure.spi.CallableStatementSupport;
import org.hibernate.query.SemanticException; import org.hibernate.query.SemanticException;
@ -70,6 +71,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.extract.internal.SequenceInformationExtractorOracleDatabaseImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.JavaObjectType; import org.hibernate.type.JavaObjectType;
@ -79,9 +82,9 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.OracleJsonBlobJdbcType;
import org.hibernate.type.descriptor.jdbc.NullJdbcType; import org.hibernate.type.descriptor.jdbc.NullJdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.OracleJsonBlobJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; 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;
@ -1441,4 +1444,13 @@ public class OracleDialect extends Dialect {
public String rowId(String rowId) { public String rowId(String rowId) {
return "rowid"; return "rowid";
} }
@Override
public MutationOperation createOptionalTableUpdateOperation(
EntityMutationTarget mutationTarget,
OptionalTableUpdate optionalTableUpdate,
SessionFactoryImplementor factory) {
final OracleSqlAstTranslator<?> translator = new OracleSqlAstTranslator<>( factory, optionalTableUpdate );
return translator.createMergeOperation( optionalTableUpdate );
}
} }

View File

@ -18,12 +18,11 @@ import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.FrameExclusion; import org.hibernate.query.sqm.FrameExclusion;
import org.hibernate.query.sqm.FrameKind; import org.hibernate.query.sqm.FrameKind;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
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.CteMaterialization; import org.hibernate.sql.ast.tree.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression; import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.FunctionExpression; import org.hibernate.sql.ast.tree.expression.FunctionExpression;
import org.hibernate.sql.ast.tree.expression.Literal; import org.hibernate.sql.ast.tree.expression.Literal;
@ -45,6 +44,8 @@ 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.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.ast.ColumnValueBinding;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
/** /**
@ -52,7 +53,7 @@ import org.hibernate.type.SqlTypes;
* *
* @author Christian Beikov * @author Christian Beikov
*/ */
public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> { public class OracleSqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslatorWithUpsert<T> {
public OracleSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { public OracleSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement ); super( sessionFactory, statement );
@ -542,4 +543,42 @@ public class OracleSqlAstTranslator<T extends JdbcOperation> extends AbstractSql
public void visitAggregateColumnWriteExpression(AggregateColumnWriteExpression aggregateColumnWriteExpression) { public void visitAggregateColumnWriteExpression(AggregateColumnWriteExpression aggregateColumnWriteExpression) {
aggregateColumnWriteExpression.appendWriteExpression( this, this ); aggregateColumnWriteExpression.appendWriteExpression( this, this );
} }
@Override
protected void renderMergeTargetAlias() {
appendSql( " t" );
}
@Override
protected void renderMergeSourceAlias() {
appendSql( " s" );
}
protected void renderMergeSource(OptionalTableUpdate optionalTableUpdate) {
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
appendSql( "(select " );
for ( int i = 0; i < keyBindings.size(); i++ ) {
final ColumnValueBinding keyBinding = keyBindings.get( i );
if ( i > 0 ) {
appendSql( ", " );
}
renderCasted( keyBinding.getValueExpression() );
appendSql( " " );
appendSql( keyBinding.getColumnReference().getColumnExpression() );
}
for ( int i = 0; i < valueBindings.size(); i++ ) {
appendSql( ", " );
final ColumnValueBinding valueBinding = valueBindings.get( i );
renderCasted( valueBinding.getValueExpression() );
appendSql( " " );
appendSql( valueBinding.getColumnReference().getColumnExpression() );
}
appendSql( " from dual)" );
renderMergeSourceAlias();
}
} }

View File

@ -18,7 +18,7 @@ import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.sql.model.jdbc.MergeOperation; import org.hibernate.sql.model.jdbc.MergeOperation;
/** /**
* Base SqlAstTranslator for translators which support a full insert/update/delete MERGE statement * Base for translators which support a full insert-or-update-or-delete (MERGE) command
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -53,7 +53,7 @@ public abstract class SqlAstTranslatorWithMerge<T extends JdbcOperation> extends
// and s.[columns] is null // and s.[columns] is null
// then delete // then delete
// when matched // when matched
// then update set ... // then update ...
renderMergeInto( optionalTableUpdate ); renderMergeInto( optionalTableUpdate );
appendSql( " " ); appendSql( " " );

View File

@ -0,0 +1,202 @@
/*
* 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.persister.entity.mutation.EntityTableMapping;
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.DeleteOrUpsertOperation;
import org.hibernate.sql.model.jdbc.UpsertOperation;
/**
* Base SqlAstTranslator for translators which support an insert-or-update (UPSERT) command
*
* @author Steve Ebersole
*/
public class SqlAstTranslatorWithUpsert<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
protected SqlAstTranslatorWithUpsert(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement );
}
/**
* Create the MutationOperation for performing the DELETE or UPSERT
*/
public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) {
renderUpsertStatement( optionalTableUpdate );
final UpsertOperation upsertOperation = new UpsertOperation(
optionalTableUpdate.getMutatingTable().getTableMapping(),
optionalTableUpdate.getMutationTarget(),
getSql(),
getParameterBinders()
);
return new DeleteOrUpsertOperation(
optionalTableUpdate.getMutationTarget(),
(EntityTableMapping) optionalTableUpdate.getMutatingTable().getTableMapping(),
upsertOperation,
optionalTableUpdate
);
}
private void renderUpsertStatement(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
// then update ...
renderMergeInto( optionalTableUpdate );
appendSql( " " );
renderMergeUsing( optionalTableUpdate );
appendSql( " " );
renderMergeOn( optionalTableUpdate );
appendSql( " " );
renderMergeInsert( 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;
}
protected 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 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" );
}
}
}

View File

@ -0,0 +1,202 @@
/*
* 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.sql.model.jdbc;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.ParameterUsage;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions;
import org.hibernate.engine.jdbc.mutation.internal.PreparedStatementGroupSingleTable;
import org.hibernate.engine.jdbc.mutation.spi.Binding;
import org.hibernate.engine.jdbc.mutation.spi.BindingGroup;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.persister.entity.mutation.UpdateValuesAnalysis;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.model.MutationTarget;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.SelfExecutingUpdateOperation;
import org.hibernate.sql.model.TableMapping;
import org.hibernate.sql.model.ValuesAnalysis;
import org.hibernate.sql.model.ast.ColumnValueParameter;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.sql.model.internal.TableDeleteStandard;
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
/**
* @author Steve Ebersole
*/
public class DeleteOrUpsertOperation implements SelfExecutingUpdateOperation {
private final EntityMutationTarget mutationTarget;
private final EntityTableMapping tableMapping;
private final UpsertOperation upsertOperation;
private final OptionalTableUpdate optionalTableUpdate;
public DeleteOrUpsertOperation(
EntityMutationTarget mutationTarget,
EntityTableMapping tableMapping,
UpsertOperation upsertOperation,
OptionalTableUpdate optionalTableUpdate) {
this.mutationTarget = mutationTarget;
this.tableMapping = tableMapping;
this.upsertOperation = upsertOperation;
this.optionalTableUpdate = optionalTableUpdate;
}
@Override
public MutationType getMutationType() {
return MutationType.UPDATE;
}
@Override
public MutationTarget<?> getMutationTarget() {
return mutationTarget;
}
@Override
public TableMapping getTableDetails() {
return tableMapping;
}
@Override
public JdbcValueDescriptor findValueDescriptor(String columnName, ParameterUsage usage) {
return upsertOperation.findValueDescriptor( columnName, usage );
}
@Override
public void performMutation(
JdbcValueBindings jdbcValueBindings,
ValuesAnalysis valuesAnalysis,
SharedSessionContractImplementor session) {
final UpdateValuesAnalysis analysis = (UpdateValuesAnalysis) valuesAnalysis;
if ( !analysis.getTablesWithNonNullValues().contains( tableMapping ) ) {
// all the new values are null - delete
performDelete( jdbcValueBindings, session );
}
else {
performUpsert( jdbcValueBindings, session );
}
}
private void performDelete(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) {
MODEL_MUTATION_LOGGER.tracef( "#performDelete(%s)", tableMapping.getTableName() );
final TableDeleteStandard upsertDeleteAst = new TableDeleteStandard(
optionalTableUpdate.getMutatingTable(),
mutationTarget,
"upsert delete",
optionalTableUpdate.getKeyBindings(),
Collections.emptyList(),
Collections.emptyList()
);
final SqlAstTranslator<JdbcDeleteMutation> translator = session
.getJdbcServices()
.getJdbcEnvironment()
.getSqlAstTranslatorFactory()
.buildModelMutationTranslator( upsertDeleteAst, session.getFactory() );
final JdbcDeleteMutation upsertDelete = translator.translate( null, MutationQueryOptions.INSTANCE );
final PreparedStatementGroupSingleTable statementGroup = new PreparedStatementGroupSingleTable( upsertDelete, session );
final PreparedStatementDetails statementDetails = statementGroup.resolvePreparedStatementDetails( tableMapping.getTableName() );
final PreparedStatement upsertDeleteStatement = statementDetails.resolveStatement();
session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() );
bindDeleteKeyValues(
jdbcValueBindings,
optionalTableUpdate.getParameters(),
statementDetails,
session
);
final int rowCount = session.getJdbcCoordinator().getResultSetReturn()
.executeUpdate( upsertDeleteStatement, statementDetails.getSqlString() );
MODEL_MUTATION_LOGGER.tracef( "`%s` rows upsert-deleted from `%s`", rowCount, tableMapping.getTableName() );
}
private void bindDeleteKeyValues(
JdbcValueBindings jdbcValueBindings,
List<ColumnValueParameter> parameters,
PreparedStatementDetails statementDetails,
SharedSessionContractImplementor session) {
final PreparedStatement statement = statementDetails.resolveStatement();
final BindingGroup bindingGroup = jdbcValueBindings.getBindingGroup( tableMapping.getTableName() );
final Set<Binding> bindings = bindingGroup.getBindings();
int jdbcBindingPosition = 1;
for ( Binding binding : bindings ) {
if ( binding.getValueDescriptor().getUsage() != ParameterUsage.RESTRICT ) {
continue;
}
bindKeyValue(
jdbcBindingPosition++,
binding,
binding.getValueDescriptor(),
statement,
statementDetails.getSqlString(),
tableMapping,
session
);
}
}
private static void bindKeyValue(
int jdbcPosition,
Binding binding,
JdbcValueDescriptor valueDescriptor,
PreparedStatement statement,
String sql,
EntityTableMapping tableMapping,
SharedSessionContractImplementor session) {
try {
binding.getValueBinder().bind( statement, binding.getValue(), jdbcPosition, session );
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
String.format(
Locale.ROOT,
"Unable to bind parameter for upsert insert : %s.%s",
tableMapping.getTableName(),
valueDescriptor.getColumnName()
),
sql
);
}
}
private void performUpsert(JdbcValueBindings jdbcValueBindings, SharedSessionContractImplementor session) {
MODEL_MUTATION_LOGGER.tracef( "#performUpsert(%s)", tableMapping.getTableName() );
final PreparedStatementGroupSingleTable statementGroup = new PreparedStatementGroupSingleTable( upsertOperation, session );
final PreparedStatementDetails statementDetails = statementGroup.resolvePreparedStatementDetails( tableMapping.getTableName() );
final PreparedStatement updateStatement = statementDetails.resolveStatement();
session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() );
jdbcValueBindings.beforeStatement( statementDetails );
final int rowCount = session.getJdbcCoordinator().getResultSetReturn()
.executeUpdate( updateStatement, statementDetails.getSqlString() );
MODEL_MUTATION_LOGGER.tracef( "`%s` rows upserted into `%s`", rowCount, tableMapping.getTableName() );
}
}

View File

@ -72,7 +72,7 @@ public class OptionalTableUpdateOperation implements SelfExecutingUpdateOperatio
private final List<ColumnValueBinding> optimisticLockBindings; private final List<ColumnValueBinding> optimisticLockBindings;
private final List<ColumnValueParameter> parameters; private final List<ColumnValueParameter> parameters;
private final List<JdbcValueDescriptorImpl> jdbcValueDescriptors; private final List<JdbcValueDescriptor> jdbcValueDescriptors;
public OptionalTableUpdateOperation( public OptionalTableUpdateOperation(
MutationTarget<?> mutationTarget, MutationTarget<?> mutationTarget,
@ -205,7 +205,7 @@ public class OptionalTableUpdateOperation implements SelfExecutingUpdateOperatio
bindings: for ( Binding binding : bindings ) { bindings: for ( Binding binding : bindings ) {
// binding-position here is 1-based (JDBC) // binding-position here is 1-based (JDBC)
final JdbcValueDescriptorImpl valueDescriptor = jdbcValueDescriptors.get( binding.getPosition() - 1 ); final JdbcValueDescriptor valueDescriptor = jdbcValueDescriptors.get( binding.getPosition() - 1 );
// key bindings would have a usage of RESTRICT relative to the UPDATE // key bindings would have a usage of RESTRICT relative to the UPDATE
if ( valueDescriptor.getUsage() != ParameterUsage.RESTRICT ) { if ( valueDescriptor.getUsage() != ParameterUsage.RESTRICT ) {
@ -224,6 +224,7 @@ public class OptionalTableUpdateOperation implements SelfExecutingUpdateOperatio
valueDescriptor, valueDescriptor,
statement, statement,
jdbcDelete.getSqlString(), jdbcDelete.getSqlString(),
tableMapping,
session session
); );
break; break;
@ -238,12 +239,13 @@ public class OptionalTableUpdateOperation implements SelfExecutingUpdateOperatio
} }
} }
private void bindKeyValue( private static void bindKeyValue(
int jdbcPosition, int jdbcPosition,
Binding binding, Binding binding,
JdbcValueDescriptorImpl valueDescriptor, JdbcValueDescriptor valueDescriptor,
PreparedStatement statement, PreparedStatement statement,
String sql, String sql,
EntityTableMapping tableMapping,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
try { try {
binding.getValueBinder().bind( statement, binding.getValue(), jdbcPosition, session ); binding.getValueBinder().bind( statement, binding.getValue(), jdbcPosition, session );

View File

@ -0,0 +1,37 @@
/*
* 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.sql.model.jdbc;
import java.util.List;
import org.hibernate.jdbc.Expectations;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.model.MutationTarget;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.TableMapping;
/**
* JdbcMutation implementation for UPSERT handling
*
* @author Steve Ebersole
*/
public class UpsertOperation extends AbstractJdbcMutation {
public UpsertOperation(
TableMapping tableDetails,
MutationTarget<?> mutationTarget,
String sql,
List<? extends JdbcParameterBinder> parameterBinders) {
super( tableDetails, mutationTarget, sql, false, Expectations.NONE, parameterBinders );
}
@Override
public MutationType getMutationType() {
return MutationType.UPDATE;
}
}