diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 index 4b0bb9a14c..2c79fc040b 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 @@ -314,8 +314,10 @@ literal | NULL | booleanLiteral | numericLiteral - | dateTimeLiteral | binaryLiteral + | temporalLiteral + | arrayLiteral + | generalizedLiteral ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-boolean-literals @@ -326,30 +328,157 @@ booleanLiteral // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-numeric-literals numericLiteral - : INTEGER_LITERAL - | LONG_LITERAL - | BIG_INTEGER_LITERAL - | FLOAT_LITERAL - | DOUBLE_LITERAL - | BIG_DECIMAL_LITERAL - | HEX_LITERAL - ; + : INTEGER_LITERAL + | LONG_LITERAL + | BIG_INTEGER_LITERAL + | FLOAT_LITERAL + | DOUBLE_LITERAL + | BIG_DECIMAL_LITERAL + | HEX_LITERAL + ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-datetime-literals +/** + * A literal datetime, in braces, or with the 'datetime' keyword + */ dateTimeLiteral - : LOCAL_DATE - | LOCAL_TIME - | LOCAL_DATETIME - | CURRENT_DATE - | CURRENT_TIME - | CURRENT_TIMESTAMP - | OFFSET_DATETIME - | (LOCAL | CURRENT) DATE - | (LOCAL | CURRENT) TIME - | (LOCAL | CURRENT | OFFSET) DATETIME - | INSTANT + : localDateTimeLiteral + | zonedDateTimeLiteral + | offsetDateTimeLiteral + ; + +localDateTimeLiteral + : '(' localDateTime ')' + | LOCAL? DATETIME localDateTime + ; + +zonedDateTimeLiteral + : '(' zonedDateTime ')' + | ZONED? DATETIME zonedDateTime + ; + +offsetDateTimeLiteral + : '(' offsetDateTime ')' + | OFFSET? DATETIME offsetDateTimeWithMinutes + ; +/** + * A literal date, in braces, or with the 'date' keyword + */ +dateLiteral + : '(' date ')' + | LOCAL? DATE date ; +/** + * A literal time, in braces, or with the 'time' keyword + */ +timeLiteral + : '(' time ')' + | LOCAL? TIME time + ; + +/** + * A literal datetime + */ + dateTime + : localDateTime + | zonedDateTime + | offsetDateTime + ; + +localDateTime + : date time + ; + +zonedDateTime + : date time zoneId + ; + +offsetDateTime + : date time offset + ; + +offsetDateTimeWithMinutes + : date time offsetWithMinutes + ; + +/** + * A JDBC-style timestamp escape, as required by JPQL + */ +jdbcTimestampLiteral + : TIMESTAMP_ESCAPE_START (dateTime | genericTemporalLiteralText) '}' + ; + +/** + * A JDBC-style date escape, as required by JPQL + */ +jdbcDateLiteral + : DATE_ESCAPE_START (date | genericTemporalLiteralText) '}' + ; + +/** + * A JDBC-style time escape, as required by JPQL + */ +jdbcTimeLiteral + : TIME_ESCAPE_START (time | genericTemporalLiteralText) '}' + ; + +genericTemporalLiteralText + : STRING_LITERAL + ; + +/** + * A generic format for specifying literal values of arbitary types + */ +arrayLiteral + : '[' (expression (',' expression)*)? ']' + ; + +/** + * A generic format for specifying literal values of arbitary types + */ +generalizedLiteral + : '(' generalizedLiteralType ':' generalizedLiteralText ')' + ; + +generalizedLiteralType : STRING_LITERAL; +generalizedLiteralText : STRING_LITERAL; + +/** + * A literal date + */ +date + : year '-' month '-' day + ; + +/** + * A literal time + */ +time + : hour ':' minute (':' second)? + ; + +/** + * A literal offset + */ +offset + : (PLUS | MINUS) hour (':' minute)? + ; + +offsetWithMinutes + : (PLUS | MINUS) hour ':' minute + ; + +year: INTEGER_LITERAL; +month: INTEGER_LITERAL; +day: INTEGER_LITERAL; +hour: INTEGER_LITERAL; +minute: INTEGER_LITERAL; +second: INTEGER_LITERAL | DOUBLE_LITERAL; +zoneId + : IDENTIFIER ('/' IDENTIFIER)? + | STRING_LITERAL; + /** * A field that may be extracted from a date, time, or datetime */ @@ -402,8 +531,17 @@ binaryLiteral | '{' HEX_LITERAL (',' HEX_LITERAL)* '}' ; -// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-enum-literals -// TBD +/** + * A literal date, time, or datetime, in HQL syntax, or as a JDBC-style "escape" syntax + */ +temporalLiteral + : dateTimeLiteral + | dateLiteral + | timeLiteral + | jdbcTimestampLiteral + | jdbcDateLiteral + | jdbcTimeLiteral + ; // https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-java-constants // TBD @@ -1685,6 +1823,16 @@ BINARY_LITERAL : [xX] '\'' HEX_DIGIT+ '\'' | [xX] '"' HEX_DIGIT+ '"' ; +// ESCAPE start tokens +TIMESTAMP_ESCAPE_START : '{ts'; +DATE_ESCAPE_START : '{d'; +TIME_ESCAPE_START : '{t'; + +PLUS : '+'; +MINUS : '-'; + + + fragment LETTER : [a-zA-Z\u0080-\ufffe_$]; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java index ee47787b0d..26a4b775c8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java @@ -1038,8 +1038,12 @@ public QueryTokenStream visitLiteral(HqlParser.LiteralContext ctx) { return QueryRendererBuilder.from(QueryTokens.expression(ctx.STRING_LITERAL())); } else if (ctx.numericLiteral() != null) { return visit(ctx.numericLiteral()); - } else if (ctx.dateTimeLiteral() != null) { - return visit(ctx.dateTimeLiteral()); + } else if (ctx.temporalLiteral() != null) { + return visit(ctx.temporalLiteral()); + } else if (ctx.arrayLiteral() != null) { + return visit(ctx.arrayLiteral()); + } else if (ctx.generalizedLiteral() != null) { + return visit(ctx.generalizedLiteral()); } else if (ctx.binaryLiteral() != null) { return visit(ctx.binaryLiteral()); } else { @@ -1084,48 +1088,365 @@ public QueryTokenStream visitNumericLiteral(HqlParser.NumericLiteralContext ctx) @Override public QueryTokenStream visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ctx) { - QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.localDateTimeLiteral() != null) { + return visit(ctx.localDateTimeLiteral()); + } - if (ctx.LOCAL_DATE() != null) { - builder.append(QueryTokens.expression(ctx.LOCAL_DATE())); - } else if (ctx.LOCAL_TIME() != null) { - builder.append(QueryTokens.expression(ctx.LOCAL_TIME())); - } else if (ctx.LOCAL_DATETIME() != null) { - builder.append(QueryTokens.expression(ctx.LOCAL_DATETIME())); - } else if (ctx.CURRENT_DATE() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT_DATE())); - } else if (ctx.CURRENT_TIME() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT_TIME())); - } else if (ctx.CURRENT_TIMESTAMP() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT_TIMESTAMP())); - } else if (ctx.OFFSET_DATETIME() != null) { - builder.append(QueryTokens.expression(ctx.OFFSET_DATETIME())); - } else { + if (ctx.offsetDateTimeLiteral() != null) { + return visit(ctx.offsetDateTimeLiteral()); + } + + if (ctx.zonedDateTimeLiteral() != null) { + return visit(ctx.zonedDateTimeLiteral()); + } + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitLocalDateTimeLiteral(HqlParser.LocalDateTimeLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.DATETIME() != null) { if (ctx.LOCAL() != null) { builder.append(QueryTokens.expression(ctx.LOCAL())); - } else if (ctx.CURRENT() != null) { - builder.append(QueryTokens.expression(ctx.CURRENT())); - } else if (ctx.OFFSET() != null) { + } + builder.append(QueryTokens.expression(ctx.DATETIME())); + builder.append(visit(ctx.localDateTime())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.localDateTime())); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitZonedDateTimeLiteral(HqlParser.ZonedDateTimeLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.DATETIME() != null) { + if (ctx.ZONED() != null) { + builder.append(QueryTokens.expression(ctx.ZONED())); + } + builder.append(QueryTokens.expression(ctx.DATETIME())); + builder.append(visit(ctx.zonedDateTime())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.zonedDateTime())); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitOffsetDateTimeLiteral(HqlParser.OffsetDateTimeLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.DATETIME() != null) { + if (ctx.OFFSET() != null) { builder.append(QueryTokens.expression(ctx.OFFSET())); } + builder.append(QueryTokens.expression(ctx.DATETIME())); + builder.append(visit(ctx.offsetDateTimeWithMinutes())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.offsetDateTime())); + builder.append(TOKEN_CLOSE_PAREN); + } - if (ctx.DATE() != null) { - builder.append(QueryTokens.expression(ctx.DATE())); - } else if (ctx.TIME() != null) { - builder.append(QueryTokens.expression(ctx.TIME())); - } else if (ctx.DATETIME() != null) { - builder.append(QueryTokens.expression(ctx.DATETIME())); + return builder; + } + + @Override + public QueryTokenStream visitDateLiteral(HqlParser.DateLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.DATE() != null) { + if (ctx.LOCAL() != null) { + builder.append(QueryTokens.expression(ctx.LOCAL())); } + builder.append(QueryTokens.expression(ctx.DATE())); + builder.append(visit(ctx.date())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.date())); + builder.append(TOKEN_CLOSE_PAREN); + } - if (ctx.INSTANT() != null) { - builder.append(QueryTokens.expression(ctx.INSTANT())); + return builder; + } + + @Override + public QueryTokenStream visitTimeLiteral(HqlParser.TimeLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.TIME() != null) { + if (ctx.LOCAL() != null) { + builder.append(QueryTokens.expression(ctx.LOCAL())); } + builder.append(QueryTokens.expression(ctx.TIME())); + builder.append(visit(ctx.time())); + } else { + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.time())); + builder.append(TOKEN_CLOSE_PAREN); + } + + return builder; + } + + @Override + public QueryTokenStream visitDateTime(HqlParser.DateTimeContext ctx) { + + if (ctx.localDateTime() != null) { + return visit(ctx.localDateTime()); + } + + if (ctx.offsetDateTime() != null) { + return visit(ctx.offsetDateTime()); + } + + if (ctx.zonedDateTime() != null) { + return visit(ctx.zonedDateTime()); + } + + return QueryTokenStream.empty(); + } + + @Override + public QueryTokenStream visitLocalDateTime(HqlParser.LocalDateTimeContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.date())); + builder.appendExpression(visit(ctx.time())); + + return builder; + } + + @Override + public QueryTokenStream visitZonedDateTime(HqlParser.ZonedDateTimeContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.date())); + builder.appendExpression(visit(ctx.time())); + builder.appendExpression(visit(ctx.zoneId())); + + return builder; + } + + @Override + public QueryTokenStream visitOffsetDateTime(HqlParser.OffsetDateTimeContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.date())); + builder.appendInline(visit(ctx.time())); + builder.appendInline(visit(ctx.offset())); + + return builder; + } + + @Override + public QueryTokenStream visitOffsetDateTimeWithMinutes(HqlParser.OffsetDateTimeWithMinutesContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.appendExpression(visit(ctx.date())); + builder.appendInline(visit(ctx.time())); + builder.appendInline(visit(ctx.offsetWithMinutes())); + + return builder; + } + + @Override + public QueryTokenStream visitJdbcTimestampLiteral(HqlParser.JdbcTimestampLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_BRACE); + builder.append(QueryTokens.token("ts")); + builder.append(visit(ctx.dateTime() != null ? ctx.dateTime() : ctx.genericTemporalLiteralText())); + builder.append(QueryTokens.TOKEN_CLOSE_BRACE); + + return builder; + } + + @Override + public QueryTokenStream visitJdbcDateLiteral(HqlParser.JdbcDateLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_BRACE); + builder.append(QueryTokens.token("d")); + builder.append(visit(ctx.date() != null ? ctx.date() : ctx.genericTemporalLiteralText())); + builder.append(QueryTokens.TOKEN_CLOSE_BRACE); + + return builder; + } + + @Override + public QueryTokenStream visitJdbcTimeLiteral(HqlParser.JdbcTimeLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + builder.append(TOKEN_OPEN_BRACE); + builder.append(QueryTokens.token("t")); + builder.append(visit(ctx.time() != null ? ctx.time() : ctx.genericTemporalLiteralText())); + builder.append(QueryTokens.TOKEN_CLOSE_BRACE); + + return builder; + } + + @Override + public QueryTokenStream visitGenericTemporalLiteralText(HqlParser.GenericTemporalLiteralTextContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + @Override + public QueryTokenStream visitArrayLiteral(HqlParser.ArrayLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(TOKEN_OPEN_SQUARE_BRACKET); + builder.append(QueryTokenStream.concat(ctx.expression(), this::visit, TOKEN_COMMA)); + builder.append(TOKEN_CLOSE_SQUARE_BRACKET); + + return builder; + } + + @Override + public QueryTokenStream visitGeneralizedLiteral(HqlParser.GeneralizedLiteralContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(TOKEN_OPEN_PAREN); + builder.append(visit(ctx.generalizedLiteralType())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.generalizedLiteralText())); + builder.append(TOKEN_CLOSE_PAREN); + + return builder; + } + + @Override + public QueryTokenStream visitGeneralizedLiteralType(HqlParser.GeneralizedLiteralTypeContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + @Override + public QueryTokenStream visitGeneralizedLiteralText(HqlParser.GeneralizedLiteralTextContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + + @Override + public QueryTokenStream visitDate(HqlParser.DateContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(visit(ctx.year())); + builder.append(TOKEN_DASH); + builder.append(visit(ctx.month())); + builder.append(TOKEN_DASH); + builder.append(visit(ctx.day())); + + return builder; + } + + @Override + public QueryTokenStream visitTime(HqlParser.TimeContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + builder.append(visit(ctx.hour())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.minute())); + + if (ctx.second() != null) { + builder.append(TOKEN_COLON); + builder.append(visit(ctx.second())); + } + + return builder; + } + + @Override + public QueryTokenStream visitOffset(HqlParser.OffsetContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + if (ctx.MINUS() != null) { + builder.append(QueryTokens.token(ctx.MINUS())); + } else if (ctx.PLUS() != null) { + builder.append(QueryTokens.token(ctx.PLUS())); + } + builder.append(visit(ctx.hour())); + + if (ctx.minute() != null) { + builder.append(TOKEN_COLON); + builder.append(visit(ctx.minute())); + } + + return builder; + } + + @Override + public QueryTokenStream visitOffsetWithMinutes(HqlParser.OffsetWithMinutesContext ctx) { + + QueryRendererBuilder builder = QueryRenderer.builder(); + + if (ctx.MINUS() != null) { + builder.append(QueryTokens.token(ctx.MINUS())); + } else if (ctx.PLUS() != null) { + builder.append(QueryTokens.token(ctx.PLUS())); } + builder.append(visit(ctx.hour())); + builder.append(TOKEN_COLON); + builder.append(visit(ctx.minute())); + return builder; } + @Override + public QueryTokenStream visitYear(HqlParser.YearContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } + + @Override + public QueryTokenStream visitMonth(HqlParser.MonthContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } + + @Override + public QueryTokenStream visitDay(HqlParser.DayContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } + + @Override + public QueryTokenStream visitHour(HqlParser.HourContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } + + @Override + public QueryTokenStream visitMinute(HqlParser.MinuteContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } + + @Override + public QueryTokenStream visitSecond(HqlParser.SecondContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.INTEGER_LITERAL())); + } + + @Override + public QueryTokenStream visitZoneId(HqlParser.ZoneIdContext ctx) { + return QueryRendererBuilder.from(QueryTokens.token(ctx.STRING_LITERAL())); + } + @Override public QueryTokenStream visitDatetimeField(HqlParser.DatetimeFieldContext ctx) { @@ -1250,6 +1571,36 @@ public QueryTokenStream visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) { return builder; } + @Override + public QueryTokenStream visitTemporalLiteral(HqlParser.TemporalLiteralContext ctx) { + + if (ctx.dateTimeLiteral() != null) { + return visit(ctx.dateTimeLiteral()); + } + + if (ctx.dateLiteral() != null) { + return visit(ctx.dateLiteral()); + } + + if (ctx.timeLiteral() != null) { + return visit(ctx.timeLiteral()); + } + + if (ctx.jdbcTimestampLiteral() != null) { + return visit(ctx.jdbcTimestampLiteral()); + } + + if (ctx.jdbcDateLiteral() != null) { + return visit(ctx.jdbcDateLiteral()); + } + + if (ctx.jdbcTimeLiteral() != null) { + return visit(ctx.jdbcTimeLiteral()); + } + + return QueryTokenStream.empty(); + } + @Override public QueryTokenStream visitPlainPrimaryExpression(HqlParser.PlainPrimaryExpressionContext ctx) { return visit(ctx.primaryExpression()); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java index 4059b630c2..ea95343d42 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokens.java @@ -47,6 +47,7 @@ class QueryTokens { static final QueryToken TOKEN_OPEN_SQUARE_BRACKET = token("["); static final QueryToken TOKEN_CLOSE_SQUARE_BRACKET = token("]"); static final QueryToken TOKEN_COLON = token(":"); + static final QueryToken TOKEN_DASH = token("-"); static final QueryToken TOKEN_QUESTION_MARK = token("?"); static final QueryToken TOKEN_OPEN_BRACE = token("{"); static final QueryToken TOKEN_CLOSE_BRACE = token("}"); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java index 6cde039b62..725e09950d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java @@ -1749,6 +1749,36 @@ void durationLiteralsShouldWork(String dtField) { assertQuery("SELECT ce.id as id, cd.startDate + 5 %s AS summedDate FROM CalendarEvent ce".formatted(dtField)); } + @Test // GH-3739 + void dateTimeLiterals() { + + assertQuery("SELECT e FROM Employee e WHERE e.startDate = {d'2012-01-03'}"); + assertQuery("SELECT e FROM Employee e WHERE e.startTime = {t'09:00:00'}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts'2012-01-03 09:00:00'}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts'something weird'}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00+1}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00-1}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00+1:00}"); + assertQuery("SELECT e FROM Employee e WHERE e.version = {ts2012-01-03 09:00:00-1:00}"); + + assertQuery("SELECT e FROM Employee e WHERE e.version = OFFSET DATETIME 2012-01-03 09:00:00+1:01"); + assertQuery("SELECT e FROM Employee e WHERE e.version = OFFSET DATETIME 2012-01-03 09:00:00-1:01"); + } + + @Test + void literals() { + + assertQuery("SELECT e FROM Employee e WHERE e.name = 'Bob'"); + assertQuery("SELECT e FROM Employee e WHERE e.names = [e.firstName, e.lastName]"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + assertQuery("SELECT e FROM Employee e WHERE e.active = TRUE"); + assertQuery("SELECT e FROM Employee e WHERE e.gender = org.acme.Gender.MALE"); + assertQuery("UPDATE Employee e SET e.manager = NULL WHERE e.manager = :manager"); + } + @ParameterizedTest // GH-3711 @ValueSource(strings = { "1", "1_000", "1L", "1_000L", "1bi", "1.1f", "2.2d", "2.2bd" }) void numberLiteralsShouldWork(String literal) {