HHH-16101 - MERGE for optional table updates on Oracle
This commit is contained in:
parent
ee8d80a8bd
commit
1d62d2d66e
|
@ -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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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( " " );
|
||||||
|
|
|
@ -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" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue