HHH-16110 - MERGE for optional table update PostgreSQL

This commit is contained in:
Steve Ebersole 2023-01-26 20:57:57 -06:00
parent b39c52b4d9
commit 67f8bee35a
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";
}
private boolean supportsOffsetFetchClause() {
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.PostgreSQLAggregateSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction;
import org.hibernate.dialect.function.PostgreSQLMinMaxFunction;
import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport;
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.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport;
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.tree.Statement;
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.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
@ -1366,23 +1369,12 @@ public class PostgreSQLDialect extends Dialect {
return OTHER;
}
// @Override
// public String getDisableConstraintsStatement() {
// return "set constraints all deferred";
// }
//
// @Override
// public String getEnableConstraintsStatement() {
// 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";
// }
@Override
public MutationOperation createOptionalTableUpdateOperation(
EntityMutationTarget mutationTarget,
OptionalTableUpdate optionalTableUpdate,
SessionFactoryImplementor factory) {
final PostgreSQLSqlAstTranslator<?> translator = new PostgreSQLSqlAstTranslator<>( factory, optionalTableUpdate );
return translator.createMergeOperation( optionalTableUpdate );
}
}

View File

@ -6,11 +6,10 @@
*/
package org.hibernate.dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.ComparisonOperator;
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.cte.CteMaterialization;
import org.hibernate.sql.ast.tree.cte.CteStatement;
@ -33,7 +32,7 @@ import org.hibernate.type.SqlTypes;
*
* @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) {
super( sessionFactory, statement );
@ -280,5 +279,4 @@ public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends Abstrac
arithmeticExpression.getRightHandOperand().accept( this );
appendSql( CLOSE_PARENTHESIS );
}
}

View File

@ -11,14 +11,18 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import jakarta.persistence.TemporalType;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.dialect.function.CommonFunctionFactory;
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.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;
@ -112,4 +116,13 @@ public class PostgresPlusDialect extends PostgreSQLDialect {
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 org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.ast.ColumnValueBinding;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.sql.model.jdbc.MergeOperation;
/**
* Base 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
*/
@ -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 );
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) {
// template:
//
// merge into [table] as t
// using values([bindings]) as s ([column-names])
// on t.[key] = s.[key]
// merge into <target-table> as t
// using (select col_1, col_2, ... from dual) as s
// on (t.key = s.key)
// when not matched
// then insert ...
// when matched
// and s.[columns] is null
// and s.col_1 is null
// and s.col_2 is null
// and ...
// then delete
// when matched
// then update ...
// `merge into <target-table> [as] t`
renderMergeInto( optionalTableUpdate );
appendSql( " " );
// using (select col_1, col_2, ... from dual) as s
renderMergeUsing( optionalTableUpdate );
appendSql( " " );
// on (t.key = s.key)
renderMergeOn( optionalTableUpdate );
appendSql( " " );
// when not matched
// then insert ...
renderMergeInsert( optionalTableUpdate );
appendSql( " " );
// when matched
// and s.col_1 is null
// and s.col_2 is null
// and ...
// then delete
renderMergeDelete( optionalTableUpdate );
appendSql( " " );
// when matched
// then update ...
renderMergeUpdate( optionalTableUpdate );
}
protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
appendSql( "merge into " );
renderMergeTarget( optionalTableUpdate );
}
private void renderMergeTarget(OptionalTableUpdate optionalTableUpdate) {
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
appendSql( " " );
renderMergeTargetAlias();
}
protected void renderMergeTargetAlias() {
appendSql( " as t" );
appendSql( "as t" );
}
protected void renderMergeUsing(OptionalTableUpdate optionalTableUpdate) {
appendSql( "using " );
renderMergeSource( optionalTableUpdate );
}
protected boolean wrapMergeSourceExpression() {
return true;
}
private void renderMergeSource(OptionalTableUpdate optionalTableUpdate) {
if ( wrapMergeSourceExpression() ) {
appendSql( " (" );
}
final List<ColumnValueBinding> valueBindings = optionalTableUpdate.getValueBindings();
final List<ColumnValueBinding> keyBindings = optionalTableUpdate.getKeyBindings();
final StringBuilder columnList = new StringBuilder();
appendSql( " values (" );
for ( int i = 0; i < keyBindings.size(); i++ ) {
final ColumnValueBinding keyBinding = keyBindings.get( i );
if ( i > 0 ) {
appendSql( ", " );
columnList.append( ", " );
}
columnList.append( keyBinding.getColumnReference().getColumnExpression() );
renderCasted( keyBinding.getValueExpression() );
}
for ( int i = 0; i < valueBindings.size(); i++ ) {
appendSql( ", " );
columnList.append( ", " );
final ColumnValueBinding valueBinding = valueBindings.get( i );
columnList.append( valueBinding.getColumnReference().getColumnExpression() );
renderCasted( valueBinding.getValueExpression() );
}
appendSql( "using (" );
renderMergeUsingQuery( optionalTableUpdate );
appendSql( ") " );
if ( wrapMergeSourceExpression() ) {
appendSql( ") " );
}
renderMergeSourceAlias();
appendSql( "(" );
appendSql( columnList.toString() );
appendSql( ")" );
}
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) {
@ -203,7 +225,7 @@ public abstract class SqlAstTranslatorWithMerge<T extends JdbcOperation> extends
if ( i > 0 ) {
appendSql( ", " );
}
binding.getColumnReference().appendColumnForWrite( this, "t" );
binding.getColumnReference().appendColumnForWrite( this, null );
appendSql( "=" );
binding.getColumnReference().appendColumnForWrite( this, "s" );
}

View File

@ -74,6 +74,10 @@ public class SqlAstTranslatorWithUpsert<T extends JdbcOperation> extends Abstrac
protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) {
appendSql( "merge into " );
renderMergeTarget( optionalTableUpdate );
}
private void renderMergeTarget(OptionalTableUpdate optionalTableUpdate) {
appendSql( optionalTableUpdate.getMutatingTable().getTableName() );
renderMergeTargetAlias();
}