Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-17242 Improve temporal arithmetic SQL rendering #7356

Merged
merged 1 commit into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@
* @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