/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sargs/SearchArgument.hh"
#include "wrap/gtest-wrapper.h"

#include <unordered_set>

namespace orc {

  TEST(TestSearchArgument, literalTest) {
    Literal literal0(static_cast<int64_t>(1234));
    EXPECT_EQ(PredicateDataType::LONG, literal0.getType());
    EXPECT_TRUE("1234" == literal0.toString());

    Literal literal1(0.1234);
    EXPECT_EQ(PredicateDataType::FLOAT, literal1.getType());
    EXPECT_TRUE("0.1234" == literal1.toString());

    Literal literal2(false);
    EXPECT_EQ(PredicateDataType::BOOLEAN, literal2.getType());
    EXPECT_TRUE("false" == literal2.toString());

    Literal literal3(static_cast<int64_t>(123456), 123456789);
    EXPECT_EQ(PredicateDataType::TIMESTAMP, literal3.getType());
    EXPECT_TRUE("123456.123456789" == literal3.toString());
    EXPECT_EQ(123456123, literal3.getTimestamp().getMillis());

    Literal literal4(Int128(54321), 6, 2);
    EXPECT_EQ(PredicateDataType::DECIMAL, literal4.getType());
    EXPECT_TRUE("543.21" == literal4.toString());

    Literal literal5("test", 4);
    EXPECT_EQ(PredicateDataType::STRING, literal5.getType());
    EXPECT_TRUE("test" == literal5.toString());

    Literal left(static_cast<int64_t>(123)), right(static_cast<int64_t>(123));
    EXPECT_TRUE(left == right);
    EXPECT_FALSE(left != right);
    Literal left2(static_cast<int64_t>(321));
    EXPECT_TRUE(left2 != right);
    EXPECT_FALSE(left2 == right);
  }

  TEST(TestSearchArgument, predicateLeafTest) {
    PredicateLeaf leaf1(
            PredicateLeaf::Operator::EQUALS,
            PredicateDataType::LONG,
            "id",
            Literal(static_cast<int64_t>(1234)));
    EXPECT_EQ(PredicateLeaf::Operator::EQUALS, leaf1.getOperator());
    EXPECT_EQ(PredicateDataType::LONG, leaf1.getType());
    EXPECT_EQ("id", leaf1.getColumnName());
    EXPECT_EQ("(id = 1234)", leaf1.toString());

    const char * str1 = "aaa", * str2 = "zzz";
    PredicateLeaf leaf2(
            PredicateLeaf::Operator::BETWEEN,
            PredicateDataType::STRING,
            "name",
            { Literal(str1, 3), Literal(str2, 3)});
    EXPECT_EQ(PredicateLeaf::Operator::BETWEEN, leaf2.getOperator());
    EXPECT_EQ(PredicateDataType::STRING, leaf2.getType());
    EXPECT_EQ("name", leaf2.getColumnName());
    EXPECT_EQ("(name between [aaa, zzz])", leaf2.toString());

    PredicateLeaf leaf3(leaf2);
    EXPECT_TRUE(leaf2 == leaf3);
    EXPECT_FALSE(leaf1 == leaf3);

    PredicateLeaf leaf4(
            PredicateLeaf::Operator::LESS_THAN_EQUALS,
            PredicateDataType::DECIMAL,
            "sales",
            { Literal(111222, 6, 3) });
    EXPECT_EQ(PredicateLeaf::Operator::LESS_THAN_EQUALS, leaf4.getOperator());
    EXPECT_EQ(PredicateDataType::DECIMAL, leaf4.getType());
    EXPECT_EQ("(sales <= 111.222)", leaf4.toString());
  }

  TEST(TestSearchArgument, truthValueTest) {
    EXPECT_EQ(TruthValue::YES, TruthValue::YES && TruthValue::YES);
    EXPECT_EQ(TruthValue::NO, TruthValue::YES && TruthValue::NO);
    EXPECT_EQ(TruthValue::IS_NULL, TruthValue::YES && TruthValue::IS_NULL);
    EXPECT_EQ(TruthValue::YES_NULL, TruthValue::YES && TruthValue::YES_NULL);

    EXPECT_EQ(TruthValue::NO, TruthValue::NO || TruthValue::NO);
    EXPECT_EQ(TruthValue::YES, TruthValue::YES || TruthValue::NO);

    EXPECT_EQ(TruthValue::YES, !TruthValue::NO);
  }

  static TreeNode _not(TreeNode arg) {
    return std::make_shared<ExpressionTree>(ExpressionTree::Operator::NOT,
      NodeList{arg});
  }

