mirror of
https://github.com/milvus-io/milvus.git
synced 2026-10-12 02:19:10 +00:00
issue: #50615 ## What this PR does Makes the C++ core build cleanly on **macOS (Apple Silicon) with Homebrew LLVM/clang 19**. > [!IMPORTANT] > **Scope / impact is NOT macOS-only.** Although this PR is framed as macOS/clang-19 enablement, two of the changes are **top-level `requires(...)` in `internal/core/conanfile.py`, so they apply to every platform, including Linux production builds:** > - **`azure-sdk-for-cpp` `1.11.3@milvus/dev` → `1.16.0@milvus/dev`** (5 minor versions, `force=True`). This is the SDK Arrow uses for the **Azure Blob remote-storage path**, so this is effectively a major Azure SDK bump on the **production Azure object-storage path for Linux deployments** too. > - **`arrow` recipe `17.0.0@milvus/dev-2.6#c743ea7a…` → `17.0.0@milvus/dev#f411aa73…`** — same Arrow version, but **rebuilt against azure 1.16.0** with a patched `FindAzure.cmake`. The Arrow **`package_id` (recipe revision) DID change**; it is not the same binary as before. > - **`libbson/1.30.6@milvus/dev`** added (also global, but new — no pre-existing behavior to regress). > > **Testing blind spot:** CI green proves compile/link/UT/e2e pass, but the UT and integration suites **do not exercise real Azure Blob read/write**, so the Azure object-storage path is **not covered** by this PR's CI. A real Azure Blob remote-storage regression is recommended before merge (see Verification). ### Why the dependency bumps are required for clang 19 - **azure 1.16.0**: azure 1.11.3 bundles an older `nlohmann/json` that uses `std::char_traits<unsigned char>`, which was **removed in libc++ 19** → 1.11.3 fails to compile on clang 19. 1.16.0 fixes this. - **arrow rebuilt against azure 1.16.0**: when Arrow is built from source on clang 19 it must link the same azure 1.16.0; the recipe also patches `FindAzure.cmake` to the monolithic `Azure` config so `find_package(Azure)` resolves. Hence a new Arrow recipe revision. ### Toolchain / build enablement - **`scripts/setenv.sh`**: probe LLVM 19/20/21 first (libc++ 17/18 `chrono operator<<` clashes with Arrow's vendored `date`; libc++ 19 fixes it). - **`scripts/3rdparty_build.sh`**: arm64 `-march=armv8-a+crypto+crc` so folly's F14 (SimdAndCrc on Apple Silicon) matches core/knowhere — fixes `F14LinkCheck` undefined-symbol link errors. - **`InvertedIndexTantivy.cpp`**: drop spurious `->template` on non-template members (clang 19 `-Wmissing-template-arg-list-after-template-kw`). ### Drop bsoncxx / libmongoc → libbson only The BSON layer used the **bsoncxx** C++ driver, whose CMake unconditionally builds mongo-cxx-driver → **libmongoc** → bundled **utf8proc**, which fails to configure under newer CMake/clang on macOS. Milvus only needs BSON (de)serialization. - New Conan package **`libbson/1.30.6@milvus/dev`** (libbson-only, `ENABLE_MONGOC=OFF`; recipe published to milvus conanfiles & production). - New **`src/common/bson_shim.h`** — a thin `milvus::bson` C++ layer over libbson's C API. - Migrated `bson_view.h`, `bson_builder.{h,cpp}`, json_stats consumers and unit tests off bsoncxx. - CMake: `find_package(bson-1.0)` + link `mongo::bson_shared`; removed FetchContent `thirdparty/bsoncxx`. All three global Conan requires (arrow / libbson / azure) pin an explicit recipe `#revision` for reproducibility. ## Verification - Full clean rebuild on macOS arm64 / clang 19: `bin/milvus` builds; `libmilvus_core` links **only `libbson-1.0.dylib`** (no libmongoc/mongocxx/utf8proc). - libbson / arrow / azure resolve from production Conan at the pinned revisions. - All 21 BSON unit tests (BsonView / BsonBuilder / DomNode) pass. - Full ci-v2 suite green on Linux (build / build-ut-cov / ut-cpp / ut-go / integration / e2e / go-sdk). - **TODO before merge:** real Azure Blob remote-storage read/write regression to cover the azure-1.16.0 / arrow-rebuild blast radius (not exercised by UT/integration). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: xiaofanluan <[email protected]> Co-authored-by: xiaofanluan <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
315 lines
11 KiB
C++
315 lines
11 KiB
C++
// 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.
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
|
|
#include <fmt/core.h>
|
|
|
|
#include "common/EasyAssert.h"
|
|
#include "common/Types.h"
|
|
#include "common/Vector.h"
|
|
#include "exec/expression/Expr.h"
|
|
#include "segcore/SegmentInterface.h"
|
|
#include "query/Utils.h"
|
|
#include "common/bson_view.h"
|
|
namespace milvus {
|
|
namespace exec {
|
|
|
|
inline bool
|
|
IsCompareOp(proto::plan::OpType op) {
|
|
return op == proto::plan::OpType::Equal ||
|
|
op == proto::plan::OpType::NotEqual ||
|
|
op == proto::plan::OpType::GreaterEqual ||
|
|
op == proto::plan::OpType::GreaterThan ||
|
|
op == proto::plan::OpType::LessEqual ||
|
|
op == proto::plan::OpType::LessThan;
|
|
}
|
|
|
|
[[maybe_unused]] static ColumnVectorPtr
|
|
GetColumnVector(const VectorPtr& result) {
|
|
ColumnVectorPtr res;
|
|
if (auto convert_vector = std::dynamic_pointer_cast<ColumnVector>(result)) {
|
|
res = convert_vector;
|
|
} else if (auto convert_vector =
|
|
std::dynamic_pointer_cast<RowVector>(result)) {
|
|
if (auto convert_flat_vector = std::dynamic_pointer_cast<ColumnVector>(
|
|
convert_vector->child(0))) {
|
|
res = convert_flat_vector;
|
|
} else {
|
|
ThrowInfo(
|
|
UnexpectedError,
|
|
"RowVector result must have a first ColumnVector children");
|
|
}
|
|
} else {
|
|
ThrowInfo(UnexpectedError,
|
|
"expr result must have a ColumnVector or RowVector result");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
template <typename T>
|
|
bool
|
|
CompareTwoJsonArray(T arr1, const proto::plan::Array& arr2) {
|
|
int json_array_length = 0;
|
|
if constexpr (std::is_same_v<
|
|
T,
|
|
simdjson::simdjson_result<simdjson::ondemand::array>>) {
|
|
json_array_length = arr1.count_elements();
|
|
}
|
|
if constexpr (std::is_same_v<T,
|
|
std::vector<simdjson::simdjson_result<
|
|
simdjson::ondemand::value>>>) {
|
|
json_array_length = arr1.size();
|
|
}
|
|
|
|
if constexpr (std::is_same_v<
|
|
T,
|
|
simdjson::simdjson_result<simdjson::dom::array>>) {
|
|
json_array_length = arr1.size();
|
|
}
|
|
|
|
if constexpr (std::is_same_v<T, simdjson::dom::array>) {
|
|
json_array_length = arr1.size();
|
|
}
|
|
if (arr2.array_size() != json_array_length) {
|
|
return false;
|
|
}
|
|
int i = 0;
|
|
for (auto&& it : arr1) {
|
|
switch (arr2.array(i).val_case()) {
|
|
case proto::plan::GenericValue::kBoolVal: {
|
|
auto val = it.template get<bool>();
|
|
if (val.error() || val.value() != arr2.array(i).bool_val()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case proto::plan::GenericValue::kInt64Val: {
|
|
auto val = it.template get<int64_t>();
|
|
if (val.error() || val.value() != arr2.array(i).int64_val()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case proto::plan::GenericValue::kFloatVal: {
|
|
auto val = it.template get<double>();
|
|
if (val.error() || val.value() != arr2.array(i).float_val()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case proto::plan::GenericValue::kStringVal: {
|
|
auto val = it.template get<std::string_view>();
|
|
if (val.error() || val.value() != arr2.array(i).string_val()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
ThrowInfo(DataTypeInvalid,
|
|
"unsupported data type {}",
|
|
arr2.array(i).val_case());
|
|
}
|
|
i++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <>
|
|
inline bool
|
|
CompareTwoJsonArray<milvus::bson::array_view>(milvus::bson::array_view arr1,
|
|
const proto::plan::Array& arr2) {
|
|
size_t bson_array_length = std::distance(arr1.begin(), arr1.end());
|
|
|
|
if (arr2.array_size() != bson_array_length) {
|
|
return false;
|
|
}
|
|
|
|
auto bson_it = arr1.begin();
|
|
for (int i = 0; i < arr2.array_size(); ++i, ++bson_it) {
|
|
if (bson_it == arr1.end()) {
|
|
return false;
|
|
}
|
|
|
|
const auto& bson_elem = *bson_it;
|
|
const auto& proto_elem = arr2.array(i);
|
|
|
|
switch (proto_elem.val_case()) {
|
|
case proto::plan::GenericValue::kBoolVal: {
|
|
if (bson_elem.type() != milvus::bson::type::k_bool) {
|
|
return false;
|
|
}
|
|
if (bson_elem.get_bool().value != proto_elem.bool_val()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case proto::plan::GenericValue::kInt64Val: {
|
|
if (bson_elem.type() == milvus::bson::type::k_int32) {
|
|
const int32_t val = bson_elem.get_int32().value;
|
|
if (val != proto_elem.int64_val()) {
|
|
return false;
|
|
}
|
|
} else if (bson_elem.type() == milvus::bson::type::k_int64) {
|
|
const int64_t val = bson_elem.get_int64().value;
|
|
if (val != proto_elem.int64_val()) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case proto::plan::GenericValue::kFloatVal: {
|
|
double bson_val;
|
|
switch (bson_elem.type()) {
|
|
case milvus::bson::type::k_int32:
|
|
bson_val = bson_elem.get_int32().value;
|
|
break;
|
|
case milvus::bson::type::k_int64:
|
|
bson_val = bson_elem.get_int64().value;
|
|
break;
|
|
case milvus::bson::type::k_double:
|
|
bson_val = bson_elem.get_double().value;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if (bson_val != proto_elem.float_val()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case proto::plan::GenericValue::kStringVal: {
|
|
if (bson_elem.type() != milvus::bson::type::k_string) {
|
|
return false;
|
|
}
|
|
auto bson_str_view = bson_elem.get_string().value;
|
|
if (std::string(bson_str_view.data(), bson_str_view.size()) !=
|
|
proto_elem.string_val()) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (bson_it != arr1.end()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
T
|
|
GetValueFromProtoInternal(const milvus::proto::plan::GenericValue& value_proto,
|
|
bool& overflowed) {
|
|
if constexpr (std::is_same_v<T, bool>) {
|
|
Assert(value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kBoolVal);
|
|
return static_cast<T>(value_proto.bool_val());
|
|
} else if constexpr (std::is_integral_v<T>) {
|
|
Assert(value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kInt64Val);
|
|
auto val = value_proto.int64_val();
|
|
if (milvus::query::out_of_range<T>(val)) {
|
|
overflowed = true;
|
|
return T();
|
|
} else {
|
|
return static_cast<T>(val);
|
|
}
|
|
} else if constexpr (std::is_floating_point_v<T>) {
|
|
Assert(value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kFloatVal);
|
|
return static_cast<T>(value_proto.float_val());
|
|
} else if constexpr (std::is_same_v<T, std::string> ||
|
|
std::is_same_v<T, std::string_view>) {
|
|
Assert(value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kStringVal);
|
|
return static_cast<T>(value_proto.string_val());
|
|
} else if constexpr (std::is_same_v<T, proto::plan::Array>) {
|
|
Assert(value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kArrayVal);
|
|
return static_cast<T>(value_proto.array_val());
|
|
} else if constexpr (std::is_same_v<T, milvus::proto::plan::GenericValue>) {
|
|
return static_cast<T>(value_proto);
|
|
} else {
|
|
ThrowInfo(Unsupported,
|
|
"unsupported generic value {}",
|
|
value_proto.ShortDebugString());
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
T
|
|
GetValueFromProto(const milvus::proto::plan::GenericValue& value_proto) {
|
|
bool dummy_overflowed = false;
|
|
return GetValueFromProtoInternal<T>(value_proto, dummy_overflowed);
|
|
}
|
|
|
|
template <typename T>
|
|
T
|
|
GetValueFromProtoWithOverflow(
|
|
const milvus::proto::plan::GenericValue& value_proto, bool& overflowed) {
|
|
return GetValueFromProtoInternal<T>(value_proto, overflowed);
|
|
}
|
|
|
|
template <typename T>
|
|
T
|
|
GetValueWithCastNumber(const milvus::proto::plan::GenericValue& value_proto) {
|
|
if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>) {
|
|
Assert(value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kFloatVal ||
|
|
value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kInt64Val);
|
|
if (value_proto.val_case() ==
|
|
milvus::proto::plan::GenericValue::kInt64Val) {
|
|
return static_cast<T>(value_proto.int64_val());
|
|
} else {
|
|
return static_cast<T>(value_proto.float_val());
|
|
}
|
|
} else {
|
|
return GetValueFromProto<T>(value_proto);
|
|
}
|
|
}
|
|
|
|
// Locale-independent ASCII lowercase conversion
|
|
// Converts only ASCII uppercase letters (A-Z) to lowercase (a-z)
|
|
// Non-ASCII characters and non-uppercase characters remain unchanged
|
|
inline unsigned char
|
|
asciiToLower(unsigned char c) {
|
|
if (c >= 'A' && c <= 'Z') {
|
|
return static_cast<unsigned char>('a' + (c - 'A'));
|
|
}
|
|
return c;
|
|
}
|
|
|
|
inline std::string
|
|
sanitizeName(const std::string& name) {
|
|
std::string sanitizedName;
|
|
sanitizedName.resize(name.size());
|
|
std::transform(
|
|
name.begin(), name.end(), sanitizedName.begin(), asciiToLower);
|
|
return sanitizedName;
|
|
}
|
|
|
|
} // namespace exec
|
|
} // namespace milvus
|