HHH-17242 Improve temporal arithmetic SQL rendering

This commit is contained in:
Christian Beikov 2023-09-22 13:01:53 +02:00
parent 4faa30f172
commit 5b97f49bc8
5 changed files with 168 additions and 68 deletions

View File

@ -473,8 +473,16 @@ public class OracleLegacyDialect extends Dialect {
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
return timestampaddPattern( unit, temporalType, intervalType, false );
}
StringBuilder pattern = new StringBuilder();
@Override
public String timestampaddPattern(
TemporalUnit unit,
TemporalType temporalType,
IntervalType intervalType,
boolean hasTimeZone) {
final StringBuilder pattern = new StringBuilder();
switch ( unit ) {
case YEAR:
pattern.append( ADD_YEAR_EXPRESSION );
@ -486,22 +494,32 @@ public class OracleLegacyDialect extends Dialect {
pattern.append( ADD_MONTH_EXPRESSION );
break;
case WEEK:
pattern.append("(?3+numtodsinterval((?2)*7,'day'))");
if ( hasTimeZone ) {
pattern.append( "(?3+numtodsinterval(?2*7,'day'))" );
}
else {
pattern.append( "(?3+?2" ).append( unit.conversionFactor( DAY, this ) ).append( ")" );
}
break;
case DAY:
case HOUR:
case MINUTE:
case SECOND:
pattern.append("(?3+numtodsinterval(?2,'?1'))");
if ( hasTimeZone ) {
pattern.append( "(?3+numtodsinterval(?2,'?1'))" );
}
else {
pattern.append( "(?3+?2" ).append( unit.conversionFactor( DAY, this ) ).append( ")" );
}
break;
case NANOSECOND:
pattern.append("(?3+numtodsinterval((?2)/1e9,'second'))");
pattern.append( "(?3+numtodsinterval((?2)/1e9,'second'))" );
break;
case NATIVE:
pattern.append("(?3+numtodsinterval(?2,'second'))");
pattern.append( "(?3+numtodsinterval(?2,'second'))" );
break;
default:
throw new SemanticException(unit + " is not a legal field");
throw new SemanticException( unit + " is not a legal field" );
}
return pattern.toString();
}
@ -522,36 +540,40 @@ public class OracleLegacyDialect extends Dialect {
extractField( pattern, MONTH, unit );
pattern.append( ")" );
break;
case WEEK:
case DAY:
extractField( pattern, DAY, unit );
break;
case HOUR:
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "(cast(?3 as date)-cast(?2 as date))" );
}
else {
pattern.append( "(?3-?2)" );
}
pattern.append( ")" );
break;
case WEEK:
case MINUTE:
pattern.append( "(" );
extractField( pattern, DAY, unit );
case SECOND:
case HOUR:
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "((cast(?3 as date)-cast(?2 as date))" );
}
else {
pattern.append( "((?3-?2)" );
}
pattern.append( TemporalUnit.DAY.conversionFactor(unit ,this ) );
pattern.append( ")" );
break;
case NATIVE:
case NANOSECOND:
case SECOND:
if ( hasTimePart ) {
if ( supportsLateral() ) {
pattern.append( "(select extract(day from t.i)" ).append( TemporalUnit.DAY.conversionFactor( unit, this ) )
.append( "+extract(hour from t.i)" ).append( TemporalUnit.HOUR.conversionFactor( unit, this ) )
.append( "+extract(minute from t.i)" ).append( MINUTE.conversionFactor( unit, this ) )
.append( "+extract(second from t.i)" ).append( SECOND.conversionFactor( unit, this ) )
.append( " from(select ?3-?2 i from dual)t" );
}
else {
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
@ -559,6 +581,11 @@ public class OracleLegacyDialect extends Dialect {
pattern.append( "+" );
extractField( pattern, SECOND, unit );
}
}
else {
pattern.append( "((?3-?2)" );
pattern.append( TemporalUnit.DAY.conversionFactor( unit, this ) );
}
pattern.append( ")" );
break;
default:
@ -570,17 +597,16 @@ public class OracleLegacyDialect extends Dialect {
private void extractField(StringBuilder pattern, TemporalUnit unit, TemporalUnit toUnit) {
pattern.append( "extract(" );
pattern.append( translateExtractField( unit ) );
pattern.append( " from (?3-?2) " );
pattern.append( " from (?3-?2)" );
switch ( unit ) {
case YEAR:
case MONTH:
pattern.append( "year to month" );
pattern.append( " year(9) to month" );
break;
case DAY:
case HOUR:
case MINUTE:
case SECOND:
pattern.append( "day to second" );
break;
default:
throw new SemanticException( unit + " is not a legal field" );

View File

@ -1441,6 +1441,22 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
* @param temporalType The type of the temporal
* @param intervalType The type of interval to add or null if it's not a native interval
*/
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType, boolean withTimeZone) {
return timestampaddPattern( unit, temporalType, intervalType );
}
/**
* Obtain a pattern for the SQL equivalent to a
* {@code timestampadd()} function call. The resulting
* pattern must contain ?1, ?2, and ?3 placeholders
* for the arguments.
*
* @param unit The unit to add to the temporal
* @param temporalType The type of the temporal
* @param intervalType The type of interval to add or null if it's not a native interval
* @deprecated use {@link #timestampaddPattern(TemporalUnit, TemporalType, IntervalType, boolean)} instead
*/
@Deprecated
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
throw new UnsupportedOperationException( "`" + getClass().getName() + "` does not yet support #timestampaddPattern" );
}

View File

@ -302,6 +302,15 @@ public class DialectDelegateWrapper extends Dialect {
return wrapped.timestampdiffPattern( unit, fromTemporalType, toTemporalType );
}
@Override
public String timestampaddPattern(
TemporalUnit unit,
TemporalType temporalType,
IntervalType intervalType,
boolean withTimeZone) {
return wrapped.timestampaddPattern( unit, temporalType, intervalType, withTimeZone );
}
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
return wrapped.timestampaddPattern( unit, temporalType, intervalType );
@ -1640,4 +1649,14 @@ public class DialectDelegateWrapper extends Dialect {
public String getRowIdColumnString(String rowId) {
return wrapped.getRowIdColumnString( rowId );
}
@Override
public boolean useArrayForMultiValuedParameters() {
return wrapped.useArrayForMultiValuedParameters();
}
@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return wrapped.getDmlTargetColumnQualifierSupport();
}
}

View File

@ -494,8 +494,16 @@ public class OracleDialect extends Dialect {
@Override
public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) {
return timestampaddPattern( unit, temporalType, intervalType, false );
}
StringBuilder pattern = new StringBuilder();
@Override
public String timestampaddPattern(
TemporalUnit unit,
TemporalType temporalType,
IntervalType intervalType,
boolean hasTimeZone) {
final StringBuilder pattern = new StringBuilder();
switch ( unit ) {
case YEAR:
pattern.append( ADD_YEAR_EXPRESSION );
@ -507,22 +515,32 @@ public class OracleDialect extends Dialect {
pattern.append( ADD_MONTH_EXPRESSION );
break;
case WEEK:
pattern.append("(?3+numtodsinterval((?2)*7,'day'))");
if ( hasTimeZone ) {
pattern.append( "(?3+numtodsinterval(?2*7,'day'))" );
}
else {
pattern.append( "(?3+?2" ).append( unit.conversionFactor( DAY, this ) ).append( ")" );
}
break;
case DAY:
case HOUR:
case MINUTE:
case SECOND:
pattern.append("(?3+numtodsinterval(?2,'?1'))");
if ( hasTimeZone ) {
pattern.append( "(?3+numtodsinterval(?2,'?1'))" );
}
else {
pattern.append( "(?3+?2" ).append( unit.conversionFactor( DAY, this ) ).append( ")" );
}
break;
case NANOSECOND:
pattern.append("(?3+numtodsinterval((?2)/1e9,'second'))");
pattern.append( "(?3+numtodsinterval((?2)/1e9,'second'))" );
break;
case NATIVE:
pattern.append("(?3+numtodsinterval(?2,'second'))");
pattern.append( "(?3+numtodsinterval(?2,'second'))" );
break;
default:
throw new SemanticException(unit + " is not a legal field");
throw new SemanticException( unit + " is not a legal field" );
}
return pattern.toString();
}
@ -543,36 +561,40 @@ public class OracleDialect extends Dialect {
extractField( pattern, MONTH, unit );
pattern.append( ")" );
break;
case WEEK:
case DAY:
extractField( pattern, DAY, unit );
break;
case HOUR:
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "(cast(?3 as date)-cast(?2 as date))" );
}
else {
pattern.append( "(?3-?2)" );
}
pattern.append( ")" );
break;
case WEEK:
case MINUTE:
pattern.append( "(" );
extractField( pattern, DAY, unit );
case SECOND:
case HOUR:
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "((cast(?3 as date)-cast(?2 as date))" );
}
else {
pattern.append( "((?3-?2)" );
}
pattern.append( TemporalUnit.DAY.conversionFactor(unit ,this ) );
pattern.append( ")" );
break;
case NATIVE:
case NANOSECOND:
case SECOND:
if ( hasTimePart ) {
if ( supportsLateral() ) {
pattern.append( "(select extract(day from t.i)" ).append( TemporalUnit.DAY.conversionFactor( unit, this ) )
.append( "+extract(hour from t.i)" ).append( TemporalUnit.HOUR.conversionFactor( unit, this ) )
.append( "+extract(minute from t.i)" ).append( MINUTE.conversionFactor( unit, this ) )
.append( "+extract(second from t.i)" ).append( SECOND.conversionFactor( unit, this ) )
.append( " from(select ?3-?2 i from dual)t" );
}
else {
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
@ -580,6 +602,11 @@ public class OracleDialect extends Dialect {
pattern.append( "+" );
extractField( pattern, SECOND, unit );
}
}
else {
pattern.append( "((?3-?2)" );
pattern.append( TemporalUnit.DAY.conversionFactor( unit, this ) );
}
pattern.append( ")" );
break;
default:
@ -591,17 +618,16 @@ public class OracleDialect extends Dialect {
private void extractField(StringBuilder pattern, TemporalUnit unit, TemporalUnit toUnit) {
pattern.append( "extract(" );
pattern.append( translateExtractField( unit ) );
pattern.append( " from (?3-?2) " );
pattern.append( " from (?3-?2)" );
switch ( unit ) {
case YEAR:
case MONTH:
pattern.append( "year to month" );
pattern.append( " year(9) to month" );
break;
case DAY:
case HOUR:
case MINUTE:
case SECOND:
pattern.append( "day to second" );
break;
default:
throw new SemanticException( unit + " is not a legal field" );

View File

@ -23,6 +23,7 @@ import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.List;
@ -38,7 +39,7 @@ import static org.hibernate.type.spi.TypeConfiguration.getSqlTemporalType;
* The {@code timestampadd()} or {@code dateadd()} function has a funny
* syntax which accepts a {@link TemporalUnit} as the first argument,
* and the actual set of accepted units varies widely. This class uses
* {@link Dialect#timestampaddPattern(TemporalUnit, TemporalType, IntervalType)}
* {@link Dialect#timestampaddPattern(TemporalUnit, TemporalType, IntervalType, boolean)}
* to abstract these differences.
*
* @author Gavin King
@ -77,7 +78,19 @@ public class TimestampaddFunction
PatternRenderer patternRenderer(TemporalUnit unit, Expression interval, Expression to) {
TemporalType temporalType = getSqlTemporalType( to.getExpressionType() );
IntervalType intervalType = getSqlIntervalType( interval.getExpressionType().getSingleJdbcMapping() );
return new PatternRenderer( dialect.timestampaddPattern( unit, temporalType, intervalType ) );
boolean withTimeZone = hasTimeZone( to.getExpressionType().getSingleJdbcMapping().getJdbcType().getDefaultSqlTypeCode() );
return new PatternRenderer( dialect.timestampaddPattern( unit, temporalType, intervalType, withTimeZone ) );
}
private boolean hasTimeZone(int sqlTypeCode) {
switch ( sqlTypeCode ) {
case SqlTypes.TIME_UTC:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
return true;
}
return false;
}
// @Override