  static TreeNode _and(NodeList args) {
    return std::make_shared<ExpressionTree>(ExpressionTree::Operator::AND, args);
  }

  static TreeNode _and(TreeNode l, TreeNode r) {
    return std::make_shared<ExpressionTree>(
      ExpressionTree::Operator::AND, NodeList{l, r});
  }

  static TreeNode _or(NodeList args) {
    return std::make_shared<ExpressionTree>(ExpressionTree::Operator::OR, args);
  }

  static TreeNode _or(TreeNode l, TreeNode r) {
    return std::make_shared<ExpressionTree>(
      ExpressionTree::Operator::OR, NodeList{l, r});
  }

  static TreeNode _leaf(int leaf) {
    return std::make_shared<ExpressionTree>(leaf);
  }

  static TreeNode _constant(TruthValue val) {
    return std::make_shared<ExpressionTree>(val);
  }

  TEST(TestSearchArgument, testNotPushdown) {
    EXPECT_EQ("leaf-1",
              SearchArgumentBuilderImpl::pushDownNot(_leaf(1))->toString());
    EXPECT_EQ("(not leaf-1)",
              SearchArgumentBuilderImpl::pushDownNot(_not(_leaf(1)))->toString());
    EXPECT_EQ("leaf-1",
              SearchArgumentBuilderImpl::pushDownNot(_not(_not(_leaf(1))))
                ->toString());
    EXPECT_EQ("(not leaf-1)",
              SearchArgumentBuilderImpl::pushDownNot(_not(_not(_not(_leaf(1)))))->
                toString());
    EXPECT_EQ("(or leaf-1 (not leaf-2))",
              SearchArgumentBuilderImpl::pushDownNot(_not(_and(_not(_leaf(1)),
                                                             _leaf(2))))->toString());
    EXPECT_EQ("(and (not leaf-1) leaf-2)",
              SearchArgumentBuilderImpl::pushDownNot(_not(_or(_leaf(1),
                                                            _not(_leaf(2)))))->toString());
    EXPECT_EQ("(or (or (not leaf-1) leaf-2) leaf-3)",
              SearchArgumentBuilderImpl::pushDownNot(_or(_not(_and(_leaf(1),
                                                                 _not(_leaf(2)))),
                                                       _not(_not(_leaf(3)))))->toString());
    EXPECT_EQ("NO", SearchArgumentBuilderImpl::pushDownNot(
      _not(_constant(TruthValue::YES)))->toString());
    EXPECT_EQ("YES", SearchArgumentBuilderImpl::pushDownNot(
      _not(_constant(TruthValue::NO)))->toString());
    EXPECT_EQ("IS_NULL", SearchArgumentBuilderImpl::pushDownNot(
      _not(_constant(TruthValue::IS_NULL)))->toString());
    EXPECT_EQ("YES_NO", SearchArgumentBuilderImpl::pushDownNot(
      _not(_constant(TruthValue::YES_NO)))->toString());
    EXPECT_EQ("YES_NULL", SearchArgumentBuilderImpl::pushDownNot(
      _not(_constant(TruthValue::NO_NULL)))->toString());
    EXPECT_EQ("NO_NULL", SearchArgumentBuilderImpl::pushDownNot(
      _not(_constant(TruthValue::YES_NULL)))->toString());
    EXPECT_EQ("YES_NO_NULL", SearchArgumentBuilderImpl::pushDownNot(
      _not(_constant(TruthValue::YES_NO_NULL)))->toString());
  }

