/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed 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 "src/trace_processor/core/dataframe/dataframe.h"

#include <cctype>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <numeric>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/base/test/status_matchers.h"
#include "src/trace_processor/containers/string_pool.h"
#include "src/trace_processor/core/dataframe/adhoc_dataframe_builder.h"
#include "src/trace_processor/core/dataframe/dataframe_test_utils.h"
#include "src/trace_processor/core/dataframe/query_plan.h"
#include "src/trace_processor/core/dataframe/specs.h"
#include "src/trace_processor/core/dataframe/typed_cursor.h"
#include "src/trace_processor/core/dataframe/types.h"
#include "src/trace_processor/core/interpreter/bytecode_to_string.h"
#include "src/trace_processor/core/util/bit_vector.h"
#include "src/trace_processor/util/regex.h"
#include "test/gtest_and_gmock.h"

namespace perfetto::trace_processor::core::dataframe {

inline std::string TrimSpacePerLine(const std::string& s) {
  std::string result;
  result.reserve(s.size());
  bool at_line_start = true;
  for (char c : s) {
    if (c == '\n') {
      at_line_start = true;
      result += c;
    } else if (at_line_start && std::isspace(c)) {
      // Skip whitespace at line start
      continue;
    } else {
      at_line_start = false;
      result += c;
    }
  }
  return result;
}

template <typename... Args>
std::vector<Column> MakeColumnVector(Args&&... args) {
  std::vector<Column> container;
  container.reserve(sizeof...(Args));
  ((container.emplace_back(std::forward<Args>(args))), ...);
  return container;
}

// Custom matcher that compares strings ignoring all whitespace
MATCHER_P(EqualsIgnoringWhitespace,
          expected_str,
          "equals (ignoring all whitespace)") {
  std::string stripped_expected =
      TrimSpacePerLine(base::TrimWhitespace(expected_str));
  std::string stripped_actual = TrimSpacePerLine(base::TrimWhitespace(arg));
  if (stripped_actual == stripped_expected) {
    return true;
  }
  *result_listener << "after removing all whitespace:\nExpected:\n"
                   << stripped_expected << "\nActual:\n"
                   << stripped_actual;
  return false;
}

// Test fixture for diff-based testing of bytecode generation
class DataframeBytecodeTest : public ::testing::Test {
 protected:
  // Formats the bytecode for comparison
  static std::string FormatBytecode(const Dataframe::QueryPlan& plan) {
    std::string result;
    for (const auto& bc : plan.GetImplForTesting().bytecode) {
      result += interpreter::ToString(bc) + "\n";
    }
    return result;
  }

  void RunBytecodeTest(std::vector<Column>& cols,
                       std::vector<FilterSpec>& filters,
                       const std::vector<DistinctSpec>& distinct_specs,
                       const std::vector<SortSpec>& sort_specs,
                       LimitSpec limit_spec,
                       const std::string& expected_bytecode,
                       uint64_t cols_used = 0xFFFFFFFF) {
    std::vector<std::string> col_names;
    col_names.reserve(cols.size());
    for (uint32_t i = 0; i < cols.size(); ++i) {
      col_names.emplace_back("col" + std::to_string(i));
    }
    auto df = MakeDatafame(col_names, std::move(cols));
    RunBytecodeTest(*df, filters, distinct_specs, sort_specs, limit_spec,
                    expected_bytecode, cols_used);
  }

  static void RunBytecodeTest(const Dataframe& df,
                              std::vector<FilterSpec>& filters,
                              const std::vector<DistinctSpec>& distinct_specs,
                              const std::vector<SortSpec>& sort_specs,
                              LimitSpec limit_spec,
                              const std::string& expected_bytecode,
                              uint64_t cols_used = 0xFFFFFFFF) {
    // Sanitize cols_used to ensure it only references valid columns.
    PERFETTO_CHECK(df.column_names().size() < 64);
    uint64_t sanitized_cols_used =
        cols_used & ((1ull << df.column_names().size()) - 1ull);

    ASSERT_OK_AND_ASSIGN(Dataframe::QueryPlan plan,
                         df.PlanQuery(filters, distinct_specs, sort_specs,
                                      limit_spec, sanitized_cols_used));
    EXPECT_THAT(FormatBytecode(plan),
                EqualsIgnoringWhitespace(expected_bytecode));
  }

  std::unique_ptr<Dataframe> MakeDatafame(std::vector<std::string> col_names,
                                          std::vector<Column> cols) {
    std::vector<std::shared_ptr<Column>> col_fixed_vec;
    col_fixed_vec.reserve(cols.size());
    for (auto& col : cols) {
      col_fixed_vec.emplace_back(std::make_shared<Column>(std::move(col)));
    }
    return std::unique_ptr<Dataframe>(new Dataframe(false, std::move(col_names),
                                                    std::move(col_fixed_vec), 0,
                                                    &string_pool_));
  }

  StringPool string_pool_;
};

// Simple test case with no filters
TEST_F(DataframeBytecodeTest, NoFilters) {
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{}, NoDuplicates{}},
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{},
             NoDuplicates{}});
  std::vector<FilterSpec> filters;
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
  )");
}

// Test case with a single filter
TEST_F(DataframeBytecodeTest, SingleFilter) {
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{}, NoDuplicates{}},
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{},
             NoDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Id>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    SortedFilter<Id, EqualRange>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
    Iota: [source_register=Register(0), update_register=Register(4)]
  )");
}

// Test case with multiple filters
TEST_F(DataframeBytecodeTest, MultipleFilters) {
  // Direct initialization of column specs
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{}, NoDuplicates{}},
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{}, NoDuplicates{}},
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{},
             NoDuplicates{}});

  // Direct initialization of filter specs
  std::vector<FilterSpec> filters = {
      {0, 0, Eq{}, std::nullopt},  // Filter on column 0
      {1, 1, Eq{}, std::nullopt}   // Filter on column 1
  };
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Id>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    SortedFilter<Id, EqualRange>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(0)]
    CastFilterValue<Id>: [fval_handle=FilterValue(1), write_register=Register(3), op=NonNullOp(0)]
    SortedFilter<Id, EqualRange>: [storage_register=Register(4), val_register=Register(3), update_register=Register(0), write_result_to=BoundModifier(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(5), dest_span_register=Register(6)]
    Iota: [source_register=Register(0), update_register=Register(6)]
  )");
}

TEST_F(DataframeBytecodeTest, NumericSortedEq) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Sorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    SortedFilter<Uint32, EqualRange>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
    Iota: [source_register=Register(0), update_register=Register(4)]
  )");
}

TEST_F(DataframeBytecodeTest, InFilter) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, In{}, std::nullopt}};
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValueList<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    Iota: [source_register=Register(0), update_register=Register(3)]
    In<Uint32>: [storage_register=Register(4), value_list_register=Register(1), source_register=Register(3), update_register=Register(3)]
  )");
}

TEST_F(DataframeBytecodeTest, NumericSortedInEq) {
  {
    std::vector<Column> cols = MakeColumnVector(Column{
        Storage::Uint32{}, NullStorage::NonNull{}, Sorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters;
    filters = {{0, 0, Lt{}, std::nullopt}};
    RunBytecodeTest(cols, filters, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(2)]
      SortedFilter<Uint32, LowerBound>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(2)]
      AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
      Iota: [source_register=Register(0), update_register=Register(4)]
    )");
  }
  {
    std::vector<Column> cols = MakeColumnVector(Column{
        Storage::Uint32{}, NullStorage::NonNull{}, Sorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters;
    filters = {{0, 0, Le{}, std::nullopt}};
    RunBytecodeTest(cols, filters, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(3)]
      SortedFilter<Uint32, UpperBound>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(2)]
      AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
      Iota: [source_register=Register(0), update_register=Register(4)]
    )");
  }
  {
    std::vector<Column> cols = MakeColumnVector(Column{
        Storage::Uint32{}, NullStorage::NonNull{}, Sorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters;
    filters = {{0, 0, Gt{}, std::nullopt}};
    RunBytecodeTest(cols, filters, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(4)]
      SortedFilter<Uint32, UpperBound>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(1)]
      AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
      Iota: [source_register=Register(0), update_register=Register(4)]
    )");
  }
  {
    std::vector<Column> cols = MakeColumnVector(Column{
        Storage::Uint32{}, NullStorage::NonNull{}, Sorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters;
    filters = {{0, 0, Ge{}, std::nullopt}};
    RunBytecodeTest(cols, filters, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(5)]
      SortedFilter<Uint32, LowerBound>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(1)]
      AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
      Iota: [source_register=Register(0), update_register=Register(4)]
    )");
  }
}

