Skip to content

Commit 25840c5

Browse files
committed
Address comments
1 parent 3c73569 commit 25840c5

File tree

9 files changed

+286
-278
lines changed

9 files changed

+286
-278
lines changed

presto-clp/src/main/java/com/facebook/presto/plugin/clp/ClpExpression.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,26 @@
1818
import java.util.Optional;
1919

2020
/**
21-
* Represents the result of converting a Presto RowExpression into a CLP-compatible KQL query. In
22-
* every case, `pushDownExpression` represents the part of the RowExpression that could be
23-
* converted to a KQL expression, and `remainingExpression` represents the part that could not be
24-
* converted.
21+
* Represents the result of converting a Presto RowExpression into a CLP-compatible KQL query and
22+
* a SQL query for filtering splits by metadata database. In every case, `pushDownExpression`
23+
* represents the part of the RowExpression that could be converted to a KQL expression, and
24+
* `remainingExpression` represents the part that could not be converted.
2525
*/
2626
public class ClpExpression
2727
{
2828
// Optional KQL query or column name representing the fully or partially translatable part of the expression.
2929
private final Optional<String> pushDownExpression;
30+
3031
// Optional SQL string extracted from the query plan, which is only made of given metadata columns.
31-
private final Optional<String> metadataSql;
32+
private final Optional<String> metadataSqlQuery;
33+
3234
// The remaining (non-translatable) portion of the RowExpression, if any.
3335
private final Optional<RowExpression> remainingExpression;
3436

35-
public ClpExpression(String pushDownExpression, String metadataSql, RowExpression remainingExpression)
37+
public ClpExpression(String pushDownExpression, String metadataSqlQuery, RowExpression remainingExpression)
3638
{
3739
this.pushDownExpression = Optional.ofNullable(pushDownExpression);
38-
this.metadataSql = Optional.ofNullable(metadataSql);
40+
this.metadataSqlQuery = Optional.ofNullable(metadataSqlQuery);
3941
this.remainingExpression = Optional.ofNullable(remainingExpression);
4042
}
4143

@@ -58,14 +60,15 @@ public ClpExpression(String pushDownExpression)
5860
}
5961

6062
/**
61-
* Creates a ClpExpression from a fully translatable KQL string and a metadata SQL string.
63+
* Creates a ClpExpression from a fully translatable KQL string or column name, as well as a
64+
* metadata SQL string.
6265
*
6366
* @param pushDownExpression
64-
* @param metadataSql
67+
* @param metadataSqlQuery
6568
*/
66-
public ClpExpression(String pushDownExpression, String metadataSql)
69+
public ClpExpression(String pushDownExpression, String metadataSqlQuery)
6770
{
68-
this(pushDownExpression, metadataSql, null);
71+
this(pushDownExpression, metadataSqlQuery, null);
6972
}
7073

7174
/**
@@ -83,9 +86,9 @@ public Optional<String> getPushDownExpression()
8386
return pushDownExpression;
8487
}
8588

86-
public Optional<String> getMetadataSql()
89+
public Optional<String> getMetadataSqlQuery()
8790
{
88-
return metadataSql;
91+
return metadataSqlQuery;
8992
}
9093

9194
public Optional<RowExpression> getRemainingExpression()

presto-clp/src/main/java/com/facebook/presto/plugin/clp/ClpFilterToKqlConverter.java

Lines changed: 111 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
package com.facebook.presto.plugin.clp;
1515

1616
import com.facebook.presto.common.function.OperatorType;
17+
import com.facebook.presto.common.type.DecimalType;
1718
import com.facebook.presto.common.type.RowType;
1819
import com.facebook.presto.common.type.Type;
1920
import com.facebook.presto.common.type.VarcharType;
21+
import com.facebook.presto.plugin.clp.metadata.ClpSchemaTree;
2022
import com.facebook.presto.spi.ColumnHandle;
2123
import com.facebook.presto.spi.PrestoException;
2224
import com.facebook.presto.spi.function.FunctionHandle;
@@ -48,22 +50,27 @@
4850
import static com.facebook.presto.common.function.OperatorType.NEGATION;
4951
import static com.facebook.presto.common.function.OperatorType.NOT_EQUAL;
5052
import static com.facebook.presto.common.function.OperatorType.flip;
53+
import static com.facebook.presto.common.type.BigintType.BIGINT;
5154
import 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;
5260
import 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;
5561
import static com.facebook.presto.spi.relation.SpecialFormExpression.Form.AND;
5662
import static java.lang.Integer.parseInt;
5763
import static java.lang.String.format;
5864
import 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

Comments
 (0)