HHH-15812 Firebird dialect improvements

This commit is contained in:
Mark Rotteveel 2022-12-05 12:29:48 +01:00 committed by Christian Beikov
parent 0745a2e294
commit 9290f8b754
38 changed files with 507 additions and 158 deletions

View File

@ -24,6 +24,7 @@ import jakarta.persistence.TypedQuery;
import org.hibernate.CacheMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.H2Dialect;
@ -1536,6 +1537,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
@SkipForDialect(dialectClass = DerbyDialect.class)
@SkipForDialect(dialectClass = SybaseASEDialect.class)
@SkipForDialect(dialectClass = FirebirdDialect.class, reason = "order by not supported in list")
public void test_hql_aggregate_functions_within_group_example() {
doInJPA(this::entityManagerFactory, entityManager -> {
//tag::hql-aggregate-functions-within-group-example[]

View File

@ -15,6 +15,7 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Synchronize;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -39,6 +40,7 @@ import static org.junit.Assert.assertEquals;
)
@SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support a CONCAT function")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase doesn't support a CONCAT function")
@SkipForDialect(dialectClass = FirebirdDialect.class, reason = "Firebird doesn't support a CONCAT function")
public class SubselectTest {
@Test

View File

@ -82,6 +82,7 @@ public class TimeZoneStorageMappingTests {
}
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
public void testOffsetRetainedAuto(SessionFactoryScope scope) {
testOffsetRetained( scope, "Auto" );
}

View File

@ -205,14 +205,14 @@ ext {
'connection.init_sql' : ''
],
firebird : [
'db.dialect' : 'org.hibernate.dialect.FirebirdDialect',
'db.dialect' : 'org.hibernate.community.dialect.FirebirdDialect',
'jdbc.driver': 'org.firebirdsql.jdbc.FBDriver',
'jdbc.user' : 'sysdba',
'jdbc.pass' : 'masterkey',
// Overriding default transaction definition (5 seconds instead of infinite wait) to prevent problems in test cleanup
// Expects alias 'hibernate_orm_test' in aliases.conf (FB2.5 and earlier) or databases.conf (FB3.0 and later)
// Created database must either use default character set NONE, or UTF8 with page size 16384 or higher (to prevent issues with indexes due to keysize)
'jdbc.url' : 'jdbc:firebirdsql://' + dbHost +'localhost/hibernate_orm_test?charSet=utf-8;TRANSACTION_READ_COMMITTED=read_committed,rec_version,wait,lock_timeout=5',
'jdbc.url' : 'jdbc:firebirdsql://' + dbHost +'/hibernate_orm_test?charSet=utf-8;TRANSACTION_READ_COMMITTED=read_committed,rec_version,wait,lock_timeout=5',
'connection.init_sql' : ''
],
]

View File

@ -109,6 +109,9 @@ dependencies {
else if ( db.startsWith( 'mariadb' ) ) {
testRuntimeOnly dbLibs.mariadb
}
else if ( db.startsWith( 'firebird' ) ) {
testRuntimeOnly dbLibs.firebird
}
annotationProcessor libs.loggingProcessor
annotationProcessor libs.logging

View File

@ -82,7 +82,8 @@ public enum CommunityDatabase {
}
@Override
public String getUrlPrefix() {
return "jdbc:firebirdsql:";
// Jaybird 4 and higher support jdbc:firebird: and jdbc:firebirdsql: as JDBC protocol
return "jdbc:firebird";
}
},

View File

@ -9,15 +9,23 @@ package org.hibernate.community.dialect;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.community.dialect.identity.FirebirdIdentityColumnSupport;
import org.hibernate.community.dialect.pagination.SkipFirstLimitHandler;
import org.hibernate.community.dialect.sequence.FirebirdSequenceSupport;
@ -26,6 +34,7 @@ import org.hibernate.community.dialect.sequence.SequenceInformationExtractorFire
import org.hibernate.dialect.BooleanDecoder;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.identity.IdentityColumnSupport;
@ -44,10 +53,13 @@ import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Index;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.NullOrdering;
import org.hibernate.query.sqm.TemporalUnit;
@ -57,6 +69,7 @@ import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableM
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAppender;
@ -65,9 +78,12 @@ import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.internal.SequenceNameExtractorImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.tool.schema.internal.StandardIndexExporter;
import org.hibernate.tool.schema.spi.Exporter;
import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.DateTimeUtils;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.BinaryFloatDdlType;
@ -87,12 +103,8 @@ import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_END;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_DATE;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_TIME;
import static org.hibernate.type.descriptor.DateTimeUtils.JDBC_ESCAPE_START_TIMESTAMP;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
/**
@ -184,7 +196,7 @@ public class FirebirdDialect extends Dialect {
@Override
public int getMaxVarbinaryLength() {
return 32_756;
return 32_765;
}
@Override
@ -249,6 +261,12 @@ public class FirebirdDialect extends Dialect {
final BasicType<Character> characterType = basicTypeRegistry.resolve( StandardBasicTypes.CHARACTER );
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
// Firebird needs an actual argument type for aggregates like SUM, AVG, MIN, MAX to determine the result type
functionFactory.aggregates( this, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
// AVG by default uses the input type, so we possibly need to cast the argument type, hence a special function
functionFactory.avg_castingNonDoubleArguments( this, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER );
functionFactory.concat_pipeOperator();
functionFactory.cot();
functionFactory.cosh();
@ -511,6 +529,7 @@ public class FirebirdDialect extends Dialect {
DatabaseMetaData dbMetaData) throws SQLException {
// Any use of keywords as identifiers will result in token unknown error, so enable auto quote always
builder.setAutoQuoteKeywords( true );
builder.setAutoQuoteInitialUnderscore( true );
// Additional reserved words
// The Hibernate list of SQL:2003 reserved words doesn't contain all SQL:2003 reserved words,
@ -594,7 +613,7 @@ public class FirebirdDialect extends Dialect {
@Override
public void appendBooleanValueString(SqlAppender appender, boolean bool) {
//'boolean' type introduced in 3.0
if ( getVersion().isSameOrAfter( 3, 0 ) ) {
if ( getVersion().isBefore( 3 ) ) {
appender.appendSql( bool ? '1' : '0' );
}
else {
@ -611,14 +630,18 @@ public class FirebirdDialect extends Dialect {
@Override
public SequenceSupport getSequenceSupport() {
if ( getVersion().isBefore( 2, 0 ) ) {
return InterbaseSequenceSupport.INSTANCE;
DatabaseVersion version = getVersion();
if ( version.isSameOrAfter( 4 ) ) {
return FirebirdSequenceSupport.INSTANCE;
}
else if ( getVersion().isBefore( 3, 0 ) ) {
else if ( version.isSame( 3 ) ) {
return FirebirdSequenceSupport.FB3_INSTANCE;
}
else if ( version.isSame( 2 ) ) {
return FirebirdSequenceSupport.LEGACY_INSTANCE;
}
else {
return FirebirdSequenceSupport.INSTANCE;
return InterbaseSequenceSupport.INSTANCE;
}
}
@ -702,6 +725,11 @@ public class FirebirdDialect extends Dialect {
return true;
}
@Override
public boolean supportsFetchClause(FetchClauseType type) {
return type == FetchClauseType.ROWS_ONLY && getVersion().isSameOrAfter( 3 );
}
@Override
public boolean supportsValuesListForInsert() {
return false;
@ -717,6 +745,38 @@ public class FirebirdDialect extends Dialect {
return getVersion().isSameOrAfter( 4, 0 );
}
@Override
public NationalizationSupport getNationalizationSupport() {
return NationalizationSupport.IMPLICIT;
}
@Override
public boolean supportsDistinctFromPredicate() {
return true;
}
@Override
public boolean supportsRecursiveCTE() {
// Since Firebird 2.1
return true;
}
@Override
protected boolean supportsPredicateAsExpression() {
return getVersion().isSameOrAfter( 3 );
}
@Override
public String generatedAs(String generatedAs) {
return " generated always as (" + generatedAs + ")";
}
@Override
public boolean hasDataTypeBeforeGeneratedAs() {
// data type is optional
return false;
}
@Override
public String translateExtractField(TemporalUnit unit) {
switch ( unit ) {
@ -734,19 +794,19 @@ public class FirebirdDialect extends Dialect {
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( JDBC_ESCAPE_START_DATE );
appender.appendSql( "date '" );
appendAsDate( appender, temporalAccessor );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( '\'' );
break;
case TIME:
appender.appendSql( JDBC_ESCAPE_START_TIME );
appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( "time '" );
FirebirdDateTimeUtils.appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( '\'' );
break;
case TIMESTAMP:
appender.appendSql( JDBC_ESCAPE_START_TIMESTAMP );
appendAsTimestampWithMillis( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( "timestamp '" );
FirebirdDateTimeUtils.appendAsTimestampWithMillis( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( '\'' );
break;
default:
throw new IllegalArgumentException();
@ -756,19 +816,19 @@ public class FirebirdDialect extends Dialect {
public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType precision, TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( JDBC_ESCAPE_START_DATE );
appender.appendSql( "date '" );
appendAsDate( appender, date );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( '\'' );
break;
case TIME:
appender.appendSql( JDBC_ESCAPE_START_TIME );
appendAsTime( appender, date );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( "time '" );
appendAsLocalTime( appender, date );
appender.appendSql( '\'' );
break;
case TIMESTAMP:
appender.appendSql( JDBC_ESCAPE_START_TIMESTAMP );
appender.appendSql( "timestamp '" );
appendAsTimestampWithMillis( appender, date, jdbcTimeZone );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( '\'' );
break;
default:
throw new IllegalArgumentException();
@ -782,19 +842,19 @@ public class FirebirdDialect extends Dialect {
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( JDBC_ESCAPE_START_DATE );
appender.appendSql( "date '" );
appendAsDate( appender, calendar );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( '\'' );
break;
case TIME:
appender.appendSql( JDBC_ESCAPE_START_TIME );
appendAsTime( appender, calendar );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( "time '" );
appendAsLocalTime( appender, calendar );
appender.appendSql( '\'' );
break;
case TIMESTAMP:
appender.appendSql( JDBC_ESCAPE_START_TIMESTAMP );
appender.appendSql( "timestamp '" );
appendAsTimestampWithMillis( appender, calendar, jdbcTimeZone );
appender.appendSql( JDBC_ESCAPE_END );
appender.appendSql( '\'' );
break;
default:
throw new IllegalArgumentException();
@ -806,6 +866,13 @@ public class FirebirdDialect extends Dialect {
throw new UnsupportedOperationException( "format() function not supported on Firebird" );
}
@Override
public void appendUUIDLiteral(SqlAppender appender, UUID literal) {
appender.appendSql( "char_to_uuid('" );
appender.appendSql( literal.toString() );
appender.appendSql( "')" );
}
@Override
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return EXTRACTOR;
@ -930,4 +997,101 @@ public class FirebirdDialect extends Dialect {
public String getTemporaryTableCreateOptions() {
return "on commit delete rows";
}
private final FirebirdIndexExporter indexExporter = new FirebirdIndexExporter( this );
@Override
public Exporter<Index> getIndexExporter() {
return indexExporter;
}
private static class FirebirdIndexExporter extends StandardIndexExporter {
public FirebirdIndexExporter(Dialect dialect) {
super( dialect );
}
@Override
public String[] getSqlCreateStrings(Index index, Metadata metadata, SqlStringGenerationContext context) {
final String tableName = context.format( index.getTable().getQualifiedTableName() );
final Dialect dialect = getDialect();
final String indexNameForCreation = index.getQuotedName( dialect );
// In firebird the index is only sortable on top-level, not per column, use the first column to decide
final String sortOrder = index.getColumnOrderMap().getOrDefault( index.getColumns().get( 0 ), "asc" );
final StringBuilder buf = new StringBuilder()
// Although `create asc index` is valid, generate without (some tests check for a specific syntax prefix)
.append( "desc".equalsIgnoreCase( sortOrder ) || "descending".equalsIgnoreCase( sortOrder ) ? "create desc index " : "create index " )
.append( indexNameForCreation )
.append( " on " )
.append( tableName )
.append( " (" );
boolean first = true;
for ( Column column : index.getColumns() ) {
if ( first ) {
first = false;
}
else {
buf.append( ", " );
}
buf.append( ( column.getQuotedName( dialect ) ) );
}
buf.append( ')' );
return new String[] { buf.toString() };
}
}
private static final class FirebirdDateTimeUtils {
// Default formatting of DateTimeUtils renders UTC as Z, while Firebird expects +00:00
private static final DateTimeFormatter OFFSET_TIME = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append( DateTimeUtils.DATE_TIME_FORMATTER_TIME )
.parseLenient()
.appendOffset( "+HH:MM", "+00:00" )
.parseStrict()
.toFormatter( Locale.ENGLISH );
private static final DateTimeFormatter OFFSET_DATE_TIME_MILLIS = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append( DateTimeUtils.DATE_TIME_FORMATTER_TIMESTAMP_WITH_MILLIS )
.parseLenient()
.appendOffset( "+HH:MM", "+00:00" )
.parseStrict()
.toFormatter( Locale.ENGLISH );
private static void appendAsTime(
SqlAppender appender,
TemporalAccessor temporalAccessor,
boolean supportsOffset,
TimeZone jdbcTimeZone) {
if ( supportsOffset && temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) {
OFFSET_TIME.formatTo( temporalAccessor, appender );
}
else {
DateTimeUtils.appendAsTime( appender, temporalAccessor, supportsOffset, jdbcTimeZone );
}
}
public static void appendAsTimestampWithMillis(
SqlAppender appender,
TemporalAccessor temporalAccessor,
boolean supportsOffset,
TimeZone jdbcTimeZone) {
if ( supportsOffset && temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) {
OFFSET_DATE_TIME_MILLIS.formatTo( temporalAccessor, appender );
}
else if ( supportsOffset && temporalAccessor instanceof Instant ) {
OFFSET_DATE_TIME_MILLIS.formatTo(
( (Instant) temporalAccessor ).atZone( jdbcTimeZone.toZoneId() ),
appender
);
}
else {
DateTimeUtils.appendAsTimestampWithMillis( appender, temporalAccessor, supportsOffset, jdbcTimeZone );
}
}
}
}

View File

@ -7,15 +7,19 @@
package org.hibernate.community.dialect;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.FunctionExpression;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
@ -26,6 +30,7 @@ import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InListPredicate;
import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
@ -63,6 +68,58 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
// Firebird does not allow CASE expressions where all result arms contain plain parameters.
// At least one result arm must provide some type context for inference,
// so we cast the first result arm if we encounter this condition
@Override
protected void visitAnsiCaseSearchedExpression(
CaseSearchedExpression caseSearchedExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSearchedExpression ) ) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSearchedExpression(
caseSearchedExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSearchedExpression( caseSearchedExpression, resultRenderer );
}
}
@Override
protected void visitAnsiCaseSimpleExpression(
CaseSimpleExpression caseSimpleExpression,
Consumer<Expression> resultRenderer) {
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSimpleExpression ) ) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
final Expression firstResult = whenFragments.get( 0 ).getResult();
super.visitAnsiCaseSimpleExpression(
caseSimpleExpression,
e -> {
if ( e == firstResult ) {
renderCasted( e );
}
else {
resultRenderer.accept( e );
}
}
);
}
else {
super.visitAnsiCaseSimpleExpression( caseSimpleExpression, resultRenderer );
}
}
@Override
protected String getForUpdate() {
return " with lock";
@ -104,6 +161,7 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
try {
appendSql( "select " );
visitSqlSelections( selectClause );
renderVirtualSelections( selectClause );
}
finally {
clauseStack.pop();
@ -133,9 +191,9 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
@Override
protected boolean supportsSimpleQueryGrouping() {
// Firebird is quite strict i.e. it requires `select .. union all select * from (select ...)`
// Firebird 4 and earlier are quite strict i.e. it requires `select .. union all select * from (select ...)`
// rather than `select .. union all (select ...)`
return false;
return getDialect().getVersion().isSameOrAfter( 5 );
}
@Override
@ -167,6 +225,28 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
@Override
public void visitInListPredicate(InListPredicate inListPredicate) {
final List<Expression> listExpressions = inListPredicate.getListExpressions();
if ( listExpressions.isEmpty() ) {
appendSql( "1=0" );
return;
}
final Expression testExpression = inListPredicate.getTestExpression();
if ( isParameter( testExpression ) ) {
renderCasted( testExpression );
if ( inListPredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " in(" );
renderCommaSeparated( listExpressions );
appendSql( CLOSE_PARENTHESIS );
}
else {
super.visitInListPredicate( inListPredicate );
}
}
@Override
protected boolean supportsRowValueConstructorSyntax() {
return false;
@ -196,6 +276,16 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
return getDialect().getVersion().isSameOrAfter( 3 );
}
@Override
protected boolean supportsIntersect() {
return false;
}
@Override
protected boolean supportsNestedWithClause() {
return false;
}
@Override
public void visitSelfRenderingPredicate(SelfRenderingPredicate selfRenderingPredicate) {
// see comments in visitParameter
@ -213,9 +303,9 @@ public class FirebirdSqlAstTranslator<T extends JdbcOperation> extends AbstractS
public void visitSelfRenderingExpression(SelfRenderingExpression expression) {
// see comments in visitParameter
boolean inFunction = this.inFunction;
this.inFunction = !( expression instanceof FunctionExpression ) || !"cast".equals( ( (FunctionExpression) expression).getFunctionName() );
this.inFunction = !( expression instanceof FunctionExpression ) || !"cast".equals( ( (FunctionExpression) expression ).getFunctionName() );
try {
super.visitSelfRenderingExpression( expression);
super.visitSelfRenderingExpression( expression );
}
finally {
this.inFunction = inFunction;

View File

@ -18,14 +18,14 @@ import org.hibernate.dialect.sequence.SequenceSupport;
*/
public class FirebirdSequenceSupport extends ANSISequenceSupport {
public static final SequenceSupport INSTANCE = new FirebirdSequenceSupport() {
public static final SequenceSupport INSTANCE = new FirebirdSequenceSupport();
public static final SequenceSupport FB3_INSTANCE = new FirebirdSequenceSupport() {
@Override
public String getCreateSequenceString(String sequenceName, int initialValue, int incrementSize) {
// NOTE Firebird 3 has an 'off by increment' bug, see
// http://tracker.firebirdsql.org/browse/CORE-6084
// NOTE Firebird 3 has an 'off by increment' bug, see https://github.com/FirebirdSQL/firebird/issues/6334
if (initialValue == 1 && incrementSize == 1) {
// Workaround for initial value and increment 1
// This workaround also works fine in Firebird 4, so we don't need to add yet another specialization
return getCreateSequenceString( sequenceName );
}
return super.getCreateSequenceString( sequenceName, initialValue, incrementSize);

View File

@ -60,8 +60,11 @@ public class LoadFetchGraphWithEagerSelfReferencingEagerToOneTest {
@AfterEach
public void tearDown(EntityManagerFactoryScope scope) {
scope.inTransaction(
entityManager ->
entityManager.createQuery( "delete from Sample" ).executeUpdate()
entityManager -> {
entityManager.createQuery( "delete from Sample where id = 1" ).executeUpdate();
entityManager.createQuery( "delete from Sample where id = 2" ).executeUpdate();
entityManager.createQuery( "delete from Sample where id = 3" ).executeUpdate();
}
);
}

View File

@ -18,6 +18,7 @@ import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.ParamDef;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
@ -83,6 +84,7 @@ public class FilterParameterTests {
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "HANA silently converts a boolean to string types")
@SkipForDialect(dialectClass = CockroachDialect.class, matchSubTypes = true, reason = "Cockroach silently converts a boolean to string types")
@SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "PostgresPlus silently converts a boolean to string types")
@SkipForDialect(dialectClass = FirebirdDialect.class, reason = "Firebird silently converts a boolean to string")
public void testYesNoMismatch(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final EntityOne loaded = session.byId( EntityOne.class ).load( 1 );
@ -129,6 +131,7 @@ public class FilterParameterTests {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server silently converts a boolean to integral types")
@SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase silently converts a boolean to integral types")
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "HANA silently converts a boolean to integral types")
@SkipForDialect(dialectClass = FirebirdDialect.class, matchSubTypes = true, reason = "Firebird silently converts a boolean to integral types")
public void testNumericMismatch(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final EntityTwo loaded = session.byId( EntityTwo.class ).load( 1 );

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.orm.test.hbm.collectionpk;
import java.util.Arrays;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
@ -44,11 +46,12 @@ public class CollectionPkTest extends BaseUnitTestCase {
public void testSet() {
verifyPkNameUsed(
"org/hibernate/orm/test/hbm/collectionpk/person_set.hbm.xml",
"primary key (group, name)"
"primary key (group, name)",
"primary key (\"group\", name)"
);
}
private void verifyPkNameUsed(String mappingResource, String expectedName) {
private void verifyPkNameUsed(String mappingResource, String... expectedName) {
final Metadata metadata = new MetadataSources( ssr )
.addResource( mappingResource )
.buildMetadata();
@ -57,8 +60,8 @@ public class CollectionPkTest extends BaseUnitTestCase {
new SchemaCreatorImpl( ssr ).doCreation( metadata, false, target );
assertTrue(
"Expected foreign-key name [" + expectedName + "] not seen in schema creation output",
target.containedText( expectedName )
"Expected foreign-key name [" + Arrays.toString(expectedName) + "] not seen in schema creation output",
Arrays.stream( expectedName ).anyMatch( target::containedText )
);
}
@ -66,7 +69,8 @@ public class CollectionPkTest extends BaseUnitTestCase {
public void testMap() {
verifyPkNameUsed(
"org/hibernate/orm/test/hbm/collectionpk/person_map.hbm.xml",
"primary key (group, locale)"
"primary key (group, locale)",
"primary key (\"group\", locale)"
);
}

View File

@ -48,12 +48,15 @@ public class NonUniqueIdTest {
session.createNativeQuery(
"create table CATEGORY( id integer not null, name varchar(255) )"
).executeUpdate();
}
);
scope.inTransaction(
session -> {
session.createNativeQuery( "insert into CATEGORY( id, name) VALUES( 1, 'clothes' )" )
.executeUpdate();
session.createNativeQuery( "insert into CATEGORY( id, name) VALUES( 1, 'shoes' )" )
.executeUpdate();
}
);
}

View File

@ -16,6 +16,7 @@ import java.sql.Statement;
import org.hibernate.JDBCException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.spi.SessionImplementor;
@ -59,6 +60,7 @@ public class BasicConnectionTest extends BaseCoreFunctionalTestCase {
JdbcCoordinator jdbcCoord = sessionImpl.getJdbcCoordinator();
try {
Transaction ddlTxn = session.beginTransaction();
Statement statement = jdbcCoord.getStatementPreparer().createStatement();
String dropSql = sessionFactory().getJdbcServices().getDialect().getDropTableString( "SANDBOX_JDBC_TST" );
try {
@ -74,6 +76,7 @@ public class BasicConnectionTest extends BaseCoreFunctionalTestCase {
getResourceRegistry( jdbcCoord ).release( statement );
assertFalse( getResourceRegistry( jdbcCoord ).hasRegisteredResources() );
assertTrue( jdbcCoord.getLogicalConnection().isPhysicallyConnected() ); // after_transaction specified
ddlTxn.commit();
PreparedStatement ps = jdbcCoord.getStatementPreparer().prepareStatement(
"insert into SANDBOX_JDBC_TST( ID, NAME ) values ( ?, ? )" );

View File

@ -302,6 +302,7 @@ public class BatchingTest extends BaseCoreFunctionalTestCase implements BatchKey
}
private void exportSandboxSchema(SessionImplementor sessionImpl) {
Transaction txn = sessionImpl.beginTransaction();
final JdbcCoordinator jdbcCoordinator = sessionImpl.getJdbcCoordinator();
LogicalConnectionImplementor logicalConnection = jdbcCoordinator.getLogicalConnection();
@ -320,6 +321,7 @@ public class BatchingTest extends BaseCoreFunctionalTestCase implements BatchKey
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( statement );
assertFalse( jdbcCoordinator.getLogicalConnection().getResourceRegistry().hasRegisteredResources() );
assertTrue( logicalConnection.isPhysicallyConnected() ); // after_transaction specified
txn.commit();
}
@Override

View File

@ -19,6 +19,7 @@ import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.TransactionException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DerbyDialect;
@ -1175,6 +1176,7 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase {
@SkipForDialect(value = HSQLDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = AbstractHANADialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = CockroachDialect.class, comment = "Cockroach supports the 'for no key update' syntax but it doesn't work")
@SkipForDialect(value = FirebirdDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
public void testLockInsertFkTarget() {
Lock lock = new Lock();
lock.setName( "name" );
@ -1212,6 +1214,7 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase {
@SkipForDialect(value = HSQLDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = AbstractHANADialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
@SkipForDialect(value = CockroachDialect.class, comment = "Cockroach supports the 'for no key update' syntax but it doesn't work")
@SkipForDialect(value = FirebirdDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
public void testLockUpdateFkTarget() {
Lock lock1 = new Lock();
lock1.setName( "l1" );

View File

@ -24,6 +24,7 @@ import org.hibernate.Session;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.Nationalized;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.AbstractTransactSQLDialect;
import org.hibernate.dialect.CockroachDialect;
@ -149,6 +150,7 @@ public class NativeQueryResultTypeAutoDiscoveryTest {
@SkipForDialect(dialectClass = AbstractTransactSQLDialect.class, matchSubTypes = true, reason = "No support for the tinyint datatype so we use smallint")
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "No support for the tinyint datatype so we use smallint")
@SkipForDialect(dialectClass = OracleDialect.class, reason = "Oracle maps tinyint to number")
@SkipForDialect(dialectClass = FirebirdDialect.class, reason = "No support for the tinyint datatype so we use smallint")
public void tinyintType() {
createEntityManagerFactory( TinyintEntity.class );
doTest( TinyintEntity.class, (byte)127 );
@ -178,6 +180,7 @@ public class NativeQueryResultTypeAutoDiscoveryTest {
@SkipForDialect(dialectClass = OracleDialect.class, reason = "Value is too big for the maximum allowed precision of Oracle")
@SkipForDialect(dialectClass = AbstractTransactSQLDialect.class, matchSubTypes = true, reason = "Value is too big for the maximum allowed precision of SQL Server and Sybase")
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "Value is too big for the maximum allowed precision of HANA")
@SkipForDialect(dialectClass = FirebirdDialect.class, reason = "Value is too big for the maximum allowed precision of Firebird")
public void numericType() {
createEntityManagerFactory(
NumericEntity.class
@ -191,6 +194,7 @@ public class NativeQueryResultTypeAutoDiscoveryTest {
@SkipForDialect(dialectClass = OracleDialect.class, reason = "Value is too big for the maximum allowed precision of Oracle")
@SkipForDialect(dialectClass = AbstractTransactSQLDialect.class, matchSubTypes = true, reason = "Value is too big for the maximum allowed precision of SQL Server and Sybase")
@SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "Value is too big for the maximum allowed precision of HANA")
@SkipForDialect(dialectClass = FirebirdDialect.class, reason = "Value is too big for the maximum allowed precision of Firebird")
public void decimalType() {
createEntityManagerFactory( DecimalEntity.class );
doTest( DecimalEntity.class, new BigDecimal( "5464384284258458485484848458.48465843584584684" ) );

View File

@ -11,6 +11,7 @@ import java.util.List;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.mapping.Collection;
@ -25,7 +26,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertThat;
@SkipForDialect({SQLServerDialect.class, SybaseDialect.class})
@SkipForDialect({SQLServerDialect.class, SybaseDialect.class, FirebirdDialect.class})
public class SubselectFetchWithFormulaTest extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected String getBaseForMappings() {

View File

@ -15,6 +15,7 @@ import jakarta.persistence.criteria.Root;
import org.hibernate.annotations.DialectOverride;
import org.hibernate.annotations.Formula;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.HSQLDialect;
@ -105,7 +106,7 @@ public class FormulaTests {
@Formula(value = "credit * rate")
private Double interest;
@Formula(value = "rate * 100 || '%'")
@Formula(value = "(rate * 100) || '%'")
@DialectOverride.Formula(dialect = MySQLDialect.class,
override = @Formula("concat(rate * 100, '%')"))
@DialectOverride.Formula(dialect = HSQLDialect.class,
@ -120,6 +121,8 @@ public class FormulaTests {
override = @Formula("ltrim(str(rate * 100, 10, 2)) + '%'"))
@DialectOverride.Formula(dialect = SybaseDialect.class,
override = @Formula("ltrim(str(rate * 100, 10, 2)) + '%'"))
@DialectOverride.Formula(dialect = FirebirdDialect.class,
override = @Formula("cast(rate * 100 as decimal(10,2)) || '%'"))
private String ratePercent;
public Long getId() {

View File

@ -14,6 +14,9 @@ import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Selectable;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
@ -68,7 +71,7 @@ public class LegacyJpaNamingWithAnnotationBindingTests extends BaseAnnotationBin
@Override
protected void validateOrderPrimaryTableName(String name) {
assertEquals( "Order", name );
assertThat( name, anyOf( equalTo( "Order"), equalTo( "`Order`") ) );
}
@Override
@ -172,7 +175,7 @@ public class LegacyJpaNamingWithAnnotationBindingTests extends BaseAnnotationBin
@Override
protected void validateCustomerOrdersTableName(String name) {
assertEquals( "Order", name );
assertThat( name, anyOf( equalTo( "Order"), equalTo( "`Order`") ) );
}
@Override

View File

@ -16,6 +16,9 @@ import org.hibernate.mapping.Selectable;
import org.hibernate.testing.TestForIssue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
@ -74,7 +77,7 @@ public class LegacyJpaNamingWithHbmBindingTests extends BaseHbmBindingTests {
@Override
protected void validateOrderPrimaryTableName(String name) {
assertEquals( "Order", name );
assertThat( name, anyOf( equalTo( "Order"), equalTo( "`Order`") ) );
}
@Override
@ -174,7 +177,7 @@ public class LegacyJpaNamingWithHbmBindingTests extends BaseHbmBindingTests {
@Override
protected void validateCustomerOrdersTableName(String name) {
assertEquals( "Order", name );
assertThat( name, anyOf( equalTo( "Order"), equalTo( "`Order`") ) );
}
@Override

View File

@ -6,9 +6,12 @@
*/
package org.hibernate.orm.test.query;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
@ -22,6 +25,9 @@ import jakarta.persistence.Table;
@DomainModel(
annotatedClasses = NativeQueryWithParenthesesTest.Person.class
)
@SkipForDialect(dialectClass = FirebirdDialect.class, majorVersion = 4, reason = "Firebird 4.0 and earlier don't support simple query grouping")
@SkipForDialect(dialectClass = FirebirdDialect.class, majorVersion = 3, reason = "Firebird 4.0 and earlier don't support simple query grouping")
@SkipForDialect(dialectClass = FirebirdDialect.class, majorVersion = 2, reason = "Firebird 4.0 and earlier don't support simple query grouping")
@SessionFactory
public class NativeQueryWithParenthesesTest {

View File

@ -365,8 +365,8 @@ public class FunctionTests {
public void testAsciiChrFunctions(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
assertThat( session.createQuery("select chr(65)").getSingleResult(), is('A') );
assertThat( session.createQuery("select ascii('A')").getSingleResult(), is(65) );
assertThat( session.createQuery("select chr(65)").getSingleResult(), is( 'A' ) );
assertThat( session.createQuery("select ascii('A')").getSingleResult(), anyOf( is( 65 ), is( (short) 65 ) ) );
}
);
}
@ -1327,6 +1327,7 @@ public class FunctionTests {
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTimezoneTypes.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class, comment = "We extract the offset with a format function")
public void testExtractFunctionTimeZone(SessionFactoryScope scope) {
scope.inTransaction(
session -> {

View File

@ -629,6 +629,7 @@ public class StandardFunctionTests {
@Test
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTimezoneTypes.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class, comment = "We extract the offset with a format function")
public void testExtractFunctionTimeZone(SessionFactoryScope scope) {
scope.inTransaction(
session -> {

View File

@ -15,6 +15,7 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
@ -27,6 +28,7 @@ import org.junit.Test;
*/
@SkipForDialect(value = DB2Dialect.class, comment = "DB2 is far more resistant to the reserved keyword usage. See HHH-12832.")
@SkipForDialect(value = DerbyDialect.class, comment = "Derby is far more resistant to the reserved keyword usage.")
@SkipForDialect(value = FirebirdDialect.class, comment = "FirebirdDialect has autoQuoteKeywords enabled, so it is far more resistant to the reserved keyword usage.")
public class SchemaMigratorHaltOnErrorTest extends BaseEntityManagerFunctionalTestCase {
@Override

View File

@ -20,6 +20,7 @@ import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.junit4.CustomRunner;
@ -39,6 +40,7 @@ import org.junit.runner.RunWith;
*/
@SkipForDialect(value = DB2Dialect.class, comment = "DB2 is far more resistant to the reserved keyword usage. See HHH-12832.")
@SkipForDialect(value = DerbyDialect.class, comment = "Derby is far more resistant to the reserved keyword usage.")
@SkipForDialect(value = FirebirdDialect.class, comment = "FirebirdDialect has autoQuoteKeywords enabled, so it is far more resistant to the reserved keyword usage.")
@RunWith(CustomRunner.class)
public class SchemaUpdateHaltOnErrorTest {

View File

@ -12,11 +12,14 @@ import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.List;
import java.util.function.UnaryOperator;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
@ -27,7 +30,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertThat;
/**
@ -166,13 +170,8 @@ public class ForeignKeyGenerationTest extends BaseUnitTestCase {
throws Exception {
final String expectedAlterTableStatement = alterTableStatement.toSQL();
final List<String> sqlLines = Files.readAllLines( output.toPath(), Charset.defaultCharset() );
boolean found = false;
for ( String line : sqlLines ) {
if ( line.contains( expectedAlterTableStatement ) ) {
return;
}
}
assertThat( "Expected alter table statement not found : " + expectedAlterTableStatement, found, is( true ) );
assertThat( "Expected alter table statement not found", sqlLines, hasItem( containsString( expectedAlterTableStatement ) ) );
}
private static class AlterTableStatement {
@ -196,7 +195,13 @@ public class ForeignKeyGenerationTest extends BaseUnitTestCase {
}
public String toSQL() {
return ssr.getService( JdbcEnvironment.class ).getDialect().getAlterTableString( tableName ) + " add constraint " + fkConstraintName + " foreign key (" + fkColumnName + ") references " + referenceTableName;
JdbcEnvironment jdbcEnvironment = ssr.getService( JdbcEnvironment.class );
Dialect dialect = jdbcEnvironment.getDialect();
IdentifierHelper identifierHelper = jdbcEnvironment.getIdentifierHelper();
UnaryOperator<String> asIdentifier = identifier -> identifierHelper.toIdentifier( identifier ).render( dialect );
return dialect.getAlterTableString( asIdentifier.apply( tableName ) )
+ " add constraint " + asIdentifier.apply( fkConstraintName )
+ " foreign key (" + asIdentifier.apply( fkColumnName ) + ") references " + asIdentifier.apply( referenceTableName );
}
}

View File

@ -22,11 +22,14 @@ import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.List;
import java.util.function.UnaryOperator;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
@ -38,7 +41,8 @@ import org.junit.Test;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertThat;
/**
@ -77,13 +81,8 @@ public class JoinedInheritanceForeignKeyTest extends BaseUnitTestCase {
throws Exception {
final String expectedAlterTableStatement = alterTableStatement.toSQL();
final List<String> sqlLines = Files.readAllLines( output.toPath(), Charset.defaultCharset() );
boolean found = false;
for ( String line : sqlLines ) {
if ( line.contains( expectedAlterTableStatement ) ) {
return;
}
}
assertThat( "Expected alter table statement not found : " + expectedAlterTableStatement, found, is( true ) );
assertThat( "Expected alter table statement not found", sqlLines, hasItem( containsString( expectedAlterTableStatement ) ) );
}
private static class AlterTableStatement {
@ -106,7 +105,13 @@ public class JoinedInheritanceForeignKeyTest extends BaseUnitTestCase {
}
public String toSQL() {
return ssr.getService( JdbcEnvironment.class ).getDialect().getAlterTableString( tableName ) + " add constraint " + fkConstraintName + " foreign key (" + fkColumnName + ") references " + referenceTableName;
JdbcEnvironment jdbcEnvironment = ssr.getService( JdbcEnvironment.class );
Dialect dialect = jdbcEnvironment.getDialect();
IdentifierHelper identifierHelper = jdbcEnvironment.getIdentifierHelper();
UnaryOperator<String> asIdentifier = identifier -> identifierHelper.toIdentifier( identifier ).render( dialect );
return dialect.getAlterTableString( asIdentifier.apply( tableName ) )
+ " add constraint " + asIdentifier.apply( fkConstraintName )
+ " foreign key (" + asIdentifier.apply( fkColumnName ) + ") references " + asIdentifier.apply( referenceTableName );
}
}

View File

@ -28,7 +28,8 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@BaseUnitTest
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsSequences.class)
@ -56,7 +57,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 50" ) );
assertThat( fileContent, containsString( "increment by 50" ) );
}
@Test
@ -66,7 +67,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 1" ) );
assertThat( fileContent, containsString( "increment by 1" ) );
}
@Test
@ -76,7 +77,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 50" ) );
assertThat( fileContent, containsString( "increment by 50" ) );
}
@Test
@ -86,7 +87,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 1" ) );
assertThat( fileContent, containsString( "increment by 1" ) );
}
@Test
@ -96,7 +97,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 50" ) );
assertThat( fileContent, containsString( "increment by 50" ) );
}
@Test
@ -106,7 +107,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 50" ) );
assertThat( fileContent, containsString( "increment by 50" ) );
}
@Test
@ -116,7 +117,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 20" ) );
assertThat( fileContent, containsString( "increment by 20" ) );
}
@Test
@ -126,7 +127,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 20" ) );
assertThat( fileContent, containsString( "increment by 20" ) );
}
@Test
@ -136,7 +137,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 50" ) );
assertThat( fileContent, containsString( "increment by 50" ) );
}
@Test
@ -146,7 +147,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 1" ) );
assertThat( fileContent, containsString( "increment by 1" ) );
}
@Test
@ -156,7 +157,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 10" ) );
assertThat( fileContent, containsString( "increment by 10" ) );
}
@Test
@ -166,7 +167,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 10" ) );
assertThat( fileContent, containsString( "increment by 10" ) );
}
@Test
@ -176,7 +177,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 50" ) );
assertThat( fileContent, containsString( "increment by 50" ) );
}
@Test
@ -189,7 +190,7 @@ public class SequenceGeneratorIncrementTest {
createSchema();
final String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( fileContent.contains( "increment by 1" ) );
assertThat( fileContent, containsString( "increment by 1" ) );
}
private void buildMetadata(Class annotatedClass) {

View File

@ -78,6 +78,30 @@ public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCa
"primary key (ID, CODE) )"
).executeUpdate();
session.createNativeQuery(
"create table ASSOCIATION_TABLE( " +
"MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " +
"ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " +
"primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))"
).executeUpdate();
session.createNativeQuery(
"create table MATERIAL_RATINGS( " +
"MATERIAL_ID integer not null, RATING_ID integer not null," +
" primary key (MATERIAL_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"create table BUILDING_RATINGS( " +
"BUILDING_ID integer not null, RATING_ID integer not null," +
" primary key (BUILDING_ID, RATING_ID))"
).executeUpdate();
}
);
doInHibernate(
this::sessionFactory, session -> {
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" )
.executeUpdate();
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" )
@ -93,13 +117,6 @@ public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCa
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" )
.executeUpdate();
session.createNativeQuery(
"create table ASSOCIATION_TABLE( " +
"MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " +
"ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " +
"primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))"
).executeUpdate();
session.createNativeQuery(
"insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " +
"VALUES( 1, 'MATERIAL', 1, 'RATING' )"
@ -128,23 +145,10 @@ public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCa
"VALUES( 1, 'BUILDING', 1, 'SIZE' )"
).executeUpdate();
session.createNativeQuery(
"create table MATERIAL_RATINGS( " +
"MATERIAL_ID integer not null, RATING_ID integer not null," +
" primary key (MATERIAL_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )"
).executeUpdate();
session.createNativeQuery(
"create table BUILDING_RATINGS( " +
"BUILDING_ID integer not null, RATING_ID integer not null," +
" primary key (BUILDING_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 1 )"
).executeUpdate();

View File

@ -72,6 +72,11 @@ public class LazyOneToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCas
"MATERIAL_OWNER_ID integer, BUILDING_OWNER_ID integer, " +
"primary key (ID, CODE) )"
).executeUpdate();
}
);
doInHibernate(
this::sessionFactory, session -> {
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" )
.executeUpdate();

View File

@ -57,6 +57,29 @@ public class LazyManyToManyNonUniqueIdNotFoundWhereTest extends BaseCoreFunction
"primary key (ID, CODE) )"
).executeUpdate();
session.createNativeQuery(
"create table ASSOCIATION_TABLE( " +
"MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " +
"ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " +
"primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))"
).executeUpdate();
session.createNativeQuery(
"create table BUILDING_RATINGS( " +
"BUILDING_ID integer not null, RATING_ID integer not null," +
" primary key (BUILDING_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"create table MATERIAL_RATINGS( " +
"MATERIAL_ID integer not null, RATING_ID integer not null," +
" primary key (MATERIAL_ID, RATING_ID))"
).executeUpdate();
}
);
doInHibernate(
this::sessionFactory, session -> {
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" )
.executeUpdate();
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" )
@ -66,13 +89,6 @@ public class LazyManyToManyNonUniqueIdNotFoundWhereTest extends BaseCoreFunction
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'small', 'SIZE' )" )
.executeUpdate();
session.createNativeQuery(
"create table ASSOCIATION_TABLE( " +
"MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " +
"ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " +
"primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))"
).executeUpdate();
session.createNativeQuery(
"insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " +
"VALUES( 1, 'MATERIAL', 1, 'RATING' )"
@ -118,12 +134,6 @@ public class LazyManyToManyNonUniqueIdNotFoundWhereTest extends BaseCoreFunction
"VALUES( 1, 'BUILDING', 2, 'SIZE' )"
).executeUpdate();
session.createNativeQuery(
"create table MATERIAL_RATINGS( " +
"MATERIAL_ID integer not null, RATING_ID integer not null," +
" primary key (MATERIAL_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )"
).executeUpdate();
@ -133,13 +143,6 @@ public class LazyManyToManyNonUniqueIdNotFoundWhereTest extends BaseCoreFunction
"insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 2 )"
).executeUpdate();
session.createNativeQuery(
"create table BUILDING_RATINGS( " +
"BUILDING_ID integer not null, RATING_ID integer not null," +
" primary key (BUILDING_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 1 )"
).executeUpdate();

View File

@ -63,6 +63,30 @@ public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCa
"primary key (ID, CODE) )"
).executeUpdate();
session.createNativeQuery(
"create table ASSOCIATION_TABLE( " +
"MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " +
"ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " +
"primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))"
).executeUpdate();
session.createNativeQuery(
"create table MATERIAL_RATINGS( " +
"MATERIAL_ID integer not null, RATING_ID integer not null," +
" primary key (MATERIAL_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"create table BUILDING_RATINGS( " +
"BUILDING_ID integer not null, RATING_ID integer not null," +
" primary key (BUILDING_ID, RATING_ID))"
).executeUpdate();
}
);
doInHibernate(
this::sessionFactory, session -> {
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" )
.executeUpdate();
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" )
@ -78,13 +102,6 @@ public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCa
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 2, 'medium', 'SIZE' )" )
.executeUpdate();
session.createNativeQuery(
"create table ASSOCIATION_TABLE( " +
"MAIN_ID integer not null, MAIN_CODE varchar(10) not null, " +
"ASSOCIATION_ID int not null, ASSOCIATION_CODE varchar(10) not null, " +
"primary key (MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE))"
).executeUpdate();
session.createNativeQuery(
"insert into ASSOCIATION_TABLE(MAIN_ID, MAIN_CODE, ASSOCIATION_ID, ASSOCIATION_CODE) " +
"VALUES( 1, 'MATERIAL', 1, 'RATING' )"
@ -113,23 +130,10 @@ public class LazyManyToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCa
"VALUES( 1, 'BUILDING', 1, 'SIZE' )"
).executeUpdate();
session.createNativeQuery(
"create table MATERIAL_RATINGS( " +
"MATERIAL_ID integer not null, RATING_ID integer not null," +
" primary key (MATERIAL_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"insert into MATERIAL_RATINGS(MATERIAL_ID, RATING_ID) VALUES( 1, 1 )"
).executeUpdate();
session.createNativeQuery(
"create table BUILDING_RATINGS( " +
"BUILDING_ID integer not null, RATING_ID integer not null," +
" primary key (BUILDING_ID, RATING_ID))"
).executeUpdate();
session.createNativeQuery(
"insert into BUILDING_RATINGS(BUILDING_ID, RATING_ID) VALUES( 1, 1 )"
).executeUpdate();

View File

@ -59,7 +59,11 @@ public class LazyOneToManyNonUniqueIdWhereTest extends BaseCoreFunctionalTestCas
"MATERIAL_OWNER_ID integer, BUILDING_OWNER_ID integer, " +
"primary key (ID, CODE) )"
).executeUpdate();
}
);
doInHibernate(
this::sessionFactory, session -> {
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'plastic', 'MATERIAL' )" )
.executeUpdate();
session.createNativeQuery( "insert into MAIN_TABLE(ID, NAME, CODE) VALUES( 1, 'house', 'BUILDING' )" )

View File

@ -11,6 +11,7 @@ apply from: rootProject.file( 'gradle/published-java-module.gradle' )
dependencies {
api project( ':hibernate-core' )
api project( ':hibernate-community-dialects' )
api testLibs.junit4
api testLibs.junit5Api

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.testing;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
@ -229,7 +230,9 @@ abstract public class DialectChecks {
public static class SupportsJdbcDriverProxying implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return !( dialect instanceof DB2Dialect ) && !( dialect instanceof DerbyDialect );
return !( dialect instanceof DB2Dialect
|| dialect instanceof DerbyDialect
|| dialect instanceof FirebirdDialect );
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.testing.orm.junit;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DB2Dialect;
@ -152,7 +153,9 @@ abstract public class DialectFeatureChecks {
public static class SupportsJdbcDriverProxying implements DialectFeatureCheck {
public boolean apply(Dialect dialect) {
return !( dialect instanceof DB2Dialect ) && !( dialect instanceof DerbyDialect );
return !( dialect instanceof DB2Dialect
|| dialect instanceof DerbyDialect
|| dialect instanceof FirebirdDialect );
}
}
@ -452,6 +455,7 @@ abstract public class DialectFeatureChecks {
return !( dialect instanceof MySQLDialect
|| dialect instanceof SybaseDialect
|| dialect instanceof DerbyDialect
|| dialect instanceof FirebirdDialect
|| dialect instanceof DB2Dialect && ( (DB2Dialect) dialect ).getDB2Version().isBefore( 11 ) )
|| dialect instanceof MariaDBDialect;
}

View File

@ -202,7 +202,7 @@ dependencyResolutionManagement {
alias( "hana" ).to( "com.sap.cloud.db.jdbc", "ngdbc" ).version( "2.4.59" )
alias( "sybase" ).to( "net.sourceforge.jtds", "jtds" ).version( "1.3.1" )
alias( "informix" ).to( "com.ibm.informix", "jdbc" ).version( "4.10.12" )
alias( "firebird" ).to( "org.firebirdsql.jdbc", "jaybird" ).version( "4.0.3.java8" )
alias( "firebird" ).to( "org.firebirdsql.jdbc", "jaybird" ).version( "4.0.8.java11" )
}
mavenLibs {
alias( "mavenCore" ).to( "org.apache.maven", "maven-core" ).version( "3.8.1" )