Oracle does support offsets/zones in datetime literals

+ some minor cleanups
This commit is contained in:
Gavin 2023-01-14 12:25:35 +01:00 committed by Gavin King
parent 6a238adc6c
commit a1d43adad4
2 changed files with 77 additions and 23 deletions

View File

@ -11,10 +11,12 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.hibernate.LockOptions;
import org.hibernate.QueryTimeoutException; import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.TypeContributions;
@ -72,7 +74,6 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOr
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.JavaObjectType; import org.hibernate.type.JavaObjectType;
import org.hibernate.type.NullType; import org.hibernate.type.NullType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
@ -89,6 +90,9 @@ import org.hibernate.type.spi.TypeConfiguration;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static org.hibernate.LockOptions.NO_WAIT;
import static org.hibernate.LockOptions.SKIP_LOCKED;
import static org.hibernate.LockOptions.WAIT_FOREVER;
import static org.hibernate.cfg.AvailableSettings.BATCH_VERSIONED_DATA; import static org.hibernate.cfg.AvailableSettings.BATCH_VERSIONED_DATA;
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isEmpty;
@ -104,6 +108,8 @@ import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BOOLEAN; import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.DATE; import static org.hibernate.type.SqlTypes.DATE;
import static org.hibernate.type.SqlTypes.DECIMAL; import static org.hibernate.type.SqlTypes.DECIMAL;
import static org.hibernate.type.SqlTypes.DOUBLE;
import static org.hibernate.type.SqlTypes.FLOAT;
import static org.hibernate.type.SqlTypes.GEOMETRY; import static org.hibernate.type.SqlTypes.GEOMETRY;
import static org.hibernate.type.SqlTypes.INTEGER; import static org.hibernate.type.SqlTypes.INTEGER;
import static org.hibernate.type.SqlTypes.JSON; import static org.hibernate.type.SqlTypes.JSON;
@ -111,13 +117,15 @@ import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR; import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.REAL; import static org.hibernate.type.SqlTypes.REAL;
import static org.hibernate.type.SqlTypes.SMALLINT; import static org.hibernate.type.SqlTypes.SMALLINT;
import static org.hibernate.type.SqlTypes.STRUCT;
import static org.hibernate.type.SqlTypes.SQLXML; import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.STRUCT;
import static org.hibernate.type.SqlTypes.TIME; import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIME_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT; import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY; import static org.hibernate.type.SqlTypes.VARBINARY;
import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.SqlTypes.VARCHAR;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithMicros;
import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithNanos;
/** /**
* A {@linkplain Dialect SQL dialect} for Oracle 11g Release 2 and above. * A {@linkplain Dialect SQL dialect} for Oracle 11g Release 2 and above.
@ -166,14 +174,14 @@ public class OracleDialect extends Dialect {
protected static boolean isAutonomous(DatabaseMetaData databaseMetaData) { protected static boolean isAutonomous(DatabaseMetaData databaseMetaData) {
if ( databaseMetaData != null ) { if ( databaseMetaData != null ) {
try (java.sql.Statement s = databaseMetaData.getConnection().createStatement() ) { try ( java.sql.Statement s = databaseMetaData.getConnection().createStatement() ) {
// v$pdbs is available to any user on Autonomous database // v$pdbs is available to any user on Autonomous database
try( ResultSet rs = s.executeQuery( "select p.name, t.region, t.base_size, t.service, t.infrastructure\n" + try ( ResultSet rs = s.executeQuery( "select p.name, t.region, t.base_size, t.service, t.infrastructure\n" +
"from v$pdbs p, JSON_TABLE(p.cloud_identity, '$' COLUMNS (region path '$.REGION', base_size number path '$.BASE_SIZE', service path '$.SERVICE', infrastructure path '$.INFRASTRUCTURE')) t" )) { "from v$pdbs p, JSON_TABLE(p.cloud_identity, '$' COLUMNS (region path '$.REGION', base_size number path '$.BASE_SIZE', service path '$.SERVICE', infrastructure path '$.INFRASTRUCTURE')) t" ) ) {
return rs.next(); return rs.next();
} }
} }
catch (SQLException ex) { catch ( SQLException ex ) {
// Ignore // Ignore
} }
} }
@ -531,9 +539,7 @@ public class OracleDialect extends Dialect {
} }
@Override @Override
public String timestampdiffPattern( public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) {
TemporalUnit unit,
TemporalType fromTemporalType, TemporalType toTemporalType) {
StringBuilder pattern = new StringBuilder(); StringBuilder pattern = new StringBuilder();
boolean timestamp = toTemporalType == TemporalType.TIMESTAMP || fromTemporalType == TemporalType.TIMESTAMP; boolean timestamp = toTemporalType == TemporalType.TIMESTAMP || fromTemporalType == TemporalType.TIMESTAMP;
switch (unit) { switch (unit) {
@ -716,7 +722,7 @@ public class OracleDialect extends Dialect {
return jdbcTypeRegistry.getDescriptor( JSON ); return jdbcTypeRegistry.getDescriptor( JSON );
case STRUCT: case STRUCT:
if ( "MDSYS.SDO_GEOMETRY".equals( columnTypeName ) ) { if ( "MDSYS.SDO_GEOMETRY".equals( columnTypeName ) ) {
jdbcTypeCode = SqlTypes.GEOMETRY; jdbcTypeCode = GEOMETRY;
} }
else { else {
final AggregateJdbcType aggregateDescriptor = jdbcTypeRegistry.findAggregateDescriptor( final AggregateJdbcType aggregateDescriptor = jdbcTypeRegistry.findAggregateDescriptor(
@ -728,17 +734,16 @@ public class OracleDialect extends Dialect {
} }
} }
break; break;
case Types.NUMERIC: case NUMERIC:
if ( scale == -127 ) { if ( scale == -127 ) {
// For some reason, the Oracle JDBC driver reports FLOAT // For some reason, the Oracle JDBC driver reports FLOAT
// as NUMERIC with scale -127 // as NUMERIC with scale -127
if ( precision <= getFloatPrecision() ) { return precision <= getFloatPrecision()
return jdbcTypeRegistry.getDescriptor( Types.FLOAT ); ? jdbcTypeRegistry.getDescriptor( FLOAT )
} : jdbcTypeRegistry.getDescriptor( DOUBLE );
return jdbcTypeRegistry.getDescriptor( Types.DOUBLE );
} }
//intentional fall-through: //intentional fall-through:
case Types.DECIMAL: case DECIMAL:
if ( scale == 0 ) { if ( scale == 0 ) {
// Don't infer TINYINT or SMALLINT on Oracle, since the // Don't infer TINYINT or SMALLINT on Oracle, since the
// range of values of a NUMBER(3,0) or NUMBER(5,0) just // range of values of a NUMBER(3,0) or NUMBER(5,0) just
@ -749,10 +754,10 @@ public class OracleDialect extends Dialect {
// since we can assume the most likely reason to find // since we can assume the most likely reason to find
// a column of type NUMBER(10,0) in an Oracle database // a column of type NUMBER(10,0) in an Oracle database
// is that it's intended to store an integer. // is that it's intended to store an integer.
return jdbcTypeRegistry.getDescriptor( Types.INTEGER ); return jdbcTypeRegistry.getDescriptor( INTEGER );
} }
else if ( precision <= 19 ) { else if ( precision <= 19 ) {
return jdbcTypeRegistry.getDescriptor( Types.BIGINT ); return jdbcTypeRegistry.getDescriptor( BIGINT );
} }
} }
} }
@ -1218,12 +1223,12 @@ public class OracleDialect extends Dialect {
} }
private String withTimeout(String lockString, int timeout) { private String withTimeout(String lockString, int timeout) {
switch (timeout) { switch ( timeout ) {
case LockOptions.NO_WAIT: case NO_WAIT:
return supportsNoWait() ? lockString + " nowait" : lockString; return supportsNoWait() ? lockString + " nowait" : lockString;
case LockOptions.SKIP_LOCKED: case SKIP_LOCKED:
return supportsSkipLocked() ? lockString + " skip locked" : lockString; return supportsSkipLocked() ? lockString + " skip locked" : lockString;
case LockOptions.WAIT_FOREVER: case WAIT_FOREVER:
return lockString; return lockString;
default: default:
return supportsWait() ? lockString + " wait " + Math.round(timeout / 1e3f) : lockString; return supportsWait() ? lockString + " wait " + Math.round(timeout / 1e3f) : lockString;
@ -1250,6 +1255,32 @@ public class OracleDialect extends Dialect {
return getWriteLockString( aliases, timeout ); return getWriteLockString( aliases, timeout );
} }
@Override
public boolean supportsTemporalLiteralOffset() {
// Oracle *does* support offsets, but only
// in the ANSI syntax, not in the JDBC
// escape-based syntax, which we use in
// almost all circumstances (see below)
return false;
}
@Override
public void appendDateTimeLiteral(SqlAppender appender, TemporalAccessor temporalAccessor, TemporalType precision, TimeZone jdbcTimeZone) {
// we usually use the JDBC escape-based syntax
// because we want to let the JDBC driver handle
// TIME (a concept which does not exist in Oracle)
// but for the special case of timestamps with an
// offset we need to use the ANSI syntax
if ( precision == TemporalType.TIMESTAMP && temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) {
appender.appendSql( "timestamp '" );
appendAsTimestampWithNanos( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone );
appender.appendSql( '\'' );
}
else {
super.appendDateTimeLiteral( appender, temporalAccessor, precision, jdbcTimeZone );
}
}
@Override @Override
public void appendDatetimeFormat(SqlAppender appender, String format) { public void appendDatetimeFormat(SqlAppender appender, String format) {
// Unlike other databases, Oracle requires an explicit reset for the fm modifier, // Unlike other databases, Oracle requires an explicit reset for the fm modifier,

View File

@ -48,6 +48,7 @@ public final class DateTimeUtils {
public static final String FORMAT_STRING_TIMESTAMP_WITH_MICROS_AND_OFFSET = FORMAT_STRING_TIMESTAMP_WITH_MICROS + "XXX"; public static final String FORMAT_STRING_TIMESTAMP_WITH_MICROS_AND_OFFSET = FORMAT_STRING_TIMESTAMP_WITH_MICROS + "XXX";
public static final String FORMAT_STRING_TIMESTAMP_WITH_NANOS_AND_OFFSET = FORMAT_STRING_TIMESTAMP_WITH_NANOS + "XXX"; public static final String FORMAT_STRING_TIMESTAMP_WITH_NANOS_AND_OFFSET = FORMAT_STRING_TIMESTAMP_WITH_NANOS + "XXX";
public static final String FORMAT_STRING_TIMESTAMP_WITH_MICROS_AND_OFFSET_NOZ = FORMAT_STRING_TIMESTAMP_WITH_MICROS + "xxx"; public static final String FORMAT_STRING_TIMESTAMP_WITH_MICROS_AND_OFFSET_NOZ = FORMAT_STRING_TIMESTAMP_WITH_MICROS + "xxx";
public static final String FORMAT_STRING_TIMESTAMP_WITH_NANOS_AND_OFFSET_NOZ = FORMAT_STRING_TIMESTAMP_WITH_NANOS + "xxx";
public static final DateTimeFormatter DATE_TIME_FORMATTER_DATE = DateTimeFormatter.ofPattern( FORMAT_STRING_DATE, Locale.ENGLISH ); public static final DateTimeFormatter DATE_TIME_FORMATTER_DATE = DateTimeFormatter.ofPattern( FORMAT_STRING_DATE, Locale.ENGLISH );
public static final DateTimeFormatter DATE_TIME_FORMATTER_TIME_WITH_OFFSET = DateTimeFormatter.ofPattern( FORMAT_STRING_TIME_WITH_OFFSET, Locale.ENGLISH ); public static final DateTimeFormatter DATE_TIME_FORMATTER_TIME_WITH_OFFSET = DateTimeFormatter.ofPattern( FORMAT_STRING_TIME_WITH_OFFSET, Locale.ENGLISH );
@ -80,6 +81,10 @@ public final class DateTimeUtils {
FORMAT_STRING_TIMESTAMP_WITH_NANOS_AND_OFFSET, FORMAT_STRING_TIMESTAMP_WITH_NANOS_AND_OFFSET,
Locale.ENGLISH Locale.ENGLISH
); );
public static final DateTimeFormatter DATE_TIME_FORMATTER_TIMESTAMP_WITH_NANOS_AND_OFFSET_NOZ = DateTimeFormatter.ofPattern(
FORMAT_STRING_TIMESTAMP_WITH_NANOS_AND_OFFSET_NOZ,
Locale.ENGLISH
);
public static final String JDBC_ESCAPE_START_DATE = "{d '"; public static final String JDBC_ESCAPE_START_DATE = "{d '";
public static final String JDBC_ESCAPE_START_TIME = "{t '"; public static final String JDBC_ESCAPE_START_TIME = "{t '";
@ -145,6 +150,24 @@ public final class DateTimeUtils {
); );
} }
public static void appendAsTimestampWithNanos(
SqlAppender appender,
TemporalAccessor temporalAccessor,
boolean supportsOffset,
TimeZone jdbcTimeZone,
boolean allowZforZeroOffset) {
appendAsTimestamp(
appender,
temporalAccessor,
supportsOffset,
jdbcTimeZone,
DATE_TIME_FORMATTER_TIMESTAMP_WITH_MICROS,
allowZforZeroOffset
? DATE_TIME_FORMATTER_TIMESTAMP_WITH_NANOS_AND_OFFSET
: DATE_TIME_FORMATTER_TIMESTAMP_WITH_NANOS_AND_OFFSET_NOZ
);
}
public static void appendAsTimestampWithMicros( public static void appendAsTimestampWithMicros(
SqlAppender appender, SqlAppender appender,
TemporalAccessor temporalAccessor, TemporalAccessor temporalAccessor,