Skip to content

Commit

Permalink
HHH-17242 Improve temporal arithmetic SQL rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Sep 22, 2023
1 parent 4faa30f commit 875221c
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,16 @@ public String extractPattern(TemporalUnit unit) {

@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 );
Expand All @@ -486,22 +494,32 @@ public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType,
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();
}
Expand All @@ -522,42 +540,51 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT
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:
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "+" );
extractField( pattern, SECOND, unit );
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 );
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "+" );
extractField( pattern, SECOND, unit );
}
}
else {
pattern.append( "((?3-?2)" );
pattern.append( TemporalUnit.DAY.conversionFactor( unit, this ) );
}
pattern.append( ")" );
break;
Expand All @@ -570,17 +597,16 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT
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" );
Expand Down
16 changes: 16 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,22 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT
* @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 );

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation Note

Invoking
Dialect.timestampaddPattern
should be avoided because it has been deprecated.
}

/**
* 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" );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT
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 );
Expand Down Expand Up @@ -1640,4 +1649,14 @@ public int rowIdSqlType() {
public String getRowIdColumnString(String rowId) {
return wrapped.getRowIdColumnString( rowId );
}

@Override
public boolean useArrayForMultiValuedParameters() {
return wrapped.useArrayForMultiValuedParameters();
}

@Override
public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() {
return wrapped.getDmlTargetColumnQualifierSupport();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,16 @@ public String extractPattern(TemporalUnit unit) {

@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 );
Expand All @@ -507,22 +515,32 @@ public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType,
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();
}
Expand All @@ -543,42 +561,51 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT
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:
pattern.append( "(" );
extractField( pattern, DAY, unit );
if ( hasTimePart ) {
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "+" );
extractField( pattern, SECOND, unit );
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 );
pattern.append( "+" );
extractField( pattern, HOUR, unit );
pattern.append( "+" );
extractField( pattern, MINUTE, unit );
pattern.append( "+" );
extractField( pattern, SECOND, unit );
}
}
else {
pattern.append( "((?3-?2)" );
pattern.append( TemporalUnit.DAY.conversionFactor( unit, this ) );
}
pattern.append( ")" );
break;
Expand All @@ -591,17 +618,16 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT
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" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand All @@ -38,7 +39,7 @@
* 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
Expand Down Expand Up @@ -77,7 +78,19 @@ public void render(
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
Expand Down

0 comments on commit 875221c

Please sign in to comment.