| 256 | } |
| 257 | |
| 258 | Chunk NegativeLimitByTransform::generate() |
| 259 | { |
| 260 | if (!offset_rows_dropped) |
| 261 | { |
| 262 | /// Drop trailing `offset` rows per group, then release the hash table and per-group |
| 263 | /// state - from here on `candidate_list` alone carries the answer in input order. |
| 264 | for (auto & window : group_windows) |
| 265 | dropOffsetRows(window); |
| 266 | offset_rows_dropped = true; |
| 267 | releaseHashTable(data); |
| 268 | hash_method_context.reset(); |
| 269 | std::vector<GroupWindow>{}.swap(group_windows); |
| 270 | } |
| 271 | |
| 272 | if (candidate_list.empty()) |
| 273 | return {}; |
| 274 | |
| 275 | /// At this point, `candidate_list` contains all the rows that will be part of the output. |
| 276 | /// Per grouping key we have at most `group_length` rows. `candidate_list` holds the data in |
| 277 | /// input order, so we can emit from the head of the list. |
| 278 | |
| 279 | /// Coalesce consecutive slices sharing one source chunk into a single output chunk. |
| 280 | /// This avoids emitting many tiny chunks when the grouping key has high cardinality. |
| 281 | auto coalesce_slice_begin = candidate_list.begin(); |
| 282 | auto columns_ptr = coalesce_slice_begin->columns; |
| 283 | auto coalesce_slice_end = std::next(coalesce_slice_begin); |
| 284 | while (coalesce_slice_end != candidate_list.end() && coalesce_slice_end->columns == columns_ptr) |
| 285 | ++coalesce_slice_end; |
| 286 | |
| 287 | Chunk chunk; |
| 288 | if (std::next(coalesce_slice_begin) == coalesce_slice_end) /// Only one slice in the chunk |
| 289 | { |
| 290 | chunk = materializeSliceToChunkIfNeeded(*coalesce_slice_begin); |
| 291 | } |
| 292 | else |
| 293 | { |
| 294 | const Columns & source = *columns_ptr; |
| 295 | UInt64 chunk_size = source.front()->size(); |
| 296 | |
| 297 | /// Compute some statistics to pick an optimized materialization strategy below. |
| 298 | UInt64 num_output_rows = 0; |
| 299 | UInt64 first_slice_start_row = coalesce_slice_begin->start; |
| 300 | UInt64 last_slice_end_row = 0; |
| 301 | for (auto it = coalesce_slice_begin; it != coalesce_slice_end; ++it) |
| 302 | { |
| 303 | num_output_rows += it->length; |
| 304 | last_slice_end_row = it->start + it->length; |
| 305 | } |
| 306 | |
| 307 | if (num_output_rows == chunk_size) |
| 308 | { |
| 309 | /// Every row of the source chunk survived across different keys. |
| 310 | chunk = Chunk(Columns(source), num_output_rows); |
| 311 | } |
| 312 | else if (last_slice_end_row - first_slice_start_row == num_output_rows) |
| 313 | { |
| 314 | /// Slices form one contiguous segment - single cut per column, which is more efficient than mask+filter. |
| 315 | Columns result; |
nothing calls this directly
no test coverage detected