// Licensed to the LF AI & Data foundation 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.
//
// 移植自 milvus-io/milvus client/column/generic_base.go 的
// `getFieldDataValidData` / `validateAndNormalizeFieldDataValidData` 与
// `countValidBounds`(Apache-2.0)。
//
// 有效性位图在两个地方放过:`FieldData.valid_data`(旧)和
// `ScalarField.valid_data` / `VectorField.valid_data`(新)。读的一侧先看
// 旧字段,读不到再回落到字段级,与上游 `getFieldDataValidData` 的读取顺序
// 一致;两处都有且不一致就是坏负载,上报而不猜哪一边对。上游
// `validateAndNormalizeFieldDataValidData` 同理。
///|
/// 取这条 `FieldData` 的有效性位图。旧字段(`FieldData.valid_data`)优先,
/// 读不到再回落到字段级——与上游 `getFieldDataValidData` 的读取顺序一致。
/// 返回空数组表示非空字段。
fn field_valid_data(field : @schema.FieldData) -> Array[Bool] {
if !field.valid_data.is_empty() {
return field.valid_data
}
match field.field {
@schema.FieldData_Field::Scalars(s) => s.valid_data
@schema.FieldData_Field::Vectors(v) => v.valid_data
@schema.FieldData_Field::NotSet => []
}
}
///|
/// 两处有效性位图都在且不一致时返回 `true`。上游
/// `fieldDataValidDataConsistent` 的判定。
fn valid_data_conflicts(field : @schema.FieldData) -> Bool {
let legacy = field.valid_data
let current = match field.field {
@schema.FieldData_Field::Scalars(s) => s.valid_data
@schema.FieldData_Field::Vectors(v) => v.valid_data
@schema.FieldData_Field::NotSet => []
}
!legacy.is_empty() && !current.is_empty() && legacy != current
}
///|
/// 圈出 `valid` 的 `[begin, end)` 段。`end` 为负表示到末尾。
/// 区间按逻辑行算,所以这里只处理 `valid`;数据段的位置另算。
fn valid_slice(valid : Array[Bool], begin : Int, end : Int) -> Array[Bool] {
if valid.is_empty() {
return []
}
let n = valid.length()
let b = clamp_begin(begin, n)
let e = normalize_end(end, n)
if b >= e {
return []
}
valid[b:e].to_owned()
}
///|
/// 把 `begin` 夹到 `[0, n]`。上游对越界的 `begin` 直接报错,这里做成
/// 夹取是因为调用方通常传的是「我已经知道要哪几行」;越界即空,不静默
/// 取错行。真正的区间校验在 `rows_in_range` 里,只有明确要整列时才用。
fn clamp_begin(begin : Int, n : Int) -> Int {
if begin < 0 {
0
} else if begin > n {
n
} else {
begin
}
}
///|
/// `end` 归一:负数表示到末尾,超过 `n` 夹到 `n`。
fn normalize_end(end : Int, n : Int) -> Int {
if end < 0 {
n
} else if end > n {
n
} else {
end
}
}
///|
/// 校验行区间:`begin ∈ [0, n]`,`end` 为负或 `∈ [begin, n]`。
/// 上游 `normalizeFieldDataRange` 在这里报错而不是夹取,因为它要区分
/// 「调用方要了不存在的行」和「数据就这么长」。本模块沿用同一条线。
fn check_range(
field_name : String,
begin : Int,
end : Int,
n : Int,
) -> Unit raise ColumnError {
if begin < 0 || begin > n {
raise IndexOutOfRange(
"field \{field_name} row range start \{begin} is outside [0, \{n})",
)
}
let e = if end < 0 { n } else { end }
if e < begin || e > n {
raise IndexOutOfRange(
"field \{field_name} row range [\{begin}, \{e}) is invalid for \{n} rows",
)
}
}
///|
/// 逻辑行数:有 `valid_data` 就是它的长度,否则是数据数组的长度。
/// 上游 `parseScalarData` 里 `logicalLen` 的取法。
fn logical_len(data_len : Int, valid : Array[Bool]) -> Int {
if valid.is_empty() {
data_len
} else {
valid.length()
}
}
///|
/// 紧凑布局的行区间:`valid` 里 `[begin, end)` 段的有效值,在数据数组里
/// 占哪一段,以及有多少个。
///
/// 上游 `countValidBounds`。返回 `(value_begin, value_end, count)`。
/// 「行满」布局(数据长度等于逻辑行数)下数据段就是 `[begin, end)`,
/// 由调用方先行判断,不走这里。
fn count_valid_bounds(
valid : Array[Bool],
begin : Int,
end : Int,
) -> (Int, Int, Int) {
let mut value_begin = 0
let mut value_end = 0
let mut count = 0
for idx, is_valid in valid {
if !is_valid {
continue
}
count = count + 1
if idx < begin {
value_begin = value_begin + 1
}
if idx < end {
value_end = value_end + 1
}
}
(value_begin, value_end, count)
}
///|
/// 把一段标量数据按 `[begin, end)` 圈出来。
///
/// 可空字段的数据分两种布局:
/// - **行满**(数据长度等于逻辑行数):数据与逻辑行一一对应,直接切。
/// - **紧凑**(数据长度等于有效数):null 行不占位,先按 `valid` 算出
/// 数据段,再切。
/// 数据长度与两者都对不上就是坏负载。
fn[T] slice_scalar_data(
field_name : String,
data : Array[T],
valid : Array[Bool],
begin : Int,
end : Int,
) -> Array[T] raise ColumnError {
let n = logical_len(data.length(), valid)
check_range(field_name, begin, end, n)
if valid.is_empty() {
let e = normalize_end(end, n)
return data[clamp_begin(begin, n):e].to_owned()
}
if n == valid.length() && data.length() == n {
// 行满:数据段与逻辑段重合。
return data[begin:normalize_end(end, n)].to_owned()
}
let (value_begin, value_end, count) = count_valid_bounds(
valid,
begin,
normalize_end(end, n),
)
if data.length() != count {
raise MalformedPayload(
"field \{field_name} compact payload has \{data.length()} values but \{count} valid rows",
)
}
data[value_begin:value_end].to_owned()
}