Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
464056e
Support explicit row counts for zero-column tables
madsbk Jun 29, 2026
9162cc7
tmp
madsbk Jun 29, 2026
30f44ed
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jun 29, 2026
eea103c
remove default num_rows
madsbk Jun 29, 2026
e73b9b2
add missing test cover
madsbk Jun 29, 2026
2f32b75
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jun 30, 2026
2f691ab
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jun 30, 2026
bb798cc
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 1, 2026
feb9f33
Based on @davidwendt suggestions
madsbk Jul 1, 2026
0fc70d2
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 1, 2026
3e13c54
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 2, 2026
5a3ab10
Table.__init__: check that all columns have the same size.
madsbk Jul 2, 2026
8b8b824
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 2, 2026
46d8779
Merge branch 'main' into cudf-zero-column-with-multiple-rows
madsbk Jul 6, 2026
59e5817
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 7, 2026
5323b93
typo
madsbk Jul 7, 2026
1bca3d0
detail::scatter the whole table and not per column
madsbk Jul 7, 2026
81e8b4c
Merge branch 'cudf-zero-column-with-multiple-rows' of github.com:mads…
madsbk Jul 7, 2026
54fea03
Table.__init__: remove the all-columns-same-size check when num_rows=…
madsbk Jul 7, 2026
8f63418
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 7, 2026
ff4c5e1
Merge branch 'main' into cudf-zero-column-with-multiple-rows
madsbk Jul 7, 2026
3b1d1f6
Merge branch 'main' into cudf-zero-column-with-multiple-rows
madsbk Jul 8, 2026
b7d3d93
Merge branch 'main' into cudf-zero-column-with-multiple-rows
madsbk Jul 9, 2026
713a64e
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 10, 2026
5576bd0
fix java empty select
madsbk Jul 10, 2026
5b9988f
Merge branch 'main' of github.com:rapidsai/cudf into cudf-zero-column…
madsbk Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cpp/doxygen/developer_guide/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ A type representing a single element of a data type.

### Table

