Repository: prestodb/presto
Pull request #24970: [native] Support between filter for index join
Author: login: zacw7
Labels: name: from:Meta
description: PR from Meta
Created: 2025-04-24T06:04:08Z | Merged: 2025-04-27T05:10:55Z

```
== NO RELEASE NOTE ==
```




Diff:
diff --git a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp
index 6033e084fc963..b997cd4b6eb8f 100644
--- a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp
+++ b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp
@@ -73,10 +73,10 @@ std::string mapScalarFunction(const std::string& name) {
       {"presto.default.$operator$subscript",
        util::addDefaultNamespacePrefix(
            prestoDefaultNamespacePrefix, "subscript")},
-      {"presto.default.$operator$xx_hash_64", 
+      {"presto.default.$operator$xx_hash_64",
        util::addDefaultNamespacePrefix(
            prestoDefaultNamespacePrefix, "xxhash64_internal")},
-      {"presto.default.combine_hash", 
+      {"presto.default.combine_hash",
        util::addDefaultNamespacePrefix(
            prestoDefaultNamespacePrefix, "combine_hash_internal")},
       // Special form function overrides.
diff --git a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
index e790965934fd0..8b8c7db6cc4d3 100644
--- a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
+++ b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
@@ -479,7 +479,7 @@ namespace {
 bool equal(
     const std::shared_ptr<protocol::RowExpression>& actual,
     const protocol::VariableReferenceExpression& expected) {
-  if (auto variableReference =
+  if (const auto variableReference =
           std::dynamic_pointer_cast<protocol::VariableReferenceExpression>(
               actual)) {
     return (
@@ -489,12 +489,14 @@ bool equal(
   return false;
 }
 
+// Returns the cast function call if row 'expression' is the specified special
+// 'functionName'.
 std::shared_ptr<protocol::CallExpression> isFunctionCall(
     const std::shared_ptr<protocol::RowExpression>& expression,
     const std::string_view& functionName) {
   if (auto call =
           std::dynamic_pointer_cast<protocol::CallExpression>(expression)) {
-    if (auto builtin =
+    if (const auto builtin =
             std::dynamic_pointer_cast<protocol::BuiltInFunctionHandle>(
                 call->functionHandle)) {
       if (builtin->signature.kind == protocol::FunctionKind::SCALAR &&
@@ -506,8 +508,23 @@ std::shared_ptr<protocol::CallExpression> isFunctionCall(
   return nullptr;
 }
 
-/// Check if input RowExpression is a 'NOT x' expression and returns it as
-/// CallExpression. Returns nullptr if input expression is something else.
+// Returns the cast special form if row 'expression' is the specified special
+// 'form'.
+std::shared_ptr<protocol::SpecialFormExpression> isSpecialForm(
+    const std::shared_ptr<protocol::RowExpression>& expression,
+    const protocol::Form& form) {
+  if (const auto specialForm =
+          std::dynamic_pointer_cast<protocol::SpecialFormExpression>(
+              expression)) {
+    if (specialForm->form == form) {
+      return specialForm;
+    }
+  }
+  return nullptr;
+}
+
+// Check if input RowExpression is a 'NOT x' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
 std::shared_ptr<protocol::CallExpression> isNot(
     const std::shared_ptr<protocol::RowExpression>& expression) {
   static const std::string prestoDefaultNamespacePrefix =
@@ -517,17 +534,33 @@ std::shared_ptr<protocol::CallExpression> isNot(
   return isFunctionCall(expression, kNot);
 }
 
-/// Check if input RowExpression is an 'a > b' expression and returns it as
-/// CallExpression. Returns nullptr if input expression is something else.
+// Check if input RowExpression is an 'a > b' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
 std::shared_ptr<protocol::CallExpression> isGreaterThan(
     const std::shared_ptr<protocol::RowExpression>& expression) {
-  static const std::string_view kGreaterThan =
+  static constexpr std::string_view kGreaterThan =
       "presto.default.$operator$greater_than";
   return isFunctionCall(expression, kGreaterThan);
 }
 
-/// Checks if input PlanNode represents a local exchange with single source and
-/// returns it as ExchangeNode. Returns nullptr if input node is something else.
+// Check if input RowExpression is a 'between' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::CallExpression> isBetween(
+    const std::shared_ptr<protocol::RowExpression>& expression) {
+  static constexpr std::string_view kBetween =
+      "presto.default.$operator$between";
+  return isFunctionCall(expression, kBetween);
+}
+
+// Check if input RowExpression is an 'and' expression and returns it as
+// SpecialFormExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::SpecialFormExpression> isAnd(
+    const std::shared_ptr<protocol::RowExpression>& expression) {
+  return isSpecialForm(expression, protocol::Form::AND);
+}
+
+// Checks if input PlanNode represents a local exchange with single source and
+// returns it as ExchangeNode. Returns nullptr if input node is something else.
 std::shared_ptr<const protocol::ExchangeNode> isLocalSingleSourceExchange(
     const std::shared_ptr<const protocol::PlanNode>& node) {
   if (auto exchange =
@@ -541,14 +574,14 @@ std::shared_ptr<const protocol::ExchangeNode> isLocalSingleSourceExchange(
   return nullptr;
 }
 
-/// Checks if input PlanNode represents an identity projection and returns it as
-/// ProjectNode. Returns nullptr if input node is something else.
+// Checks if input PlanNode represents an identity projection and returns it as
+// ProjectNode. Returns nullptr if input node is something else.
 std::shared_ptr<const protocol::ProjectNode> isIdentityProjection(
     const std::shared_ptr<const protocol::PlanNode>& node) {
   if (auto project =
           std::dynamic_pointer_cast<const protocol::ProjectNode>(node)) {
-    for (auto entry : project->assignments.assignments) {
-      if (!equal(entry.second, entry.first)) {
+    for (const auto& [first, second] : project->assignments.assignments) {
+      if (!equal(second, first)) {
         return nullptr;
       }
     }
@@ -888,6 +921,44 @@ void VeloxQueryPlanConverterBase::toAggregations(
   }
 }
 
+void VeloxQueryPlanConverterBase::parseIndexLookupCondition(
+    const std::shared_ptr<protocol::RowExpression>& filter,
+    std::vector<core::IndexLookupConditionPtr>& joinConditionPtrs) {
+  if (const auto andForm = isAnd(filter)) {
+    VELOX_CHECK_EQ(andForm->arguments.size(), 2);
+    for (const auto& child : andForm->arguments) {
+      parseIndexLookupCondition(child, joinConditionPtrs);
+    }
+    return;
+  }
+
+  if (const auto between = isBetween(filter)) {
+    VELOX_CHECK_EQ(between->arguments.size(), 3);
+    const auto keyColumnExpr = exprConverter_.toVeloxExpr(
+        std::dynamic_pointer_cast<protocol::VariableReferenceExpression>(
+            between->arguments[0]));
+    VELOX_CHECK_NOT_NULL(
+        keyColumnExpr, "{}", toJsonString(between->arguments[0]));
+
+    const auto lowerExpr = exprConverter_.toVeloxExpr(between->arguments[1]);
+    const auto upperExpr = exprConverter_.toVeloxExpr(between->arguments[2]);
+
+    VELOX_CHECK(
+        !(core::TypedExprs::isConstant(lowerExpr) &&
+          core::TypedExprs::isConstant(upperExpr)),
+        "At least one of the between condition bounds needs to be not constant: {}",
+        toJsonString(filter));
+
+    joinConditionPtrs.push_back(
+        std::make_shared<core::BetweenIndexLookupCondition>(
+            keyColumnExpr, lowerExpr, upperExpr));
+    return;
+  }
+
+  VELOX_UNSUPPORTED(
+      "Unsupported index lookup condition: {}", toJsonString(filter));
+}
+
 std::shared_ptr<const core::ValuesNode>
 VeloxQueryPlanConverterBase::toVeloxQueryPlan(
     const std::shared_ptr<const protocol::ValuesNode>& node,
@@ -1220,12 +1291,17 @@ VeloxQueryPlanConverterBase::toVeloxQueryPlan(
     auto left = toVeloxQueryPlan(node->probeSource, tableWriteInfo, taskId);
     auto right = toVeloxQueryPlan(node->indexSource, tableWriteInfo, taskId);
 
+    std::vector<core::IndexLookupConditionPtr> joinConditionPtrs{};
+    if (node->filter) {
+      parseIndexLookupCondition(*node->filter, joinConditionPtrs);
+    }
+
     return std::make_shared<core::IndexLookupJoinNode>(
         node->id,
         core::JoinType::kInner,
         /*leftKeys=*/leftKeys,
         /*rightKeys=*/rightKeys,
-        /*joinConditions=*/std::vector<velox::core::IndexLookupConditionPtr>{},
+        /*joinConditions=*/joinConditionPtrs,
         /*left=*/left,
         /*right=*/std::dynamic_pointer_cast<const core::TableScanNode>(right),
         /*outputType=*/type::concatRowTypes({left->outputType(), right->outputType()}));
diff --git a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.h b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.h
index 976c2146a89f8..ff48ca9e11384 100644
--- a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.h
+++ b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.h
@@ -227,6 +227,10 @@ class VeloxQueryPlanConverterBase {
       std::vector<velox::core::AggregationNode::Aggregate>& aggregates,
       std::vector<std::string>& aggregateNames);
 
+  void parseIndexLookupCondition(
+      const std::shared_ptr<protocol::RowExpression>& filter,
+      std::vector<velox::core::IndexLookupConditionPtr>& joinConditionPtrs);
+
   velox::memory::MemoryPool* const pool_;
   velox::core::QueryCtx* const queryCtx_;
   VeloxExprConverter exprConverter_;


Reviews:
user: login: xiaoxmeng
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T16:45:54Z
user: login: zacw7
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T01:02:22Z
user: login: zacw7
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T01:08:55Z
user: login: xiaoxmeng
body: @zacw7 LGTM % minors. Thanks!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-26T18:49:06Z
user: login: zacw7
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T22:18:25Z
user: login: xiaoxmeng
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T05:10:48Z

Review comments:
diff_hunk: @@ -526,6 +526,15 @@ std::shared_ptr<protocol::CallExpression> isGreaterThan(
   return isFunctionCall(expression, kGreaterThan);
 }
 
+/// Check if input RowExpression is a 'between' expression and returns it as
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: // for non-public comments.
created_at: 2025-04-24T16:41:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -526,6 +526,15 @@ std::shared_ptr<protocol::CallExpression> isGreaterThan(
   return isFunctionCall(expression, kGreaterThan);
 }
 
+/// Check if input RowExpression is a 'between' expression and returns it as
+/// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::CallExpression> isBetween(
+  const std::shared_ptr<protocol::RowExpression>& expression) {
+static const std::string_view kBetween =
+    "presto.default.$operator$between";
+return isFunctionCall(expression, kBetween);
+}
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: This is in anonymous namespace?
created_at: 2025-04-24T16:42:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1220,12 +1229,26 @@ VeloxQueryPlanConverterBase::toVeloxQueryPlan(
     auto left = toVeloxQueryPlan(node->probeSource, tableWriteInfo, taskId);
     auto right = toVeloxQueryPlan(node->indexSource, tableWriteInfo, taskId);
 
+    std::vector<core::IndexLookupConditionPtr> joinConditionPtrs{};
+    if (node->filter) {
+      if (const auto between = isBetween(*node->filter)) {
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: We could have more than on filter? Have you tried more than one between conditions? Thanks!
created_at: 2025-04-24T16:44:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1220,12 +1229,26 @@ VeloxQueryPlanConverterBase::toVeloxQueryPlan(
     auto left = toVeloxQueryPlan(node->probeSource, tableWriteInfo, taskId);
     auto right = toVeloxQueryPlan(node->indexSource, tableWriteInfo, taskId);
 
+    std::vector<core::IndexLookupConditionPtr> joinConditionPtrs{};
+    if (node->filter) {
+      if (const auto between = isBetween(*node->filter)) {
+        VELOX_CHECK_EQ(between->arguments.size(), 3);
+        const auto keyColumnExpr = exprConverter_.toVeloxExpr(std::dynamic_pointer_cast<protocol::VariableReferenceExpression>(between->arguments[0]));
+        VELOX_CHECK_NOT_NULL(keyColumnExpr, "{}", toJsonString(between->arguments[0]));
+
+        const auto lowerExpr = exprConverter_.toVeloxExpr(between->arguments[1]);
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: Have you check if they are constants or not? Thanks!
created_at: 2025-04-24T16:45:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -526,6 +526,15 @@ std::shared_ptr<protocol::CallExpression> isGreaterThan(
   return isFunctionCall(expression, kGreaterThan);
 }
 
+/// Check if input RowExpression is a 'between' expression and returns it as
+/// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::CallExpression> isBetween(
+  const std::shared_ptr<protocol::RowExpression>& expression) {
+static const std::string_view kBetween =
+    "presto.default.$operator$between";
+return isFunctionCall(expression, kBetween);
+}
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: zacw7
body: Yes. same as the existing ones.
created_at: 2025-04-26T01:02:22Z
author_association: MEMBER
diff_hunk: @@ -1220,12 +1229,26 @@ VeloxQueryPlanConverterBase::toVeloxQueryPlan(
     auto left = toVeloxQueryPlan(node->probeSource, tableWriteInfo, taskId);
     auto right = toVeloxQueryPlan(node->indexSource, tableWriteInfo, taskId);
 
+    std::vector<core::IndexLookupConditionPtr> joinConditionPtrs{};
+    if (node->filter) {
+      if (const auto between = isBetween(*node->filter)) {
+        VELOX_CHECK_EQ(between->arguments.size(), 3);
+        const auto keyColumnExpr = exprConverter_.toVeloxExpr(std::dynamic_pointer_cast<protocol::VariableReferenceExpression>(between->arguments[0]));
+        VELOX_CHECK_NOT_NULL(keyColumnExpr, "{}", toJsonString(between->arguments[0]));
+
+        const auto lowerExpr = exprConverter_.toVeloxExpr(between->arguments[1]);
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: zacw7
body: `exprConverter_.toVeloxExpr()` can handle constant expression right? do we need special handling here?
created_at: 2025-04-26T01:08:55Z
author_association: MEMBER
diff_hunk: @@ -506,8 +506,21 @@ std::shared_ptr<protocol::CallExpression> isFunctionCall(
   return nullptr;
 }
 
-/// Check if input RowExpression is a 'NOT x' expression and returns it as
-/// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::SpecialFormExpression> isSpecialForm(
+    const std::shared_ptr<protocol::RowExpression>& expression,
+    const protocol::Form form) {
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: const reference?
created_at: 2025-04-26T18:38:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -517,17 +530,33 @@ std::shared_ptr<protocol::CallExpression> isNot(
   return isFunctionCall(expression, kNot);
 }
 
-/// Check if input RowExpression is an 'a > b' expression and returns it as
-/// CallExpression. Returns nullptr if input expression is something else.
+// Check if input RowExpression is an 'a > b' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
 std::shared_ptr<protocol::CallExpression> isGreaterThan(
     const std::shared_ptr<protocol::RowExpression>& expression) {
-  static const std::string_view kGreaterThan =
+  static constexpr std::string_view kGreaterThan =
       "presto.default.$operator$greater_than";
   return isFunctionCall(expression, kGreaterThan);
 }
 
-/// Checks if input PlanNode represents a local exchange with single source and
-/// returns it as ExchangeNode. Returns nullptr if input node is something else.
+// Check if input RowExpression is a 'between' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::CallExpression> isBetween(
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: ```
s/isBetween/isBetweenFunctionCall/
```
created_at: 2025-04-26T18:43:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -517,17 +530,33 @@ std::shared_ptr<protocol::CallExpression> isNot(
   return isFunctionCall(expression, kNot);
 }
 
-/// Check if input RowExpression is an 'a > b' expression and returns it as
-/// CallExpression. Returns nullptr if input expression is something else.
+// Check if input RowExpression is an 'a > b' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
 std::shared_ptr<protocol::CallExpression> isGreaterThan(
     const std::shared_ptr<protocol::RowExpression>& expression) {
-  static const std::string_view kGreaterThan =
+  static constexpr std::string_view kGreaterThan =
       "presto.default.$operator$greater_than";
   return isFunctionCall(expression, kGreaterThan);
 }
 
-/// Checks if input PlanNode represents a local exchange with single source and
-/// returns it as ExchangeNode. Returns nullptr if input node is something else.
+// Check if input RowExpression is a 'between' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::CallExpression> isBetween(
+    const std::shared_ptr<protocol::RowExpression>& expression) {
+  static constexpr std::string_view kBetween =
+      "presto.default.$operator$between";
+  return isFunctionCall(expression, kBetween);
+}
+
+// Check if input RowExpression is an 'and' expression and returns it as
+// SpecialFormExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::SpecialFormExpression> isAnd(
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: ```
s/isAnd/isAndFunctionCall/
```
created_at: 2025-04-26T18:43:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -506,8 +506,21 @@ std::shared_ptr<protocol::CallExpression> isFunctionCall(
   return nullptr;
 }
 
-/// Check if input RowExpression is a 'NOT x' expression and returns it as
-/// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::SpecialFormExpression> isSpecialForm(
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: ```
// Returns the cast special form if row 'expression' is the specified special 'form'.
```
created_at: 2025-04-26T18:46:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -888,6 +917,38 @@ void VeloxQueryPlanConverterBase::toAggregations(
   }
 }
 
+void VeloxQueryPlanConverterBase::parseIndexLookupCondition(
+    const std::shared_ptr<protocol::RowExpression>& filter,
+    std::vector<core::IndexLookupConditionPtr>& joinConditionPtrs) {
+  if (auto andForm = isAnd(filter)) {
+    VELOX_CHECK_EQ(andForm->arguments.size(), 2);
+    for (const auto& child : andForm->arguments) {
+      parseIndexLookupCondition(child, joinConditionPtrs);
+    }
+    return;
+  }
+
+  if (auto between = isBetween(filter)) {
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: const auto between
created_at: 2025-04-26T18:47:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -888,6 +917,38 @@ void VeloxQueryPlanConverterBase::toAggregations(
   }
 }
 
+void VeloxQueryPlanConverterBase::parseIndexLookupCondition(
+    const std::shared_ptr<protocol::RowExpression>& filter,
+    std::vector<core::IndexLookupConditionPtr>& joinConditionPtrs) {
+  if (auto andForm = isAnd(filter)) {
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: const auto andForm
created_at: 2025-04-26T18:47:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -888,6 +917,38 @@ void VeloxQueryPlanConverterBase::toAggregations(
   }
 }
 
+void VeloxQueryPlanConverterBase::parseIndexLookupCondition(
+    const std::shared_ptr<protocol::RowExpression>& filter,
+    std::vector<core::IndexLookupConditionPtr>& joinConditionPtrs) {
+  if (auto andForm = isAnd(filter)) {
+    VELOX_CHECK_EQ(andForm->arguments.size(), 2);
+    for (const auto& child : andForm->arguments) {
+      parseIndexLookupCondition(child, joinConditionPtrs);
+    }
+    return;
+  }
+
+  if (auto between = isBetween(filter)) {
+    VELOX_CHECK_EQ(between->arguments.size(), 3);
+    const auto keyColumnExpr = exprConverter_.toVeloxExpr(
+        std::dynamic_pointer_cast<protocol::VariableReferenceExpression>(
+            between->arguments[0]));
+    VELOX_CHECK_NOT_NULL(
+        keyColumnExpr, "{}", toJsonString(between->arguments[0]));
+
+    const auto lowerExpr = exprConverter_.toVeloxExpr(between->arguments[1]);
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: xiaoxmeng
body: We need to check here at least one of the expression are not const? Thanks!
created_at: 2025-04-26T18:48:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -517,17 +530,33 @@ std::shared_ptr<protocol::CallExpression> isNot(
   return isFunctionCall(expression, kNot);
 }
 
-/// Check if input RowExpression is an 'a > b' expression and returns it as
-/// CallExpression. Returns nullptr if input expression is something else.
+// Check if input RowExpression is an 'a > b' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
 std::shared_ptr<protocol::CallExpression> isGreaterThan(
     const std::shared_ptr<protocol::RowExpression>& expression) {
-  static const std::string_view kGreaterThan =
+  static constexpr std::string_view kGreaterThan =
       "presto.default.$operator$greater_than";
   return isFunctionCall(expression, kGreaterThan);
 }
 
-/// Checks if input PlanNode represents a local exchange with single source and
-/// returns it as ExchangeNode. Returns nullptr if input node is something else.
+// Check if input RowExpression is a 'between' expression and returns it as
+// CallExpression. Returns nullptr if input expression is something else.
+std::shared_ptr<protocol::CallExpression> isBetween(
path: presto-native-execution/presto_cpp/main/types/PrestoToVeloxQueryPlan.cpp
user: login: zacw7
body: Let's keep the naming consistent with the existing ones? like `isNot`, `isGreaterThan`.
created_at: 2025-04-26T22:18:25Z
author_association: MEMBER

PR comments:
user: login: aditi-pandit
created_at: 2025-04-25T00:27:56Z
body: @zacw7, @xiaoxmeng : Is it possible to have a test for this code ?
author_association: CONTRIBUTOR
user: login: zacw7
created_at: 2025-04-26T00:48:05Z
body: > @zacw7, @xiaoxmeng : Is it possible to have a test for this code ?

I couldn't find the test for this file. Is there any pointer that I can refer to?
author_association: MEMBER