diff --git a/cpp/benchmarks/join/join.cu b/cpp/benchmarks/join/join.cu index 647e37aa97d..1b9e8cb1cfe 100644 --- a/cpp/benchmarks/join/join.cu +++ b/cpp/benchmarks/join/join.cu @@ -27,7 +27,10 @@ void nvbench_inner_join(nvbench::state& state, cudf::table_view const& right_input, cudf::null_equality compare_nulls, rmm::cuda_stream_view stream) { - cudf::hash_join hj_obj(left_input, compare_nulls, stream); + auto const has_nulls = cudf::has_nested_nulls(left_input) || cudf::has_nested_nulls(right_input) + ? cudf::nullable_join::YES + : cudf::nullable_join::NO; + cudf::hash_join hj_obj(left_input, has_nulls, compare_nulls, stream); return hj_obj.inner_join(right_input, std::nullopt, stream); }; @@ -44,7 +47,10 @@ void nvbench_left_join(nvbench::state& state, cudf::table_view const& right_input, cudf::null_equality compare_nulls, rmm::cuda_stream_view stream) { - cudf::hash_join hj_obj(left_input, compare_nulls, stream); + auto const has_nulls = cudf::has_nested_nulls(left_input) || cudf::has_nested_nulls(right_input) + ? cudf::nullable_join::YES + : cudf::nullable_join::NO; + cudf::hash_join hj_obj(left_input, has_nulls, compare_nulls, stream); return hj_obj.left_join(right_input, std::nullopt, stream); }; @@ -61,7 +67,10 @@ void nvbench_full_join(nvbench::state& state, cudf::table_view const& right_input, cudf::null_equality compare_nulls, rmm::cuda_stream_view stream) { - cudf::hash_join hj_obj(left_input, compare_nulls, stream); + auto const has_nulls = cudf::has_nested_nulls(left_input) || cudf::has_nested_nulls(right_input) + ? cudf::nullable_join::YES + : cudf::nullable_join::NO; + cudf::hash_join hj_obj(left_input, has_nulls, compare_nulls, stream); return hj_obj.full_join(right_input, std::nullopt, stream); }; diff --git a/cpp/include/cudf/detail/join.hpp b/cpp/include/cudf/detail/join.hpp index ce32be59983..fe97eb5a806 100644 --- a/cpp/include/cudf/detail/join.hpp +++ b/cpp/include/cudf/detail/join.hpp @@ -73,10 +73,10 @@ struct hash_join { hash_join& operator=(hash_join&&) = delete; private: - bool const _is_empty; ///< true if `_hash_table` is empty - rmm::device_buffer const _composite_bitmask; ///< Bitmask to denote whether a row is valid - cudf::null_equality const _nulls_equal; ///< whether to consider nulls as equal - cudf::table_view _build; ///< input table to build the hash map + bool const _is_empty; ///< true if `_hash_table` is empty + bool const _has_nulls; ///< true if nulls are present in either build table or any probe table + cudf::null_equality const _nulls_equal; ///< whether to consider nulls as equal + cudf::table_view _build; ///< input table to build the hash map std::shared_ptr _preprocessed_build; ///< input table preprocssed for row operators map_type _hash_table; ///< hash table built on `_build` @@ -89,10 +89,13 @@ struct hash_join { * @throw cudf::logic_error if the number of rows in `build` table exceeds MAX_JOIN_SIZE. * * @param build The build table, from which the hash table is built. + * @param has_nulls Flag to indicate if the there exists any nulls in the `build` table or + * any `probe` table that will be used later for join. * @param compare_nulls Controls whether null join-key values should match or not. * @param stream CUDA stream used for device memory operations and kernel launches. */ hash_join(cudf::table_view const& build, + bool has_nulls, cudf::null_equality compare_nulls, rmm::cuda_stream_view stream); diff --git a/cpp/include/cudf/join.hpp b/cpp/include/cudf/join.hpp index b613a661d95..278949aa955 100644 --- a/cpp/include/cudf/join.hpp +++ b/cpp/include/cudf/join.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022, NVIDIA CORPORATION. + * Copyright (c) 2019-2023, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -256,6 +256,16 @@ std::unique_ptr cross_join( cudf::table_view const& right, rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); +/** + * @brief The enum class to specify if any of the input join tables (`build` table and any later + * `probe` table) has nulls. + * + * This is used upon hash_join object construction to specify the existence of nulls in all the + * possible input tables. If such null existence is unknown, `YES` should be used as the default + * option. + */ +enum class nullable_join : bool { YES, NO }; + /** * @brief Hash join that builds hash table in creation and probes results in subsequent `*_join` * member functions. @@ -289,6 +299,17 @@ class hash_join { null_equality compare_nulls, rmm::cuda_stream_view stream = cudf::get_default_stream()); + /** + * @copydoc hash_join(cudf::table_view const&, null_equality, rmm::cuda_stream_view) + * + * @param has_nulls Flag to indicate if the there exists any nulls in the `build` table or + * any `probe` table that will be used later for join + */ + hash_join(cudf::table_view const& build, + nullable_join has_nulls, + null_equality compare_nulls, + rmm::cuda_stream_view stream = cudf::get_default_stream()); + /** * Returns the row indices that can be used to construct the result of performing * an inner join between two tables. @see cudf::inner_join(). Behavior is undefined if the @@ -300,6 +321,9 @@ class hash_join { * @param mr Device memory resource used to allocate the returned table and columns' device * memory. * + * @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not + * constructed with null check. + * * @return A pair of columns [`left_indices`, `right_indices`] that can be used to construct * the result of performing an inner join between two tables with `build` and `probe` * as the the join keys . @@ -322,6 +346,9 @@ class hash_join { * @param mr Device memory resource used to allocate the returned table and columns' device * memory. * + * @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not + * constructed with null check. + * * @return A pair of columns [`left_indices`, `right_indices`] that can be used to construct * the result of performing a left join between two tables with `build` and `probe` * as the the join keys . @@ -344,6 +371,9 @@ class hash_join { * @param mr Device memory resource used to allocate the returned table and columns' device * memory. * + * @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not + * constructed with null check. + * * @return A pair of columns [`left_indices`, `right_indices`] that can be used to construct * the result of performing a full join between two tables with `build` and `probe` * as the the join keys . @@ -362,6 +392,9 @@ class hash_join { * @param probe The probe table, from which the tuples are probed * @param stream CUDA stream used for device memory operations and kernel launches * + * @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not + * constructed with null check. + * * @return The exact number of output when performing an inner join between two tables with * `build` and `probe` as the the join keys . */ @@ -375,6 +408,9 @@ class hash_join { * @param probe The probe table, from which the tuples are probed * @param stream CUDA stream used for device memory operations and kernel launches * + * @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not + * constructed with null check. + * * @return The exact number of output when performing a left join between two tables with `build` * and `probe` as the the join keys . */ @@ -390,6 +426,9 @@ class hash_join { * @param mr Device memory resource used to allocate the intermediate table and columns' device * memory. * + * @throw cudf::logic_error If the input probe table has nulls while this hash_join object was not + * constructed with null check. + * * @return The exact number of output when performing a full join between two tables with `build` * and `probe` as the the join keys . */ diff --git a/cpp/src/join/hash_join.cu b/cpp/src/join/hash_join.cu index d6652bd7a0c..33f44dbf8f5 100644 --- a/cpp/src/join/hash_join.cu +++ b/cpp/src/join/hash_join.cu @@ -359,11 +359,11 @@ std::size_t get_full_join_size( template hash_join::hash_join(cudf::table_view const& build, + bool has_nulls, cudf::null_equality compare_nulls, rmm::cuda_stream_view stream) - : _is_empty{build.num_rows() == 0}, - _composite_bitmask{ - cudf::detail::bitmask_and(build, stream, rmm::mr::get_current_device_resource()).first}, + : _has_nulls(has_nulls), + _is_empty{build.num_rows() == 0}, _nulls_equal{compare_nulls}, _hash_table{compute_hash_table_size(build.num_rows()), cuco::empty_key{std::numeric_limits::max()}, @@ -381,11 +381,14 @@ hash_join::hash_join(cudf::table_view const& build, if (_is_empty) { return; } + auto const row_bitmask = + cudf::detail::bitmask_and(build, stream, rmm::mr::get_current_device_resource()).first; cudf::detail::build_join_hash_table(_build, _preprocessed_build, _hash_table, + _has_nulls, _nulls_equal, - static_cast(_composite_bitmask.data()), + reinterpret_cast(row_bitmask.data()), stream); } @@ -434,19 +437,21 @@ std::size_t hash_join::inner_join_size(cudf::table_view const& probe, // Return directly if build table is empty if (_is_empty) { return 0; } + CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe), + "Probe table has nulls while build table was not hashed with null check."); + auto const preprocessed_probe = cudf::experimental::row::equality::preprocessed_table::create(probe, stream); - return cudf::detail::compute_join_output_size( - _build, - probe, - _preprocessed_build, - preprocessed_probe, - _hash_table, - cudf::detail::join_kind::INNER_JOIN, - cudf::has_nested_nulls(probe) || cudf::has_nested_nulls(_build), - _nulls_equal, - stream); + return cudf::detail::compute_join_output_size(_build, + probe, + _preprocessed_build, + preprocessed_probe, + _hash_table, + cudf::detail::join_kind::INNER_JOIN, + _has_nulls, + _nulls_equal, + stream); } template @@ -458,19 +463,21 @@ std::size_t hash_join::left_join_size(cudf::table_view const& probe, // Trivial left join case - exit early if (_is_empty) { return probe.num_rows(); } + CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe), + "Probe table has nulls while build table was not hashed with null check."); + auto const preprocessed_probe = cudf::experimental::row::equality::preprocessed_table::create(probe, stream); - return cudf::detail::compute_join_output_size( - _build, - probe, - _preprocessed_build, - preprocessed_probe, - _hash_table, - cudf::detail::join_kind::LEFT_JOIN, - cudf::has_nested_nulls(probe) || cudf::has_nested_nulls(_build), - _nulls_equal, - stream); + return cudf::detail::compute_join_output_size(_build, + probe, + _preprocessed_build, + preprocessed_probe, + _hash_table, + cudf::detail::join_kind::LEFT_JOIN, + _has_nulls, + _nulls_equal, + stream); } template @@ -483,19 +490,21 @@ std::size_t hash_join::full_join_size(cudf::table_view const& probe, // Trivial left join case - exit early if (_is_empty) { return probe.num_rows(); } + CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe), + "Probe table has nulls while build table was not hashed with null check."); + auto const preprocessed_probe = cudf::experimental::row::equality::preprocessed_table::create(probe, stream); - return cudf::detail::get_full_join_size( - _build, - probe, - _preprocessed_build, - preprocessed_probe, - _hash_table, - cudf::has_nested_nulls(probe) || cudf::has_nested_nulls(_build), - _nulls_equal, - stream, - mr); + return cudf::detail::get_full_join_size(_build, + probe, + _preprocessed_build, + preprocessed_probe, + _hash_table, + _has_nulls, + _nulls_equal, + stream, + mr); } template @@ -514,20 +523,22 @@ hash_join::probe_join_indices(cudf::table_view const& probe_table, CUDF_EXPECTS(!_is_empty, "Hash table of hash join is null."); + CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe_table), + "Probe table has nulls while build table was not hashed with null check."); + auto const preprocessed_probe = cudf::experimental::row::equality::preprocessed_table::create(probe_table, stream); - auto join_indices = cudf::detail::probe_join_hash_table( - _build, - probe_table, - _preprocessed_build, - preprocessed_probe, - _hash_table, - join, - cudf::has_nested_nulls(probe_table) || cudf::has_nested_nulls(_build), - _nulls_equal, - output_size, - stream, - mr); + auto join_indices = cudf::detail::probe_join_hash_table(_build, + probe_table, + _preprocessed_build, + preprocessed_probe, + _hash_table, + join, + _has_nulls, + _nulls_equal, + output_size, + stream, + mr); if (join == cudf::detail::join_kind::FULL_JOIN) { auto complement_indices = detail::get_left_join_indices_complement( @@ -553,6 +564,9 @@ hash_join::compute_hash_join(cudf::table_view const& probe, CUDF_EXPECTS(_build.num_columns() == probe.num_columns(), "Mismatch in number of columns to be joined on"); + CUDF_EXPECTS(_has_nulls || !cudf::has_nested_nulls(probe), + "Probe table has nulls while build table was not hashed with null check."); + if (is_trivial_join(probe, _build, join)) { return std::pair(std::make_unique>(0, stream, mr), std::make_unique>(0, stream, mr)); @@ -574,7 +588,17 @@ hash_join::~hash_join() = default; hash_join::hash_join(cudf::table_view const& build, null_equality compare_nulls, rmm::cuda_stream_view stream) - : _impl{std::make_unique(build, compare_nulls, stream)} + // If we cannot know beforehand about null existence then let's assume that there are nulls. + : hash_join(build, nullable_join::YES, compare_nulls, stream) +{ +} + +hash_join::hash_join(cudf::table_view const& build, + nullable_join has_nulls, + null_equality compare_nulls, + rmm::cuda_stream_view stream) + : _impl{std::make_unique( + build, has_nulls == nullable_join::YES, compare_nulls, stream)} { } diff --git a/cpp/src/join/join.cu b/cpp/src/join/join.cu index dbc543f4dcd..ae025b1a213 100644 --- a/cpp/src/join/join.cu +++ b/cpp/src/join/join.cu @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022, NVIDIA CORPORATION. + * Copyright (c) 2019-2023, NVIDIA CORPORATION. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,18 +43,21 @@ inner_join(table_view const& left_input, rmm::mr::get_current_device_resource()); // temporary objects returned // now rebuild the table views with the updated ones - auto const left = matched.second.front(); - auto const right = matched.second.back(); + auto const left = matched.second.front(); + auto const right = matched.second.back(); + auto const has_nulls = cudf::has_nested_nulls(left) || cudf::has_nested_nulls(right) + ? cudf::nullable_join::YES + : cudf::nullable_join::NO; // For `inner_join`, we can freely choose either the `left` or `right` table to use for // building/probing the hash map. Because building is typically more expensive than probing, we // build the hash map from the smaller table. if (right.num_rows() > left.num_rows()) { - cudf::hash_join hj_obj(left, compare_nulls, stream); + cudf::hash_join hj_obj(left, has_nulls, compare_nulls, stream); auto [right_result, left_result] = hj_obj.inner_join(right, std::nullopt, stream, mr); return std::pair(std::move(left_result), std::move(right_result)); } else { - cudf::hash_join hj_obj(right, compare_nulls, stream); + cudf::hash_join hj_obj(right, has_nulls, compare_nulls, stream); return hj_obj.inner_join(left, std::nullopt, stream, mr); } } @@ -76,8 +79,11 @@ left_join(table_view const& left_input, // now rebuild the table views with the updated ones table_view const left = matched.second.front(); table_view const right = matched.second.back(); + auto const has_nulls = cudf::has_nested_nulls(left) || cudf::has_nested_nulls(right) + ? cudf::nullable_join::YES + : cudf::nullable_join::NO; - cudf::hash_join hj_obj(right, compare_nulls, stream); + cudf::hash_join hj_obj(right, has_nulls, compare_nulls, stream); return hj_obj.left_join(left, std::nullopt, stream, mr); } @@ -98,8 +104,11 @@ full_join(table_view const& left_input, // now rebuild the table views with the updated ones table_view const left = matched.second.front(); table_view const right = matched.second.back(); + auto const has_nulls = cudf::has_nested_nulls(left) || cudf::has_nested_nulls(right) + ? cudf::nullable_join::YES + : cudf::nullable_join::NO; - cudf::hash_join hj_obj(right, compare_nulls, stream); + cudf::hash_join hj_obj(right, has_nulls, compare_nulls, stream); return hj_obj.full_join(left, std::nullopt, stream, mr); } diff --git a/cpp/src/join/join_common_utils.cuh b/cpp/src/join/join_common_utils.cuh index 5daf9988768..0784e2a8ea3 100644 --- a/cpp/src/join/join_common_utils.cuh +++ b/cpp/src/join/join_common_utils.cuh @@ -158,6 +158,7 @@ get_trivial_left_join_indices(table_view const& left, * @param preprocessed_build shared_ptr to cudf::experimental::row::equality::preprocessed_table for * build * @param hash_table Build hash table. + * @param has_nulls Flag to denote if build or probe tables have nested nulls * @param nulls_equal Flag to denote nulls are equal or not. * @param bitmask Bitmask to denote whether a row is valid. * @param stream CUDA stream used for device memory operations and kernel launches. @@ -168,6 +169,7 @@ void build_join_hash_table( cudf::table_view const& build, std::shared_ptr const& preprocessed_build, MultimapType& hash_table, + bool has_nulls, null_equality nulls_equal, [[maybe_unused]] bitmask_type const* bitmask, rmm::cuda_stream_view stream) @@ -176,7 +178,7 @@ void build_join_hash_table( CUDF_EXPECTS(0 != build.num_rows(), "Build side table has no rows"); auto const row_hash = experimental::row::hash::row_hasher{preprocessed_build}; - auto const hash_build = row_hash.device_hasher(nullate::DYNAMIC{cudf::has_nested_nulls(build)}); + auto const hash_build = row_hash.device_hasher(nullate::DYNAMIC{has_nulls}); auto const empty_key_sentinel = hash_table.get_empty_key_sentinel(); make_pair_function pair_func{hash_build, empty_key_sentinel}; diff --git a/cpp/src/join/mixed_join.cu b/cpp/src/join/mixed_join.cu index 26200e41d5f..e796ca7520d 100644 --- a/cpp/src/join/mixed_join.cu +++ b/cpp/src/join/mixed_join.cu @@ -110,7 +110,7 @@ mixed_join( // output column and follow the null-supporting expression evaluation code // path. auto const has_nulls = - cudf::has_nulls(left_equality) || cudf::has_nulls(right_equality) || + cudf::has_nested_nulls(left_equality) || cudf::has_nested_nulls(right_equality) || binary_predicate.may_evaluate_null(left_conditional, right_conditional, stream); auto const parser = ast::detail::expression_parser{ @@ -146,6 +146,7 @@ mixed_join( build_join_hash_table(build, preprocessed_build, hash_table, + has_nulls, compare_nulls, static_cast(row_bitmask.data()), stream); @@ -365,7 +366,7 @@ compute_mixed_join_output_size(table_view const& left_equality, // output column and follow the null-supporting expression evaluation code // path. auto const has_nulls = - cudf::has_nulls(left_equality) || cudf::has_nulls(right_equality) || + cudf::has_nested_nulls(left_equality) || cudf::has_nested_nulls(right_equality) || binary_predicate.may_evaluate_null(left_conditional, right_conditional, stream); auto const parser = ast::detail::expression_parser{ @@ -401,6 +402,7 @@ compute_mixed_join_output_size(table_view const& left_equality, build_join_hash_table(build, preprocessed_build, hash_table, + has_nulls, compare_nulls, static_cast(row_bitmask.data()), stream); diff --git a/cpp/tests/join/join_tests.cpp b/cpp/tests/join/join_tests.cpp index 4062c3bd921..e0b42ff9797 100644 --- a/cpp/tests/join/join_tests.cpp +++ b/cpp/tests/join/join_tests.cpp @@ -1340,7 +1340,7 @@ TEST_F(JoinTest, HashJoinSequentialProbes) Table t1(std::move(cols1)); - cudf::hash_join hash_join(t1, cudf::null_equality::EQUAL); + cudf::hash_join hash_join(t1, cudf::nullable_join::NO, cudf::null_equality::EQUAL); { CVector cols0; @@ -1429,8 +1429,11 @@ TEST_F(JoinTest, HashJoinWithStructsAndNulls) Table t0(std::move(cols0)); Table t1(std::move(cols1)); + auto const has_nulls = cudf::has_nested_nulls(t0) || cudf::has_nested_nulls(t1) + ? cudf::nullable_join::YES + : cudf::nullable_join::NO; - auto hash_join = cudf::hash_join(t1, cudf::null_equality::EQUAL); + auto hash_join = cudf::hash_join(t1, has_nulls, cudf::null_equality::EQUAL); { auto output_size = hash_join.left_join_size(t0); @@ -1463,6 +1466,110 @@ TEST_F(JoinTest, HashJoinWithStructsAndNulls) } } +TEST_F(JoinTest, HashJoinWithNullsOneSide) +{ + auto const t0 = [] { + column_wrapper col0{2, 2, 0, 4, 3}; + column_wrapper col1{1, 10, 1, 2, 1}; + CVector cols; + cols.emplace_back(col0.release()); + cols.emplace_back(col1.release()); + return Table{std::move(cols)}; + }(); + + auto const t1 = [] { + column_wrapper col0{1, 2, 3, 4, 5, 2, 2, 0, 4, 3, 1, 2, 3, 4, 5}; + column_wrapper col1{{1, 2, 3, 4, 5, 1, 0, 1, 2, 1, 1, 2, 3, 4, 5}, + cudf::test::iterators::null_at(6)}; + CVector cols; + cols.emplace_back(col0.release()); + cols.emplace_back(col1.release()); + return Table{std::move(cols)}; + }(); + + auto const hash_join = cudf::hash_join(t0, cudf::null_equality::EQUAL); + auto constexpr invalid = std::numeric_limits::min(); // invalid index sentinel + + auto const sort_result = [](auto const& result) { + auto const left_cv = cudf::column_view{cudf::data_type{cudf::type_id::INT32}, + static_cast(result.first->size()), + result.first->data()}; + auto const right_cv = cudf::column_view{cudf::data_type{cudf::type_id::INT32}, + static_cast(result.second->size()), + result.second->data()}; + auto sorted_left = cudf::sort(cudf::table_view{{left_cv}}); + auto sorted_right = cudf::sort(cudf::table_view{{right_cv}}); + return std::pair{std::move(sorted_left), std::move(sorted_right)}; + }; + + { + auto const output_size = hash_join.left_join_size(t1); + auto const result = hash_join.left_join(t1, std::optional{output_size}); + auto const [sorted_left_indices, sorted_right_indices] = sort_result(result); + + auto const expected_left_indices = + column_wrapper{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; + auto const expected_right_indices = column_wrapper{invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + 0, + 2, + 3, + 4}; + + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected_left_indices, sorted_left_indices->get_column(0)); + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected_right_indices, sorted_right_indices->get_column(0)); + } + + { + auto const output_size = hash_join.inner_join_size(t1); + auto const result = hash_join.inner_join(t1, std::optional{output_size}); + auto const [sorted_left_indices, sorted_right_indices] = sort_result(result); + + auto const expected_left_indices = column_wrapper{5, 7, 8, 9}; + auto const expected_right_indices = column_wrapper{0, 2, 3, 4}; + + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected_left_indices, sorted_left_indices->get_column(0)); + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected_right_indices, sorted_right_indices->get_column(0)); + } + + { + auto const output_size = hash_join.full_join_size(t1); + auto const result = hash_join.full_join(t1, std::optional{output_size}); + auto const [sorted_left_indices, sorted_right_indices] = sort_result(result); + + auto const expected_left_indices = + column_wrapper{invalid, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; + auto const expected_right_indices = column_wrapper{invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + invalid, + 0, + 1, + 2, + 3, + 4}; + + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected_left_indices, sorted_left_indices->get_column(0)); + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected_right_indices, sorted_right_indices->get_column(0)); + } +} + TEST_F(JoinTest, HashJoinLargeOutputSize) { // self-join a table of zeroes to generate an output row count that would overflow int32_t @@ -1472,7 +1579,7 @@ TEST_F(JoinTest, HashJoinLargeOutputSize) cudaMemsetAsync(zeroes.data(), 0, zeroes.size(), cudf::get_default_stream().value())); cudf::column_view col_zeros(cudf::data_type{cudf::type_id::INT32}, col_size, zeroes.data()); cudf::table_view tview{{col_zeros}}; - cudf::hash_join hash_join(tview, cudf::null_equality::UNEQUAL); + cudf::hash_join hash_join(tview, cudf::nullable_join::NO, cudf::null_equality::UNEQUAL); std::size_t output_size = hash_join.inner_join_size(tview); EXPECT_EQ(col_size * col_size, output_size); }