// 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/milvusclient/{read.go,read_options.go}
// 的 `Query` / `queryOption`(Apache-2.0)。
///|
/// `query_params` 里服务端认的键。字面量与上游逐条对齐。
pub let query_param_limit : String = "limit"
///|
pub let query_param_offset : String = "offset"
///|
pub let query_param_order_by_fields : String = "order_by_fields"
///|
/// 一次查询的结果:按输出字段摊成的列,外加列名。
///
/// 上游返回带 schema 的 `ResultSet`,字段的 `DataType` 也留在列上;
/// 这里保留 `@column.ColumnValue` 的 `FieldData.type_` 信息,
/// 由 `QueryResult::column` 按名取。
pub(all) struct QueryResult {
columns : Array[@column.Column]
names : Array[String]
} derive(Debug)
///|
/// 结果行数。没有列时是 0。
pub fn QueryResult::len(self : QueryResult) -> Int {
if self.columns.length() == 0 {
0
} else {
self.columns[0].len()
}
}
///|
/// `true` 表示结果里没有这一列 —— 与「列存在但全是 null」不同,
/// 后者也返回 `Some`。
pub fn QueryResult::has_column(self : QueryResult, name : String) -> Bool {
self.names.contains(name)
}
///|
/// 按字段名取一列。
pub fn QueryResult::column(
self : QueryResult,
name : String,
) -> @column.Column? {
for i = 0; i < self.names.length(); i = i + 1 {
if self.names[i] == name {
return Some(self.columns[i])
}
}
None
}
///|
/// 查询的入参。对应上游 `queryOption`。
pub(all) struct QueryOption {
collection_name : String
partition_names : Array[String]
expr : String
output_fields : Array[String]
query_params : Array[(String, String)]
consistency_level : ConsistencyLevel?
}
///|
/// 上游 `NewQueryOption`:默认一致性 `Bounded`,其余空。
///
/// `query_params` 不预置任何键——尤其不预置 `limit`。默认 limit 是**适配层
/// 的概念**,上游由 `CheckAndSetLimit` 在发请求前补齐,本移植的编排里没有
/// 这一层,所以「没给 limit」就是真的不发;要限制行数就显式 `with_limit`。
/// 别在这里悄悄塞一个 16384 假装对齐上游,那只是把「没实现适配层」这件事
/// 藏起来,顺手给出一个错的因果。
pub fn new_query_option(collection_name : String) -> QueryOption {
{
collection_name,
partition_names: [],
expr: "",
output_fields: [],
query_params: [],
consistency_level: None,
}
}
///|
pub fn QueryOption::with_filter(
self : QueryOption,
expr : String,
) -> QueryOption {
{ ..self, expr, }
}
///|
pub fn QueryOption::with_output_fields(
self : QueryOption,
output_fields : Array[String],
) -> QueryOption {
{ ..self, output_fields, }
}
///|
pub fn QueryOption::with_partitions(
self : QueryOption,
partition_names : Array[String],
) -> QueryOption {
{ ..self, partition_names, }
}
///|
pub fn QueryOption::with_consistency_level(
self : QueryOption,
level : ConsistencyLevel,
) -> QueryOption {
{ ..self, consistency_level: Some(level), }
}
///|
pub fn QueryOption::with_limit(self : QueryOption, limit : Int) -> QueryOption {
set_query_param(self, query_param_limit, limit.to_string())
}
///|
pub fn QueryOption::with_offset(
self : QueryOption,
offset : Int,
) -> QueryOption {
set_query_param(self, query_param_offset, offset.to_string())
}
///|
/// 排序字段。每项写作 `name` 或 `name:asc` / `name:desc`,
/// 缺省升序;服务端要求带排序字段时必须显式给 limit。
pub fn QueryOption::with_order_by_fields(
self : QueryOption,
fields : Array[String],
) -> QueryOption {
set_query_param(self, query_param_order_by_fields, fields.join(","))
}
///|
fn set_query_param(
option : QueryOption,
key : String,
value : String,
) -> QueryOption {
let query_params : Array[(String, String)] = []
let mut replaced = false
for pair in option.query_params {
if pair.0 == key {
query_params.push((key, value))
replaced = true
} else {
query_params.push(pair)
}
}
if !replaced {
query_params.push((key, value))
}
{ ..option, query_params, }
}
///|
/// 按主键查。与上游 `WithIDs` 一致,主键被拼进表达式而不是单独传 —
/// 服务端没有「按主键批量取」的字段,只有 `expr`。
pub fn QueryOption::with_int64_ids(
self : QueryOption,
field_name : String,
ids : Array[Int64],
) -> QueryOption {
let parts : Array[String] = ids.map(id => id.to_string())
{ ..self, expr: field_name + " in [" + parts.join(",") + "]", }
}
///|
pub fn QueryOption::with_string_ids(
self : QueryOption,
field_name : String,
ids : Array[String],
) -> QueryOption {
let parts : Array[String] = ids.map(id => "\"" + id + "\"")
{ ..self, expr: field_name + " in [" + parts.join(",") + "]", }
}
///|
/// 查询。
///
/// 与上游一致:响应里带了 `output_fields` 就优先用响应的那份
/// (服务端可能因为 `*` 通配或动态字段补出更多列),否则回落到请求里的。
pub async fn Client::query(
self : Client,
option : QueryOption,
) -> QueryResult raise ClientError {
let query_params = option.query_params.map(pair => {
@common.KeyValuePair::KeyValuePair(pair.0, pair.1)
})
let consistency = match option.consistency_level {
Some(level) => level.to_proto()
None => @common.ConsistencyLevel::Bounded
}
let request = @milvus.QueryRequest::{
base: None,
db_name: "",
collection_name: option.collection_name,
expr: option.expr,
output_fields: option.output_fields,
partition_names: option.partition_names,
travel_timestamp: 0UL,
guarantee_timestamp: 0UL,
query_params,
not_return_all_meta: false,
consistency_level: consistency,
use_default_consistency: option.consistency_level is None,
namespace_: None,
}
let response : @milvus.QueryResults = self.call_service(query_path, request)
match check_status(response.status) {
Some(err) => raise err
None => decode_query_results(response)
}
}
///|
fn decode_query_results(
response : @milvus.QueryResults,
) -> QueryResult raise ClientError {
let columns : Array[@column.Column] = []
let names : Array[String] = []
for field in response.fields_data {
let column = @column.from_field_data(field) catch {
err => raise ClientError::Decode(column_error_message(err))
}
names.push(field.field_name)
columns.push(column)
}
{ columns, names, }
}