  TEST(TestSearchArgument, testFlatten) {
    EXPECT_EQ("leaf-1",
              SearchArgumentBuilderImpl::flatten(_leaf(1))->toString());
    EXPECT_EQ("NO",
              SearchArgumentBuilderImpl::flatten(_constant(TruthValue::NO))->toString());
    EXPECT_EQ("(not (not leaf-1))",
              SearchArgumentBuilderImpl::flatten(_not(_not(_leaf(1))))->toString());
    EXPECT_EQ("(and leaf-1 leaf-2)",
              SearchArgumentBuilderImpl::flatten(_and(_leaf(1), _leaf(2)))->toString());
    EXPECT_EQ("(and (or leaf-1 leaf-2) leaf-3)",
              SearchArgumentBuilderImpl::flatten(_and(_or(_leaf(1), _leaf(2)), _leaf(3))
              )->toString());
    EXPECT_EQ("(and leaf-1 leaf-2 leaf-3 leaf-4)",
              SearchArgumentBuilderImpl::flatten(_and(_and(_leaf(1), _leaf(2)),
                                                    _and(_leaf(3), _leaf(4))))->toString());
    EXPECT_EQ("(or leaf-1 leaf-2 leaf-3 leaf-4)",
              SearchArgumentBuilderImpl::flatten(
                _or(_leaf(1), _or(_leaf(2), _or(_leaf(3), _leaf(4)))))->toString());
    EXPECT_EQ("(or leaf-1 leaf-2 leaf-3 leaf-4)",
              SearchArgumentBuilderImpl::flatten(
                _or(_or(_or(_leaf(1), _leaf(2)), _leaf(3)), _leaf(4)))->toString());
    EXPECT_EQ("(or leaf-1 leaf-2 leaf-3 leaf-4 leaf-5 leaf-6)",
              SearchArgumentBuilderImpl::flatten(
                _or(_or(_leaf(1), _or(_leaf(2), _leaf(3))), _or(_or(_leaf(4), _leaf(5)), _leaf(6))))->toString());
    EXPECT_EQ("(and (not leaf-1) leaf-2 (not leaf-3) leaf-4 (not leaf-5) leaf-6)",
              SearchArgumentBuilderImpl::flatten(_and(_and(_not(_leaf(1)),
                _and(_leaf(2), _not(_leaf(3)))), _and(_and(_leaf(4), _not(_leaf(5))), _leaf(6)))
              )->toString());
    EXPECT_EQ("(not (and leaf-1 leaf-2 leaf-3))",
              SearchArgumentBuilderImpl::flatten(_not(_and(_leaf(1), _and(_leaf(2), _leaf(3))))
              )->toString());
  }

  TEST(TestSearchArgument, testFoldMaybe) {
    EXPECT_EQ("(and leaf-1)",
              SearchArgumentBuilderImpl::foldMaybe(
                _and(_leaf(1), _constant(TruthValue::YES_NO_NULL)))->toString());
    EXPECT_EQ("(and leaf-1 leaf-2)",
              SearchArgumentBuilderImpl::foldMaybe(
                _and(NodeList{_leaf(1),
                              _constant(TruthValue::YES_NO_NULL),
                              _leaf(2)
                }))->toString());
    EXPECT_EQ("(and leaf-1 leaf-2)",
              SearchArgumentBuilderImpl::
              foldMaybe(_and(NodeList {_constant(TruthValue::YES_NO_NULL),
                             _leaf(1),
                             _leaf(2),
                             _constant(TruthValue::YES_NO_NULL)
              }))->toString());
    EXPECT_EQ("YES_NO_NULL",
              SearchArgumentBuilderImpl::
              foldMaybe(_and(_constant(TruthValue::YES_NO_NULL),
                             _constant(TruthValue::YES_NO_NULL)))->toString());
    EXPECT_EQ("YES_NO_NULL",
              SearchArgumentBuilderImpl::
              foldMaybe(_or(_leaf(1),
                            _constant(TruthValue::YES_NO_NULL)))->toString());
    EXPECT_EQ("(or leaf-1 (and leaf-2))",
      SearchArgumentBuilderImpl::foldMaybe(
        _or(_leaf(1),
          _and(_leaf(2),
            _constant(TruthValue::YES_NO_NULL))))->toString());
    EXPECT_EQ("(and leaf-1)",SearchArgumentBuilderImpl::foldMaybe(
      _and(_or(_leaf(2),
        _constant(TruthValue::YES_NO_NULL)),
          _leaf(1)))->toString());
    EXPECT_EQ("(and leaf-100)",
      SearchArgumentBuilderImpl::foldMaybe(
        SearchArgumentBuilderImpl::convertToCNF(_and(_leaf(100),
        _or(NodeList { _and(_leaf(0), _leaf(1)),
                       _and(_leaf(2), _leaf(3)),
                       _and(_leaf(4), _leaf(5)),
                       _and(_leaf(6), _leaf(7)),
                       _and(_leaf(8), _leaf(9)),
                       _and(_leaf(10), _leaf(11)),
                       _and(_leaf(12), _leaf(13)),
                       _and(_leaf(14), _leaf(15)),
                       _and(_leaf(16), _leaf(17))}))))->toString());
  }

  static void assertNoSharedNodes(TreeNode tree,
                                  std::unordered_set<TreeNode>& seen)  {
    if (seen.find(tree) != seen.end() &&
        tree->getOperator() != ExpressionTree::Operator::LEAF) {
      throw std::runtime_error(
        "repeated node in expression " + tree->toString());
    }
    seen.insert(tree);
    for (auto& child : tree->getChildren()) {
      assertNoSharedNodes(child, seen);
    }
  }