TEST_F(DataframeBytecodeTest, Numeric) {
  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                                Unsorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters;
    filters = {{0, 0, Eq{}, std::nullopt}};
    RunBytecodeTest(cols, filters, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
      AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
      LinearFilterEq<Uint32>: [storage_register=Register(4), filter_value_reg=Register(1), popcount_register=Register(4294967295), source_register=Register(0), update_register=Register(3)]
    )");
  }
  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                                Unsorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters;
    filters = {{0, 0, Ge{}, std::nullopt}};
    RunBytecodeTest(cols, filters, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(5)]
      AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
      Iota: [source_register=Register(0), update_register=Register(3)]
      NonStringFilter<Uint32, Ge>: [storage_register=Register(4), val_register=Register(1), source_register=Register(3), update_register=Register(3)]
  )");
  }
}

TEST_F(DataframeBytecodeTest, SortingOfFilters) {
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{}, NoDuplicates{}},
      Column{Storage::Uint32{}, NullStorage::NonNull{}, Sorted{},
             HasDuplicates{}},
      Column{Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{},
             HasDuplicates{}},
      Column{Storage::String{}, NullStorage::NonNull{}, Sorted{},
             HasDuplicates{}},
      Column{Storage::String{}, NullStorage::NonNull{}, Unsorted{},
             HasDuplicates{}});
  std::vector<FilterSpec> filters = {
      {0, 0, Le{}, std::nullopt}, {1, 0, Eq{}, std::nullopt},
      {0, 0, Eq{}, std::nullopt}, {4, 0, Le{}, std::nullopt},
      {2, 0, Eq{}, std::nullopt}, {3, 0, Le{}, std::nullopt},
      {3, 0, Eq{}, std::nullopt}, {1, 0, Le{}, std::nullopt},
  };
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Id>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    SortedFilter<Id, EqualRange>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(0)]
    CastFilterValue<Id>: [fval_handle=FilterValue(1), write_register=Register(3), op=NonNullOp(3)]
    SortedFilter<Id, UpperBound>: [storage_register=Register(2), val_register=Register(3), update_register=Register(0), write_result_to=BoundModifier(2)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(2), write_register=Register(4), op=NonNullOp(0)]
    SortedFilter<Uint32, EqualRange>: [storage_register=Register(5), val_register=Register(4), update_register=Register(0), write_result_to=BoundModifier(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(3), write_register=Register(6), op=NonNullOp(3)]
    SortedFilter<Uint32, UpperBound>: [storage_register=Register(5), val_register=Register(6), update_register=Register(0), write_result_to=BoundModifier(2)]
    CastFilterValue<String>: [fval_handle=FilterValue(4), write_register=Register(7), op=NonNullOp(0)]
    SortedFilter<String, EqualRange>: [storage_register=Register(8), val_register=Register(7), update_register=Register(0), write_result_to=BoundModifier(0)]
    CastFilterValue<String>: [fval_handle=FilterValue(5), write_register=Register(9), op=NonNullOp(3)]
    SortedFilter<String, UpperBound>: [storage_register=Register(8), val_register=Register(9), update_register=Register(0), write_result_to=BoundModifier(2)]
    CastFilterValue<String>: [fval_handle=FilterValue(6), write_register=Register(10), op=NonNullOp(3)]
    AllocateIndices: [size=0, dest_slab_register=Register(11), dest_span_register=Register(12)]
    Iota: [source_register=Register(0), update_register=Register(12)]
    StringFilter<Le>: [storage_register=Register(13), val_register=Register(10), source_register=Register(12), update_register=Register(12)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(7), write_register=Register(14), op=NonNullOp(0)]
    NonStringFilter<Uint32, Eq>: [storage_register=Register(15), val_register=Register(14), source_register=Register(12), update_register=Register(12)]
  )");
}

TEST_F(DataframeBytecodeTest, StringFilter) {
  if constexpr (!regex::IsRegexSupported()) {
    GTEST_SKIP() << "Regex is not supported";
  }
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::String{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {
      {0, 0, Regex{}, std::nullopt},
  };
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<String>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(7)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    Iota: [source_register=Register(0), update_register=Register(3)]
    StringFilter<Regex>: [storage_register=Register(4), val_register=Register(1), source_register=Register(3), update_register=Register(3)]
  )");
}

TEST_F(DataframeBytecodeTest, StringFilterGlob) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::String{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {
      {0, 0, Glob{}, std::nullopt},
  };
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<String>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(6)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    Iota: [source_register=Register(0), update_register=Register(3)]
    StringFilter<Glob>: [storage_register=Register(4), val_register=Register(1), source_register=Register(3), update_register=Register(3)]
  )");
}

TEST_F(DataframeBytecodeTest, SparseNullFilters) {
  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::SparseNull{},
                                Unsorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters_isnull = {{0, 0, IsNull{}, std::nullopt}};
    RunBytecodeTest(cols, filters_isnull, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    NullFilter<IsNull>: [null_bv_register=Register(3), update_register=Register(2)]
  )",
                    /*cols_used=*/0);
  }

  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::SparseNull{},
                                Unsorted{}, HasDuplicates{}});
    std::vector<FilterSpec> filters_isnotnull = {
        {0, 0, IsNotNull{}, std::nullopt},
    };
    RunBytecodeTest(cols, filters_isnotnull, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    NullFilter<IsNotNull>: [null_bv_register=Register(3), update_register=Register(2)]
  )",
                    /*cols_used=*/0);
  }
}

TEST_F(DataframeBytecodeTest, DenseNullFilters) {
  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::DenseNull{},
                                Unsorted{}, HasDuplicates{}});

    // Test IsNull
    std::vector<FilterSpec> filters_isnull = {{0, 0, IsNull{}, std::nullopt}};
    RunBytecodeTest(cols, filters_isnull, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    NullFilter<IsNull>: [null_bv_register=Register(3), update_register=Register(2)]
  )",
                    /*cols_used=*/0);
  }
  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::DenseNull{},
                                Unsorted{}, HasDuplicates{}});

    // Test IsNotNull
    std::vector<FilterSpec> filters_isnotnull = {
        {0, 0, IsNotNull{}, std::nullopt}};
    RunBytecodeTest(cols, filters_isnotnull, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    NullFilter<IsNotNull>: [null_bv_register=Register(3), update_register=Register(2)]
  )",
                    /*cols_used=*/0);
  }
}

TEST_F(DataframeBytecodeTest, NonNullFilters) {
  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                                Unsorted{}, HasDuplicates{}});

    // Test IsNull: Should result in an empty result set as the column is
    // NonNull
    std::vector<FilterSpec> filters_isnull = {{0, 0, IsNull{}, std::nullopt}};
    RunBytecodeTest(cols, filters_isnull, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    )");
  }

  {
    std::vector<Column> cols =
        MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                                Unsorted{}, HasDuplicates{}});

    // Test IsNotNull: Should have no effect as the column is already NonNull
    std::vector<FilterSpec> filters_isnotnull = {
        {0, 0, IsNotNull{}, std::nullopt}};
    RunBytecodeTest(cols, filters_isnotnull, {}, {}, {}, R"(
      InitRange: [size=0, dest_register=Register(0)]
      AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
      Iota: [source_register=Register(0), update_register=Register(2)]
    )");
  }
}

TEST_F(DataframeBytecodeTest, StandardFilterOnSparseNull) {
  // Test a standard filter (Eq) on a SparseNull column.
  // Expect bytecode to handle nulls first, then apply the filter.
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::SparseNull{},
                              Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};

  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    Iota: [source_register=Register(0), update_register=Register(3)]
    NullFilter<IsNotNull>: [null_bv_register=Register(4), update_register=Register(3)]
    AllocateIndices: [size=0, dest_slab_register=Register(5), dest_span_register=Register(6)]
    PrefixPopcount: [null_bv_register=Register(4), dest_register=Register(7)]
    TranslateSparseNullIndices: [null_bv_register=Register(4), popcount_register=Register(7), source_register=Register(3), update_register=Register(6)]
    NonStringFilter<Uint32, Eq>: [storage_register=Register(8), val_register=Register(1), source_register=Register(6), update_register=Register(3)]
  )",
                  /*cols_used=*/0);
}

