HHH-15958 support the @RowId annotation on DB2 LUW

... and perhaps also on DB2 for z and i (no way to test it)
This commit is contained in:
Gavin 2023-01-01 16:09:26 +01:00 committed by Gavin King
parent 366208924f
commit 76b2f92f39
13 changed files with 112 additions and 25 deletions

View File

@ -13,25 +13,35 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies that an Oracle-style {@code rowid} should be used in SQL
* {@code update} statements for an entity, instead of the primary key.
* Specifies that a {@code rowid}-like column or pseudo-column should be
* used as the row locator in SQL {@code update} statements for an entity,
* instead of the primary key of the table.
* <p>
* If the {@linkplain org.hibernate.dialect.Dialect SQL dialect} does
* not support some sort of {@code rowid}, this annotation is ignored.
* not support some sort of {@code rowid}-like column or pseudo-column,
* then this annotation is ignored, and the primary key is used as the
* row locator.
*
* @author Steve Ebersole
*
* @see org.hibernate.dialect.Dialect#rowId
*/
@Target(TYPE)
@Retention(RUNTIME)
public @interface RowId {
/**
* Specifies the {@code rowid} identifier.
* Specifies the name of the {@code rowid}-like column for databases
* where the column is declared explicitly in DDL.
* <p>
* For example, on Oracle, this should be just {@code "rowid"}.
* It is <em>not</em> necessary to specify the name for databases where
* the {@code rowid}-like value is an implicitly-existing pseudo-column,
* and on those databases, this annotation member is ignored.
*
* @deprecated the {@code rowid} identifier is now inferred
* automatically from the {@link org.hibernate.dialect.Dialect}
* @apiNote Previously, this annotation member was required. But the
* name of the column it is now usually determined by calling
* {@link org.hibernate.dialect.Dialect#rowId}, and so this
* member is now usually ignored. The exception is for certain
* flavors of DB2.
*/
@Deprecated(since = "6.2")
String value() default "";
}

View File

@ -948,8 +948,8 @@ public class DB2Dialect extends Dialect {
@Override
public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData)
throws SQLException {
builder.setAutoQuoteInitialUnderscore(true);
return super.buildIdentifierHelper(builder, dbMetaData);
builder.setAutoQuoteInitialUnderscore( true );
return super.buildIdentifierHelper( builder, dbMetaData );
}
@Override
@ -976,4 +976,20 @@ public class DB2Dialect extends Dialect {
public String getCreateUserDefinedTypeExtensionsString() {
return " instantiable mode db2sql";
}
/**
* The more "standard" syntax is {@code rid_bit(alias)} but here we use {@code alias.rowid}.
* <p>
* There is also an alternative {@code rid()} of type {@code bigint}, but it cannot be used
* with partitioning.
*/
@Override
public String rowId(String rowId) {
return "rowid";
}
@Override
public int rowIdSqlType() {
return VARBINARY;
}
}

View File

