diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java index 953a288343..afc887bb72 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java @@ -297,6 +297,7 @@ public class H2SqlAstTranslator extends SqlAstTranslato return " from dual"; } + private boolean supportsOffsetFetchClause() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 552972101c..9a9e4eb364 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -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 ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java index 50bae691f2..3c18a3d963 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java @@ -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 extends AbstractSqlAstTranslator { +public class PostgreSQLSqlAstTranslator extends SqlAstTranslatorWithMerge { public PostgreSQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { super( sessionFactory, statement ); @@ -280,5 +279,4 @@ public class PostgreSQLSqlAstTranslator extends Abstrac arithmeticExpression.getRightHandOperand().accept( this ); appendSql( CLOSE_PARENTHESIS ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java index 68676119ce..00e2c6f11b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java @@ -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 ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java index a65c216a63..128ef26ec7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java @@ -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. + *

+ * Use {@link #createMergeOperation(OptionalTableUpdate)} to translate an + * {@linkplain OptionalTableUpdate} into an executable {@linkplain MergeOperation} + * operation. + *

+ * * * @author Steve Ebersole */ @@ -28,9 +34,12 @@ public abstract class SqlAstTranslatorWithMerge 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 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 as t + // using (select col_1, col_2, ... from dual) as s + // on (t.key = s.key) // when not matched - // then insert ... + // then insert ... // when matched - // and s.[columns] is null - // then delete + // and s.col_1 is null + // and s.col_2 is null + // and ... + // then delete // when matched - // then update ... + // then update ... + // `merge into [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 valueBindings = optionalTableUpdate.getValueBindings(); - final List 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 valueBindings = optionalTableUpdate.getValueBindings(); + final List 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 extends if ( i > 0 ) { appendSql( ", " ); } - binding.getColumnReference().appendColumnForWrite( this, "t" ); + binding.getColumnReference().appendColumnForWrite( this, null ); appendSql( "=" ); binding.getColumnReference().appendColumnForWrite( this, "s" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java index 7778292228..50988e8adc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java @@ -74,6 +74,10 @@ public class SqlAstTranslatorWithUpsert extends Abstrac protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) { appendSql( "merge into " ); + renderMergeTarget( optionalTableUpdate ); + } + + private void renderMergeTarget(OptionalTableUpdate optionalTableUpdate) { appendSql( optionalTableUpdate.getMutatingTable().getTableName() ); renderMergeTargetAlias(); }