TEST_F(DataframeBytecodeTest, StandardFilterOnDenseNull) {
  // Test a standard filter (Eq) on a DenseNull column.
  // Expect bytecode to handle nulls first, then apply the filter directly.
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::DenseNull{},
                              Unsorted{}, HasDuplicates{}});

  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};

  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    Iota: [source_register=Register(0), update_register=Register(3)]
    NullFilter<IsNotNull>: [null_bv_register=Register(4), update_register=Register(3)]
    NonStringFilter<Uint32, Eq>: [storage_register=Register(5), val_register=Register(1), source_register=Register(3), update_register=Register(3)]
  )",
                  /*cols_used=*/0);
}

TEST_F(DataframeBytecodeTest, OutputSparseNullColumn) {
  // Test requesting a SparseNull column in the output
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}},
                       Column{Storage::Int64{}, NullStorage::SparseNull{},
                              Unsorted{}, HasDuplicates{}});

  std::vector<FilterSpec> filters;  // No filters

  // cols_used_bitmap: 0b10 means use column at index 1 (col_sparse)
  uint64_t cols_used = 0b10;

  // Since we request a nullable column (col_sparse at index 1), the output
  // needs two slots per row:
  // Slot 0: Original index (copied by StrideCopy)
  // Slot 1: Translated index for col_sparse (or UINT32_MAX for null)
  // Therefore, stride = 2.
  // col_sparse (index 1) maps to offset 1 in the output row.
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
    StrideCopy: [source_register=Register(2), update_register=Register(4), stride=2]
    PrefixPopcount: [null_bv_register=Register(6), dest_register=Register(5)]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(6), popcount_register=Register(5), update_register=Register(4), offset=1, stride=2]
  )",
                  cols_used);
}

TEST_F(DataframeBytecodeTest, OutputDenseNullColumn) {
  // Test requesting a DenseNull column in the output
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}},
                       Column{Storage::Int64{}, NullStorage::DenseNull{},
                              Unsorted{}, HasDuplicates{}});

  std::vector<FilterSpec> filters;  // No filters

  // cols_used_bitmap: 0b10 means use column at index 1 (col_dense)
  uint64_t cols_used = 0b10;

  // Since we request a nullable column (col_dense at index 1), the output
  // needs two slots per row:
  // Slot 0: Original index (copied by StrideCopy)
  // Slot 1: Original index if non-null, else UINT32_MAX (copied by
  // StrideCopyDenseNullIndices) Therefore, stride = 2. col_dense (index 1)
  // maps to offset 1 in the output row.
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
    StrideCopy: [source_register=Register(2), update_register=Register(4), stride=2]
    StrideCopyDenseNullIndices: [null_bv_register=Register(5), update_register=Register(4), offset=1, stride=2]
  )",
                  cols_used);
}

TEST_F(DataframeBytecodeTest, OutputMultipleNullableColumns) {
  // Test requesting both a SparseNull and a DenseNull column
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}},
                       Column{Storage::Int64{}, NullStorage::SparseNull{},
                              Unsorted{}, HasDuplicates{}},
                       Column{Storage::Double{}, NullStorage::DenseNull{},
                              Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;  // No filters

  // cols_used_bitmap: 0b110 means use columns at index 1 (sparse) and 2
  // (dense)
  uint64_t cols_used = 0b110;

  // Output needs 3 slots per row:
  // Slot 0: Original index (StrideCopy)
  // Slot 1: Translated index for col_sparse (index 1)
  // Slot 2: Copied index for col_dense (index 2)
  // Stride = 3.
  // col_sparse (index 1) maps to offset 1.
  // col_dense (index 2) maps to offset 2.
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
    StrideCopy: [source_register=Register(2), update_register=Register(4), stride=3]
    PrefixPopcount: [null_bv_register=Register(6), dest_register=Register(5)]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(6), popcount_register=Register(5), update_register=Register(4), offset=1, stride=3]
    StrideCopyDenseNullIndices: [null_bv_register=Register(7), update_register=Register(4), offset=2, stride=3]
  )",
                  cols_used);
}

TEST_F(DataframeBytecodeTest, Uint32SetIdSortedEqGeneration) {
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                              SetIdSorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};

  // Expect the specialized Uint32SetIdSortedEq bytecode for this combination
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    Uint32SetIdSortedEq: [storage_register=Register(2), val_register=Register(1), update_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
    Iota: [source_register=Register(0), update_register=Register(4)]
  )");
}
// Test sorting by a single Uint32 column, ascending.
TEST_F(DataframeBytecodeTest, SortSingleUint32Asc) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kAscending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    CopyToRowLayout<Uint32, NonNull>: [storage_register=Register(4), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=4, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(3), total_row_stride=4, indices_register=Register(2)]
  )",
                  /*cols_used=*/1);
}

// Test sorting by a single String column, descending.
TEST_F(DataframeBytecodeTest, SortSingleStringDesc) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::String{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kDescending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    InitRankMap: [dest_register=Register(3)]
    CollectIdIntoRankMap: [storage_register=Register(4), source_register=Register(2), rank_map_register=Register(3)]
    FinalizeRanksInMap: [update_register=Register(3)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(5)]
    CopyToRowLayout<String, NonNull>: [storage_register=Register(4), null_bv_register=Register(6), source_indices_register=Register(2), dest_buffer_register=Register(5), row_layout_offset=0, row_layout_stride=4, invert_copied_bits=1, popcount_register=Register(4294967295), rank_map_register=Register(3)]
    SortRowLayout: [buffer_register=Register(5), total_row_stride=4, indices_register=Register(2)]
  )",
                  /*cols_used=*/1);
}

// Test multi-column sorting (Stable Sort).
TEST_F(DataframeBytecodeTest, SortMultiColumnStable) {
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Int64{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}},
                       Column{Storage::Double{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  // Sort specs: Primary Int64 DESC, Secondary Double ASC
  std::vector<SortSpec> sorts = {{0, SortDirection::kDescending},
                                 {1, SortDirection::kAscending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    CopyToRowLayout<Int64, NonNull>: [storage_register=Register(4), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=16, invert_copied_bits=1, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    CopyToRowLayout<Double, NonNull>: [storage_register=Register(6), null_bv_register=Register(7), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=8, row_layout_stride=16, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(3), total_row_stride=16, indices_register=Register(2)]
  )",
                  /*cols_used=*/3);
}

// Test sorting combined with filtering.
TEST_F(DataframeBytecodeTest, SortWithFilter) {
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{}, NoDuplicates{}},
      Column{Storage::Double{}, NullStorage::NonNull{}, Unsorted{},
             HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Gt{}, std::nullopt}};
  std::vector<SortSpec> sorts = {{1, SortDirection::kAscending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Id>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(4)]
    SortedFilter<Id, UpperBound>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(1)]
    AllocateIndices: [size=0, dest_slab_register=Register(3), dest_span_register=Register(4)]
    Iota: [source_register=Register(0), update_register=Register(4)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(5)]
    CopyToRowLayout<Double, NonNull>: [storage_register=Register(6), null_bv_register=Register(7), source_indices_register=Register(4), dest_buffer_register=Register(5), row_layout_offset=0, row_layout_stride=8, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(5), total_row_stride=8, indices_register=Register(4)]
  )",
                  /*cols_used=*/3);
}

// Test planning sort on a nullable column.
TEST_F(DataframeBytecodeTest, SortNullableColumn) {
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Int32{}, NullStorage::SparseNull{},
                              Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kDescending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    PrefixPopcount: [null_bv_register=Register(5), dest_register=Register(4)]
    CopyToRowLayout<Int32, SparseNull>: [storage_register=Register(6), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=5, invert_copied_bits=1, popcount_register=Register(4), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(3), total_row_stride=5, indices_register=Register(2)]
    AllocateIndices: [size=0, dest_slab_register=Register(7), dest_span_register=Register(8)]
    StrideCopy: [source_register=Register(2), update_register=Register(8), stride=2]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(5), popcount_register=Register(4), update_register=Register(8), offset=1, stride=2]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, PlanQuery_DistinctTwoNonNullCols) {
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Int32{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}},
                       Column{Storage::String{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}});

  std::vector<FilterSpec> filters;
  std::vector<DistinctSpec> distinct_specs = {{0}, {1}};
  uint64_t cols_used = 0b11;

  const std::string expected_bytecode = R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    CopyToRowLayout<Int32, NonNull>: [storage_register=Register(4), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=8, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    CopyToRowLayout<String, NonNull>: [storage_register=Register(6), null_bv_register=Register(7), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=4, row_layout_stride=8, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    Distinct: [buffer_register=Register(3), total_row_stride=8, indices_register=Register(2)]
  )";

  RunBytecodeTest(cols, filters, distinct_specs, {}, {}, expected_bytecode,
                  cols_used);
}

