HHH-16533 Fix issues with jConnect driver related to temporal literals. Also improve truncation and casting SQL

This commit is contained in:
Christian Beikov 2023-04-28 13:31:37 +02:00
parent 64b4a94c88
commit de37f328c2
5 changed files with 201 additions and 28 deletions

View File

@ -9,6 +9,10 @@ package org.hibernate.community.dialect;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.TypeContributions;
@ -64,6 +68,11 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
/** /**
* Superclass for all Sybase dialects. * Superclass for all Sybase dialects.
@ -295,16 +304,95 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect {
if ( to == CastType.STRING ) { if ( to == CastType.STRING ) {
switch ( from ) { switch ( from ) {
case DATE: case DATE:
return "str_replace(convert(varchar,?1,102),'.','-')"; return "substring(convert(varchar,?1,23),1,10)";
case TIME: case TIME:
return "convert(varchar,?1,108)"; return "convert(varchar,?1,8)";
case TIMESTAMP: case TIMESTAMP:
return "str_replace(convert(varchar,?1,23),'T',' ')"; return "convert(varchar,?1,140)";
} }
} }
return super.castPattern( from, to ); return super.castPattern( from, to );
} }
/* Something odd is going on with the jConnect driver when using JDBC escape syntax, so let's use native functions */
@Override
public void appendDateTimeLiteral(
SqlAppender appender,
TemporalAccessor temporalAccessor,
TemporalType precision,
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "convert(date,'" );
appendAsDate( appender, temporalAccessor );
appender.appendSql( "',140)" );
break;
case TIME:
appender.appendSql( "convert(time,'" );
appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( "',8)" );
break;
case TIMESTAMP:
appender.appendSql( "convert(datetime,'" );
appendAsTimestampWithMillis( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( "',140)" );
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType precision, TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "convert(date,'" );
appendAsDate( appender, date );
appender.appendSql( "',140)" );
break;
case TIME:
appender.appendSql( "convert(time,'" );
appendAsLocalTime( appender, date );
appender.appendSql( "',8)" );
break;
case TIMESTAMP:
appender.appendSql( "convert(datetime,'" );
appendAsTimestampWithMillis( appender, date, jdbcTimeZone );
appender.appendSql( "',140)" );
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public void appendDateTimeLiteral(
SqlAppender appender,
Calendar calendar,
TemporalType precision,
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "convert(date,'" );
appendAsDate( appender, calendar );
appender.appendSql( "',140)" );
break;
case TIME:
appender.appendSql( "convert(time,'" );
appendAsLocalTime( appender, calendar );
appender.appendSql( "',8)" );
break;
case TIMESTAMP:
appender.appendSql( "convert(datetime,'" );
appendAsTimestampWithMillis( appender, calendar, jdbcTimeZone );
appender.appendSql( "',140)" );
break;
default:
throw new IllegalArgumentException();
}
}
@Override @Override
public String translateExtractField(TemporalUnit unit) { public String translateExtractField(TemporalUnit unit) {
switch ( unit ) { switch ( unit ) {

View File

@ -9,6 +9,10 @@ package org.hibernate.dialect;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.TypeContributions;
@ -63,6 +67,11 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMillis;
/** /**
* Superclass for all Sybase dialects. * Superclass for all Sybase dialects.
@ -313,16 +322,95 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
if ( to == CastType.STRING ) { if ( to == CastType.STRING ) {
switch ( from ) { switch ( from ) {
case DATE: case DATE:
return "str_replace(convert(varchar,?1,102),'.','-')"; return "substring(convert(varchar,?1,23),1,10)";
case TIME: case TIME:
return "convert(varchar,?1,108)"; return "convert(varchar,?1,8)";
case TIMESTAMP: case TIMESTAMP:
return "str_replace(convert(varchar,?1,23),'T',' ')"; return "convert(varchar,?1,140)";
} }
} }
return super.castPattern( from, to ); return super.castPattern( from, to );
} }
/* Something odd is going on with the jConnect driver when using JDBC escape syntax, so let's use native functions */
@Override
public void appendDateTimeLiteral(
SqlAppender appender,
TemporalAccessor temporalAccessor,
TemporalType precision,
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "convert(date,'" );
appendAsDate( appender, temporalAccessor );
appender.appendSql( "',140)" );
break;
case TIME:
appender.appendSql( "convert(time,'" );
appendAsTime( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( "',8)" );
break;
case TIMESTAMP:
appender.appendSql( "convert(datetime,'" );
appendAsTimestampWithMillis( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( "',140)" );
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public void appendDateTimeLiteral(SqlAppender appender, Date date, TemporalType precision, TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "convert(date,'" );
appendAsDate( appender, date );
appender.appendSql( "',140)" );
break;
case TIME:
appender.appendSql( "convert(time,'" );
appendAsLocalTime( appender, date );
appender.appendSql( "',8)" );
break;
case TIMESTAMP:
appender.appendSql( "convert(datetime,'" );
appendAsTimestampWithMillis( appender, date, jdbcTimeZone );
appender.appendSql( "',140)" );
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public void appendDateTimeLiteral(
SqlAppender appender,
Calendar calendar,
TemporalType precision,
TimeZone jdbcTimeZone) {
switch ( precision ) {
case DATE:
appender.appendSql( "convert(date,'" );
appendAsDate( appender, calendar );
appender.appendSql( "',140)" );
break;
case TIME:
appender.appendSql( "convert(time,'" );
appendAsLocalTime( appender, calendar );
appender.appendSql( "',8)" );
break;
case TIMESTAMP:
appender.appendSql( "convert(datetime,'" );
appendAsTimestampWithMillis( appender, calendar, jdbcTimeZone );
appender.appendSql( "',140)" );
break;
default:
throw new IllegalArgumentException();
}
}
@Override @Override
public String translateExtractField(TemporalUnit unit) { public String translateExtractField(TemporalUnit unit) {
switch ( unit ) { switch ( unit ) {

View File

@ -95,7 +95,7 @@ public class SybaseTruncFunction extends TruncFunction {
sqlAppender.append( '(' ); sqlAppender.append( '(' );
sqlAppender.append( "datetime,substring(convert(varchar," ); sqlAppender.append( "datetime,substring(convert(varchar," );
sqlAstArguments.get( 0 ).accept( walker ); sqlAstArguments.get( 0 ).accept( walker );
sqlAppender.append( ",21),1,17" ); sqlAppender.append( ",140),1,26" );
if ( sqlAstArguments.size() > 1 ) { if ( sqlAstArguments.size() > 1 ) {
sqlAppender.append( "-len(" ); sqlAppender.append( "-len(" );
sqlAstArguments.get( 1 ).accept( walker ); sqlAstArguments.get( 1 ).accept( walker );
@ -105,7 +105,7 @@ public class SybaseTruncFunction extends TruncFunction {
else { else {
sqlAppender.append( ')' ); sqlAppender.append( ')' );
} }
sqlAppender.append( ",21)" ); sqlAppender.append( ",140)" );
} }
@Override @Override
@ -119,22 +119,22 @@ public class SybaseTruncFunction extends TruncFunction {
final String literal; final String literal;
switch ( temporalUnit ) { switch ( temporalUnit ) {
case YEAR: case YEAR:
literal = "/01/01 00:00:00"; literal = "-01-01T00:00:00.000000";
break; break;
case MONTH: case MONTH:
literal = "/01 00:00:00"; literal = "-01T00:00:00.000000";
break; break;
case DAY: case DAY:
literal = " 00:00:00"; literal = "T00:00:00.000000";
break; break;
case HOUR: case HOUR:
literal = ":00:00"; literal = ":00:00.000000";
break; break;
case MINUTE: case MINUTE:
literal = ":00"; literal = ":00.000000";
break; break;
case SECOND: case SECOND:
literal = null; literal = ".000000";
break; break;
default: default:
throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" ); throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" );

View File

@ -18,6 +18,7 @@ import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgresPlusDialect; import org.hibernate.dialect.PostgresPlusDialect;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
import org.hibernate.type.descriptor.JdbcTypeNameMapper; import org.hibernate.type.descriptor.JdbcTypeNameMapper;
@ -31,6 +32,7 @@ import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting; import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -40,13 +42,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@RequiresDialectFeatureGroup( @SkipForDialect(dialectClass = OracleDialect.class, reason = "HHH-6834")
value = { @SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "HHH-6834")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsExpectedLobUsagePattern.class), @SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "jConnect reports the type code 11 for bigdatetime columns, which is an unknown type code..")
@RequiresDialectFeature(feature = BasicOperationsTest.OracleDialectChecker.class) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsExpectedLobUsagePattern.class, jiraKey = "HHH-6834")
},
jiraKey = "HHH-6834"
)
@DomainModel( @DomainModel(
annotatedClasses = { SomeEntity.class, SomeOtherEntity.class } annotatedClasses = { SomeEntity.class, SomeOtherEntity.class }
) )
@ -60,13 +59,6 @@ public class BasicOperationsTest {
private static final String SOME_OTHER_ENTITY_TABLE_NAME = "SOMEOTHERENTITY"; private static final String SOME_OTHER_ENTITY_TABLE_NAME = "SOMEOTHERENTITY";
public static class OracleDialectChecker implements DialectFeatureCheck {
@Override
public boolean apply(Dialect dialect) {
return !( dialect instanceof OracleDialect ) && !( dialect instanceof PostgresPlusDialect );
}
}
@Test @Test
public void testCreateAndDelete(SessionFactoryScope scope) { public void testCreateAndDelete(SessionFactoryScope scope) {
Date now = new Date(); Date now = new Date();

View File

@ -14,6 +14,7 @@ import org.hibernate.Session;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; import org.hibernate.type.descriptor.java.JdbcTimestampJavaType;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -93,6 +94,10 @@ public class DbVersionTest extends BaseCoreFunctionalTestCase {
s.close(); s.close();
Timestamp steveTimestamp = steve.getTimestamp(); Timestamp steveTimestamp = steve.getTimestamp();
if ( getDialect() instanceof SybaseDialect ) {
// Sybase has 1/300th sec precision, but not for the `getdate()` function which we use for DB generation
steveTimestamp = new Timestamp( steveTimestamp.getTime() );
}
s = openSession(); s = openSession();
t = s.beginTransaction(); t = s.beginTransaction();