From ae978b3d10fa77da691c7751764edc97062fe5b3 Mon Sep 17 00:00:00 2001 From: Gavin Date: Fri, 13 Jan 2023 14:56:34 +0100 Subject: [PATCH] fix an ambiguity in the grammar of datetime literals this was my very stupid mistake --- .../org/hibernate/grammars/hql/HqlLexer.g4 | 1 + .../org/hibernate/grammars/hql/HqlParser.g4 | 66 +++++-- .../hql/internal/SemanticQueryBuilder.java | 176 +++++++++--------- 3 files changed, 145 insertions(+), 98 deletions(-) diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index c228fc5c74..96870f50d8 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -304,6 +304,7 @@ WITH : [wW] [iI] [tT] [hH]; WITHIN : [wW] [iI] [tT] [hH] [iI] [nN]; WITHOUT : [wW] [iI] [tT] [hH] [oO] [uU] [tT]; YEAR : [yY] [eE] [aA] [rR]; +ZONED : [zZ] [oO] [nN] [eE] [dD]; // case-insensitive true, false and null recognition (split vote :) TRUE : [tT] [rR] [uU] [eE]; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index af772b8715..8593ba83dd 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -877,8 +877,24 @@ temporalLiteral * A literal datetime, in braces, or with the 'datetime' keyword */ dateTimeLiteral - : LEFT_BRACE dateTime RIGHT_BRACE - | DATETIME dateTime + : localDateTimeLiteral + | zonedDateTimeLiteral + | offsetDateTimeLiteral + ; + +localDateTimeLiteral + : LEFT_BRACE localDateTime RIGHT_BRACE + | LOCAL? DATETIME localDateTime + ; + +zonedDateTimeLiteral + : LEFT_BRACE zonedDateTime RIGHT_BRACE + | ZONED? DATETIME zonedDateTime + ; + +offsetDateTimeLiteral + : LEFT_BRACE offsetDateTime RIGHT_BRACE + | OFFSET? DATETIME offsetDateTimeWithMinutes ; /** @@ -900,8 +916,26 @@ timeLiteral /** * A literal datetime */ -dateTime - : date time (zoneId | offset)? + dateTime + : localDateTime + | zonedDateTime + | offsetDateTime + ; + +localDateTime + : date time + ; + +zonedDateTime + : date time zoneId + ; + +offsetDateTime + : date time offset + ; + +offsetDateTimeWithMinutes + : date time offsetWithMinutes ; /** @@ -925,6 +959,10 @@ offset : (PLUS | MINUS) hour (COLON minute)? ; +offsetWithMinutes + : (PLUS | MINUS) hour COLON minute + ; + year: INTEGER_LITERAL; month: INTEGER_LITERAL; day: INTEGER_LITERAL; @@ -1174,19 +1212,19 @@ frameClause */ frameStart : UNBOUNDED PRECEDING - | expression PRECEDING - | CURRENT ROW - | expression FOLLOWING + | expression PRECEDING + | CURRENT ROW + | expression FOLLOWING ; /** * The end of the window content */ frameEnd - : expression PRECEDING - | CURRENT ROW - | expression FOLLOWING - | UNBOUNDED FOLLOWING + : expression PRECEDING + | CURRENT ROW + | expression FOLLOWING + | UNBOUNDED FOLLOWING ; /** @@ -1194,9 +1232,9 @@ frameEnd */ frameExclusion : EXCLUDE CURRENT ROW - | EXCLUDE GROUP - | EXCLUDE TIES - | EXCLUDE NO OTHERS + | EXCLUDE GROUP + | EXCLUDE TIES + | EXCLUDE NO OTHERS ; /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 7753c400b5..d56c4e6d1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -3288,7 +3288,30 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public Object visitDateTimeLiteral(HqlParser.DateTimeLiteralContext ctx) { - return ctx.getChild( 1 ).accept( this ); + return ctx.getChild( 0 ).accept( this ); + } + + @Override + public Object visitLocalDateTimeLiteral(HqlParser.LocalDateTimeLiteralContext ctx) { + return ctx.localDateTime().accept( this ); + } + + @Override + public Object visitZonedDateTimeLiteral(HqlParser.ZonedDateTimeLiteralContext ctx) { + return ctx.zonedDateTime().accept( this ); + } + + @Override + public Object visitOffsetDateTimeLiteral(HqlParser.OffsetDateTimeLiteralContext ctx) { + if ( ctx.offsetDateTime() != null ) { + return ctx.offsetDateTime().accept(this); + } + else if ( ctx.offsetDateTimeWithMinutes() != null ) { + return ctx.offsetDateTimeWithMinutes().accept(this); + } + else { + return null; + } } @Override @@ -3336,42 +3359,48 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem @Override public Object visitDateTime(HqlParser.DateTimeContext ctx) { - final ParseTree parseTree = ctx.getChild( 2 ); - if ( parseTree instanceof HqlParser.ZoneIdContext || parseTree == null ) { - return dateTimeLiteralFrom( - (HqlParser.DateContext) ctx.getChild( 0 ), - (HqlParser.TimeContext) ctx.getChild( 1 ), - (HqlParser.ZoneIdContext) parseTree - ); - } - else { - return offsetDatetimeLiteralFrom( - (HqlParser.DateContext) ctx.getChild( 0 ), - (HqlParser.TimeContext) ctx.getChild( 1 ), - (HqlParser.OffsetContext) parseTree - ); - } + return ctx.getChild( 0 ).accept( this ); } - private SqmLiteral dateTimeLiteralFrom( + @Override + public Object visitLocalDateTime(HqlParser.LocalDateTimeContext ctx) { + return localDateTimeLiteralFrom( ctx.date(), ctx.time() ); + } + + @Override + public Object visitOffsetDateTime(HqlParser.OffsetDateTimeContext ctx) { + return offsetDatetimeLiteralFrom( ctx.date(), ctx.time(), ctx.offset() ); + } + + @Override + public Object visitOffsetDateTimeWithMinutes(HqlParser.OffsetDateTimeWithMinutesContext ctx) { + return offsetDatetimeLiteralFrom( ctx.date(), ctx.time(), ctx.offsetWithMinutes() ); + } + + @Override + public Object visitZonedDateTime(HqlParser.ZonedDateTimeContext ctx) { + return zonedDateTimeLiteralFrom( ctx.date(), ctx.time(), ctx.zoneId() ); + } + + private SqmLiteral localDateTimeLiteralFrom( + HqlParser.DateContext date, + HqlParser.TimeContext time) { + return new SqmLiteral<>( + LocalDateTime.of( localDate( date ), localTime( time ) ), + resolveExpressibleTypeBasic( LocalDateTime.class ), + creationContext.getNodeBuilder() + ); + } + + private SqmLiteral zonedDateTimeLiteralFrom( HqlParser.DateContext date, HqlParser.TimeContext time, HqlParser.ZoneIdContext timezone) { - if ( timezone == null ) { - return new SqmLiteral<>( - LocalDateTime.of( localDate( date ), localTime( time ) ), - resolveExpressibleTypeBasic( LocalDateTime.class ), - creationContext.getNodeBuilder() - ); - } - else { - final ZoneId zoneId = visitZoneId( timezone ); - return new SqmLiteral<>( - ZonedDateTime.of( localDate( date ), localTime( time ), zoneId ), - resolveExpressibleTypeBasic( ZonedDateTime.class ), - creationContext.getNodeBuilder() - ); - } + return new SqmLiteral<>( + ZonedDateTime.of( localDate( date ), localTime( time ), visitZoneId( timezone ) ), + resolveExpressibleTypeBasic( ZonedDateTime.class ), + creationContext.getNodeBuilder() + ); } @Override @@ -3404,6 +3433,17 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } + private SqmLiteral offsetDatetimeLiteralFrom( + HqlParser.DateContext date, + HqlParser.TimeContext time, + HqlParser.OffsetWithMinutesContext offset) { + return new SqmLiteral<>( + OffsetDateTime.of( localDate( date ), localTime( time ), zoneOffset( offset ) ), + resolveExpressibleTypeBasic( OffsetDateTime.class ), + creationContext.getNodeBuilder() + ); + } + @Override public Object visitDate(HqlParser.DateContext ctx) { return new SqmLiteral<>( @@ -3423,10 +3463,10 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem } private static LocalTime localTime(HqlParser.TimeContext ctx) { - final int hour = Integer.parseInt( ctx.getChild( 0 ).getText() ); - final int minute = Integer.parseInt( ctx.getChild( 2 ).getText() ); - if ( ctx.getChildCount() == 5 ) { - final String secondText = ctx.getChild( 4 ).getText(); + final int hour = Integer.parseInt( ctx.hour().getText() ); + final int minute = Integer.parseInt( ctx.minute().getText() ); + if ( ctx.second() != null ) { + final String secondText = ctx.second().getText(); final int index = secondText.indexOf( '.'); if ( index < 0 ) { return LocalTime.of( @@ -3451,67 +3491,35 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem private static LocalDate localDate(HqlParser.DateContext ctx) { return LocalDate.of( - Integer.parseInt( ctx.getChild( 0 ).getText() ), - Integer.parseInt( ctx.getChild( 2 ).getText() ), - Integer.parseInt( ctx.getChild( 4 ).getText() ) + Integer.parseInt( ctx.year().getText() ), + Integer.parseInt( ctx.month().getText() ), + Integer.parseInt( ctx.day().getText() ) ); } private static ZoneOffset zoneOffset(HqlParser.OffsetContext offset) { final int factor = ( (TerminalNode) offset.getChild( 0 ) ).getSymbol().getType() == PLUS ? 1 : -1; - final int hour = factor * Integer.parseInt( offset.getChild( 1 ).getText() ); + final int hour = factor * Integer.parseInt( offset.hour().getText() ); if ( offset.getChildCount() == 2 ) { return ZoneOffset.ofHours( hour ); } return ZoneOffset.ofHoursMinutes( hour, - factor * Integer.parseInt( offset.getChild( 3 ).getText() ) + factor * Integer.parseInt( offset.minute().getText() ) ); } -// private SqmLiteral offsetDatetimeLiteralFrom(String literalText) { -// TemporalAccessor parsed = OFFSET_DATE_TIME.parse( literalText ); -// return new SqmLiteral<>( -// OffsetDateTime.from( parsed ), -// resolveExpressibleTypeBasic( OffsetDateTime.class ), -// creationContext.getNodeBuilder() -// ); -// } -// -// private SqmLiteral dateTimeLiteralFrom(String literalText) { -// //TO DO: return an OffsetDateTime when appropriate? -// TemporalAccessor parsed = DATE_TIME.parse( literalText ); -// try { -// return new SqmLiteral<>( -// ZonedDateTime.from( parsed ), -// resolveExpressibleTypeBasic( ZonedDateTime.class ), -// creationContext.getNodeBuilder() -// ); -// } -// catch (DateTimeException dte) { -// return new SqmLiteral<>( -// LocalDateTime.from( parsed ), -// resolveExpressibleTypeBasic( LocalDateTime.class ), -// creationContext.getNodeBuilder() -// ); -// } -// } -// -// private SqmLiteral localDateLiteralFrom(String literalText) { -// return new SqmLiteral<>( -// LocalDate.from( ISO_LOCAL_DATE.parse( literalText ) ), -// resolveExpressibleTypeBasic( LocalDate.class ), -// creationContext.getNodeBuilder() -// ); -// } -// -// private SqmLiteral localTimeLiteralFrom(String literalText) { -// return new SqmLiteral<>( -// LocalTime.from( ISO_LOCAL_TIME.parse( literalText ) ), -// resolveExpressibleTypeBasic( LocalTime.class ), -// creationContext.getNodeBuilder() -// ); -// } + private static ZoneOffset zoneOffset(HqlParser.OffsetWithMinutesContext offset) { + final int factor = ( (TerminalNode) offset.getChild( 0 ) ).getSymbol().getType() == PLUS ? 1 : -1; + final int hour = factor * Integer.parseInt( offset.hour().getText() ); + if ( offset.getChildCount() == 2 ) { + return ZoneOffset.ofHours( hour ); + } + return ZoneOffset.ofHoursMinutes( + hour, + factor * Integer.parseInt( offset.minute().getText() ) + ); + } private SqmLiteral sqlTimestampLiteralFrom(String literalText) { final TemporalAccessor parsed = DATE_TIME.parse( literalText.subSequence( 1, literalText.length() - 1 ) );