TEST_F(DataframeBytecodeTest, LimitOffsetPlacement) {
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}},
                       Column{Storage::Int64{}, NullStorage::SparseNull{},
                              Unsorted{}, HasDuplicates{}});

  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  LimitSpec spec;
  spec.offset = 2;
  spec.limit = 10;

  RunBytecodeTest(cols, filters, {}, {}, spec, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    LinearFilterEq<Uint32>: [storage_register=Register(4), filter_value_reg=Register(1), popcount_register=Register(4294967295), source_register=Register(0), update_register=Register(3)]
    LimitOffsetIndices: [offset_value=2, limit_value=10, update_register=Register(3)]
    AllocateIndices: [size=0, dest_slab_register=Register(5), dest_span_register=Register(6)]
    StrideCopy: [source_register=Register(3), update_register=Register(6), stride=2]
    PrefixPopcount: [null_bv_register=Register(8), dest_register=Register(7)]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(8), popcount_register=Register(7), update_register=Register(6), offset=1, stride=2]
  )",
                  /*cols_used=*/2);
}

TEST_F(DataframeBytecodeTest, PlanQuery_MinOptimizationApplied) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<DistinctSpec> distinct_specs;
  std::vector<SortSpec> sort_specs = {{0, SortDirection::kAscending}};
  LimitSpec limit_spec;
  limit_spec.limit = 1;

  std::string expected_bytecode = R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    FindMinMaxIndex<Uint32, MinOp>: [storage_register=Register(3), update_register=Register(2)]
  )";

  RunBytecodeTest(cols, filters, distinct_specs, sort_specs, limit_spec,
                  expected_bytecode, /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, SortOptimizationApplied_SingleAscNonNullSorted) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Sorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kAscending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, SortOptimizationNotApplied_MultipleSpecs) {
  std::vector<Column> cols =
      MakeColumnVector(Column{Storage::Uint32{}, NullStorage::NonNull{},
                              Sorted{}, HasDuplicates{}},
                       Column{Storage::Int32{}, NullStorage::NonNull{},
                              Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kAscending},
                                 {1, SortDirection::kAscending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    CopyToRowLayout<Uint32, NonNull>: [storage_register=Register(4), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=8, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    CopyToRowLayout<Int32, NonNull>: [storage_register=Register(6), null_bv_register=Register(7), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=4, row_layout_stride=8, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(3), total_row_stride=8, indices_register=Register(2)]
  )",
                  /*cols_used=*/3);  // 0b11
}

TEST_F(DataframeBytecodeTest, SortOptimization_Reverse) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Sorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kDescending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    Reverse: [update_register=Register(2)]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, SortOptimizationNotApplied_NullableColumn) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::SparseNull{}, Sorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kAscending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    PrefixPopcount: [null_bv_register=Register(5), dest_register=Register(4)]
    CopyToRowLayout<Uint32, SparseNull>: [storage_register=Register(6), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=5, invert_copied_bits=0, popcount_register=Register(4), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(3), total_row_stride=5, indices_register=Register(2)]
    AllocateIndices: [size=0, dest_slab_register=Register(7), dest_span_register=Register(8)]
    StrideCopy: [source_register=Register(2), update_register=Register(8), stride=2]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(5), popcount_register=Register(4), update_register=Register(8), offset=1, stride=2]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, SortOptimizationNotApplied_UnsortedColumn) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters;
  std::vector<SortSpec> sorts = {{0, SortDirection::kAscending}};
  RunBytecodeTest(cols, filters, {}, sorts, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    CopyToRowLayout<Uint32, NonNull>: [storage_register=Register(4), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=4, invert_copied_bits=0, popcount_register=Register(4294967295), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(3), total_row_stride=4, indices_register=Register(2)]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, PlanQuery_MinOptimizationNotAppliedNullable) {
  auto bv = core::BitVector::CreateWithSize(0);
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Uint32{},
             NullStorage{NullStorage::SparseNull{std::move(bv), {}}},
             Unsorted{}, HasDuplicates{}});

  std::vector<FilterSpec> filters;
  std::vector<DistinctSpec> distinct_specs;
  std::vector<SortSpec> sort_specs = {{0, SortDirection::kAscending}};
  LimitSpec limit_spec;
  limit_spec.limit = 1;

  std::string expected_bytecode = R"(
    InitRange: [size=0, dest_register=Register(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(1), dest_span_register=Register(2)]
    Iota: [source_register=Register(0), update_register=Register(2)]
    AllocateRowLayoutBuffer: [buffer_size=0, dest_buffer_register=Register(3)]
    PrefixPopcount: [null_bv_register=Register(5), dest_register=Register(4)]
    CopyToRowLayout<Uint32, SparseNull>: [storage_register=Register(6), null_bv_register=Register(5), source_indices_register=Register(2), dest_buffer_register=Register(3), row_layout_offset=0, row_layout_stride=5, invert_copied_bits=0, popcount_register=Register(4), rank_map_register=Register(4294967295)]
    SortRowLayout: [buffer_register=Register(3), total_row_stride=5, indices_register=Register(2)]
    LimitOffsetIndices: [offset_value=0, limit_value=1, update_register=Register(2)]
    AllocateIndices: [size=0, dest_slab_register=Register(7), dest_span_register=Register(8)]
    StrideCopy: [source_register=Register(2), update_register=Register(8), stride=2]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(5), popcount_register=Register(4), update_register=Register(8), offset=1, stride=2]
  )";
  RunBytecodeTest(cols, filters, distinct_specs, sort_specs, limit_spec,
                  expected_bytecode, /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, PlanQuery_SingleColIndex_EqFilter_NonNullInt) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"col1"}, CreateTypedColumnSpec(Uint32{}, NonNull{}, Unsorted{}));
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &string_pool_);
  for (uint32_t i = 0; i < 100; ++i) {
    df.InsertUnchecked(kSpec, i);
  }
  df.Finalize();

  std::vector<uint32_t> p_vec(100);
  std::iota(p_vec.begin(), p_vec.end(), 0);
  df.AddIndex(
      Index({0}, std::make_shared<std::vector<uint32_t>>(std::move(p_vec))));

  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  std::string expected_bytecode = R"(
    InitRange: [size=100, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(3), op=NonNullOp(0)]
    IndexedFilterEq<Uint32, NonNull>: [storage_register=Register(5), null_bv_register=Register(6), filter_value_reg=Register(3), popcount_register=Register(4), source_register=Register(1), dest_register=Register(2)]
    AllocateIndices: [size=100, dest_slab_register=Register(7), dest_span_register=Register(8)]
    CopySpanIntersectingRange: [source_register=Register(2), source_range_register=Register(0), update_register=Register(8)]
  )";
  RunBytecodeTest(df, filters, {}, {}, {}, expected_bytecode,
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest,
       PlanQuery_SingleColIndex_EqFilter_NullableString) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"col_str_nullable"},
      CreateTypedColumnSpec(String(), SparseNull(), Unsorted()));

  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &string_pool_);
  df.InsertUnchecked(kSpec,
                     std::make_optional(string_pool_.InternString("apple")));
  df.InsertUnchecked(kSpec, std::nullopt);
  df.InsertUnchecked(kSpec,
                     std::make_optional(string_pool_.InternString("banana")));
  df.InsertUnchecked(kSpec,
                     std::make_optional(string_pool_.InternString("apple")));
  df.Finalize();
  df.AddIndex(Index({0}, std::make_shared<std::vector<uint32_t>>(
                             std::vector<uint32_t>{1, 0, 3, 2})));

  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  std::string expected_bytecode = R"(
    InitRange: [size=4, dest_register=Register(0)]
    CastFilterValue<String>: [fval_handle=FilterValue(0), write_register=Register(3), op=NonNullOp(0)]
    PrefixPopcount: [null_bv_register=Register(5), dest_register=Register(4)]
    IndexedFilterEq<String, SparseNull>: [storage_register=Register(6), null_bv_register=Register(5), filter_value_reg=Register(3), popcount_register=Register(4), source_register=Register(1), dest_register=Register(2)]
    AllocateIndices: [size=4, dest_slab_register=Register(7), dest_span_register=Register(8)]
    CopySpanIntersectingRange: [source_register=Register(2), source_range_register=Register(0), update_register=Register(8)]
    AllocateIndices: [size=8, dest_slab_register=Register(9), dest_span_register=Register(10)]
    StrideCopy: [source_register=Register(8), update_register=Register(10), stride=2]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(5), popcount_register=Register(4), update_register=Register(10), offset=1, stride=2]
  )";
  RunBytecodeTest(df, filters, {}, {}, {}, expected_bytecode);
}

