blob: 87d18153408180efc615e30ef698645bd75d33a3 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
"""Worker sharding and row-position selection for Python scan planning."""
from typing import List, Tuple
from pypaimon.read.sliced_split import SlicedSplit
from pypaimon.read.split import Split
def validate_shard(index: int, count: int) -> None:
if not isinstance(count, int) or count <= 0:
raise ValueError("number_of_para_subtasks must be a positive integer")
if not isinstance(index, int) or index < 0:
raise ValueError("idx_of_this_subtask must be a non-negative integer")
if index >= count:
raise ValueError("idx_of_this_subtask must be less than number_of_para_subtasks")
def validate_slice(start: int, end: int) -> None:
if not isinstance(start, int) or not isinstance(end, int) or start < 0 or start >= end:
raise ValueError("start_pos must be non-negative and less than end_pos; both must be integers")
def shard_range(total: int, index: int, count: int) -> Tuple[int, int]:
base, remainder = divmod(total, count)
start = index * base + min(index, remainder)
return start, start + base + int(index < remainder)
def java_file_name_shard(file_name: str, count: int) -> int:
"""Java SnapshotReaderImpl: abs(String.hashCode(file_name) % count)."""
encoded = file_name.encode('utf-16-be', 'surrogatepass')
hash_code = 0
for offset in range(0, len(encoded), 2):
unit = (encoded[offset] << 8) | encoded[offset + 1]
hash_code = (31 * hash_code + unit) & 0xffffffff
if hash_code >= 0x80000000:
hash_code -= 0x100000000
# Python integers do not overflow for Integer.MIN_VALUE. abs(hash) % count
# therefore equals Java's abs(hash % count), including negative hashes.
return abs(hash_code) % count
def slice_append_splits(splits: List[Split], start: int, end: int) -> List[Split]:
"""Select physical rows in [start, end), preserving file order and DVs.
File statistics have already pruned the input; residual row predicates and
deletion vectors are applied by the reader after selecting these positions.
An end beyond the table is naturally clamped by each file's row count.
"""
if start >= end:
return []
selected = []
offset = 0
for split in splits:
keep_ids, ranges = set(), {}
for file in split.files:
begin, stop = max(0, start - offset), min(file.row_count, end - offset)
offset += file.row_count
if begin < stop:
keep_ids.add(id(file))
if begin != 0 or stop != file.row_count:
ranges[file.file_name] = (begin, stop)
if not keep_ids:
continue
kept = split.filter_file(lambda file: id(file) in keep_ids)
selected.append(SlicedSplit(kept, ranges) if ranges else kept)
if offset >= end:
break
return selected