Skip to content

Commit

Permalink
[#61] Add DATE and TIME literals to grammar and allow comparison betw…
Browse files Browse the repository at this point in the history
…een date and timestamp
  • Loading branch information
beikov committed Jun 1, 2022
1 parent e871da9 commit e9252ad
Show file tree
Hide file tree
Showing 42 changed files with 7,376 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ public class BaseContributor implements DomainContributor, ExpressionServiceCont
public static final String INTEGER_TYPE_NAME = "Integer";
public static final String NUMERIC_TYPE_NAME = "Numeric";
public static final String TIMESTAMP_TYPE_NAME = "Timestamp";
public static final Class<?> TIME = LocalTime.class;
public static final String TIME_TYPE_NAME = "Time";
public static final String INTERVAL_TYPE_NAME = "Interval";
public static final String STRING_TYPE_NAME = "String";
Expand Down Expand Up @@ -170,15 +169,15 @@ public void contribute(DomainBuilder domainBuilder) {

domainBuilder.withOperationTypeResolver(TIMESTAMP_TYPE_NAME, DomainOperator.PLUS, StaticDomainOperationTypeResolvers.returning(TIMESTAMP_TYPE_NAME, new String[][]{ { TIMESTAMP_TYPE_NAME }, { INTERVAL_TYPE_NAME }}));
domainBuilder.withOperationTypeResolver(TIMESTAMP_TYPE_NAME, DomainOperator.MINUS, StaticDomainOperationTypeResolvers.returning(TIMESTAMP_TYPE_NAME, new String[][]{ { TIMESTAMP_TYPE_NAME }, { INTERVAL_TYPE_NAME }}));
withPredicateTypeResolvers(domainBuilder, TIMESTAMP_TYPE_NAME, TIMESTAMP_TYPE_NAME);
withPredicateTypeResolvers(domainBuilder, TIMESTAMP_TYPE_NAME, TIMESTAMP_TYPE_NAME, DATE_TYPE_NAME);

domainBuilder.withOperationTypeResolver(TIME_TYPE_NAME, DomainOperator.PLUS, StaticDomainOperationTypeResolvers.returning(TIME_TYPE_NAME, new String[][]{ { TIME_TYPE_NAME }, { INTERVAL_TYPE_NAME }}));
domainBuilder.withOperationTypeResolver(TIME_TYPE_NAME, DomainOperator.MINUS, StaticDomainOperationTypeResolvers.returning(TIME_TYPE_NAME, new String[][]{ { TIME_TYPE_NAME }, { INTERVAL_TYPE_NAME }}));
withPredicateTypeResolvers(domainBuilder, TIME_TYPE_NAME, TIME_TYPE_NAME);

domainBuilder.withOperationTypeResolver(DATE_TYPE_NAME, DomainOperator.PLUS, StaticDomainOperationTypeResolvers.returning(DATE_TYPE_NAME, new String[][]{ { DATE_TYPE_NAME }, { INTERVAL_TYPE_NAME }}));
domainBuilder.withOperationTypeResolver(DATE_TYPE_NAME, DomainOperator.MINUS, StaticDomainOperationTypeResolvers.returning(DATE_TYPE_NAME, new String[][]{ { DATE_TYPE_NAME }, { INTERVAL_TYPE_NAME }}));
withPredicateTypeResolvers(domainBuilder, DATE_TYPE_NAME, DATE_TYPE_NAME);
withPredicateTypeResolvers(domainBuilder, DATE_TYPE_NAME, DATE_TYPE_NAME, TIMESTAMP_TYPE_NAME);

domainBuilder.withOperationTypeResolver(INTERVAL_TYPE_NAME, DomainOperator.PLUS, StaticDomainOperationTypeResolvers.widest(TIMESTAMP_TYPE_NAME, TIME_TYPE_NAME, INTERVAL_TYPE_NAME));
domainBuilder.withOperationTypeResolver(INTERVAL_TYPE_NAME, DomainOperator.MINUS, StaticDomainOperationTypeResolvers.widest(TIMESTAMP_TYPE_NAME, TIME_TYPE_NAME, INTERVAL_TYPE_NAME));
Expand Down Expand Up @@ -296,6 +295,16 @@ public <T> T serialize(ExpressionService expressionService, TemporalLiteralResol
return (T) "\"TemporalLiteralResolver\"";
}

@Override
public ResolvedLiteral resolveDateLiteral(ExpressionCompiler.Context context, LocalDate value) {
return new DefaultResolvedLiteral(context.getExpressionService().getDomainModel().getType(DATE_TYPE_NAME), value);
}

@Override
public ResolvedLiteral resolveTimeLiteral(ExpressionCompiler.Context context, LocalTime value) {
return new DefaultResolvedLiteral(context.getExpressionService().getDomainModel().getType(TIME_TYPE_NAME), value);
}

@Override
public ResolvedLiteral resolveTimestampLiteral(ExpressionCompiler.Context context, Instant value) {
return new DefaultResolvedLiteral(context.getExpressionService().getDomainModel().getType(TIMESTAMP_TYPE_NAME), value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
import com.blazebit.expression.spi.DomainOperatorInterpreter;

import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.Temporal;

/**
* @author Yevhen Tucha
Expand All @@ -41,26 +44,36 @@ private DateOperatorInterpreter() {
}

@Override
@SuppressWarnings("unchecked")
public Boolean interpret(ExpressionInterpreter.Context context, DomainType leftType, DomainType rightType,
Object leftValue, Object rightValue, ComparisonOperator operator) {
if (leftValue instanceof LocalDate && rightValue instanceof LocalDate) {
LocalDate l = (LocalDate) leftValue;
LocalDate r = (LocalDate) rightValue;
switch (operator) {
case EQUAL:
return l.compareTo(r) == 0;
case NOT_EQUAL:
return l.compareTo(r) != 0;
case GREATER_OR_EQUAL:
return l.compareTo(r) > -1;
case GREATER:
return l.compareTo(r) > 0;
case LOWER_OR_EQUAL:
return l.compareTo(r) < 1;
case LOWER:
return l.compareTo(r) < 0;
default:
break;
if (leftValue instanceof LocalDate) {
Comparable<Temporal> l = (Comparable<Temporal>) leftValue;
Temporal r = null;
if (rightValue instanceof LocalDate) {
r = (LocalDate) rightValue;
} else if (rightValue instanceof Instant) {
//noinspection rawtypes
l = (Comparable) ((LocalDate) leftValue).atStartOfDay().toInstant(ZoneOffset.UTC);
r = (Instant) rightValue;
}
if (r != null) {
switch (operator) {
case EQUAL:
return l.compareTo(r) == 0;
case NOT_EQUAL:
return l.compareTo(r) != 0;
case GREATER_OR_EQUAL:
return l.compareTo(r) > -1;
case GREATER:
return l.compareTo(r) > 0;
case LOWER_OR_EQUAL:
return l.compareTo(r) < 1;
case LOWER:
return l.compareTo(r) < 0;
default:
break;
}
}
} else {
throw new DomainModelException("Illegal arguments [" + leftValue + ", " + rightValue + "]!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;

/**
* @author Christian Beikov
Expand All @@ -41,27 +43,32 @@ private TimestampOperatorInterpreter() {

@Override
public Boolean interpret(ExpressionInterpreter.Context context, DomainType leftType, DomainType rightType, Object leftValue, Object rightValue, ComparisonOperator operator) {
if (leftValue instanceof Instant && rightValue instanceof Instant) {
if (leftValue instanceof Instant) {
Instant l = (Instant) leftValue;
Instant r = (Instant) rightValue;
switch (operator) {
case EQUAL:
return l.compareTo(r) == 0;
case NOT_EQUAL:
return l.compareTo(r) != 0;
case GREATER_OR_EQUAL:
return l.compareTo(r) > -1;
case GREATER:
return l.compareTo(r) > 0;
case LOWER_OR_EQUAL:
return l.compareTo(r) < 1;
case LOWER:
return l.compareTo(r) < 0;
default:
break;
Instant r = null;
if (rightValue instanceof Instant) {
r = (Instant) rightValue;
} else if (rightValue instanceof LocalDate) {
r = ((LocalDate) rightValue).atStartOfDay().toInstant(ZoneOffset.UTC);
}
if (r != null) {
switch (operator) {
case EQUAL:
return l.compareTo(r) == 0;
case NOT_EQUAL:
return l.compareTo(r) != 0;
case GREATER_OR_EQUAL:
return l.compareTo(r) > -1;
case GREATER:
return l.compareTo(r) > 0;
case LOWER_OR_EQUAL:
return l.compareTo(r) < 1;
case LOWER:
return l.compareTo(r) < 0;
default:
break;
}
}
} else {
throw new DomainModelException("Illegal arguments [" + leftValue + ", " + rightValue + "]!");
}

throw new DomainModelException("Can't handle the operator " + operator + " for the arguments [" + leftValue + ", " + rightValue + "]!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,14 @@ public void testStringly5() {
Assert.assertEquals(true, testPredicate("'EUR' = Currency.EUR"));
}

@Test
public void testTemporal1() {
Assert.assertEquals(true, testPredicate("TIMESTAMP(2020-01-01) = DATE(2020-01-01)"));
}

@Test
public void testTemporal2() {
Assert.assertEquals(true, testPredicate("DATE(2020-01-01) = TIMESTAMP(2020-01-01)"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.blazebit.expression.ExpressionCompiler;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;

/**
* A literal resolver for temporal values.
Expand All @@ -29,6 +31,24 @@
*/
public interface TemporalLiteralResolver {

/**
* Resolves the given local date value to a resolved domain literal.
*
* @param context The compiler context
* @param value The local date value
* @return the resolved literal
*/
ResolvedLiteral resolveDateLiteral(ExpressionCompiler.Context context, LocalDate value);

/**
* Resolves the given local time value to a resolved domain literal.
*
* @param context The compiler context
* @param value The local time value
* @return the resolved literal
*/
ResolvedLiteral resolveTimeLiteral(ExpressionCompiler.Context context, LocalTime value);

/**
* Resolves the given instant value to a resolved domain literal.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ LEADING_ZERO_INTEGER_LITERAL

AND : [aA] [nN] [dD];
BETWEEN : [bB] [eE] [tT] [wW] [eE] [eE] [nN];
DATE : [dD] [aA] [tT] [eE];
DAYS : [dD] [aA] [yY] [sS];
EMPTY : [eE] [mM] [pP] [tT] [yY];
FALSE : [fF] [aA] [lL] [sS] [eE];
Expand All @@ -60,6 +61,7 @@ NOT : [nN] [oO] [tT];
NULL : [nN] [uU] [lL] [lL];
OR : [oO] [rR];
SECONDS : [sS] [eE] [cC] [oO] [nN] [dD] [sS];
TIME : [tT] [iI] [mM] [eE];
TIMESTAMP : [tT] [iI] [mM] [eE] [sS] [tT] [aA] [mM] [pP];
TRUE : [tT] [rR] [uU] [eE];
YEARS : [yY] [eE] [aA] [rR] [sS];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ literal
| stringLiteral
| TRUE
| FALSE
| dateLiteral
| timeLiteral
| timestampLiteral
| temporalIntervalLiteral
| collectionLiteral
Expand All @@ -113,6 +115,14 @@ functionInvocation
| name=identifier LP ((predicateOrExpression COMMA)* predicateOrExpression)? RP #IndexedFunctionInvocation
;

dateLiteral
: DATE LP datePart RP
;

timeLiteral
: TIME LP timePart (DOT fraction=(INTEGER_LITERAL | LEADING_ZERO_INTEGER_LITERAL))? RP
;

timestampLiteral
: TIMESTAMP LP datePart (timePart (DOT fraction=(INTEGER_LITERAL | LEADING_ZERO_INTEGER_LITERAL))? )? RP
;
Expand Down Expand Up @@ -141,6 +151,7 @@ identifier
| QUOTED_IDENTIFIER
| AND
| BETWEEN
| DATE
| DAYS
| HOURS
| IN
Expand All @@ -150,6 +161,7 @@ identifier
| NOT
| OR
| SECONDS
| TIME
| TIMESTAMP
| YEARS
;
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import java.math.BigInteger;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
Expand All @@ -58,6 +60,8 @@ public class LiteralFactory {
private static final DateTimeFormatter DATE_LITERAL_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
private static final DateTimeFormatter DATE_TIME_LITERAL_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);
private static final DateTimeFormatter DATE_TIME_MILLISECONDS_LITERAL_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(ZoneOffset.UTC);
private static final DateTimeFormatter TIME_LITERAL_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneOffset.UTC);
private static final DateTimeFormatter TIME_MILLISECONDS_LITERAL_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSS").withZone(ZoneOffset.UTC);

private static final String TEMPORAL_INTERVAL_YEARS_FIELD = "years";
private static final String TEMPORAL_INTERVAL_MONTHS_FIELD = "months";
Expand Down Expand Up @@ -327,6 +331,30 @@ public void appendTemplateString(StringBuilder sb, String value) {
}
}

public ResolvedLiteral ofDateString(ExpressionCompiler.Context context, String dateString) {
try {
return ofLocalDate(context, LocalDate.parse(dateString, DATE_LITERAL_FORMAT));
} catch (DateTimeParseException e) {
throw new SyntaxErrorException("Invalid datetime literal " + dateString, e);
}
}

public ResolvedLiteral ofTimeString(ExpressionCompiler.Context context, String timeString) {
boolean hasDot = timeString.indexOf('.') != -1;

try {
LocalTime localTime;
if (!hasDot) {
localTime = OffsetTime.parse(timeString, TIME_LITERAL_FORMAT).toLocalTime();
} else {
localTime = OffsetTime.parse(timeString, TIME_MILLISECONDS_LITERAL_FORMAT).toLocalTime();
}
return ofLocalTime(context, localTime);
} catch (DateTimeParseException e) {
throw new SyntaxErrorException("Invalid datetime literal " + timeString, e);
}
}

public ResolvedLiteral ofDateTimeString(ExpressionCompiler.Context context, String dateTimeString) {
boolean hasBlank = dateTimeString.indexOf(' ') != -1;
boolean hasDot = dateTimeString.indexOf('.') != -1;
Expand All @@ -346,6 +374,22 @@ public ResolvedLiteral ofDateTimeString(ExpressionCompiler.Context context, Stri
}
}

public ResolvedLiteral ofLocalDate(ExpressionCompiler.Context context, LocalDate localDate) {
TemporalLiteralResolver temporalLiteralResolver = expressionService.getTemporalLiteralResolver();
if (temporalLiteralResolver == null) {
throw new DomainModelException("No literal resolver for temporal literals defined");
}
return temporalLiteralResolver.resolveDateLiteral(context, localDate);
}

public ResolvedLiteral ofLocalTime(ExpressionCompiler.Context context, LocalTime localTime) {
TemporalLiteralResolver temporalLiteralResolver = expressionService.getTemporalLiteralResolver();
if (temporalLiteralResolver == null) {
throw new DomainModelException("No literal resolver for temporal literals defined");
}
return temporalLiteralResolver.resolveTimeLiteral(context, localTime);
}

public ResolvedLiteral ofInstant(ExpressionCompiler.Context context, Instant instant) {
TemporalLiteralResolver temporalLiteralResolver = expressionService.getTemporalLiteralResolver();
if (temporalLiteralResolver == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,24 @@ public Expression visitUnaryPlusExpression(PredicateParser.UnaryPlusExpressionCo
// return new CollectionAtom(new Path(ctx.identifier().getText(), TermType.COLLECTION));
// }

@Override
public Expression visitDateLiteral(PredicateParser.DateLiteralContext ctx) {
return new Literal(literalFactory.ofDateString(compileContext, ctx.datePart().getText()));
}

@Override
public Expression visitTimeLiteral(PredicateParser.TimeLiteralContext ctx) {
StringBuilder sb = new StringBuilder(12);
PredicateParser.TimePartContext timePartContext = ctx.timePart();
sb.append(timePartContext.getText());

if (ctx.fraction != null) {
sb.append('.');
sb.append(ctx.fraction.getText());
}
return new Literal(literalFactory.ofTimeString(compileContext, sb.toString()));
}

@Override
public Expression visitTimestampLiteral(PredicateParser.TimestampLiteralContext ctx) {
StringBuilder sb = new StringBuilder(23);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,25 @@
import com.blazebit.expression.spi.TemporalLiteralResolver;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneOffset;

/**
* @author Christian Beikov
* @since 1.0.0
*/
public class DefaultTemporalLiteralResolver implements TemporalLiteralResolver {
@Override
public ResolvedLiteral resolveDateLiteral(ExpressionCompiler.Context context, LocalDate value) {
return new DefaultResolvedLiteral(context.getExpressionService().getDomainModel().getType(AbstractExpressionCompilerTest.TIMESTAMP), value.atStartOfDay().toInstant(ZoneOffset.UTC));
}

@Override
public ResolvedLiteral resolveTimeLiteral(ExpressionCompiler.Context context, LocalTime value) {
return new DefaultResolvedLiteral(context.getExpressionService().getDomainModel().getType(AbstractExpressionCompilerTest.TIMESTAMP), value.atDate(LocalDate.of(1970, 1, 1)).toInstant(ZoneOffset.UTC));
}

@Override
public ResolvedLiteral resolveTimestampLiteral(ExpressionCompiler.Context context, Instant value) {
return new DefaultResolvedLiteral(context.getExpressionService().getDomainModel().getType(AbstractExpressionCompilerTest.TIMESTAMP), value);
Expand Down
Loading

0 comments on commit e9252ad

Please sign in to comment.