Skip to content

Commit 1a7ca33

Browse files
authored
feat: expose the Go expr parser to C++ and embed into libmilvus-core.so (milvus-io#45703)
generated a library that wraps the go expr parser, and embedded that into libmilvus-core.so issue: milvus-io#45702 see `internal/core/src/plan/milvus_plan_parser.h` for the exposed interface <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced C++ API for plan parsing with schema registration and expression parsing capabilities. * Plan parser now available as shared libraries instead of a standalone binary tool. * **Refactor** * Reorganized build system to produce shared library artifacts instead of executable binaries. * Build outputs relocated to standardized library and include directories. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Buqian Zheng <zhengbuqian@gmail.com>
1 parent 7d6d279 commit 1a7ca33

5 files changed

Lines changed: 327 additions & 121 deletions

File tree

Makefile

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -386,15 +386,21 @@ run-test-cpp:
386386
@echo $(PWD)/scripts/run_cpp_unittest.sh arg=${filter}
387387
@(env bash $(PWD)/scripts/run_cpp_unittest.sh arg=${filter})
388388

389-
# tool for benchmark
390-
exprparser-tool:
391-
@echo "Building exprparser helper ..."
389+
plan-parser-so:
390+
@echo "Building plan parser shared library ..."
392391
@source $(PWD)/scripts/setenv.sh && \
393-
mkdir -p $(INSTALL_PATH) && go env -w CGO_ENABLED="1" && \
394-
GO111MODULE=on $(GO) build -pgo=$(PGO_PATH)/default.pgo -ldflags="-r $${RPATH}" -o $(INSTALL_PATH)/exprparser $(PWD)/cmd/tools/exprparser/main.go 1>/dev/null
392+
mkdir -p $(PWD)/internal/core/output/lib $(PWD)/internal/core/output/include && \
393+
go env -w CGO_ENABLED="1" && \
394+
GO111MODULE=on $(GO) build -buildmode=c-shared -o $(PWD)/internal/core/output/lib/libmilvus-planparser.so $(PWD)/internal/parser/planparserv2/cwrapper/wrapper.go && \
395+
mv $(PWD)/internal/core/output/lib/libmilvus-planparser.h $(PWD)/internal/core/output/include/libmilvus-planparser.h && \
396+
cp $(PWD)/internal/parser/planparserv2/cwrapper/milvus_plan_parser.h $(PWD)/internal/core/output/include/ && \
397+
g++ -shared -fPIC -o $(PWD)/internal/core/output/lib/libmilvus-planparser-cpp.so $(PWD)/internal/parser/planparserv2/cwrapper/milvus_plan_parser.cpp \
398+
-I$(PWD)/internal/core/output/include \
399+
-L$(PWD)/internal/core/output/lib -lmilvus-planparser \
400+
-Wl,-rpath,'$$ORIGIN'
395401

396402
# Build unittest with external scalar-benchmark enabled
397-
scalar-bench: generated-proto exprparser-tool
403+
scalar-bench: generated-proto plan-parser-so
398404
@echo "Building Milvus cpp unittest with scalar-benchmark ... "
399405
@(export CMAKE_EXTRA_ARGS="-DENABLE_SCALAR_BENCH=ON"; env bash $(PWD)/scripts/core_build.sh -t ${mode} -a ${use_asan} -u -n ${use_disk_index} -y ${use_dynamic_simd} ${AZURE_OPTION} -x ${index_engine} -o ${use_opendal} -f $(tantivy_features))
400406

cmd/tools/exprparser/main.go

Lines changed: 0 additions & 115 deletions
This file was deleted.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (C) 2019-2025 Zilliz. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
4+
// with the License. You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software distributed under the License
9+
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10+
// or implied. See the License for the specific language governing permissions and limitations under the License
11+
12+
//go:build exclude
13+
14+
#include "milvus_plan_parser.h"
15+
16+
#include <cstring>
17+
#include <stdexcept>
18+
19+
// This header is generated by the Go build (cgo)
20+
// and is expected to be available in the include path.
21+
extern "C" {
22+
#include "libmilvus-planparser.h"
23+
}
24+
25+
namespace milvus {
26+
namespace planparserv2 {
27+
28+
SchemaHandle PlanParser::RegisterSchema(const std::vector<uint8_t>& schema_proto) {
29+
void* proto_blob = const_cast<void*>(static_cast<const void*>(schema_proto.data()));
30+
int len = static_cast<int>(schema_proto.size());
31+
char* err_msg = nullptr;
32+
33+
SchemaHandle handle = ::RegisterSchema(proto_blob, len, &err_msg);
34+
if (handle == kInvalidSchemaHandle) {
35+
std::string err_str = "Unknown error";
36+
if (err_msg != nullptr) {
37+
err_str = std::string(err_msg);
38+
::Free(err_msg);
39+
}
40+
throw std::runtime_error("Failed to register schema: " + err_str);
41+
}
42+
43+
return handle;
44+
}
45+
46+
std::string PlanParser::UnregisterSchema(SchemaHandle handle) {
47+
char* err_msg = nullptr;
48+
49+
int result = ::UnregisterSchema(handle, &err_msg);
50+
if (result != 0) {
51+
std::string err = err_msg ? std::string(err_msg) : "unknown error";
52+
if (err_msg != nullptr) {
53+
::Free(err_msg);
54+
}
55+
return err;
56+
}
57+
return "";
58+
}
59+
60+
std::vector<uint8_t> PlanParser::Parse(SchemaHandle handle, const std::string& expr) {
61+
char* c_expr = const_cast<char*>(expr.c_str());
62+
char* err_msg = nullptr;
63+
int length = 0;
64+
65+
void* result = ::Parse(handle, c_expr, &length, &err_msg);
66+
if (result == nullptr) {
67+
std::string err_str = "Unknown error";
68+
if (err_msg != nullptr) {
69+
err_str = std::string(err_msg);
70+
::Free(err_msg);
71+
}
72+
throw std::runtime_error("Failed to parse expression: " + err_str);
73+
}
74+
75+
std::vector<uint8_t> plan(length);
76+
if (length > 0) {
77+
std::memcpy(plan.data(), result, length);
78+
}
79+
80+
::Free(result);
81+
82+
return plan;
83+
}
84+
85+
} // namespace planparserv2
86+
} // namespace milvus
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (C) 2019-2025 Zilliz. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
4+
// with the License. You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software distributed under the License
9+
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10+
// or implied. See the License for the specific language governing permissions and limitations under the License
11+
12+
#pragma once
13+
14+
#include <cstdint>
15+
#include <string>
16+
#include <vector>
17+
18+
namespace milvus {
19+
namespace planparserv2 {
20+
21+
// SchemaHandle is an opaque handle to a registered schema.
22+
// Valid handles are > 0. A handle of 0 indicates an invalid/unregistered schema.
23+
using SchemaHandle = int64_t;
24+
25+
constexpr SchemaHandle kInvalidSchemaHandle = 0;
26+
27+
// Thread-safe wrapper for the Go plan parser.
28+
//
29+
// Thread safety guarantees:
30+
// - RegisterSchema: Can be called concurrently. Each call returns a unique handle.
31+
// - UnregisterSchema: Can be called concurrently. Returns error if schema is in use or already unregistered.
32+
// - Parse: Can be called concurrently. Uses lock-free reference counting internally.
33+
//
34+
// Usage:
35+
// auto handle = PlanParser::RegisterSchema(schema_proto);
36+
// auto plan = PlanParser::Parse(handle, "field > 10");
37+
// PlanParser::UnregisterSchema(handle);
38+
class PlanParser {
39+
public:
40+
/**
41+
* @brief Register a schema to the plan parser.
42+
*
43+
* Thread-safe. Each call returns a unique handle, even for identical schemas.
44+
* The same schema can be registered multiple times, each with a different handle.
45+
*
46+
* @param schema_proto The serialized CollectionSchema protobuf.
47+
* @return SchemaHandle A unique handle for the registered schema (> 0).
48+
* @throws std::runtime_error if registration fails (e.g., invalid protobuf).
49+
*/
50+
static SchemaHandle RegisterSchema(const std::vector<uint8_t>& schema_proto);
51+
52+
/**
53+
* @brief Unregister a schema from the plan parser.
54+
*
55+
* Thread-safe. Fails if the schema is currently being used by Parse() or already unregistered.
56+
*
57+
* @param handle The handle returned by RegisterSchema.
58+
* @return Empty string on success, error message on failure.
59+
*/
60+
static std::string UnregisterSchema(SchemaHandle handle);
61+
62+
/**
63+
* @brief Parse an expression string into a serialized PlanNode protobuf.
64+
*
65+
* Thread-safe and lock-free. Multiple threads can call Parse() concurrently
66+
* with the same or different handles.
67+
*
68+
* @param handle The handle returned by RegisterSchema.
69+
* @param expr The expression string to parse.
70+
* @return std::vector<uint8_t> The serialized PlanNode protobuf.
71+
* @throws std::runtime_error if:
72+
* - handle is invalid or not found
73+
* - schema was unregistered
74+
* - parsing fails
75+
*/
76+
static std::vector<uint8_t> Parse(SchemaHandle handle, const std::string& expr);
77+
};
78+
79+
} // namespace planparserv2
80+
} // namespace milvus

0 commit comments

Comments
 (0)