A table is a collection of columns with equal number of elements. A table is the C++ equivalent to
a cuDF Python [DataFrame](https://docs.rapids.ai/api/cudf/stable/api_docs/dataframe.html).
A table is a collection of columns that all have the same number of elements (rows). A table may
also have zero columns while still carrying a row count, mirroring an `(N, 0)` DataFrame. A table is
the C++ equivalent to a cuDF Python
[DataFrame](https://docs.rapids.ai/api/cudf/stable/api_docs/dataframe.html).

### View

Expand Down
7 changes: 3 additions & 4 deletions cpp/include/cudf/contiguous_split.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -407,9 +407,6 @@ class packed_metadata_view {
/**
* @brief The number of rows in the table.
*
* This is the row count of the first top-level column.
* Returns 0 if the table has no columns.
*
* @return The row count
*/
[[nodiscard]] size_type num_rows() const;
Expand All @@ -427,6 +424,8 @@ class packed_metadata_view {
// Span from the first top-level column entry to the end of the metadata buffer.
std::span<std::uint8_t const> _entries;
size_type _num_columns{};
// Table row count, read directly from the serialized table header.
size_type _num_rows{};
};

/** @} */
Expand Down
14 changes: 12 additions & 2 deletions cpp/include/cudf/detail/contiguous_split.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <rmm/cuda_stream_view.hpp>

#include <cstdint>
#include <optional>

namespace cudf {
namespace detail {
Expand Down Expand Up @@ -48,8 +49,14 @@ class metadata_builder {
* @brief Construct a new metadata_builder.
*
* @param num_root_columns is the number of top-level columns
* @param num_rows the table row count to record, or std::nullopt to not record
* one. A row count is only needed to preserve the rows of a zero-column
* table; for a table with one or more columns the row count is derived
* from the columns, so std::nullopt should be passed. If set, num_rows
* must match the size of the table's columns (if any).
*/
explicit metadata_builder(size_type const num_root_columns);
explicit metadata_builder(size_type const num_root_columns,
std::optional<size_type> const num_rows);

/**
* @brief Destructor that will be implemented as default, required because metadata_builder_impl
Expand All @@ -70,6 +77,9 @@ class metadata_builder {
* 3) add_column_info_to_meta(col_a_child_2)
* 4) add_column_info_to_meta(col_b)
*
* @throws std::invalid_argument if a num_rows was passed to the constructor
* and does not match col_size of the first (top-level) column added
*
* @param col_type column data type
* @param col_size column row count
* @param col_null_count column null count
Expand Down Expand Up @@ -114,7 +124,7 @@ std::vector<uint8_t> pack_metadata(table_view const& table,
/**
* @brief Version of the packed metadata layout produced by `pack`/`pack_metadata`.
*/
constexpr std::int32_t packed_metadata_version = 1;
constexpr std::int32_t packed_metadata_version = 2;

} // namespace detail
} // namespace cudf
4 changes: 2 additions & 2 deletions cpp/include/cudf/detail/copy_if.cuh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -48,7 +48,7 @@ std::unique_ptr<table> copy_if(table_view const& input,
{
CUDF_FUNC_RANGE();

if (0 == input.num_rows() || 0 == input.num_columns()) { return empty_like(input); }
if (0 == input.num_rows()) { return empty_like(input); }

auto indices = rmm::device_uvector<size_type>(input.num_rows(), stream);
auto const begin = cuda::counting_iterator<size_type>{0};
Expand Down
6 changes: 4 additions & 2 deletions cpp/include/cudf/detail/gather.cuh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand Down Expand Up @@ -666,7 +666,9 @@ std::unique_ptr<table> gather(table_view const& source_table,
}
}

return std::make_unique<table>(std::move(destination_columns));
// Pass the explicit row count (the gather-map size) so a zero-column input preserves its rows.
auto const num_rows = static_cast<size_type>(cudf::distance(gather_map_begin, gather_map_end));
return std::make_unique<table>(std::move(destination_columns), num_rows);
}

} // namespace detail
Expand Down
4 changes: 2 additions & 2 deletions cpp/include/cudf/detail/scatter.cuh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -429,7 +429,7 @@ std::unique_ptr<table> scatter(table_view const& source,
}
});
}
return std::make_unique<table>(std::move(result));
return std::make_unique<table>(std::move(result), target.num_rows());
}
} // namespace detail
} // namespace cudf
8 changes: 7 additions & 1 deletion cpp/include/cudf/partitioning.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -82,6 +82,9 @@ std::pair<std::unique_ptr<table>, std::vector<size_type>> partition(
* `[offsets[i], offsets[i+1])`. The last offset is always equal to the total
* number of rows in the output table.
*
* An empty `columns_to_hash` is treated as empty, producing an empty result even when `input` has a
* non-zero row count.
*
* @throw std::out_of_range if index is `columns_to_hash` is invalid
*
* @param input The table to partition
Expand Down Expand Up @@ -113,6 +116,9 @@ std::pair<std::unique_ptr<table>, std::vector<size_type>> hash_partition(
* `[offsets[i], offsets[i+1])`. The last offset is always equal to the total
* number of rows in the output table.
*
* A zero-column `keys` table is treated as empty, producing an empty result even when `input` has a
* non-zero row count.
*
* @throw std::invalid_argument if `keys` is not empty and does not have the same number of rows as
* `input`.
*
Expand Down
4 changes: 4 additions & 0 deletions cpp/include/cudf/sorting.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ std::unique_ptr<column> rank(
* result is { 0,1,2, 6,5,4,3, 7,8,9 }
* @endcode
*
* A zero-column `keys` table is treated as empty, producing an empty column even when `keys` has a
* non-zero row count.
*
* @param keys The table that determines the ordering of elements in each segment
* @param segment_offsets The column of `size_type` type containing start offset index for each
* contiguous segment.
Expand All @@ -266,6 +269,7 @@ std::unique_ptr<column> rank(
* `null_order::BEFORE`.
* @param stream CUDA stream used for device memory operations and kernel launches
* @param mr Device memory resource to allocate any returned objects
*
* @return sorted order of the segment sorted table
*
*/
Expand Down
17 changes: 16 additions & 1 deletion cpp/include/cudf/stream_compaction.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -267,6 +267,9 @@ enum class duplicate_keep_option {
* Performance hint: if the input is pre-sorted, `cudf::unique` can produce an equivalent result
* (i.e., same set of output rows) but with less running time than `cudf::distinct`.
*
* A zero-column `input` is treated as empty, producing an empty table even when `input` has a
* non-zero row count.
*
* @throws cudf::logic_error if the `keys` column indices are out of bounds in the `input` table.
*
* @param[in] input input table_view to copy only unique rows
Expand Down Expand Up @@ -299,13 +302,17 @@ std::unique_ptr<table> unique(
* Performance hint: if the input is pre-sorted, `cudf::unique` can produce an equivalent result
* (i.e., same set of output rows) but with less running time than `cudf::distinct`.
*
* A zero-column `input` is treated as empty, producing an empty table even when `input` has a
* non-zero row count.
*
* @param input The input table
* @param keys Vector of indices indicating key columns in the `input` table
* @param keep Copy any, first, last, or none of the found duplicates
* @param nulls_equal Flag to specify whether null elements should be considered as equal
* @param nans_equal Flag to specify whether NaN elements should be considered as equal
* @param stream CUDA stream used for device memory operations and kernel launches
* @param mr Device memory resource used to allocate the returned table
*
* @return Table with distinct rows in an unspecified order
*/
std::unique_ptr<table> distinct(
Expand All @@ -323,12 +330,16 @@ std::unique_ptr<table> distinct(
* Given an `input` table_view, an output vector of all row indices of the distinct rows is
* generated. If there are duplicate rows, which index is kept depends on the `keep` parameter.
*
* A zero-column `input` is treated as empty, producing an empty column even when `input` has a
* non-zero row count.
*
* @param input The input table
* @param keep Get index of any, first, last, or none of the found duplicates
* @param nulls_equal Flag to specify whether null elements should be considered as equal
* @param nans_equal Flag to specify whether NaN elements should be considered as equal
* @param stream CUDA stream used for device memory operations and kernel launches
* @param mr Device memory resource used to allocate the returned vector
*
* @return Column containing the result indices
*/
std::unique_ptr<column> distinct_indices(
Expand All @@ -353,13 +364,17 @@ std::unique_ptr<column> distinct_indices(
* with another values column `3, 4, 5`, the result could contain values `3, 4` or `4, 5` but not
* `4, 3` or `5, 4`.
*
* A zero-column `input` is treated as empty, producing an empty table even when `input` has a
* non-zero row count.
*
* @param input The input table
* @param keys Vector of indices indicating key columns in the `input` table
* @param keep Copy any, first, last, or none of the found duplicates
* @param nulls_equal Flag to specify whether null elements should be considered as equal
* @param nans_equal Flag to specify whether NaN elements should be considered as equal
* @param stream CUDA stream used for device memory operations and kernel launches.
* @param mr Device memory resource used to allocate the returned table
*
* @return Table with distinct rows, preserving input order
*/
std::unique_ptr<table> stable_distinct(
Expand Down
25 changes: 23 additions & 2 deletions cpp/include/cudf/table/table.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand All @@ -24,6 +24,8 @@ namespace CUDF_EXPORT cudf {
/**
* @brief A set of cudf::column's of the same size.
*
* If the set of columns is empty, the table's row count may still be non-zero.
*
* @ingroup table_classes
*/
class table {
Expand Down Expand Up @@ -56,6 +58,25 @@ class table {
*/
table(std::vector<std::unique_ptr<column>>&& columns);

/**
* @brief Moves the contents from a vector of `unique_ptr`s to columns to
* construct a new table with an explicit row count.
*
* This is primarily intended for zero-column tables, which cannot otherwise
* carry a non-zero row count (the row count is normally derived from the
* columns). It is used, for example, when converting a zero-column Arrow array
* that has a non-zero length. When `columns` is non-empty, `num_rows` must equal
* the size of every column.
*
* @throws std::invalid_argument if `columns` is non-empty and `num_rows` does not
* match the size of every column.
*
* @param columns The vector of `unique_ptr`s to columns whose contents will
* be moved into the new table.
* @param num_rows The number of rows in the table.
*/
table(std::vector<std::unique_ptr<column>>&& columns, size_type num_rows);

/**
* @brief Copy the contents of a `table_view` to construct a new `table`.
*
Expand Down Expand Up @@ -146,7 +167,7 @@ class table {
std::vector<column_view> columns(std::distance(begin, end));
std::transform(
begin, end, columns.begin(), [this](auto index) { return _columns.at(index)->view(); });
return table_view{columns};
return table_view{columns, num_rows()};
}

/**
Expand Down
21 changes: 19 additions & 2 deletions cpp/include/cudf/table/table_view.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand Down Expand Up @@ -70,6 +70,21 @@ class table_view_base {
*/
explicit table_view_base(std::vector<ColumnView> const& cols);

/**
* @brief Construct a table from a vector of column views with an explicit row count.
*
* This is primarily intended for zero-column tables, which cannot otherwise carry
* a non-zero row count (the row count is normally derived from the columns). When
* `cols` is non-empty, `num_rows` must equal the size of every column.
*
* @throws std::invalid_argument If `cols` is non-empty and any view's size does not equal
* `num_rows`
*
* @param cols The vector of columns to construct the table from
* @param num_rows The number of rows in the table
*/
table_view_base(std::vector<ColumnView> const& cols, size_type num_rows);

/**
* @brief Returns an iterator to the first view in the `table`.
*
Expand Down Expand Up @@ -181,6 +196,8 @@ bool has_nested_columns(table_view const& table);
/**
* @brief A set of cudf::column_view's of the same size.
*
* If the set of columns is empty, the view's row count may still be non-zero.
*
* @ingroup table_classes
*
* All public member functions and constructors are inherited from
Expand Down Expand Up @@ -230,7 +247,7 @@ class table_view : public detail::table_view_base<column_view> {
{
std::vector<column_view> columns(std::distance(begin, end));
std::transform(begin, end, columns.begin(), [this](auto index) { return this->column(index); });
return table_view{columns};
return table_view{columns, num_rows()};
}

/**
Expand Down
16 changes: 16 additions & 0 deletions cpp/src/copying/concatenate.cu
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
#include <thrust/transform_scan.h>

#include <algorithm>
#include <limits>
#include <numeric>
#include <stdexcept>
#include <utility>

namespace cudf {
Expand Down Expand Up @@ -544,6 +546,20 @@ std::unique_ptr<table> concatenate(std::span<table_view const> tables_to_concat,
}),
"Mismatch in table columns to concatenate.");

// Zero-column tables carry only a row count; concatenation sums their rows.
if (first_table.num_columns() == 0) {
auto const total_rows = std::accumulate(
tables_to_concat.begin(),
tables_to_concat.end(),
std::size_t{0},
[](std::size_t acc, auto const& t) { return acc + static_cast<std::size_t>(t.num_rows()); });
CUDF_EXPECTS(total_rows <= static_cast<std::size_t>(std::numeric_limits<size_type>::max()),
"Total number of rows exceeds the column size limit",
std::overflow_error);
return std::make_unique<table>(std::vector<std::unique_ptr<column>>{},
static_cast<size_type>(total_rows));
}

std::vector<std::unique_ptr<column>> concat_columns;
for (size_type i = 0; i < first_table.num_columns(); ++i) {
std::vector<column_view> cols;
Expand Down
Loading
Loading