TEST_F(DataframeBytecodeTest, PlanQuery_SingleColIndex_EqFilter_DenseNullInt) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"col_dense_nullable"},
      CreateTypedColumnSpec(Uint32(), DenseNull(), Unsorted()));

  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &string_pool_);
  df.InsertUnchecked(kSpec, std::make_optional(10u));
  df.InsertUnchecked(kSpec, std::nullopt);
  df.InsertUnchecked(kSpec, std::make_optional(20u));
  df.InsertUnchecked(kSpec, std::make_optional(10u));
  df.Finalize();
  df.AddIndex(Index({0}, std::make_shared<std::vector<uint32_t>>(
                             std::vector<uint32_t>{1, 0, 3, 2})));

  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  std::string expected_bytecode = R"(
    InitRange: [size=4, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(3), op=NonNullOp(0)]
    IndexedFilterEq<Uint32, DenseNull>: [storage_register=Register(5), null_bv_register=Register(6), filter_value_reg=Register(3), popcount_register=Register(4), source_register=Register(1), dest_register=Register(2)]
    AllocateIndices: [size=4, dest_slab_register=Register(7), dest_span_register=Register(8)]
    CopySpanIntersectingRange: [source_register=Register(2), source_range_register=Register(0), update_register=Register(8)]
    AllocateIndices: [size=8, dest_slab_register=Register(9), dest_span_register=Register(10)]
    StrideCopy: [source_register=Register(8), update_register=Register(10), stride=2]
    StrideCopyDenseNullIndices: [null_bv_register=Register(6), update_register=Register(10), offset=1, stride=2]
  )";
  RunBytecodeTest(df, filters, {}, {}, {}, expected_bytecode);
}

TEST_F(DataframeBytecodeTest, PlanQuery_MultiColIndex_PrefixEqFilters) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"col0_uint32", "col1_uint32"},
      CreateTypedColumnSpec(Uint32(), NonNull(), Unsorted()),
      CreateTypedColumnSpec(Uint32(), NonNull(), Unsorted()));

  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &string_pool_);
  df.InsertUnchecked(kSpec, 10u, 100u);
  df.InsertUnchecked(kSpec, 10u, 200u);
  df.InsertUnchecked(kSpec, 20u, 100u);
  df.InsertUnchecked(kSpec, 10u, 100u);
  df.Finalize();

  std::vector<uint32_t> p_vec(4);
  std::iota(p_vec.begin(), p_vec.end(), 0);
  df.AddIndex(
      Index({0, 1}, std::make_shared<std::vector<uint32_t>>(std::move(p_vec))));

  std::vector<FilterSpec> filters = {
      {0, 0, Eq{}, std::nullopt},
      {1, 1, Eq{}, std::nullopt},
  };
  std::string expected_bytecode = R"(
    InitRange: [size=4, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(3), op=NonNullOp(0)]
    IndexedFilterEq<Uint32, NonNull>: [storage_register=Register(5), null_bv_register=Register(6), filter_value_reg=Register(3), popcount_register=Register(4), source_register=Register(1), dest_register=Register(2)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(1), write_register=Register(7), op=NonNullOp(0)]
    IndexedFilterEq<Uint32, NonNull>: [storage_register=Register(9), null_bv_register=Register(10), filter_value_reg=Register(7), popcount_register=Register(8), source_register=Register(2), dest_register=Register(2)]
    AllocateIndices: [size=4, dest_slab_register=Register(11), dest_span_register=Register(12)]
    CopySpanIntersectingRange: [source_register=Register(2), source_range_register=Register(0), update_register=Register(12)]
  )";
  RunBytecodeTest(df, filters, {}, {}, {}, expected_bytecode);
}

TEST_F(DataframeBytecodeTest, PlanQuery_LinearFilterEq_NonNullUint32) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  // Expect LinearFilterEq because:
  // 1. Input is a Range (initially).
  // 2. Operation is Eq.
  // 3. Column is NonNull.
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    LinearFilterEq<Uint32>: [storage_register=Register(4), filter_value_reg=Register(1), popcount_register=Register(4294967295), source_register=Register(0), update_register=Register(3)]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, PlanQuery_LinearFilterEq_NonNullString) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::String{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<String>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    LinearFilterEq<String>: [storage_register=Register(4), filter_value_reg=Register(1), popcount_register=Register(4294967295), source_register=Register(0), update_register=Register(3)]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest,
       PlanQuery_NoLinearFilterEq_IfInputNotRangeAfterSortedFilter) {
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Id{}, NullStorage::NonNull{}, IdSorted{},
             NoDuplicates{}},  // col0, sorted, used to make input a Span
      Column{Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{},
             HasDuplicates{}}  // col1, target for filter
  );
  std::vector<FilterSpec> filters = {
      {0, 0, Gt{}, std::nullopt},  // This filter makes indices_reg_ a Span
      {1, 1, Eq{}, std::nullopt}   // This should use NonStringFilter
  };
  // After the Gt filter on col0, indices_reg_ will be a Span (materialized by
  // Iota). So, the Eq filter on col1 should use NonStringFilter, not
  // LinearFilterEq.
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Id>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(4)]
    SortedFilter<Id, UpperBound>: [storage_register=Register(2), val_register=Register(1), update_register=Register(0), write_result_to=BoundModifier(1)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(1), write_register=Register(3), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(4), dest_span_register=Register(5)]
    LinearFilterEq<Uint32>: [storage_register=Register(6), filter_value_reg=Register(3), popcount_register=Register(4294967295), source_register=Register(0), update_register=Register(5)]
  )",
                  /*cols_used=*/3);  // 0b11
}

TEST_F(DataframeBytecodeTest, PlanQuery_NoLinearFilterEq_IfNotEqOperator) {
  std::vector<Column> cols = MakeColumnVector(Column{
      Storage::Uint32{}, NullStorage::NonNull{}, Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Gt{}, std::nullopt}};  // Not Eq
  // Should use NonStringFilter because op is Gt, not Eq.
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(4)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    Iota: [source_register=Register(0), update_register=Register(3)]
    NonStringFilter<Uint32, Gt>: [storage_register=Register(4), val_register=Register(1), source_register=Register(3), update_register=Register(3)]
  )",
                  /*cols_used=*/1);
}

TEST_F(DataframeBytecodeTest, PlanQuery_NoLinearFilterEq_IfNullableColumn) {
  std::vector<Column> cols = MakeColumnVector(
      Column{Storage::Uint32{}, NullStorage::SparseNull{},  // Nullable
             Unsorted{}, HasDuplicates{}});
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, std::nullopt}};
  // Should use NonStringFilter because column is nullable.
  RunBytecodeTest(cols, filters, {}, {}, {}, R"(
    InitRange: [size=0, dest_register=Register(0)]
    CastFilterValue<Uint32>: [fval_handle=FilterValue(0), write_register=Register(1), op=NonNullOp(0)]
    AllocateIndices: [size=0, dest_slab_register=Register(2), dest_span_register=Register(3)]
    Iota: [source_register=Register(0), update_register=Register(3)]
    NullFilter<IsNotNull>: [null_bv_register=Register(4), update_register=Register(3)]
    AllocateIndices: [size=0, dest_slab_register=Register(5), dest_span_register=Register(6)]
    PrefixPopcount: [null_bv_register=Register(4), dest_register=Register(7)]
    TranslateSparseNullIndices: [null_bv_register=Register(4), popcount_register=Register(7), source_register=Register(3), update_register=Register(6)]
    NonStringFilter<Uint32, Eq>: [storage_register=Register(8), val_register=Register(1), source_register=Register(6), update_register=Register(3)]
    AllocateIndices: [size=0, dest_slab_register=Register(9), dest_span_register=Register(10)]
    StrideCopy: [source_register=Register(3), update_register=Register(10), stride=2]
    StrideTranslateAndCopySparseNullIndices: [null_bv_register=Register(4), popcount_register=Register(7), update_register=Register(10), offset=1, stride=2]
  )",
                  /*cols_used=*/1);
}

TEST(DataframeTest, Insert) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"id", "col2", "col3", "col4"},
      CreateTypedColumnSpec(Id(), NonNull(), IdSorted()),
      CreateTypedColumnSpec(Uint32(), NonNull(), Unsorted()),
      CreateTypedColumnSpec(Int64(), DenseNull(), Unsorted()),
      CreateTypedColumnSpec(String(), SparseNull(), Unsorted()));
  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  df.InsertUnchecked(kSpec, std::monostate(), 10u, std::make_optional(0l),
                     std::make_optional(pool.InternString("foo")));
  df.InsertUnchecked(kSpec, std::monostate(), 20u, std::nullopt, std::nullopt);

  VerifyData(
      df, 0b1111,
      Rows(Row(0u, 10u, int64_t(0l), "foo"), Row(1u, 20u, nullptr, nullptr)));
}

