4141import static org .opensearch .sql .calcite .utils .UserDefinedFunctionUtils .SINGLE_FIELD_RELEVANCE_FUNCTION_SET ;
4242
4343import com .google .common .base .Throwables ;
44+ import com .google .common .collect .BoundType ;
4445import com .google .common .collect .Range ;
4546import java .math .BigDecimal ;
4647import java .util .ArrayList ;
6566import org .apache .calcite .sql .type .SqlTypeFamily ;
6667import org .apache .calcite .sql .type .SqlTypeName ;
6768import org .apache .calcite .util .NlsString ;
69+ import org .apache .calcite .util .RangeSets ;
6870import org .apache .calcite .util .Sarg ;
6971import org .opensearch .index .mapper .DateFieldMapper ;
7072import org .opensearch .index .query .BoolQueryBuilder ;
7476import org .opensearch .sql .calcite .type .ExprSqlType ;
7577import org .opensearch .sql .calcite .utils .OpenSearchTypeFactory ;
7678import org .opensearch .sql .data .model .ExprTimestampValue ;
79+ import org .opensearch .sql .data .type .ExprCoreType ;
7780import org .opensearch .sql .data .type .ExprType ;
81+ import org .opensearch .sql .opensearch .data .type .OpenSearchDataType ;
7882import org .opensearch .sql .opensearch .data .type .OpenSearchDataType .MappingType ;
7983import org .opensearch .sql .opensearch .data .type .OpenSearchTextType ;
8084import org .opensearch .sql .opensearch .storage .script .filter .lucene .relevance .MatchBoolPrefixQuery ;
@@ -230,7 +234,7 @@ private static boolean supportedRexCall(RexCall call) {
230234 case INTERNAL :
231235 switch (call .getKind ()) {
232236 case SEARCH :
233- return canBeTranslatedToTermsQuery ( call ) ;
237+ return true ;
234238 default :
235239 return false ;
236240 }
@@ -241,20 +245,6 @@ private static boolean supportedRexCall(RexCall call) {
241245 }
242246 }
243247
244- /**
245- * There are three types of the Sarg included in SEARCH RexCall: 1) Sarg is points (In ('a',
246- * 'b', 'c' ...)). In this case the search call can be translated to terms Query 2) Sarg is
247- * complementedPoints (Not in ('a', 'b')). In this case the search call can be translated to
248- * MustNot terms Query 3) Sarg is real Range( > 1 and <= 10). In this case the search call
249- * should be translated to rang Query Currently only the 1) and 2) cases are supported.
250- *
251- * @param search SEARCH RexCall
252- * @return true if it isSearchWithPoints or isSearchWithComplementedPoints, other false
253- */
254- static boolean canBeTranslatedToTermsQuery (RexCall search ) {
255- return isSearchWithPoints (search ) || isSearchWithComplementedPoints (search );
256- }
257-
258248 static boolean isSearchWithPoints (RexCall search ) {
259249 RexLiteral literal = (RexLiteral ) search .getOperands ().get (1 );
260250 final Sarg <?> sarg = requireNonNull (literal .getValueAs (Sarg .class ), "Sarg" );
@@ -520,8 +510,28 @@ private QueryExpression binary(RexCall call) {
520510 case SEARCH :
521511 if (isSearchWithComplementedPoints (call )) {
522512 return QueryExpression .create (pair .getKey ()).notIn (pair .getValue ());
523- } else {
513+ } else if ( isSearchWithPoints ( call )) {
524514 return QueryExpression .create (pair .getKey ()).in (pair .getValue ());
515+ } else {
516+ Sarg <?> sarg = pair .getValue ().literal .getValueAs (Sarg .class );
517+ Set <? extends Range <?>> rangeSet = requireNonNull (sarg ).rangeSet .asRanges ();
518+ boolean isTimeStamp =
519+ (pair .getKey () instanceof NamedFieldExpression namedField )
520+ && namedField .isTimeStampType ();
521+ List <QueryExpression > queryExpressions =
522+ rangeSet .stream ()
523+ .map (
524+ range ->
525+ RangeSets .isPoint (range )
526+ ? QueryExpression .create (pair .getKey ())
527+ .equals (range .lowerEndpoint (), isTimeStamp )
528+ : QueryExpression .create (pair .getKey ()).between (range , isTimeStamp ))
529+ .toList ();
530+ if (queryExpressions .size () == 1 ) {
531+ return queryExpressions .getFirst ();
532+ } else {
533+ return CompoundQueryExpression .or (queryExpressions .toArray (new QueryExpression [0 ]));
534+ }
525535 }
526536 default :
527537 break ;
@@ -706,6 +716,16 @@ public boolean isPartial() {
706716
707717 public abstract QueryExpression notIn (LiteralExpression literal );
708718
719+ public QueryExpression between (Range <?> literal , boolean isTimeStamp ) {
720+ throw new PredicateAnalyzer .PredicateAnalyzerException (
721+ "between cannot be applied to " + this .getClass ());
722+ }
723+
724+ public QueryExpression equals (Object point , boolean isTimeStamp ) {
725+ throw new PredicateAnalyzer .PredicateAnalyzerException (
726+ "equals cannot be applied to " + this .getClass ());
727+ }
728+
709729 public abstract QueryExpression notEquals (LiteralExpression literal );
710730
711731 public abstract QueryExpression gt (LiteralExpression literal );
@@ -951,6 +971,11 @@ private SimpleQueryExpression(NamedFieldExpression rel) {
951971 this .rel = rel ;
952972 }
953973
974+ public SimpleQueryExpression (QueryBuilder builder ) {
975+ this .builder = builder ;
976+ this .rel = null ;
977+ }
978+
954979 @ Override
955980 public QueryBuilder builder () {
956981 if (builder == null ) {
@@ -1125,6 +1150,44 @@ public QueryExpression notIn(LiteralExpression literal) {
11251150 builder = boolQuery ().mustNot (termsQuery (getFieldReferenceForTermQuery (), collection ));
11261151 return this ;
11271152 }
1153+
1154+ @ Override
1155+ public QueryExpression equals (Object point , boolean isTimeStamp ) {
1156+ builder =
1157+ termQuery (getFieldReferenceForTermQuery (), convertEndpointValue (point , isTimeStamp ));
1158+ return this ;
1159+ }
1160+
1161+ @ Override
1162+ public QueryExpression between (Range <?> range , boolean isTimeStamp ) {
1163+ Object lowerBound =
1164+ range .hasLowerBound () ? convertEndpointValue (range .lowerEndpoint (), isTimeStamp ) : null ;
1165+ Object upperBound =
1166+ range .hasUpperBound () ? convertEndpointValue (range .upperEndpoint (), isTimeStamp ) : null ;
1167+ RangeQueryBuilder rangeQueryBuilder = rangeQuery (getFieldReference ());
1168+ rangeQueryBuilder =
1169+ range .lowerBoundType () == BoundType .CLOSED
1170+ ? rangeQueryBuilder .gte (lowerBound )
1171+ : rangeQueryBuilder .gt (lowerBound );
1172+ rangeQueryBuilder =
1173+ range .upperBoundType () == BoundType .CLOSED
1174+ ? rangeQueryBuilder .lte (upperBound )
1175+ : rangeQueryBuilder .lt (upperBound );
1176+ builder = rangeQueryBuilder ;
1177+ return this ;
1178+ }
1179+
1180+ private Object convertEndpointValue (Object value , boolean isTimeStamp ) {
1181+ value = (value instanceof NlsString nls ) ? nls .getValue () : value ;
1182+ return isTimeStamp ? timestampValueForPushDown (value .toString ()) : value ;
1183+ }
1184+ }
1185+
1186+ private static String timestampValueForPushDown (String value ) {
1187+ ExprTimestampValue exprTimestampValue = new ExprTimestampValue (value );
1188+ return DateFieldMapper .getDefaultDateTimeFormatter ()
1189+ .format (exprTimestampValue .timestampValue ());
1190+ // https://github.com/opensearch-project/sql/pull/3442
11281191 }
11291192
11301193 /**
@@ -1210,6 +1273,14 @@ ExprType getExprType() {
12101273 return type ;
12111274 }
12121275
1276+ boolean isTimeStampType () {
1277+ return type != null
1278+ && ExprCoreType .TIMESTAMP .equals (
1279+ type .getOriginalExprType () instanceof OpenSearchDataType osType
1280+ ? osType .getExprCoreType ()
1281+ : type .getOriginalExprType ());
1282+ }
1283+
12131284 boolean isTextType () {
12141285 return type != null && type .getOriginalExprType () instanceof OpenSearchTextType ;
12151286 }
@@ -1267,7 +1338,7 @@ Object value() {
12671338 } else if (isBoolean ()) {
12681339 return booleanValue ();
12691340 } else if (isTimestamp ()) {
1270- return timestampValueForPushDown ();
1341+ return timestampValueForPushDown (RexLiteral . stringValue ( literal ) );
12711342 } else if (isString ()) {
12721343 return RexLiteral .stringValue (literal );
12731344 } else {
@@ -1318,15 +1389,6 @@ String stringValue() {
13181389 return RexLiteral .stringValue (literal );
13191390 }
13201391
1321- String timestampValueForPushDown () {
1322- ExprTimestampValue exprTimestampValue =
1323- new ExprTimestampValue (RexLiteral .stringValue (literal ));
1324- return DateFieldMapper .getDefaultDateTimeFormatter ()
1325- .format (
1326- exprTimestampValue .timestampValue ()); // format using opensearch default formatter as
1327- // https://github.com/opensearch-project/sql/pull/3442
1328- }
1329-
13301392 List <Object > sargValue () {
13311393 final Sarg sarg = requireNonNull (literal .getValueAs (Sarg .class ), "Sarg" );
13321394 final RelDataType type = literal .getType ();
@@ -1376,7 +1438,7 @@ private static void checkForIncompatibleDateTimeOperands(RexCall call) {
13761438 || (SqlTypeFamily .TIMESTAMP .contains (op2 ) && !SqlTypeFamily .TIMESTAMP .contains (op1 ))
13771439 || (SqlTypeFamily .TIME .contains (op1 ) && !SqlTypeFamily .TIME .contains (op2 ))
13781440 || (SqlTypeFamily .TIME .contains (op2 ) && !SqlTypeFamily .TIME .contains (op1 ))) {
1379- throw new PredicateAnalyzerException (
1441+ throw new PredicateAnalyzer . PredicateAnalyzerException (
13801442 "Cannot handle " + call .getKind () + " expression for _id field, " + call );
13811443 }
13821444 }
0 commit comments