  TEST(TestSearchArgument, testCNF) {
    EXPECT_EQ("leaf-1",
      SearchArgumentBuilderImpl::convertToCNF(_leaf(1))->toString());
    EXPECT_EQ("NO",SearchArgumentBuilderImpl::convertToCNF(
      _constant(TruthValue::NO))->toString());
    EXPECT_EQ("(not leaf-1)", SearchArgumentBuilderImpl::convertToCNF(
      _not(_leaf(1)))->toString());
    EXPECT_EQ("(and leaf-1 leaf-2)", SearchArgumentBuilderImpl::
      convertToCNF(_and(_leaf(1), _leaf(2)))->toString());
    EXPECT_EQ("(or (not leaf-1) leaf-2)",SearchArgumentBuilderImpl::
      convertToCNF(_or(_not(_leaf(1)), _leaf(2)))->toString());
    EXPECT_EQ("(and (or leaf-1 leaf-2) (not leaf-3))",
                 SearchArgumentBuilderImpl::convertToCNF(
                 _and(_or(_leaf(1), _leaf(2)), _not(_leaf(3))))->toString());
    EXPECT_EQ("(and (or leaf-1 leaf-3) (or leaf-2 leaf-3)"
                 " (or leaf-1 leaf-4) (or leaf-2 leaf-4))",
                 SearchArgumentBuilderImpl::convertToCNF(_or(
                     _and(_leaf(1), _leaf(2)),
                     _and(_leaf(3), _leaf(4))))->toString());
    EXPECT_EQ("(and"
                 " (or leaf-1 leaf-5) (or leaf-2 leaf-5)"
                 " (or leaf-3 leaf-5) (or leaf-4 leaf-5)"
                 " (or leaf-1 leaf-6) (or leaf-2 leaf-6)"
                 " (or leaf-3 leaf-6) (or leaf-4 leaf-6))",
              SearchArgumentBuilderImpl::convertToCNF(
                _or(_and(NodeList{_leaf(1), _leaf(2), _leaf(3), _leaf(4)}),
                  _and(_leaf(5), _leaf(6)))
              )->toString());
    EXPECT_EQ("(and"
                 " (or leaf-5 leaf-6 (not leaf-7) leaf-1 leaf-3)"
                 " (or leaf-5 leaf-6 (not leaf-7) leaf-2 leaf-3)"
                 " (or leaf-5 leaf-6 (not leaf-7) leaf-1 leaf-4)"
                 " (or leaf-5 leaf-6 (not leaf-7) leaf-2 leaf-4))",
                 SearchArgumentBuilderImpl::convertToCNF(
                   _or(NodeList{_and(_leaf(1), _leaf(2)),
                                _and(_leaf(3), _leaf(4)),
                                _or(_leaf(5), _leaf(6)),
                                _not(_leaf(7))
                   }))->toString());
    EXPECT_EQ("(and"
                 " (or leaf-8 leaf-0 leaf-3 leaf-6)"
                 " (or leaf-8 leaf-1 leaf-3 leaf-6)"
                 " (or leaf-8 leaf-2 leaf-3 leaf-6)"
                 " (or leaf-8 leaf-0 leaf-4 leaf-6)"
                 " (or leaf-8 leaf-1 leaf-4 leaf-6)"
                 " (or leaf-8 leaf-2 leaf-4 leaf-6)"
                 " (or leaf-8 leaf-0 leaf-5 leaf-6)"
                 " (or leaf-8 leaf-1 leaf-5 leaf-6)"
                 " (or leaf-8 leaf-2 leaf-5 leaf-6)"
                 " (or leaf-8 leaf-0 leaf-3 leaf-7)"
                 " (or leaf-8 leaf-1 leaf-3 leaf-7)"
                 " (or leaf-8 leaf-2 leaf-3 leaf-7)"
                 " (or leaf-8 leaf-0 leaf-4 leaf-7)"
                 " (or leaf-8 leaf-1 leaf-4 leaf-7)"
                 " (or leaf-8 leaf-2 leaf-4 leaf-7)"
                 " (or leaf-8 leaf-0 leaf-5 leaf-7)"
                 " (or leaf-8 leaf-1 leaf-5 leaf-7)"
                 " (or leaf-8 leaf-2 leaf-5 leaf-7))",
                 SearchArgumentBuilderImpl::convertToCNF(_or(NodeList {
                     _and(NodeList {_leaf(0), _leaf(1), _leaf(2)}),
                     _and(NodeList {_leaf(3), _leaf(4), _leaf(5)}),
                     _and(_leaf(6), _leaf(7)),
                     _leaf(8)})
                     )->toString());
    EXPECT_EQ("YES_NO_NULL", SearchArgumentBuilderImpl::convertToCNF(
      _or(NodeList {_and(_leaf(0), _leaf(1)),
                    _and(_leaf(2), _leaf(3)),
                    _and(_leaf(4), _leaf(5)),
                    _and(_leaf(6), _leaf(7)),
                    _and(_leaf(8), _leaf(9)),
                    _and(_leaf(10), _leaf(11)),
                    _and(_leaf(12), _leaf(13)),
                    _and(_leaf(14), _leaf(15)),
                    _and(_leaf(16), _leaf(17))}))->toString());
    EXPECT_EQ("(and leaf-100 YES_NO_NULL)",
              SearchArgumentBuilderImpl::convertToCNF(
                _and(_leaf(100),
                     _or(NodeList {
                       _and(_leaf(0), _leaf(1)),
                       _and(_leaf(2), _leaf(3)),
                       _and(_leaf(4), _leaf(5)),
                       _and(_leaf(6), _leaf(7)),
                       _and(_leaf(8), _leaf(9)),
                       _and(_leaf(10), _leaf(11)),
                       _and(_leaf(12), _leaf(13)),
                       _and(_leaf(14), _leaf(15)),
                       _and(_leaf(16), _leaf(17))})))->toString());

    std::unordered_set<TreeNode> seen;
    EXPECT_NO_THROW(assertNoSharedNodes(
      SearchArgumentBuilderImpl::convertToCNF(
        _or(NodeList {_and(NodeList { _leaf(0), _leaf(1), _leaf(2) }),
                      _and(NodeList { _leaf(3), _leaf(4), _leaf(5) }),
                      _and(_leaf(6), _leaf(7)),
                      _leaf(8)})), seen));
  }