TEST(DataframeTest, GetCellAndSetCell) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"id", "col2", "col3", "col4"},
      CreateTypedColumnSpec(Id(), NonNull(), IdSorted()),
      CreateTypedColumnSpec(Uint32(), NonNull(), Unsorted()),
      CreateTypedColumnSpec(Int64(), DenseNull(), Unsorted()),
      CreateTypedColumnSpec(String(), SparseNullWithPopcountAlways(),
                            Unsorted()));
  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  df.InsertUnchecked(kSpec, std::monostate(), 10u, std::make_optional(0l),
                     std::make_optional(pool.InternString("foo")));
  df.InsertUnchecked(kSpec, std::monostate(), 20u, std::nullopt, std::nullopt);

  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 0), 0u);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0), 10u);
  ASSERT_EQ(df.GetCellUnchecked<2>(kSpec, 0), 0l);
  ASSERT_EQ(df.GetCellUnchecked<3>(kSpec, 0), pool.InternString("foo"));

  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 1), 1u);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 1), 20u);
  ASSERT_EQ(df.GetCellUnchecked<2>(kSpec, 1), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<3>(kSpec, 1), std::nullopt);

  df.SetCellUnchecked<1>(kSpec, 0, 9u);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0), 9u);

  df.SetCellUnchecked<2>(kSpec, 0, std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<2>(kSpec, 0), std::nullopt);
}

TEST(DataframeTest, SetCellUncheckedInternal_SparseNullWithPopcount) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"col_sparse_uint32", "col_sparse_str"},
      CreateTypedColumnSpec(Uint32(), SparseNullWithPopcountAlways(),
                            Unsorted()),
      CreateTypedColumnSpec(String(), SparseNullWithPopcountUntilFinalization(),
                            Unsorted()));
  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);

  // Initial values:
  // Row 0: {100, "aa"}
  // Row 1: {null, null}
  // Row 2: {200, "bb"}
  // Row 3: {null, "cc"}
  // Row 4: {300, null}
  df.InsertUnchecked(kSpec, std::make_optional(100u),
                     std::make_optional(pool.InternString("aa")));
  df.InsertUnchecked(kSpec, std::nullopt, std::nullopt);
  df.InsertUnchecked(kSpec, std::make_optional(200u),
                     std::make_optional(pool.InternString("bb")));
  df.InsertUnchecked(kSpec, std::nullopt,
                     std::make_optional(pool.InternString("cc")));
  df.InsertUnchecked(kSpec, std::make_optional(300u), std::nullopt);

  // Verify initial state
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 0), std::make_optional(100u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0),
            std::make_optional(pool.InternString("aa")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 1), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 1), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 2), std::make_optional(200u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 2),
            std::make_optional(pool.InternString("bb")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 3), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 3),
            std::make_optional(pool.InternString("cc")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 4), std::make_optional(300u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 4), std::nullopt);

  // Test 1: Set existing non-null to new non-null
  // Row 0: {100, "aa"} -> {101, "new_aa"}
  df.SetCellUnchecked<0>(kSpec, 0, std::make_optional(101u));
  df.SetCellUnchecked<1>(kSpec, 0,
                         std::make_optional(pool.InternString("new_aa")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 0), std::make_optional(101u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0),
            std::make_optional(pool.InternString("new_aa")));

  // Test 2: Set existing non-null to null (triggers memmove for deletion)
  // Row 2: {200, "bb"} -> {null, null}
  // Expected data after:
  // Row 0: {101, "new_aa"}
  // Row 1: {null, null}
  // Row 2: {null, null}
  // Row 3: {null, "cc"}
  // Row 4: {300, null}
  // Sparse Uint32 data: [101, 300]
  // Sparse String data: ["new_aa", "cc"]
  df.SetCellUnchecked<0>(kSpec, 2, std::nullopt);
  df.SetCellUnchecked<1>(kSpec, 2, std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 2), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 2), std::nullopt);
  // Check surrounding values are not affected
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 0), std::make_optional(101u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0),
            std::make_optional(pool.InternString("new_aa")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 4), std::make_optional(300u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 3),
            std::make_optional(pool.InternString("cc")));

  // Test 3: Set existing null to non-null (triggers memmove for insertion)
  // Row 1: {null, null} -> {150, "dd"}
  // Expected data after:
  // Row 0: {101, "new_aa"}
  // Row 1: {150, "dd"}
  // Row 2: {null, null}
  // Row 3: {null, "cc"}
  // Row 4: {300, null}
  // Sparse Uint32 data: [101, 150, 300]
  // Sparse String data: ["new_aa", "dd", "cc"]
  df.SetCellUnchecked<0>(kSpec, 1, std::make_optional(150u));
  df.SetCellUnchecked<1>(kSpec, 1, std::make_optional(pool.InternString("dd")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 1), std::make_optional(150u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 1),
            std::make_optional(pool.InternString("dd")));
  // Check surrounding values
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 0), std::make_optional(101u));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 4), std::make_optional(300u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0),
            std::make_optional(pool.InternString("new_aa")));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 3),
            std::make_optional(pool.InternString("cc")));

  // Test 4: Set at the beginning - null to non-null
  // Row 0 was {101, "new_aa"}. Set to {50, "start"}
  // This is effectively an update, not an insertion in sparse terms if it was
  // already non-null. Let's make Row 0 null first to test insertion at
  // beginning. Row 0: {101, "new_aa"} -> {null, null} Sparse Uint32 data: [150,
  // 300] Sparse String data: ["dd", "cc"]
  df.SetCellUnchecked<0>(kSpec, 0, std::nullopt);
  df.SetCellUnchecked<1>(kSpec, 0, std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 0), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0), std::nullopt);

  // Now set Row 0 from null to non-null: {null, null} -> {50, "start"}
  // Expected data after:
  // Row 0: {50, "start"}
  // Row 1: {150, "dd"}
  // Row 2: {null, null}
  // Row 3: {null, "cc"}
  // Row 4: {300, null}
  // Sparse Uint32 data: [50, 150, 300]
  // Sparse String data: ["start", "dd", "cc"]
  df.SetCellUnchecked<0>(kSpec, 0, std::make_optional(50u));
  df.SetCellUnchecked<1>(kSpec, 0,
                         std::make_optional(pool.InternString("start")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 0), std::make_optional(50u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 0),
            std::make_optional(pool.InternString("start")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 1),
            std::make_optional(150u));  // Check next element
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 1),
            std::make_optional(pool.InternString("dd")));

  // Test 5: Set at the end - null to non-null
  // Row 4 was {300, null}. Let's make it {null, null} first.
  // Sparse Uint32 data: [50, 150]
  // Sparse String data: ["start", "dd", "cc"] (Row 4 string was already null)
  df.SetCellUnchecked<0>(kSpec, 4, std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 4), std::nullopt);

  // Now set Row 4 from {null, null} -> {400, "end"}
  // Expected data after:
  // Row 0: {50, "start"}
  // Row 1: {150, "dd"}
  // Row 2: {null, null}
  // Row 3: {null, "cc"}
  // Row 4: {400, "end"}
  // Sparse Uint32 data: [50, 150, 400]
  // Sparse String data: ["start", "dd", "cc", "end"]
  df.SetCellUnchecked<0>(kSpec, 4, std::make_optional(400u));
  df.SetCellUnchecked<1>(kSpec, 4,
                         std::make_optional(pool.InternString("end")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 4), std::make_optional(400u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 4),
            std::make_optional(pool.InternString("end")));
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 1),
            std::make_optional(150u));  // Check previous element
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 3),
            std::make_optional(pool.InternString("cc")));

  // Test 6: Set at the end - non-null to null
  // Row 4: {400, "end"} -> {null, null}
  // Expected data after:
  // Row 0: {50, "start"}
  // Row 1: {150, "dd"}
  // Row 2: {null, null}
  // Row 3: {null, "cc"}
  // Row 4: {null, null}
  // Sparse Uint32 data: [50, 150]
  // Sparse String data: ["start", "dd", "cc"]
  df.SetCellUnchecked<0>(kSpec, 4, std::nullopt);
  df.SetCellUnchecked<1>(kSpec, 4, std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 4), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 4), std::nullopt);
  ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, 1), std::make_optional(150u));
  ASSERT_EQ(df.GetCellUnchecked<1>(kSpec, 3),
            std::make_optional(pool.InternString("cc")));

  // Test 7: Operations on an empty column (implicitly tested by starting with
  // empty and inserting) Create a new dataframe for this.
  Dataframe df_empty = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  df_empty.InsertUnchecked(kSpec, std::nullopt,
                           std::nullopt);  // Row 0: {null, null}
  df_empty.SetCellUnchecked<0>(kSpec, 0, std::make_optional(1u));
  ASSERT_EQ(df_empty.GetCellUnchecked<0>(kSpec, 0), std::make_optional(1u));
  df_empty.SetCellUnchecked<0>(kSpec, 0, std::nullopt);
  ASSERT_EQ(df_empty.GetCellUnchecked<0>(kSpec, 0), std::nullopt);

  // Test 8: Full column (all non-null), then set to null
  Dataframe df_full = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  df_full.InsertUnchecked(kSpec, std::make_optional(10u),
                          std::make_optional(pool.InternString("f1")));
  df_full.InsertUnchecked(kSpec, std::make_optional(20u),
                          std::make_optional(pool.InternString("f2")));
  df_full.SetCellUnchecked<0>(kSpec, 0, std::nullopt);
  ASSERT_EQ(df_full.GetCellUnchecked<0>(kSpec, 0), std::nullopt);
  ASSERT_EQ(df_full.GetCellUnchecked<0>(kSpec, 1), std::make_optional(20u));
  ASSERT_EQ(
      df_full.GetCellUnchecked<1>(kSpec, 0),
      std::make_optional(pool.InternString("f1")));  // String col unaffected
}

