Files
milvus/internal/core/src/exec/expression/Utils.h
T
ef166a673a enhance: support building core on macOS (Apple Silicon) with clang 19 (#50616)
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]>
2026-06-19 20:27:10 -07:00

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