HHH-16110 - MERGE for optional table update PostgreSQL

This commit is contained in:
Steve Ebersole 2023-01-26 20:57:57 -06:00
parent a6995b50a9
commit 3281f4522e
6 changed files with 117 additions and 87 deletions

View File

@ -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;
} }

View File

@ -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";
// }
} }

View File

@ -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 );
} }
} }

View File

@ -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 );
}
} }

View File

@ -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" );
} }

View File

@ -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();
} }