TEST(DataframeTest, InsertAndSet_WordBoundaryStress) {
  StringPool pool;
  constexpr auto kSpec = CreateTypedDataframeSpec(
      {"sparse_col_uint32"},
      CreateTypedColumnSpec(Uint32(), SparseNullWithPopcountAlways(),
                            Unsorted()));

  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  std::vector<std::optional<uint32_t>> current_state;

  const uint32_t kMaxRows = 130;  // Crosses two 64-bit boundaries

  uint32_t critical_indices_arr[] = {0,   1,   62,  63,  64,          65,
                                     126, 127, 128, 129, kMaxRows - 1};
  std::set<uint32_t> critical_indices(
      critical_indices_arr,
      critical_indices_arr + (sizeof(critical_indices_arr) / sizeof(uint32_t)));

  auto verify_state = [&]() {
    ASSERT_EQ(df.row_count(), current_state.size()) << "Row count mismatch.";
    for (uint32_t i = 0; i < current_state.size(); ++i) {
      ASSERT_EQ(df.GetCellUnchecked<0>(kSpec, i), current_state[i])
          << "Mismatch at index " << i;
    }
  };

  // Phase 1: Insertions (stressing InsertUncheckedInternal)
  for (uint32_t i = 0; i < kMaxRows; ++i) {
    std::optional<uint32_t> val_to_insert;
    if (i % 3 == 0) {  // Introduce some nulls
      val_to_insert = std::nullopt;
    } else {
      val_to_insert = std::make_optional(i * 10);
    }

    df.InsertUnchecked(kSpec, val_to_insert);
    current_state.push_back(val_to_insert);

    if (critical_indices.count(i)) {
      verify_state();
    }
  }
  verify_state();  // Final verification after all insertions

  // Phase 2: Set Operations (stressing SetCellUncheckedInternal)

  // Test 2.1: Non-null -> Null at critical indices
  // Setup: Ensure all elements are non-null to robustly test the transition.
  for (uint32_t i = 0; i < kMaxRows; ++i) {
    if (!current_state[i].has_value()) {
      current_state[i] =
          std::make_optional((i * 10) + 77);  // Arbitrary non-null
      df.SetCellUnchecked<0>(kSpec, i, current_state[i]);
    }
  }
  verify_state();  // Verify setup

  for (uint32_t idx : critical_indices) {
    // Precondition: current_state[idx] is non-null due to setup.
    current_state[idx] = std::nullopt;
    df.SetCellUnchecked<0>(kSpec, idx, std::nullopt);
    verify_state();
  }

  // Test 2.2: Null -> Non-null at critical indices
  // Setup: Ensure all elements are null.
  for (uint32_t i = 0; i < kMaxRows; ++i) {
    if (current_state[i].has_value()) {
      current_state[i] = std::nullopt;
      df.SetCellUnchecked<0>(kSpec, i, std::nullopt);
    }
  }
  verify_state();  // Verify setup

  for (uint32_t idx : critical_indices) {
    // Precondition: current_state[idx] is null.
    uint32_t new_val = (idx * 100) + 1;
    current_state[idx] = std::make_optional(new_val);
    df.SetCellUnchecked<0>(kSpec, idx, std::make_optional(new_val));
    verify_state();
  }

  // Test 2.3: Non-null -> Different Non-null at critical indices
  // Setup: Ensure all elements are non-null (they are from the previous step).
  for (uint32_t idx : critical_indices) {
    // Precondition: current_state[idx] is non-null.
    uint32_t new_val = current_state[idx].value() + 55;
    current_state[idx] = std::make_optional(new_val);
    df.SetCellUnchecked<0>(kSpec, idx, std::make_optional(new_val));
    verify_state();
  }

  // Test 2.4: Sequence of operations crossing a boundary (e.g., 60-70)
  uint32_t seq_start = 60;
  uint32_t seq_end = 70;

  // Sequence 2.4.1: Make the range [seq_start, seq_end] all non-null
  for (uint32_t i = seq_start; i <= seq_end; ++i) {
    uint32_t val = (i * 300) + 3;
    current_state[i] = std::make_optional(val);
    df.SetCellUnchecked<0>(kSpec, i, std::make_optional(val));
    verify_state();
  }

  // Sequence 2.4.2: Make the range [seq_start, seq_end] all null
  for (uint32_t i = seq_start; i <= seq_end; ++i) {
    current_state[i] = std::nullopt;
    df.SetCellUnchecked<0>(kSpec, i, std::nullopt);
    verify_state();
  }

  // Test 2.5: A mix of operations in a specific boundary region (e.g. 63, 64,
  // 65) Setup: 63=non-null, 64=null, 65=non-null
  current_state[63] = std::make_optional(6300u);
  df.SetCellUnchecked<0>(kSpec, 63, current_state[63]);
  current_state[64] = std::nullopt;
  df.SetCellUnchecked<0>(kSpec, 64, current_state[64]);
  current_state[65] = std::make_optional(6500u);
  df.SetCellUnchecked<0>(kSpec, 65, current_state[65]);
  verify_state();

  // Test: Flip them - 63=null
  current_state[63] = std::nullopt;
  df.SetCellUnchecked<0>(kSpec, 63, current_state[63]);
  verify_state();

  // Test: Flip them - 64=non-null
  current_state[64] = std::make_optional(6401u);
  df.SetCellUnchecked<0>(kSpec, 64, current_state[64]);
  verify_state();

  // Test: Flip them - 65=null
  current_state[65] = std::nullopt;
  df.SetCellUnchecked<0>(kSpec, 65, current_state[65]);
  verify_state();
}

TEST(DataframeTest, TypedCursor) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"id", "col2", "col3", "col4"},
      CreateTypedColumnSpec(Id(), NonNull(), IdSorted()),
      CreateTypedColumnSpec(Uint32(), NonNull(), Unsorted()),
      CreateTypedColumnSpec(Int64(), DenseNull(), Unsorted()),
      CreateTypedColumnSpec(String(), SparseNullWithPopcountAlways(),
                            Unsorted()));
  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  df.InsertUnchecked(kSpec, std::monostate(), 10u, std::make_optional(0l),
                     std::make_optional(pool.InternString("foo")));
  df.InsertUnchecked(kSpec, std::monostate(), 20u, std::nullopt, std::nullopt);

  TypedCursor cursor(&df, {FilterSpec{0, 0, Eq{}, {}}}, {});
  {
    cursor.SetFilterValueUnchecked(0, int64_t(0l));
    cursor.ExecuteUnchecked();
    ASSERT_FALSE(cursor.Eof());
    ASSERT_EQ(cursor.GetCellUnchecked<0>(kSpec), 0u);
    ASSERT_EQ(cursor.GetCellUnchecked<1>(kSpec), 10u);
    ASSERT_EQ(cursor.GetCellUnchecked<2>(kSpec), 0l);
    ASSERT_EQ(cursor.GetCellUnchecked<3>(kSpec), pool.InternString("foo"));
    cursor.Next();
    ASSERT_TRUE(cursor.Eof());
  }
  {
    cursor.SetFilterValueUnchecked(0, int64_t(1l));
    cursor.ExecuteUnchecked();
    ASSERT_FALSE(cursor.Eof());
    ASSERT_EQ(cursor.GetCellUnchecked<0>(kSpec), 1u);
    ASSERT_EQ(cursor.GetCellUnchecked<1>(kSpec), 20u);
    ASSERT_EQ(cursor.GetCellUnchecked<2>(kSpec), std::nullopt);
    ASSERT_EQ(cursor.GetCellUnchecked<3>(kSpec), std::nullopt);
    cursor.Next();
    ASSERT_TRUE(cursor.Eof());
  }
}

