@@ -1415,10 +1415,12 @@ public QueryExpression notLike(LiteralExpression literal) {
14151415
14161416 @ Override
14171417 public QueryExpression equals (LiteralExpression literal ) {
1418- Object value = literal .value ();
1419- if (literal .isDateTime ()) {
1418+ boolean isTimeStamp = isFieldOrLiteralDateTime (literal );
1419+ Object value = convertEndpointValue (literal .value (), isTimeStamp );
1420+ if (isTimeStamp ) {
14201421 builder =
1421- addFormatIfNecessary (literal , rangeQuery (getFieldReference ()).gte (value ).lte (value ));
1422+ addFormatIfNecessary (
1423+ isTimeStamp , rangeQuery (getFieldReference ()).gte (value ).lte (value ));
14221424 } else {
14231425 builder = termQuery (getFieldReferenceForTermQuery (), value );
14241426 }
@@ -1427,12 +1429,15 @@ public QueryExpression equals(LiteralExpression literal) {
14271429
14281430 @ Override
14291431 public QueryExpression notEquals (LiteralExpression literal ) {
1430- Object value = literal .value ();
1431- if (literal .isDateTime ()) {
1432+ boolean isTimeStamp = isFieldOrLiteralDateTime (literal );
1433+ Object value = convertEndpointValue (literal .value (), isTimeStamp );
1434+ if (isTimeStamp ) {
14321435 builder =
14331436 boolQuery ()
1434- .should (addFormatIfNecessary (literal , rangeQuery (getFieldReference ()).gt (value )))
1435- .should (addFormatIfNecessary (literal , rangeQuery (getFieldReference ()).lt (value )));
1437+ .should (
1438+ addFormatIfNecessary (isTimeStamp , rangeQuery (getFieldReference ()).gt (value )))
1439+ .should (
1440+ addFormatIfNecessary (isTimeStamp , rangeQuery (getFieldReference ()).lt (value )));
14361441 } else {
14371442 builder =
14381443 boolQuery ()
@@ -1445,32 +1450,48 @@ public QueryExpression notEquals(LiteralExpression literal) {
14451450
14461451 @ Override
14471452 public QueryExpression gt (LiteralExpression literal ) {
1448- Object value = literal .value ();
1449- builder = addFormatIfNecessary (literal , rangeQuery (getFieldReference ()).gt (value ));
1453+ boolean isTimeStamp = isFieldOrLiteralDateTime (literal );
1454+ Object value = convertEndpointValue (literal .value (), isTimeStamp );
1455+ builder = addFormatIfNecessary (isTimeStamp , rangeQuery (getFieldReference ()).gt (value ));
14501456 return this ;
14511457 }
14521458
14531459 @ Override
14541460 public QueryExpression gte (LiteralExpression literal ) {
1455- Object value = literal .value ();
1456- builder = addFormatIfNecessary (literal , rangeQuery (getFieldReference ()).gte (value ));
1461+ boolean isTimeStamp = isFieldOrLiteralDateTime (literal );
1462+ Object value = convertEndpointValue (literal .value (), isTimeStamp );
1463+ builder = addFormatIfNecessary (isTimeStamp , rangeQuery (getFieldReference ()).gte (value ));
14571464 return this ;
14581465 }
14591466
14601467 @ Override
14611468 public QueryExpression lt (LiteralExpression literal ) {
1462- Object value = literal .value ();
1463- builder = addFormatIfNecessary (literal , rangeQuery (getFieldReference ()).lt (value ));
1469+ boolean isTimeStamp = isFieldOrLiteralDateTime (literal );
1470+ Object value = convertEndpointValue (literal .value (), isTimeStamp );
1471+ builder = addFormatIfNecessary (isTimeStamp , rangeQuery (getFieldReference ()).lt (value ));
14641472 return this ;
14651473 }
14661474
14671475 @ Override
14681476 public QueryExpression lte (LiteralExpression literal ) {
1469- Object value = literal .value ();
1470- builder = addFormatIfNecessary (literal , rangeQuery (getFieldReference ()).lte (value ));
1477+ boolean isTimeStamp = isFieldOrLiteralDateTime (literal );
1478+ Object value = convertEndpointValue (literal .value (), isTimeStamp );
1479+ builder = addFormatIfNecessary (isTimeStamp , rangeQuery (getFieldReference ()).lte (value ));
14711480 return this ;
14721481 }
14731482
1483+ /**
1484+ * Whether the comparison is a timestamp/date range. The field type is the reliable signal:
1485+ * {@code literal.isDateTime()} reads the literal's UDT, which {@link
1486+ * org.apache.calcite.rex.RexSimplify} can strip (to VARCHAR) when a sibling clause is folded
1487+ * into a {@code Sarg}, e.g. {@code @timestamp > X AND severityText IN (...)}. Falling back to
1488+ * {@code rel.isTimeStampType()} keeps ISO-8601 normalization and the {@code "date_time"} format
1489+ * hint on the range query.
1490+ */
1491+ private boolean isFieldOrLiteralDateTime (LiteralExpression literal ) {
1492+ return literal .isDateTime () || (rel != null && rel .isTimeStampType ());
1493+ }
1494+
14741495 @ Override
14751496 public QueryExpression match (String query , Map <String , String > optionalArguments ) {
14761497 builder = new MatchQuery ().build (getFieldReference (), query , optionalArguments );
@@ -1617,6 +1638,11 @@ public QueryExpression between(Range<?> range, boolean isTimeStamp) {
16171638 }
16181639
16191640 private Object convertEndpointValue (Object value , boolean isTimeStamp ) {
1641+ // Shared normalization entry point: guard a null endpoint so the timestamp branch's
1642+ // value.toString() cannot NPE. sargPointValue never produces null from a non-null input.
1643+ if (value == null ) {
1644+ return null ;
1645+ }
16201646 value = sargPointValue (value );
16211647 return isTimeStamp ? timestampValueForPushDown (value .toString ()) : value ;
16221648 }
@@ -1749,16 +1775,19 @@ public static ScriptSortBuilder.ScriptSortType getScriptSortType(RelDataType rel
17491775 }
17501776
17511777 /**
1752- * By default, range queries on date/time need use the format of the source to parse the literal.
1753- * So we need to specify that the literal has "date_time" format
1778+ * Range queries on date/time fields need the source format to parse the literal, so we attach the
1779+ * {@code "date_time"} format. The caller resolves whether the comparison is a timestamp range
1780+ * from the field type (see {@link SimpleQueryExpression#isFieldOrLiteralDateTime}) rather than
1781+ * the literal's UDT, which {@link org.apache.calcite.rex.RexSimplify} can strip when a sibling
1782+ * clause is folded into a {@code Sarg}.
17541783 *
1755- * @param literal literal value
1756- * @param rangeQueryBuilder query builder to optionally add {@code format} expression
1757- * @return existing builder with possible {@code format} attribute
1784+ * @param isTimeStamp whether the comparison endpoint is a timestamp/date range endpoint
1785+ * @param rangeQueryBuilder query builder to optionally add the {@code format} attribute
1786+ * @return the same builder, with {@code format("date_time")} added when {@code isTimeStamp}
17581787 */
17591788 private static RangeQueryBuilder addFormatIfNecessary (
1760- LiteralExpression literal , RangeQueryBuilder rangeQueryBuilder ) {
1761- if (literal . isDateTime () ) {
1789+ boolean isTimeStamp , RangeQueryBuilder rangeQueryBuilder ) {
1790+ if (isTimeStamp ) {
17621791 rangeQueryBuilder .format ("date_time" );
17631792 }
17641793 return rangeQueryBuilder ;
0 commit comments