  TEST(TestSearchArgument, testBuilder) {
    auto sarg = SearchArgumentFactory::newBuilder()
        ->startAnd()
        .lessThan("x", PredicateDataType::LONG,
                  Literal(static_cast<int64_t>(10)))
        .lessThanEquals("y", PredicateDataType::STRING, Literal("hi", 2))
        .equals("z", PredicateDataType::FLOAT, Literal(1.1))
        .end()
        .build();
    EXPECT_EQ("leaf-0 = (x < 10), "
              "leaf-1 = (y <= hi), "
              "leaf-2 = (z = 1.1), "
              "expr = (and leaf-0 leaf-1 leaf-2)", sarg->toString());
    sarg = SearchArgumentFactory::newBuilder()
      ->startNot()
      .startOr()
      .isNull("x", PredicateDataType::LONG)
      .between("y", PredicateDataType::LONG,
               Literal(static_cast<int64_t>(10)), Literal(static_cast<int64_t>(20)))
      .in("z", PredicateDataType::LONG,
          {Literal(static_cast<int64_t>(1)),
           Literal(static_cast<int64_t>(2)), Literal(static_cast<int64_t>(3))})
      .nullSafeEquals(
              "a", PredicateDataType::STRING, Literal("stinger", 7))
      .end()
      .end()
      .build();
    EXPECT_EQ(
      "leaf-0 = (x is null), "
      "leaf-1 = (y between [10, 20]), "
      "leaf-2 = (z in [1, 2, 3]), "
      "leaf-3 = (a null_safe_= stinger), "
      "expr = (and (not leaf-0) (not leaf-1) (not leaf-2) (not leaf-3))",
      sarg->toString());
  }

