|
37 | 37 | #include "iceberg/snapshot.h" |
38 | 38 | #include "iceberg/table.h" |
39 | 39 | #include "iceberg/table_metadata.h" |
| 40 | +#include "iceberg/util/content_file_util.h" |
| 41 | +#include "iceberg/util/int128.h" |
40 | 42 | #include "iceberg/util/macros.h" |
41 | 43 | #include "iceberg/util/snapshot_util.h" |
42 | 44 | #include "iceberg/util/timepoint.h" |
@@ -179,11 +181,78 @@ FileScanTask::FileScanTask(std::shared_ptr<DataFile> data_file, |
179 | 181 | ICEBERG_DCHECK(data_file_ != nullptr, "Data file cannot be null for FileScanTask"); |
180 | 182 | } |
181 | 183 |
|
182 | | -int64_t FileScanTask::size_bytes() const { return data_file_->file_size_in_bytes; } |
| 184 | +FileScanTask::FileScanTask(std::shared_ptr<DataFile> data_file, |
| 185 | + std::vector<std::shared_ptr<DataFile>> delete_files, |
| 186 | + std::shared_ptr<Expression> residual_filter, Range range) |
| 187 | + : FileScanTask(std::move(data_file), std::move(delete_files), |
| 188 | + std::move(residual_filter)) { |
| 189 | + range_ = range; |
| 190 | +} |
| 191 | + |
| 192 | +Result<std::shared_ptr<FileScanTask>> FileScanTask::MakeSplit( |
| 193 | + std::shared_ptr<DataFile> data_file, int64_t start, int64_t length, |
| 194 | + std::vector<std::shared_ptr<DataFile>> delete_files, |
| 195 | + std::shared_ptr<Expression> filter) { |
| 196 | + if (data_file == nullptr) { |
| 197 | + return InvalidArgument("Cannot create a file scan task without a data file"); |
| 198 | + } |
| 199 | + |
| 200 | + const int64_t file_size = data_file->file_size_in_bytes; |
| 201 | + if (file_size < 0) { |
| 202 | + return InvalidArgument("Cannot split file {} with negative size {}", |
| 203 | + data_file->file_path, file_size); |
| 204 | + } |
| 205 | + if (start < 0 || start > file_size) { |
| 206 | + return InvalidArgument("Split start {} is outside file {} of size {}", start, |
| 207 | + data_file->file_path, file_size); |
| 208 | + } |
| 209 | + // Subtraction is safe after validating start, even when file_size is INT64_MAX. |
| 210 | + if (length < 0 || length > file_size - start) { |
| 211 | + return InvalidArgument("Split length {} from start {} is outside file {} of size {}", |
| 212 | + length, start, data_file->file_path, file_size); |
| 213 | + } |
| 214 | + // A full-file range is a whole-file task (is_split() is false). This is also the |
| 215 | + // only valid empty range: [0, 0) of an empty file. |
| 216 | + if (start == 0 && length == file_size) { |
| 217 | + return std::make_shared<FileScanTask>(std::move(data_file), std::move(delete_files), |
| 218 | + std::move(filter)); |
| 219 | + } |
| 220 | + if (length == 0) { |
| 221 | + return InvalidArgument("A partial split of file {} must have positive length", |
| 222 | + data_file->file_path); |
| 223 | + } |
| 224 | + |
| 225 | + return std::shared_ptr<FileScanTask>( |
| 226 | + new FileScanTask(std::move(data_file), std::move(delete_files), std::move(filter), |
| 227 | + Range{.start = start, .length = length, .file_size = file_size})); |
| 228 | +} |
| 229 | + |
| 230 | +int64_t FileScanTask::length() const { |
| 231 | + return range_ ? range_->length : data_file_->file_size_in_bytes; |
| 232 | +} |
| 233 | + |
| 234 | +int64_t FileScanTask::size_bytes() const { return length(); } |
183 | 235 |
|
184 | 236 | int32_t FileScanTask::files_count() const { return 1; } |
185 | 237 |
|
186 | | -int64_t FileScanTask::estimated_row_count() const { return data_file_->record_count; } |
| 238 | +int64_t FileScanTask::estimated_row_count() const { |
| 239 | + if (!range_) { |
| 240 | + return data_file_->record_count; |
| 241 | + } |
| 242 | + |
| 243 | + const int64_t record_count = data_file_->record_count; |
| 244 | + if (record_count <= 0) { |
| 245 | + return 0; |
| 246 | + } |
| 247 | + |
| 248 | + // floor(end * rows / size) - floor(start * rows / size), so splits that cover |
| 249 | + // the file sum exactly to record_count. int128 avoids overflow. |
| 250 | + const auto end = static_cast<int128_t>(range_->start) + range_->length; |
| 251 | + const auto count_at_end = end * record_count / range_->file_size; |
| 252 | + const auto count_at_start = |
| 253 | + static_cast<int128_t>(range_->start) * record_count / range_->file_size; |
| 254 | + return static_cast<int64_t>(count_at_end - count_at_start); |
| 255 | +} |
187 | 256 |
|
188 | 257 | // ChangelogScanTask implementation |
189 | 258 |
|
|
0 commit comments