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 473984f1eb
commit 53c7ef470a
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.SQLException;
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.TypeContributions;
@ -64,6 +68,11 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
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.
@ -295,16 +304,95 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect {
if ( to == CastType.STRING ) {
switch ( from ) {
case DATE:
return "str_replace(convert(varchar,?1,102),'.','-')";
return "substring(convert(varchar,?1,23),1,10)";
case TIME:
return "convert(varchar,?1,108)";
return "convert(varchar,?1,8)";
case TIMESTAMP:
return "str_replace(convert(varchar,?1,23),'T',' ')";
return "convert(varchar,?1,140)";
}
}
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
public String translateExtractField(TemporalUnit unit) {
switch ( unit ) {

View File

@ -9,6 +9,10 @@ package org.hibernate.dialect;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
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.TypeContributions;
@ -63,6 +67,11 @@ import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
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.
@ -313,16 +322,95 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
if ( to == CastType.STRING ) {
switch ( from ) {
case DATE:
return "str_replace(convert(varchar,?1,102),'.','-')";
return "substring(convert(varchar,?1,23),1,10)";
case TIME:
return "convert(varchar,?1,108)";
return "convert(varchar,?1,8)";
case TIMESTAMP:
return "str_replace(convert(varchar,?1,23),'T',' ')";
return "convert(varchar,?1,140)";
}
}
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
public String translateExtractField(TemporalUnit unit) {
switch ( unit ) {

View File

@ -95,7 +95,7 @@ public class SybaseTruncFunction extends TruncFunction {
sqlAppender.append( '(' );
sqlAppender.append( "datetime,substring(convert(varchar," );
sqlAstArguments.get( 0 ).accept( walker );
sqlAppender.append( ",21),1,17" );
sqlAppender.append( ",140),1,26" );
if ( sqlAstArguments.size() > 1 ) {
sqlAppender.append( "-len(" );
sqlAstArguments.get( 1 ).accept( walker );
@ -105,7 +105,7 @@ public class SybaseTruncFunction extends TruncFunction {
else {
sqlAppender.append( ')' );
}
sqlAppender.append( ",21)" );
sqlAppender.append( ",140)" );
}
@Override
@ -119,22 +119,22 @@ public class SybaseTruncFunction extends TruncFunction {
final String literal;
switch ( temporalUnit ) {
case YEAR:
literal = "/01/01 00:00:00";
literal = "-01-01T00:00:00.000000";
break;
case MONTH:
literal = "/01 00:00:00";
literal = "-01T00:00:00.000000";
break;
case DAY:
literal = " 00:00:00";
literal = "T00:00:00.000000";
break;
case HOUR:
literal = ":00:00";
literal = ":00:00.000000";
break;
case MINUTE:
literal = ":00";
literal = ":00.000000";
break;
case SECOND:
literal = null;
literal = ".000000";
break;
default:
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.OracleDialect;
import org.hibernate.dialect.PostgresPlusDialect;
import org.hibernate.dialect.SybaseASEDialect;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.jdbc.Work;
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.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -40,13 +42,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Steve Ebersole
*/
@RequiresDialectFeatureGroup(
value = {
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsExpectedLobUsagePattern.class),
@RequiresDialectFeature(feature = BasicOperationsTest.OracleDialectChecker.class)
},
jiraKey = "HHH-6834"
)
@SkipForDialect(dialectClass = OracleDialect.class, reason = "HHH-6834")
@SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "HHH-6834")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "jConnect reports the type code 11 for bigdatetime columns, which is an unknown type code..")
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsExpectedLobUsagePattern.class, jiraKey = "HHH-6834")
@DomainModel(
annotatedClasses = { SomeEntity.class, SomeOtherEntity.class }
)
@ -60,13 +59,6 @@ public class BasicOperationsTest {
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
public void testCreateAndDelete(SessionFactoryScope scope) {
Date now = new Date();

View File

@ -14,6 +14,7 @@ import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.type.descriptor.java.JdbcTimestampJavaType;
import static org.junit.Assert.assertFalse;
@ -93,6 +94,10 @@ public class DbVersionTest extends BaseCoreFunctionalTestCase {
s.close();
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();
t = s.beginTransaction();