TEST(DataframeTest, TypedCursorSetMultipleTimes) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"id", "col2", "col3", "col4"},
      CreateTypedColumnSpec(Id(), NonNull(), IdSorted()),
      CreateTypedColumnSpec(Uint32(), NonNull(), Unsorted()),
      CreateTypedColumnSpec(Int64(), DenseNull(), Unsorted()),
      CreateTypedColumnSpec(String(), SparseNullWithPopcountAlways(),
                            Unsorted()));
  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  df.InsertUnchecked(kSpec, std::monostate(), 10u, std::make_optional(0l),
                     std::make_optional(pool.InternString("foo")));
  df.InsertUnchecked(kSpec, std::monostate(), 20u, std::nullopt, std::nullopt);
  {
    TypedCursor cursor(&df, {}, {});
    cursor.ExecuteUnchecked();
    ASSERT_FALSE(cursor.Eof());
    ASSERT_EQ(cursor.GetCellUnchecked<1>(kSpec), 10u);
    cursor.SetCellUnchecked<1>(kSpec, 20u);
    ASSERT_EQ(cursor.GetCellUnchecked<1>(kSpec), 20u);
  }
  {
    TypedCursor cursor(&df, {FilterSpec{1, 0, Eq{}, {}}}, {});
    cursor.SetFilterValueUnchecked(0, int64_t(20));
    cursor.ExecuteUnchecked();
    ASSERT_FALSE(cursor.Eof());
    ASSERT_EQ(cursor.GetCellUnchecked<1>(kSpec), 20u);
    cursor.SetCellUnchecked<1>(kSpec, 20u);
    ASSERT_EQ(cursor.GetCellUnchecked<1>(kSpec), 20u);
  }
}

TEST(DataframeTest,
     QueryPlanEqualityFilterOnNoDuplicatesColumnEstimatesOneRow) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"unique_int_col", "other_col"},
      CreateTypedColumnSpec(Int64(), NonNull(), Unsorted(),
                            NoDuplicates{}),  // Target column with NoDuplicates
      CreateTypedColumnSpec(Int64(), NonNull(), Unsorted(),
                            HasDuplicates{})  // Other column
  );

  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);

  // Insert unique, non-null data into the first column
  df.InsertUnchecked(kSpec, int64_t{10}, int64_t{100});
  df.InsertUnchecked(kSpec, int64_t{20}, int64_t{200});
  df.InsertUnchecked(kSpec, int64_t{30}, int64_t{300});
  df.Finalize();

  // Plan a query with an equality filter on the "unique_int_col".
  std::vector<FilterSpec> filters = {{0, 0, Eq{}, int64_t{20}}};
  LimitSpec limit_spec;

  ASSERT_OK_AND_ASSIGN(Dataframe::QueryPlan plan,
                       df.PlanQuery(filters, {}, {}, limit_spec, 1u));

  // Assert that the estimated_row_count and max_row_count are 1.
  EXPECT_EQ(plan.GetImplForTesting().params.estimated_row_count, 1u);
  EXPECT_EQ(plan.GetImplForTesting().params.max_row_count, 1u);
}

TEST(DataframeTest, SortedFilterWithDuplicatesAndRowCountOfOne) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"sorted_col"},
      CreateTypedColumnSpec(Int64(), NonNull(), Sorted{}, HasDuplicates{}));

  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);

  df.InsertUnchecked(kSpec, int64_t{10});
  df.InsertUnchecked(kSpec, int64_t{20});
  df.InsertUnchecked(kSpec, int64_t{20});
  df.Finalize();

  std::vector<FilterSpec> filters = {{0, 0, Eq{}, int64_t{20}}};
  ASSERT_OK_AND_ASSIGN(Dataframe::QueryPlan plan,
                       df.PlanQuery(filters, {}, {}, {}, 1u));
  EXPECT_EQ(plan.GetImplForTesting().params.estimated_row_count, 1u);
}

TEST(DataframeTest, SortedFilterWithDuplicatesAndRowCountOfZero) {
  static constexpr auto kSpec = CreateTypedDataframeSpec(
      {"sorted_col"},
      CreateTypedColumnSpec(Int64(), NonNull(), Sorted{}, HasDuplicates{}));

  StringPool pool;
  Dataframe df = Dataframe::CreateFromTypedSpec(kSpec, &pool);
  df.Finalize();

  std::vector<FilterSpec> filters = {{0, 0, Eq{}, int64_t{20}}};
  ASSERT_OK_AND_ASSIGN(Dataframe::QueryPlan plan,
                       df.PlanQuery(filters, {}, {}, {}, 1u));
  EXPECT_EQ(plan.GetImplForTesting().params.estimated_row_count, 0u);
}

TEST(DataframeTest, HorizontalConcat) {
  StringPool pool;

  AdhocDataframeBuilder left_builder({"col_a"}, &pool);
  left_builder.PushNonNull(0, int64_t{1});
  left_builder.PushNonNull(0, int64_t{2});
  auto left_or = std::move(left_builder).Build();
  ASSERT_OK(left_or.status());
  Dataframe left = std::move(left_or.value());

  AdhocDataframeBuilder right_builder({"col_b"}, &pool);
  right_builder.PushNonNull(0, int64_t{10});
  right_builder.PushNonNull(0, int64_t{20});
  auto right_or = std::move(right_builder).Build();
  ASSERT_OK(right_or.status());
  Dataframe right = std::move(right_or.value());

  auto result_or =
      Dataframe::HorizontalConcat(std::move(left), std::move(right));
  ASSERT_OK(result_or.status());
  Dataframe result = std::move(result_or.value());

  EXPECT_EQ(result.row_count(), 2u);
  EXPECT_THAT(result.column_names(),
              testing::ElementsAre("col_a", "col_b", "_auto_id"));
}

TEST(DataframeTest, HorizontalConcat_RowCountMismatch) {
  StringPool pool;

  AdhocDataframeBuilder left_builder({"col_a"}, &pool);
  left_builder.PushNonNull(0, int64_t{1});
  left_builder.PushNonNull(0, int64_t{2});
  auto left_or = std::move(left_builder).Build();
  ASSERT_OK(left_or.status());

  AdhocDataframeBuilder right_builder({"col_b"}, &pool);
  right_builder.PushNonNull(0, int64_t{10});
  auto right_or = std::move(right_builder).Build();
  ASSERT_OK(right_or.status());

  auto result = Dataframe::HorizontalConcat(std::move(left_or.value()),
                                            std::move(right_or.value()));
  EXPECT_FALSE(result.ok());
}

TEST(DataframeTest, HorizontalConcat_DuplicateColumnName) {
  StringPool pool;

  AdhocDataframeBuilder left_builder({"col_a"}, &pool);
  left_builder.PushNonNull(0, int64_t{1});
  auto left_or = std::move(left_builder).Build();
  ASSERT_OK(left_or.status());

  AdhocDataframeBuilder right_builder({"col_a"}, &pool);
  right_builder.PushNonNull(0, int64_t{10});
  auto right_or = std::move(right_builder).Build();
  ASSERT_OK(right_or.status());

  auto result = Dataframe::HorizontalConcat(std::move(left_or.value()),
                                            std::move(right_or.value()));
  EXPECT_FALSE(result.ok());
}

TEST(DataframeTest, SelectRows) {
  StringPool pool;

  AdhocDataframeBuilder builder({"col_a"}, &pool);
  builder.PushNonNull(0, int64_t{10});
  builder.PushNonNull(0, int64_t{20});
  builder.PushNonNull(0, int64_t{30});
  builder.PushNonNull(0, int64_t{40});
  auto df_or = std::move(builder).Build();
  ASSERT_OK(df_or.status());

  std::vector<uint32_t> indices = {1, 3};
  Dataframe result =
      std::move(df_or.value())
          .SelectRows(indices.data(), static_cast<uint32_t>(indices.size()));

  EXPECT_EQ(result.row_count(), 2u);
}

}  // namespace perfetto::trace_processor::core::dataframe