  TEST(TestSearchArgument, testBuilderComplexTypes) {
    auto sarg = SearchArgumentFactory::newBuilder()
        ->startAnd()
        .lessThan("x", PredicateDataType::DATE,  // 1970-01-11
                  Literal(PredicateDataType::DATE, 123456L))
        .lessThanEquals("y", PredicateDataType::STRING,
                        Literal("hi        ", 10))
        .equals("z", PredicateDataType::DECIMAL, Literal(10, 2, 1))
        .end()
        .build();
    EXPECT_EQ("leaf-0 = (x < 123456), "
              "leaf-1 = (y <= hi        ), "
              "leaf-2 = (z = 1.0), "
              "expr = (and leaf-0 leaf-1 leaf-2)", sarg->toString());

    sarg = SearchArgumentFactory::newBuilder()
      ->startNot()
      .startOr()
      .isNull("x", PredicateDataType::LONG)
      .between("y", PredicateDataType::DECIMAL,
               Literal(10, 3, 0), Literal(200, 3, 1))
      .in("z", PredicateDataType::LONG,
          { Literal(static_cast<int64_t>(1)),
            Literal(static_cast<int64_t>(2)),
            Literal(static_cast<int64_t>(3)) })
      .nullSafeEquals("a", PredicateDataType::STRING,
                      Literal("stinger", 7))
      .end()
      .end()
      .build();
    EXPECT_EQ("leaf-0 = (x is null), "
              "leaf-1 = (y between [10, 20.0]), "
              "leaf-2 = (z in [1, 2, 3]), "
              "leaf-3 = (a null_safe_= stinger), "
              "expr = (and (not leaf-0) (not leaf-1) (not leaf-2) (not leaf-3))",
              sarg->toString());
  }

  TEST(TestSearchArgument, testBuilderComplexTypes2) {
    auto sarg =
      SearchArgumentFactory::newBuilder()
        ->startAnd()
        .lessThan("x", PredicateDataType::DATE,
                  Literal(PredicateDataType::DATE, 11111L))  // "2005-3-12"
        .lessThanEquals("y", PredicateDataType::STRING,
                        Literal("hi        ", 10))
        .equals("z", PredicateDataType::DECIMAL,
                Literal(10, 2, 1))
        .end()
        .build();
    EXPECT_EQ("leaf-0 = (x < 11111), "
              "leaf-1 = (y <= hi        ), "
              "leaf-2 = (z = 1.0), "
              "expr = (and leaf-0 leaf-1 leaf-2)", sarg->toString());

    sarg = SearchArgumentFactory::newBuilder()
      ->startNot()
      .startOr()
      .isNull("x", PredicateDataType::LONG)
      .between("y", PredicateDataType::DECIMAL,
               Literal(10, 2, 0), Literal(200, 3, 1))
      .in("z", PredicateDataType::LONG,
          { Literal(static_cast<int64_t>(1)),
            Literal(static_cast<int64_t>(2)),
            Literal(static_cast<int64_t>(3)) })
      .nullSafeEquals("a", PredicateDataType::STRING,
                      Literal("stinger", 7))
      .end()
      .end()
      .build();
    EXPECT_EQ("leaf-0 = (x is null), "
              "leaf-1 = (y between [10, 20.0]), "
              "leaf-2 = (z in [1, 2, 3]), "
              "leaf-3 = (a null_safe_= stinger), "
              "expr = (and (not leaf-0) (not leaf-1) (not leaf-2) (not leaf-3))",
              sarg->toString());
  }

  TEST(TestSearchArgument, testBuilderFloat) {
    auto sarg = SearchArgumentFactory::newBuilder()
      ->startAnd()
      .lessThan("x", PredicateDataType::LONG,
                Literal(static_cast<int64_t>(22)))
      .lessThan("x1", PredicateDataType::LONG,
                Literal(static_cast<int64_t>(22)))
      .lessThanEquals("y", PredicateDataType::STRING,
                      Literal("hi        ", 10))
      .equals("z", PredicateDataType::FLOAT, Literal(0.22))
      .equals("z1", PredicateDataType::FLOAT, Literal(0.22))
      .end()
      .build();
    EXPECT_EQ("leaf-0 = (x < 22), "
                 "leaf-1 = (x1 < 22), "
                 "leaf-2 = (y <= hi        ), "
                 "leaf-3 = (z = 0.22), "
                 "leaf-4 = (z1 = 0.22), "
                 "expr = (and leaf-0 leaf-1 leaf-2 leaf-3 leaf-4)",
                 sarg->toString());
  }

  TEST(TestSearchArgument, testBadLiteral) {
    EXPECT_THROW(
      SearchArgumentFactory::newBuilder()
        ->startAnd()
        .lessThan("x", PredicateDataType::LONG, Literal("hi", 2))
        .end()
        .build(),
      std::invalid_argument);
  }

  TEST(TestSearchArgument, testBadLiteralList) {
    EXPECT_THROW(
      SearchArgumentFactory::newBuilder()
        ->startAnd()
        .in("x", PredicateDataType::STRING,
            {Literal("hi                     ", 23)})
        .end()
        .build(),
      std::invalid_argument);
  }

}  // namespace orc