@ -28,8 +28,10 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
import java.util.List;
import static org.hibernate.type.SqlTypes.ROWID;
/**
* A SQL dialect for DB2 for iSeries version 7.1 and above, previously known as "DB2/400".
* A SQL dialect for DB2 for IBM i version 7.1 and above, previously known as "DB2/400".
*
* @author Peter DeGregorio (pdegregorio)
* @author Christian Beikov
@ -149,4 +151,23 @@ public class DB2iDialect extends DB2Dialect {
}
};
}
// I speculate that this is a correct implementation of rowids for DB2 for i,
// just on the basis of the DB2 docs, but I currently have no way to test it
// Note that the implementation inherited from DB2Dialect for LUW will not work!
@Override
public String rowId(String rowId) {
return rowId.isEmpty() ? "rowid_" : rowId;
}
@Override
public int rowIdSqlType() {
return ROWID;
}
@Override
public String getRowIdColumnString(String rowId) {
return rowId( rowId ) + " rowid not null generated always";
}
}

View File

@ -30,10 +30,11 @@ import jakarta.persistence.TemporalType;
import java.util.List;
import static org.hibernate.type.SqlTypes.ROWID;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
/**
* A SQL dialect for DB2 for z/OS version 12.1 and above, previously known as :
* A SQL dialect for DB2 for z/OS version 12.1 and above, previously known as:
* <ul>
* <li>"Db2 UDB for z/OS", and
* <li>"Db2 UDB for z/OS and OS/390".
@ -208,4 +209,23 @@ public class DB2zDialect extends DB2Dialect {
}
};
}
// I speculate that this is a correct implementation of rowids for DB2 for z/OS,
// just on the basis of the DB2 docs, but I currently have no way to test it
// Note that the implementation inherited from DB2Dialect for LUW will not work!
@Override
public String rowId(String rowId) {
return rowId.isEmpty() ? "rowid_" : rowId;
}
@Override
public int rowIdSqlType() {
return ROWID;
}
@Override
public String getRowIdColumnString(String rowId) {
return rowId( rowId ) + " rowid not null generated always";
}
}

View File

@ -4628,8 +4628,17 @@ public abstract class Dialect implements ConversionContext {
* The name of a {@code rowid}-like pseudo-column which
* acts as a high-performance row locator, or null if
* this dialect has no such pseudo-column.
* <p>
* If the {@code rowid}-like value is an explicitly-declared
* named column instead of an implicit pseudo-column, and if
* the given name is nonempty, return the given name.
*
* @param rowId the name specified by
* {@link org.hibernate.annotations.RowId#value()},
* which is ignored if {@link #getRowIdColumnString}
* is not overridden
*/
public String rowId() {
public String rowId(String rowId) {
return null;
}
@ -4642,4 +4651,15 @@ public abstract class Dialect implements ConversionContext {
public int rowIdSqlType() {
return ROWID;
}
/**
* If this dialect requires that the {@code rowid} column be
* declared explicitly, return the DDL column definition.
*
* @return the DDL column definition, or {@code null} if
* the {@code rowid} is an implicit pseudo-column
*/
public String getRowIdColumnString(String rowId) {
return null;
}
}

View File

@ -859,7 +859,7 @@ public class H2Dialect extends Dialect {
}
@Override
public String rowId() {
public String rowId(String rowId) {
return "_rowid_";
}

View File

@ -1408,7 +1408,7 @@ public class OracleDialect extends Dialect {
}
@Override
public String rowId() {
public String rowId(String rowId) {
return "rowid";
}
}

View File

@ -1350,7 +1350,7 @@ public class PostgreSQLDialect extends Dialect {
// (these would help if we used 'delete' instead of 'truncate')
@Override
public String rowId() {
public String rowId(String rowId) {
return "ctid";
}

View File

@ -15,7 +15,6 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
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;

View File

@ -545,12 +545,7 @@ public abstract class AbstractEntityPersister
identifierAliases = new String[identifierColumnSpan];
final String rowId = persistentClass.getRootTable().getRowId();
if ( rowId == null ) {
rowIdName = null;
}
else {
rowIdName = rowId.isEmpty() ? dialect.rowId() : rowId;
}
rowIdName = rowId == null ? null : dialect.rowId( rowId );
if ( persistentClass.getLoaderName() != null ) {
// We must resolve the named query on-demand through the boot model because it isn't initialized yet

View File

@ -71,6 +71,12 @@ public class StandardTableExporter implements Exporter<Table> {
}
appendColumn( createTable, column, table, metadata, dialect, context );
}
if ( table.getRowId() != null ) {
String rowIdColumn = dialect.getRowIdColumnString( table.getRowId() );
if ( rowIdColumn != null ) {
createTable.append(", ").append( rowIdColumn );
}
}
if ( table.hasPrimaryKey() ) {
createTable.append( ", " ).append( table.getPrimaryKey().sqlConstraintString( dialect ) );
}

View File

@ -48,7 +48,7 @@ public class RowIdTest {
void testRowId(SessionFactoryScope scope) {
final String updatedName = "Smart phone";
scope.inTransaction( session -> {
String rowId = scope.getSessionFactory().getJdbcServices().getDialect().rowId();
String rowId = scope.getSessionFactory().getJdbcServices().getDialect().rowId("");
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();

View File

@ -314,7 +314,7 @@ abstract public class DialectChecks {
public static class SupportsRowId implements DialectCheck {
@Override
public boolean isMatch(Dialect dialect) {
return dialect.rowId() != null;
return dialect.rowId("") != null;
}
}
}