1414package com .facebook .presto .plugin .clp ;
1515
1616import com .facebook .presto .common .function .OperatorType ;
17+ import com .facebook .presto .common .type .DecimalType ;
1718import com .facebook .presto .common .type .RowType ;
1819import com .facebook .presto .common .type .Type ;
1920import com .facebook .presto .common .type .VarcharType ;
21+ import com .facebook .presto .plugin .clp .metadata .ClpSchemaTree ;
2022import com .facebook .presto .spi .ColumnHandle ;
2123import com .facebook .presto .spi .PrestoException ;
2224import com .facebook .presto .spi .function .FunctionHandle ;
4850import static com .facebook .presto .common .function .OperatorType .NEGATION ;
4951import static com .facebook .presto .common .function .OperatorType .NOT_EQUAL ;
5052import static com .facebook .presto .common .function .OperatorType .flip ;
53+ import static com .facebook .presto .common .type .BigintType .BIGINT ;
5154import static com .facebook .presto .common .type .BooleanType .BOOLEAN ;
55+ import static com .facebook .presto .common .type .DoubleType .DOUBLE ;
56+ import static com .facebook .presto .common .type .IntegerType .INTEGER ;
57+ import static com .facebook .presto .common .type .RealType .REAL ;
58+ import static com .facebook .presto .common .type .SmallintType .SMALLINT ;
59+ import static com .facebook .presto .common .type .TinyintType .TINYINT ;
5260import static com .facebook .presto .plugin .clp .ClpErrorCode .CLP_PUSHDOWN_UNSUPPORTED_EXPRESSION ;
53- import static com .facebook .presto .plugin .clp .ClpUtils .escapeKqlSpecialCharsForStringValue ;
54- import static com .facebook .presto .plugin .clp .ClpUtils .isNumericType ;
5561import static com .facebook .presto .spi .relation .SpecialFormExpression .Form .AND ;
5662import static java .lang .Integer .parseInt ;
5763import static java .lang .String .format ;
5864import static java .util .Objects .requireNonNull ;
5965
6066/**
6167 * A translator to translate Presto RowExpressions into KQL (Kibana Query Language) filters used as
62- * CLP queries. This is used primarily for pushing down supported filters to the CLP engine. This
63- * class implements the RowExpressionVisitor interface and recursively walks Presto filter
64- * expressions, attempting to convert supported expressions into corresponding KQL filter strings.
65- * Any part of the expression that cannot be translated is preserved as a "remaining expression" for
66- * potential fallback processing.
68+ * CLP queries and SQL filters used for filtering splits by metadata database. This is used
69+ * primarily for pushing down supported filters to the CLP engine. This class implements the
70+ * {@link RowExpressionVisitor} interface and recursively walks Presto filter expressions,
71+ * attempting to convert supported expressions into corresponding KQL filter strings and SQL
72+ * filter strings for metadata filtering. Any part of the expression that cannot be translated to
73+ * KQL is preserved as a "remaining expression" for potential fallback processing.
6774 * <p></p>
6875 * Supported translations for KQL include:
6976 * <ul>
@@ -135,54 +142,6 @@ public ClpExpression visitCall(CallExpression node, Void context)
135142 return new ClpExpression (node );
136143 }
137144
138- /**
139- * Handles the <code>BETWEEN</code> expression. All arguments must be of numeric types.
140- * <p></p>
141- * The translation is only performed if:
142- * - The first argument is a variable reference expression.
143- * - The second and third arguments are constant expressions.
144- *
145- * <p></p>
146- * Example: <code>col1 BETWEEN 0 AND 5</code> → <code>col1 >= 0 AND col1 <= 5</code>
147- *
148- * @param node the {@code BETWEEN} call expression
149- * @return a ClpExpression containing either the equivalent KQL query, or the original
150- * expression if it couldn't be translated
151- */
152- private ClpExpression handleBetween (CallExpression node )
153- {
154- List <RowExpression > arguments = node .getArguments ();
155- if (arguments .size () != 3 ) {
156- throw new PrestoException (CLP_PUSHDOWN_UNSUPPORTED_EXPRESSION ,
157- "BETWEEN operator must have exactly three arguments. Received: " + node );
158- }
159- RowExpression first = arguments .get (0 );
160- RowExpression second = arguments .get (1 );
161- RowExpression third = arguments .get (2 );
162- if (!(first instanceof VariableReferenceExpression )
163- || !(second instanceof ConstantExpression )
164- || !(third instanceof ConstantExpression )) {
165- return new ClpExpression (node );
166- }
167- if (!isNumericType (first .getType ())
168- || !isNumericType (second .getType ())
169- || !isNumericType (third .getType ())) {
170- return new ClpExpression (node );
171- }
172- Optional <String > variableOpt = first .accept (this , null ).getPushDownExpression ();
173- if (!variableOpt .isPresent ()) {
174- return new ClpExpression (node );
175- }
176- String variable = variableOpt .get ();
177- String lowerBound = getLiteralString ((ConstantExpression ) second );
178- String upperBound = getLiteralString ((ConstantExpression ) third );
179- String kql = String .format ("%s >= %s AND %s <= %s" , variable , lowerBound , variable , upperBound );
180- String metadataSql = metadataFilterColumns .contains (variable )
181- ? String .format ("\" %s\" >= %s AND \" %s\" <= %s" , variable , lowerBound , variable , upperBound )
182- : null ;
183- return new ClpExpression (kql , metadataSql );
184- }
185-
186145 @ Override
187146 public ClpExpression visitConstant (ConstantExpression node , Void context )
188147 {
@@ -246,6 +205,54 @@ private String getVariableName(VariableReferenceExpression variable)
246205 return ((ClpColumnHandle ) assignments .get (variable )).getOriginalColumnName ();
247206 }
248207
208+ /**
209+ * Handles the <code>BETWEEN</code> expression.
210+ * <p></p>
211+ * The translation is only performed if:
212+ * <ul>All arguments must have numeric types.</ul>
213+ * <ul>The first argument is a variable reference expression.</ul>
214+ * <ul>The second and third arguments are constant expressions.</ul>
215+ * <p></p>
216+ * Example: <code>col1 BETWEEN 0 AND 5</code> → <code>col1 >= 0 AND col1 <= 5</code>
217+ *
218+ * @param node the {@code BETWEEN} call expression
219+ * @return a ClpExpression containing either the equivalent KQL query, or the original
220+ * expression if it couldn't be translated
221+ */
222+ private ClpExpression handleBetween (CallExpression node )
223+ {
224+ List <RowExpression > arguments = node .getArguments ();
225+ if (arguments .size () != 3 ) {
226+ throw new PrestoException (CLP_PUSHDOWN_UNSUPPORTED_EXPRESSION ,
227+ "BETWEEN operator must have exactly three arguments. Received: " + node );
228+ }
229+ RowExpression first = arguments .get (0 );
230+ RowExpression second = arguments .get (1 );
231+ RowExpression third = arguments .get (2 );
232+ if (!(first instanceof VariableReferenceExpression )
233+ || !(second instanceof ConstantExpression )
234+ || !(third instanceof ConstantExpression )) {
235+ return new ClpExpression (node );
236+ }
237+ if (!isNumericType (first .getType ())
238+ || !isNumericType (second .getType ())
239+ || !isNumericType (third .getType ())) {
240+ return new ClpExpression (node );
241+ }
242+ Optional <String > variableOpt = first .accept (this , null ).getPushDownExpression ();
243+ if (!variableOpt .isPresent ()) {
244+ return new ClpExpression (node );
245+ }
246+ String variable = variableOpt .get ();
247+ String lowerBound = getLiteralString ((ConstantExpression ) second );
248+ String upperBound = getLiteralString ((ConstantExpression ) third );
249+ String kql = String .format ("%s >= %s AND %s <= %s" , variable , lowerBound , variable , upperBound );
250+ String metadataSqlQuery = metadataFilterColumns .contains (variable )
251+ ? String .format ("\" %s\" >= %s AND \" %s\" <= %s" , variable , lowerBound , variable , upperBound )
252+ : null ;
253+ return new ClpExpression (kql , metadataSqlQuery );
254+ }
255+
249256 /**
250257 * Handles the logical NOT expression.
251258 * <p></p>
@@ -268,8 +275,8 @@ private ClpExpression handleNot(CallExpression node)
268275 return new ClpExpression (node );
269276 }
270277 String notPushDownExpression = "NOT " + expression .getPushDownExpression ().get ();
271- if (expression .getMetadataSql ().isPresent ()) {
272- return new ClpExpression (notPushDownExpression , "NOT " + expression .getMetadataSql ());
278+ if (expression .getMetadataSqlQuery ().isPresent ()) {
279+ return new ClpExpression (notPushDownExpression , "NOT " + expression .getMetadataSqlQuery ());
273280 }
274281 else {
275282 return new ClpExpression (notPushDownExpression );
@@ -420,16 +427,16 @@ private ClpExpression buildClpExpression(
420427 Type literalType ,
421428 RowExpression originalNode )
422429 {
423- String metadataSql = null ;
430+ String metadataSqlQuery = null ;
424431 if (operator .equals (EQUAL )) {
425432 if (literalType instanceof VarcharType ) {
426433 return new ClpExpression (format ("%s: \" %s\" " , variableName , escapeKqlSpecialCharsForStringValue (literalString )));
427434 }
428435 else {
429436 if (metadataFilterColumns .contains (variableName )) {
430- metadataSql = format ("\" %s\" = %s" , variableName , literalString );
437+ metadataSqlQuery = format ("\" %s\" = %s" , variableName , literalString );
431438 }
432- return new ClpExpression (format ("%s: %s" , variableName , literalString ), metadataSql );
439+ return new ClpExpression (format ("%s: %s" , variableName , literalString ), metadataSqlQuery );
433440 }
434441 }
435442 else if (operator .equals (NOT_EQUAL )) {
@@ -438,16 +445,16 @@ else if (operator.equals(NOT_EQUAL)) {
438445 }
439446 else {
440447 if (metadataFilterColumns .contains (variableName )) {
441- metadataSql = format ("NOT \" %s\" = %s" , variableName , literalString );
448+ metadataSqlQuery = format ("NOT \" %s\" = %s" , variableName , literalString );
442449 }
443- return new ClpExpression (format ("NOT %s: %s" , variableName , literalString ), metadataSql );
450+ return new ClpExpression (format ("NOT %s: %s" , variableName , literalString ), metadataSqlQuery );
444451 }
445452 }
446453 else if (LOGICAL_BINARY_OPS_FILTER .contains (operator ) && !(literalType instanceof VarcharType )) {
447454 if (metadataFilterColumns .contains (variableName )) {
448- metadataSql = format ("\" %s\" %s %s" , variableName , operator .getOperator (), literalString );
455+ metadataSqlQuery = format ("\" %s\" %s %s" , variableName , operator .getOperator (), literalString );
449456 }
450- return new ClpExpression (format ("%s %s %s" , variableName , operator .getOperator (), literalString ), metadataSql );
457+ return new ClpExpression (format ("%s %s %s" , variableName , operator .getOperator (), literalString ), metadataSqlQuery );
451458 }
452459 return new ClpExpression (originalNode );
453460 }
@@ -661,9 +668,9 @@ private ClpExpression handleAnd(SpecialFormExpression node)
661668 hasPushDownExpression = true ;
662669 queryBuilder .append (expression .getPushDownExpression ().get ());
663670 queryBuilder .append (" AND " );
664- if (expression .getMetadataSql ().isPresent ()) {
671+ if (expression .getMetadataSqlQuery ().isPresent ()) {
665672 hasMetadataSql = true ;
666- metadataQueryBuilder .append (expression .getMetadataSql ().get ());
673+ metadataQueryBuilder .append (expression .getMetadataSqlQuery ().get ());
667674 metadataQueryBuilder .append (" AND " );
668675 }
669676 }
@@ -719,8 +726,8 @@ private ClpExpression handleOr(SpecialFormExpression node)
719726 }
720727 queryBuilder .append (expression .getPushDownExpression ().get ());
721728 queryBuilder .append (" OR " );
722- if (hasAllMetadataSql && expression .getMetadataSql ().isPresent ()) {
723- metadataQueryBuilder .append (expression .getMetadataSql ().get ());
729+ if (hasAllMetadataSql && expression .getMetadataSqlQuery ().isPresent ()) {
730+ metadataQueryBuilder .append (expression .getMetadataSqlQuery ().get ());
724731 metadataQueryBuilder .append (" OR " );
725732 }
726733 else {
@@ -858,6 +865,42 @@ private ClpExpression handleDereference(RowExpression expression)
858865 return new ClpExpression (baseString .getPushDownExpression ().get () + "." + fieldName );
859866 }
860867
868+ /**
869+ * Refer to
870+ * <a href="https://docs.yscope.com/clp/main/user-guide/reference-json-search-syntax">here
871+ * </a> for all special chars in the string value that need to be escaped.
872+ *
873+ * @param literalString the target string to escape special chars '\', '"', '?' and '*'
874+ * @return the escaped string
875+ */
876+ public static String escapeKqlSpecialCharsForStringValue (String literalString )
877+ {
878+ String escaped = literalString ;
879+ escaped = escaped .replace ("\\ " , "\\ \\ " );
880+ escaped = escaped .replace ("\" " , "\\ \" " );
881+ escaped = escaped .replace ("?" , "\\ ?" );
882+ escaped = escaped .replace ("*" , "\\ *" );
883+ return escaped ;
884+ }
885+
886+ /**
887+ * Check if the type is one of the numeric types that CLP will handle. Refer to
888+ * {@link ClpSchemaTree} for all types that CLP will handle.
889+ *
890+ * @param type the type to check
891+ * @return is the type numeric or not
892+ */
893+ public static boolean isNumericType (Type type )
894+ {
895+ return type .equals (BIGINT )
896+ || type .equals (INTEGER )
897+ || type .equals (SMALLINT )
898+ || type .equals (TINYINT )
899+ || type .equals (DOUBLE )
900+ || type .equals (REAL )
901+ || type instanceof DecimalType ;
902+ }
903+
861904 private static class SubstrInfo
862905 {
863906 String variableName ;
0 commit comments