HHH-16110 - MERGE for optional table update PostgreSQL
This commit is contained in:
parent
a6995b50a9
commit
3281f4522e
|
@ -297,6 +297,7 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslato
|
||||||
return " from dual";
|
return " from dual";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean supportsOffsetFetchClause() {
|
private boolean supportsOffsetFetchClause() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ import org.hibernate.boot.model.TypeContributions;
|
||||||
import org.hibernate.dialect.aggregate.AggregateSupport;
|
import org.hibernate.dialect.aggregate.AggregateSupport;
|
||||||
import org.hibernate.dialect.aggregate.PostgreSQLAggregateSupport;
|
import org.hibernate.dialect.aggregate.PostgreSQLAggregateSupport;
|
||||||
import org.hibernate.dialect.function.CommonFunctionFactory;
|
import org.hibernate.dialect.function.CommonFunctionFactory;
|
||||||
import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction;
|
|
||||||
import org.hibernate.dialect.function.PostgreSQLMinMaxFunction;
|
import org.hibernate.dialect.function.PostgreSQLMinMaxFunction;
|
||||||
|
import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction;
|
||||||
import org.hibernate.dialect.identity.IdentityColumnSupport;
|
import org.hibernate.dialect.identity.IdentityColumnSupport;
|
||||||
import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport;
|
import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport;
|
||||||
import org.hibernate.dialect.pagination.LimitHandler;
|
import org.hibernate.dialect.pagination.LimitHandler;
|
||||||
|
@ -51,6 +51,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.PostgreSQLCallableStatementSupport;
|
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
|
||||||
import org.hibernate.procedure.spi.CallableStatementSupport;
|
import org.hibernate.procedure.spi.CallableStatementSupport;
|
||||||
import org.hibernate.query.SemanticException;
|
import org.hibernate.query.SemanticException;
|
||||||
|
@ -68,6 +69,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.type.JavaObjectType;
|
import org.hibernate.type.JavaObjectType;
|
||||||
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
||||||
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
|
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
|
||||||
|
@ -1366,23 +1369,12 @@ public class PostgreSQLDialect extends Dialect {
|
||||||
return OTHER;
|
return OTHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
@Override
|
||||||
// public String getDisableConstraintsStatement() {
|
public MutationOperation createOptionalTableUpdateOperation(
|
||||||
// return "set constraints all deferred";
|
EntityMutationTarget mutationTarget,
|
||||||
// }
|
OptionalTableUpdate optionalTableUpdate,
|
||||||
//
|
SessionFactoryImplementor factory) {
|
||||||
// @Override
|
final PostgreSQLSqlAstTranslator<?> translator = new PostgreSQLSqlAstTranslator<>( factory, optionalTableUpdate );
|
||||||
// public String getEnableConstraintsStatement() {
|
return translator.createMergeOperation( optionalTableUpdate );
|
||||||
// return "set constraints all immediate";
|
}
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public String getDisableConstraintStatement(String tableName, String name) {
|
|
||||||
// return "alter table " + tableName + " alter constraint " + name + " deferrable";
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public String getEnableConstraintStatement(String tableName, String name) {
|
|
||||||
// return "alter table " + tableName + " alter constraint " + name + " deferrable";
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,10 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.dialect;
|
package org.hibernate.dialect;
|
||||||
|
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
|
||||||
import org.hibernate.query.sqm.ComparisonOperator;
|
import org.hibernate.query.sqm.ComparisonOperator;
|
||||||
import org.hibernate.query.sqm.FetchClauseType;
|
import org.hibernate.query.sqm.FetchClauseType;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
|
||||||
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
|
||||||
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.cte.CteStatement;
|
import org.hibernate.sql.ast.tree.cte.CteStatement;
|
||||||
|
@ -33,7 +32,7 @@ import org.hibernate.type.SqlTypes;
|
||||||
*
|
*
|
||||||
* @author Christian Beikov
|
* @author Christian Beikov
|
||||||
*/
|
*/
|
||||||
public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
|
public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslatorWithMerge<T> {
|
||||||
|
|
||||||
public PostgreSQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
|
public PostgreSQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
|
||||||
super( sessionFactory, statement );
|
super( sessionFactory, statement );
|
||||||
|
@ -280,5 +279,4 @@ public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
||||||
arithmeticExpression.getRightHandOperand().accept( this );
|
arithmeticExpression.getRightHandOperand().accept( this );
|
||||||
appendSql( CLOSE_PARENTHESIS );
|
appendSql( CLOSE_PARENTHESIS );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,18 @@ import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
|
|
||||||
import jakarta.persistence.TemporalType;
|
|
||||||
|
|
||||||
import org.hibernate.boot.model.FunctionContributions;
|
import org.hibernate.boot.model.FunctionContributions;
|
||||||
import org.hibernate.dialect.function.CommonFunctionFactory;
|
import org.hibernate.dialect.function.CommonFunctionFactory;
|
||||||
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
|
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
|
||||||
import org.hibernate.query.sqm.CastType;
|
import org.hibernate.query.sqm.CastType;
|
||||||
import org.hibernate.query.sqm.TemporalUnit;
|
import org.hibernate.query.sqm.TemporalUnit;
|
||||||
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
|
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
||||||
|
import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
|
||||||
|
|
||||||
|
import jakarta.persistence.TemporalType;
|
||||||
|
|
||||||
import static org.hibernate.query.sqm.TemporalUnit.DAY;
|
import static org.hibernate.query.sqm.TemporalUnit.DAY;
|
||||||
|
|
||||||
|
@ -112,4 +116,13 @@ public class PostgresPlusDialect extends PostgreSQLDialect {
|
||||||
return "select uuid_generate_v1";
|
return "select uuid_generate_v1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutationOperation createOptionalTableUpdateOperation(
|
||||||
|
EntityMutationTarget mutationTarget,
|
||||||
|
OptionalTableUpdate optionalTableUpdate,
|
||||||
|
SessionFactoryImplementor factory) {
|
||||||
|
// Postgres Plus does not support full merge semantics -
|
||||||
|
// https://www.enterprisedb.com/docs/migrating/oracle/oracle_epas_comparison/notable_differences/
|
||||||
|
return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,16 +9,22 @@ package org.hibernate.dialect;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
|
||||||
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.ast.ColumnValueBinding;
|
import org.hibernate.sql.model.ast.ColumnValueBinding;
|
||||||
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
import org.hibernate.sql.model.internal.OptionalTableUpdate;
|
||||||
import org.hibernate.sql.model.jdbc.MergeOperation;
|
import org.hibernate.sql.model.jdbc.MergeOperation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for translators which support a full insert-or-update-or-delete (MERGE) command
|
* Base for translators which support a full insert-or-update-or-delete (MERGE) command.
|
||||||
|
* <p/>
|
||||||
|
* Use {@link #createMergeOperation(OptionalTableUpdate)} to translate an
|
||||||
|
* {@linkplain OptionalTableUpdate} into an executable {@linkplain MergeOperation}
|
||||||
|
* operation.
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
@ -28,9 +34,12 @@ public abstract class SqlAstTranslatorWithMerge<T extends JdbcOperation> extends
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the MutationOperation for performing a MERGE
|
* Create the MutationOperation for performing a MERGE.
|
||||||
|
*
|
||||||
|
* The OptionalTableUpdate is {@linkplain #renderMergeStatement translated}
|
||||||
|
* and wrapped as a MutationOperation
|
||||||
*/
|
*/
|
||||||
public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) {
|
public MergeOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) {
|
||||||
renderMergeStatement( optionalTableUpdate );
|
renderMergeStatement( optionalTableUpdate );
|
||||||
|
|
||||||
return new MergeOperation(
|
return new MergeOperation(
|
||||||
|
@ -41,97 +50,110 @@ public abstract class SqlAstTranslatorWithMerge<T extends JdbcOperation> extends
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the OptionalTableUpdate as a MERGE query.
|
||||||
|
*
|
||||||
|
*/
|
||||||
protected void renderMergeStatement(OptionalTableUpdate optionalTableUpdate) {
|
protected void renderMergeStatement(OptionalTableUpdate optionalTableUpdate) {
|
||||||
// template:
|
|
||||||
//
|
//
|
||||||
// merge into [table] as t
|
// merge into <target-table> as t
|
||||||
// using values([bindings]) as s ([column-names])
|
// using (select col_1, col_2, ... from dual) as s
|
||||||
// on t.[key] = s.[key]
|
// on (t.key = s.key)
|
||||||
// when not matched
|
// when not matched
|
||||||
// then insert ...
|
// then insert ...
|
||||||
// when matched
|
// when matched
|
||||||
// and s.[columns] is null
|
// and s.col_1 is null
|
||||||
// then delete
|
// and s.col_2 is null
|
||||||
|
// and ...
|
||||||
|
// then delete
|
||||||
// when matched
|
// when matched
|
||||||
// then update ...
|
// then update ...
|
||||||
|
|
||||||
|
// `merge into <target-table> [as] t`
|
||||||
renderMergeInto( optionalTableUpdate );
|
renderMergeInto( optionalTableUpdate );
|
||||||
appendSql( " " );
|
appendSql( " " );
|
||||||
|
|
||||||
|
// using (select col_1, col_2, ... from dual) as s
|
||||||
renderMergeUsing( optionalTableUpdate );
|
renderMergeUsing( optionalTableUpdate );
|
||||||
appendSql( " " );
|
appendSql( " " );
|
||||||
|
|
||||||
|
// on (t.key = s.key)
|
||||||
renderMergeOn( optionalTableUpdate );
|
renderMergeOn( optionalTableUpdate );
|
||||||
appendSql( " " );
|
appendSql( " " );
|
||||||
|
|
||||||
|
// when not matched
|
||||||
|
// then insert ...
|
||||||
renderMergeInsert( optionalTableUpdate );
|
renderMergeInsert( optionalTableUpdate );
|
||||||
appendSql( " " );
|
appendSql( " " );
|
||||||
|
|
||||||
|
// when matched
|
||||||
|
// and s.col_1 is null
|
||||||
|
// and s.col_2 is null
|
||||||
|
// and ...
|
||||||
|
// then delete
|
||||||
renderMergeDelete( optionalTableUpdate );
|
renderMergeDelete( optionalTableUpdate );
|
||||||
appendSql( " " );
|
appendSql( " " );
|
||||||
|
|
||||||
|
// when matched
|
||||||
|
// then update ...
|
||||||
renderMergeUpdate( optionalTableUpdate );
|
renderMergeUpdate( optionalTableUpdate );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
|
protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
|
||||||
appendSql( "merge into " );
|
appendSql( "merge into " );
|
||||||
|
renderMergeTarget( optionalTableUpdate );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderMergeTarget(OptionalTableUpdate optionalTableUpdate) {
|
||||||
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
|
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
|
||||||
|
appendSql( " " );
|
||||||
renderMergeTargetAlias();
|
renderMergeTargetAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderMergeTargetAlias() {
|
protected void renderMergeTargetAlias() {
|
||||||
appendSql( " as t" );
|
appendSql( "as t" );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderMergeUsing(OptionalTableUpdate optionalTableUpdate) {
|
protected void renderMergeUsing(OptionalTableUpdate optionalTableUpdate) {
|
||||||
appendSql( "using " );
|
appendSql( "using (" );
|
||||||
|
renderMergeUsingQuery( optionalTableUpdate );
|
||||||
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( ") " );
|
appendSql( ") " );
|
||||||
|
|
||||||
if ( wrapMergeSourceExpression() ) {
|
|
||||||
appendSql( ") " );
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMergeSourceAlias();
|
renderMergeSourceAlias();
|
||||||
|
|
||||||
appendSql( "(" );
|
|
||||||
appendSql( columnList.toString() );
|
|
||||||
appendSql( ")" );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderMergeSourceAlias() {
|
protected void renderMergeSourceAlias() {
|
||||||
appendSql( " as s" );
|
appendSql( "as s" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderMergeUsingQuery(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
|
||||||
|
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
|
||||||
|
|
||||||
|
appendSql( "select " );
|
||||||
|
|
||||||
|
for ( int i = 0; i < keyBindings.size(); i++ ) {
|
||||||
|
if ( i > 0 ) {
|
||||||
|
appendSql( ", " );
|
||||||
|
}
|
||||||
|
renderMergeUsingQuerySelection( keyBindings.get( i ) );
|
||||||
|
}
|
||||||
|
for ( int i = 0; i < valueBindings.size(); i++ ) {
|
||||||
|
appendSql( ", " );
|
||||||
|
renderMergeUsingQuerySelection( valueBindings.get( i ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
final String selectionTable = StringHelper.nullIfEmpty( getFromDualForSelectOnly() );
|
||||||
|
if ( selectionTable != null ) {
|
||||||
|
appendSql( " " );
|
||||||
|
appendSql( selectionTable );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void renderMergeUsingQuerySelection(ColumnValueBinding selectionBinding) {
|
||||||
|
renderCasted( selectionBinding.getValueExpression() );
|
||||||
|
appendSql( " " );
|
||||||
|
appendSql( selectionBinding.getColumnReference().getColumnExpression() );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderMergeOn(OptionalTableUpdate optionalTableUpdate) {
|
protected void renderMergeOn(OptionalTableUpdate optionalTableUpdate) {
|
||||||
|
@ -203,7 +225,7 @@ public abstract class SqlAstTranslatorWithMerge<T extends JdbcOperation> extends
|
||||||
if ( i > 0 ) {
|
if ( i > 0 ) {
|
||||||
appendSql( ", " );
|
appendSql( ", " );
|
||||||
}
|
}
|
||||||
binding.getColumnReference().appendColumnForWrite( this, "t" );
|
binding.getColumnReference().appendColumnForWrite( this, null );
|
||||||
appendSql( "=" );
|
appendSql( "=" );
|
||||||
binding.getColumnReference().appendColumnForWrite( this, "s" );
|
binding.getColumnReference().appendColumnForWrite( this, "s" );
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,10 @@ public class SqlAstTranslatorWithUpsert<T extends JdbcOperation> extends Abstrac
|
||||||
|
|
||||||
protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
|
protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
|
||||||
appendSql( "merge into " );
|
appendSql( "merge into " );
|
||||||
|
renderMergeTarget( optionalTableUpdate );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderMergeTarget(OptionalTableUpdate optionalTableUpdate) {
|
||||||
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
|
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
|
||||||
renderMergeTargetAlias();
|
renderMergeTargetAlias();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue