From ae135a53ed39d70b23f435e0a1329305c669ab51 Mon Sep 17 00:00:00 2001 From: Markus Date: Tue, 15 Oct 2024 15:36:46 +0200 Subject: [PATCH] Version 4.0.2 --- CHANGELOG.md | 16 + CMakeLists.txt | 78 ++++- README.md | 2 +- download.sh | 2 +- doxygen/Changelog.md | 16 + doxygen/Doxyfile | 2 +- examples/CMakeLists.txt | 12 +- .../flatbuffers_common_builder.h | 2 +- .../flatbuffers_common_reader.h | 2 +- examples/c-cursor-no-gen/task_builder.h | 4 +- examples/c-cursor-no-gen/task_reader.h | 11 +- examples/cpp-autogen/.gitignore | 4 + examples/cpp-autogen/CMakeLists.txt | 16 + examples/cpp-autogen/main.cpp | 32 ++ examples/cpp-autogen/tasklist-example-app.hpp | 186 +++++++++++ examples/cpp-autogen/tasklist.fbs | 11 + examples/cpp-gen-sync/tasklist.obx.cpp | 13 +- examples/cpp-gen/tasklist.obx.cpp | 13 +- examples/regenerate-schemas.sh | 11 +- examples/vectorsearch-cities/CMakeLists.txt | 18 + examples/vectorsearch-cities/README.md | 83 +++++ .../VectorSearchCitiesApp.hpp | 307 ++++++++++++++++++ examples/vectorsearch-cities/cities.csv | 213 ++++++++++++ examples/vectorsearch-cities/city.fbs | 7 + examples/vectorsearch-cities/city.obx.cpp | 55 ++++ examples/vectorsearch-cities/city.obx.hpp | 44 +++ examples/vectorsearch-cities/main.cpp | 43 +++ .../vectorsearch-cities/objectbox-model.h | 44 +++ .../vectorsearch-cities/objectbox-model.json | 46 +++ include/objectbox-sync.h | 26 +- include/objectbox-sync.hpp | 34 +- include/objectbox.h | 11 +- include/objectbox.hpp | 86 ++++- test.sh | 9 +- 34 files changed, 1398 insertions(+), 61 deletions(-) create mode 100644 examples/cpp-autogen/.gitignore create mode 100644 examples/cpp-autogen/CMakeLists.txt create mode 100644 examples/cpp-autogen/main.cpp create mode 100644 examples/cpp-autogen/tasklist-example-app.hpp create mode 100644 examples/cpp-autogen/tasklist.fbs create mode 100644 examples/vectorsearch-cities/CMakeLists.txt create mode 100644 examples/vectorsearch-cities/README.md create mode 100644 examples/vectorsearch-cities/VectorSearchCitiesApp.hpp create mode 100644 examples/vectorsearch-cities/cities.csv create mode 100644 examples/vectorsearch-cities/city.fbs create mode 100644 examples/vectorsearch-cities/city.obx.cpp create mode 100644 examples/vectorsearch-cities/city.obx.hpp create mode 100644 examples/vectorsearch-cities/main.cpp create mode 100644 examples/vectorsearch-cities/objectbox-model.h create mode 100644 examples/vectorsearch-cities/objectbox-model.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a2e50..bfe6c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ ObjectBox C and C++ API Changelog ================================= +4.0.2 (2024-10-15) +------------------ +* Made closing the store more robust; e.g. it waits for ongoing queries and transactions to finish + (please still ensure to clean up properly on your side, this is an additional safety net) +* Made Box API more robust when racing against store closing +* Improved C++ APIs for nearest neighbor search (query building etc.) +* Some minor HNSW performance improvements +* Add "vectorsearch-cities" example + +### Sync + +* Fixed a serious regression; please update to the latest version asap! +* Added a special compression for tiny transactions +* Embedded clusters (note: the cluster feature may not come with all editions of the library) +* Add FlatBuffers based configuration for Sync Server + 4.0.1 (2024-07-17) ------------------ * Query: "visit with score" added, so you can consume vector search results one-by-one with a visitor callback diff --git a/CMakeLists.txt b/CMakeLists.txt index 54469ba..1cf84be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,24 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.5) # 3.5: Since CMake 3.27 VERSION < 3.5 produce a deprecation warning. -project(ObjectBoxCRoot) # to be displayed in an IDE when this CMake is opened +# This CMake file has the following purposes: +# * Define the ObjectBox library target (target name "objectbox"; or, if the sync variant is used, "objectbox-sync") +# * Fetch (download) the ObjectBoxGenerator CMake +# * Unless this CMake is consumed from a user project, add the sub-projects (tests and examples) +# +# Options are available via plain (not cached) variables that can be declared before fetching this CMake module: +# * ObjectBoxGenerator_CMAKE_DISABLE: set to ON to skip CMake integration of ObjectBox Generator +# * ObjectBoxGenerator_CMAKE_VERSION: override the default version of the ObjectBox Generator CMake. +# Changes the version to be fetched (git tag or branch). + +project(ObjectBoxCRoot) # Remove Warning (as of CMake >= 3.24): # "The DOWNLOAD_EXTRACT_TIMESTAMP option was not given.." # We use the new behaviour (file timestamps from downloaded/extracted archives are updated). -if(POLICY CMP0135) +if (POLICY CMP0135) cmake_policy(SET CMP0135 NEW) -endif() +endif () if (${CMAKE_VERSION} VERSION_LESS "3.11.0") message("Please consider upgrading your CMake to a more recent version (v3.11+) to get automatic library download.") @@ -39,7 +49,7 @@ else () function(defineObjectBoxLib VARIANT) # Configuration updated for each release - set(DL_VERSION 4.0.1) + set(DL_VERSION 4.0.2) # Platform detection and other setup set(DL_URL https://github.com/objectbox/objectbox-c/releases/download) @@ -84,8 +94,56 @@ else () endif () endif () -# By default we exclude tests and examples. -# If you want to build them please "make" them explicitly by target name (see targets below) -add_subdirectory(src-test EXCLUDE_FROM_ALL) # target: objectbox-c-test -add_subdirectory(src-test-gen EXCLUDE_FROM_ALL) # target: objectbox-c-gen-test -add_subdirectory(examples EXCLUDE_FROM_ALL) # targets: objectbox-c-examples-tasks-{c,cpp-gen,cpp-gen-sync} +# ObjectBoxGenerator CMake Downloader +# ----------------------------------- +# Make "FindObjectBoxGenerator" available, which is used to download/find and run the ObjectBox Generator. + +if (ObjectBoxGenerator_CMAKE_DISABLE) + message(STATUS "ObjectBox Generator: CMake integration disabled") +else () + if(NOT DEFINED ObjectBoxGenerator_CMAKE_VERSION OR ObjectBoxGenerator_CMAKE_VERSION STREQUAL "") + # The default version is a specific version that "matches" the ObjectBox library version of this repo. + # Nevertheless, it's often possible to use a newer Generator version as breaking changes are infrequent. + set(ObjectBoxGenerator_CMAKE_VERSION "v4.0.0-beta") + endif () + set(OBX_GEN_DL_URL https://raw.githubusercontent.com/objectbox/objectbox-generator/${ObjectBoxGenerator_CMAKE_VERSION}/cmake/FindObjectBoxGenerator.cmake) + + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.18) + message(STATUS "ObjectBox Generator: fetching version \"${ObjectBoxGenerator_CMAKE_VERSION}\"") + include(FetchContent) + FetchContent_Declare(FindObjectBoxGenerator URL ${OBX_GEN_DL_URL} DOWNLOAD_NO_EXTRACT TRUE) + FetchContent_MakeAvailable(FindObjectBoxGenerator) + set(OBX_GEN_MODULE_DIR ${findobjectboxgenerator_SOURCE_DIR}) + else () + message(STATUS "ObjectBox Generator: fetching version \"${ObjectBoxGenerator_CMAKE_VERSION}\" (using old CMake version)") + set(OBX_GEN_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}/cmake) + file(MAKE_DIRECTORY ${OBX_GEN_MODULE_DIR}) + if (NOT EXISTS ${OBX_GEN_MODULE_DIR}/FindObjectBoxGenerator.cmake) + file(DOWNLOAD ${OBX_GEN_DL_URL} ${OBX_GEN_MODULE_DIR}/FindObjectBoxGenerator.cmake + TLS_VERIFY ON + STATUS DL_STATUS + ) + if (NOT DL_STATUS EQUAL 0) + message(WARNING "Downloading FindObjectBoxGenerator.cmake from URL ${DL_URL} failed") + endif () + endif () + endif () + + if (EXISTS ${OBX_GEN_MODULE_DIR}/FindObjectBoxGenerator.cmake) + # Enable find_package to locate ObjectBoxGenerator find module. + list(APPEND CMAKE_MODULE_PATH ${OBX_GEN_MODULE_DIR}) + get_directory_property(hasParent PARENT_DIRECTORY) + if (hasParent) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} PARENT_SCOPE) + endif () + endif () +endif () + + +# If this project is top-level, include public tests and examples. +# Otherwise, this CMake file is used from a user project, so we do not want to expose these. +if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + add_subdirectory(src-test) # target: objectbox-c-test + add_subdirectory(src-test-gen) # target: objectbox-c-gen-test + add_subdirectory(examples) # targets: objectbox-c-examples-tasks-{c,cpp-{auto}gen,cpp-gen-sync} +endif () diff --git a/README.md b/README.md index 9d5dcfb..33e6f39 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ box.put({.text = "Buy milk"}); See [ObjectBox C and C++ docs](https://cpp.objectbox.io/) for API details. -**Latest version: 4.0.1** (2024-07-17). +**Latest version: 4.0.2** (2024-10-15). See [changelog](CHANGELOG.md) for more details. ## Table of Contents: diff --git a/download.sh b/download.sh index 8d258a7..3063c68 100755 --- a/download.sh +++ b/download.sh @@ -44,7 +44,7 @@ tty -s || quiet=true # Note: optional arguments like "--quiet" shifts argument positions in the case block above -version=${1:-4.0.1} +version=${1:-4.0.2} os=${2:-$(uname)} arch=${3:-$(uname -m)} echo "Base config: OS ${os} and architecture ${arch}" diff --git a/doxygen/Changelog.md b/doxygen/Changelog.md index 93877d0..0db3df4 100644 --- a/doxygen/Changelog.md +++ b/doxygen/Changelog.md @@ -3,6 +3,22 @@ ObjectBox C and C++ API Changelog ================================= +4.0.2 (2024-10-15) +------------------ +* Made closing the store more robust; e.g. it waits for ongoing queries and transactions to finish + (please still ensure to clean up properly on your side, this is an additional safety net) +* Made Box API more robust when racing against store closing +* Improved C++ APIs for nearest neighbor search (query building etc.) +* Some minor HNSW performance improvements +* Add "vectorsearch-cities" example + +### Sync + +* Fixed a serious regression; please update to the latest version asap! +* Added a special compression for tiny transactions +* Embedded clusters (note: the cluster feature may not come with all editions of the library) +* Add FlatBuffers based configuration for Sync Server + 4.0.1 (2024-07-17) ------------------ * Query: "visit with score" added, so you can consume vector search results one-by-one with a visitor callback diff --git a/doxygen/Doxyfile b/doxygen/Doxyfile index 07c6f23..e120160 100644 --- a/doxygen/Doxyfile +++ b/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "ObjectBox C and C++ API" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "4.0.1" +PROJECT_NUMBER = "4.0.2" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8fe8d20..400b385 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,14 @@ add_subdirectory(c-cursor-no-gen) add_subdirectory(c-gen) add_subdirectory(cpp-gen) -add_subdirectory(cpp-gen-sync) \ No newline at end of file +add_subdirectory(cpp-gen-sync) + +find_package(ObjectBoxGenerator 4.0.0) +# Some platforms such as Linux ARM(64) and Windows ARM(64) are not supported. +# We exclude this example for these cases where the generator was not found. +if (ObjectBoxGenerator_FOUND) + add_subdirectory(cpp-autogen) +else () + message(WARNING "Did not add all examples, as the ObjectBoxGenerator CMake was not found") +endif () +add_subdirectory(vectorsearch-cities) diff --git a/examples/c-cursor-no-gen/flatbuffers_common_builder.h b/examples/c-cursor-no-gen/flatbuffers_common_builder.h index b5ce8a5..a918e55 100644 --- a/examples/c-cursor-no-gen/flatbuffers_common_builder.h +++ b/examples/c-cursor-no-gen/flatbuffers_common_builder.h @@ -1,7 +1,7 @@ #ifndef FLATBUFFERS_COMMON_BUILDER_H #define FLATBUFFERS_COMMON_BUILDER_H -/* Generated by flatcc 0.6.1-dev FlatBuffers schema compiler for C by dvide.com */ +/* Generated by flatcc 0.6.2 FlatBuffers schema compiler for C by dvide.com */ /* Common FlatBuffers build functionality for C. */ diff --git a/examples/c-cursor-no-gen/flatbuffers_common_reader.h b/examples/c-cursor-no-gen/flatbuffers_common_reader.h index 67971e9..2c5eb43 100644 --- a/examples/c-cursor-no-gen/flatbuffers_common_reader.h +++ b/examples/c-cursor-no-gen/flatbuffers_common_reader.h @@ -1,7 +1,7 @@ #ifndef FLATBUFFERS_COMMON_READER_H #define FLATBUFFERS_COMMON_READER_H -/* Generated by flatcc 0.6.1-dev FlatBuffers schema compiler for C by dvide.com */ +/* Generated by flatcc 0.6.2 FlatBuffers schema compiler for C by dvide.com */ /* Common FlatBuffers read functionality for C. */ diff --git a/examples/c-cursor-no-gen/task_builder.h b/examples/c-cursor-no-gen/task_builder.h index 81f559b..bb842b3 100644 --- a/examples/c-cursor-no-gen/task_builder.h +++ b/examples/c-cursor-no-gen/task_builder.h @@ -1,7 +1,7 @@ #ifndef TASK_BUILDER_H #define TASK_BUILDER_H -/* Generated by flatcc 0.6.1-dev FlatBuffers schema compiler for C by dvide.com */ +/* Generated by flatcc 0.6.2 FlatBuffers schema compiler for C by dvide.com */ #ifndef TASK_READER_H #include "task_reader.h" @@ -14,7 +14,7 @@ #define flatbuffers_identifier 0 #endif #ifndef flatbuffers_extension -#define flatbuffers_extension ".bin" +#define flatbuffers_extension "bin" #endif static const flatbuffers_voffset_t __Task_required[] = { 0 }; diff --git a/examples/c-cursor-no-gen/task_reader.h b/examples/c-cursor-no-gen/task_reader.h index 5bd6a1d..983bf90 100644 --- a/examples/c-cursor-no-gen/task_reader.h +++ b/examples/c-cursor-no-gen/task_reader.h @@ -1,7 +1,7 @@ #ifndef TASK_READER_H #define TASK_READER_H -/* Generated by flatcc 0.6.1-dev FlatBuffers schema compiler for C by dvide.com */ +/* Generated by flatcc 0.6.2 FlatBuffers schema compiler for C by dvide.com */ #ifndef FLATBUFFERS_COMMON_READER_H #include "flatbuffers_common_reader.h" @@ -15,7 +15,7 @@ #define flatbuffers_identifier 0 #endif #ifndef flatbuffers_extension -#define flatbuffers_extension ".bin" +#define flatbuffers_extension "bin" #endif @@ -24,14 +24,17 @@ typedef struct Task_table *Task_mutable_table_t; typedef const flatbuffers_uoffset_t *Task_vec_t; typedef flatbuffers_uoffset_t *Task_mutable_vec_t; #ifndef Task_file_identifier -#define Task_file_identifier flatbuffers_identifier +#define Task_file_identifier 0 #endif /* deprecated, use Task_file_identifier */ #ifndef Task_identifier -#define Task_identifier flatbuffers_identifier +#define Task_identifier 0 #endif #define Task_type_hash ((flatbuffers_thash_t)0x76ef3d8c) #define Task_type_identifier "\x8c\x3d\xef\x76" +#ifndef Task_file_extension +#define Task_file_extension "bin" +#endif diff --git a/examples/cpp-autogen/.gitignore b/examples/cpp-autogen/.gitignore new file mode 100644 index 0000000..4d9982e --- /dev/null +++ b/examples/cpp-autogen/.gitignore @@ -0,0 +1,4 @@ +*.obx.* +objectbox-model.h +objectbox-model.json + diff --git a/examples/cpp-autogen/CMakeLists.txt b/examples/cpp-autogen/CMakeLists.txt new file mode 100644 index 0000000..7f358f8 --- /dev/null +++ b/examples/cpp-autogen/CMakeLists.txt @@ -0,0 +1,16 @@ +# C++ local-only example using objectbox-generator via cmake +set(PROJECT_NAME objectbox-c-examples-tasks-cpp-autogen) +project(${PROJECT_NAME} CXX) +add_executable(${PROJECT_NAME} + main.cpp + ) + +# add_obx_schema provided by ObjectBoxGenerator package. +add_obx_schema(TARGET ${PROJECT_NAME} SCHEMA_FILES tasklist.fbs INSOURCE) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED YES + ) +target_link_libraries(${PROJECT_NAME} objectbox) +target_include_directories(${PROJECT_NAME} PRIVATE ../../include ../../external) diff --git a/examples/cpp-autogen/main.cpp b/examples/cpp-autogen/main.cpp new file mode 100644 index 0000000..4431720 --- /dev/null +++ b/examples/cpp-autogen/main.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2018-2020 ObjectBox Ltd. All rights reserved. + * + * Licensed 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. + */ + +#define OBX_CPP_FILE +#include "tasklist-example-app.hpp" + +int main(int argc, char* argv[]) { + // create_obx_model() provided by objectbox-model.h + // obx interface contents provided by objectbox.hpp + obx::Options options(create_obx_model()); + + if (int err = processArgs(argc, argv, options)) { + return err; + } + + obx::Store store(options); + TasklistCmdlineApp app(store); + return app.run(); +} \ No newline at end of file diff --git a/examples/cpp-autogen/tasklist-example-app.hpp b/examples/cpp-autogen/tasklist-example-app.hpp new file mode 100644 index 0000000..a7c1c5a --- /dev/null +++ b/examples/cpp-autogen/tasklist-example-app.hpp @@ -0,0 +1,186 @@ +/* + * Copyright 2018-2020 ObjectBox Ltd. All rights reserved. + * + * Licensed 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 +#include +#include + +#include "objectbox-model.h" +#include "objectbox.hpp" +#include "tasklist.obx.hpp" + +int processArgs(int argc, char* argv[], obx::Options& outOptions) { + // Remember, argv[0] is application path + + const char* directory = nullptr; + if (argc == 2) { + directory = argv[1]; + } else if (argc == 3) { + std::string paramName = argv[1]; + if (paramName == "-d" || paramName == "--directory") { + directory = argv[2]; + } else { + std::cerr << "Unknown argument " << paramName << ". Expected -d or --directory." << std::endl; + return 1; + } + } else if (argc > 3) { + std::cerr << "This app only takes zero, one or two arguments" << std::endl; + return 1; + } + + if (directory) { + outOptions.directory(directory); + std::cout << "Using DB directory " << directory << std::endl; + } + + return 0; +} + +class TasklistCmdlineApp { + obx::Store& store; + obx::Box taskBox; + + /// caching the query object is optimal so it's not constructed on each invocation + obx::Query unfinishedTasksQuery; + +public: + TasklistCmdlineApp(obx::Store& obxStore) + : store(obxStore), + taskBox(obxStore), + unfinishedTasksQuery(taskBox.query(Task_::date_finished.equals(0)).build()) {} + + int run() { + std::cout << "Welcome to the ObjectBox tasks-list app example" << std::endl; + printHelp(); + + std::string input; + std::string cmd; + std::string arg; + while (std::getline(std::cin, input)) { // quit the program with ctrl-d + if (input.empty()) continue; + + splitInput(input, cmd, arg); + try { + switch (getCommand(cmd)) { + case Command::New: { + Task object{}; + object.text = arg; + object.date_created = millisSinceEpoch(); + taskBox.put(object); + std::cout << "New task: " << object.id << " - " << object.text << std::endl; + break; + } + case Command::Done: { + obx_id id = std::stoull(arg); + std::unique_ptr task = taskBox.get(id); + if (!task) { + std::cerr << "Task ID " << arg << " not found" << std::endl; + } else if (task->date_finished != 0) { + std::cerr << "Task ID " << id << " is already done" << std::endl; + } else { + task->date_finished = millisSinceEpoch(); + std::cout << "Task ID " << id << " marked as done at " << task->date_finished << std::endl; + taskBox.put(*task); + } + break; + } + case Command::List: { + std::vector> list; + + if (arg == "-a") { + list = taskBox.getAll(); + } else if (arg.empty()) { + list = unfinishedTasksQuery.findUniquePtrs(); + } else { + std::cerr << "Unknown ls argument " << arg << std::endl; + printHelp(); + break; + } + + printf("%3s %-14s %-14s %s\n", "ID", "Created", "Finished", "Text"); + for (const auto& task : list) { + printf("%3" PRIu64 " %-14s %-14s %s\n", task->id, fmtTime(task->date_created).c_str(), + fmtTime(task->date_finished).c_str(), task->text.c_str()); + } + break; + } + case Command::Exit: + return 0; + case Command::Help: + printHelp(); + break; + case Command::Unknown: + default: + std::cerr << "Unknown command " << cmd << std::endl; + fflush(stderr); + printHelp(); + break; + } + + } catch (const std::exception& e) { + std::cerr << "Error executing " << input << std::endl << e.what(); + return 1; + } + } + + return 0; + } + +protected: + void splitInput(const std::string& input, std::string& outCmd, std::string& outArg) { + std::string::size_type pos = input.find(" "); + if (pos == std::string::npos) { + outCmd = input; + outArg.clear(); + } else { + outCmd = input.substr(0, pos); + outArg = input.substr(pos + 1); + } + } + + enum class Command { New, Done, Exit, List, Help, Unknown }; + + Command getCommand(const std::string& cmd) { + if (cmd == "new") return Command::New; + if (cmd == "done") return Command::Done; + if (cmd == "exit") return Command::Exit; + if (cmd == "ls") return Command::List; + if (cmd == "help") return Command::Help; + return Command::Unknown; + } + + void printHelp() { + std::cout << "Available commands are: " << std::endl + << " ls [-a] list tasks - unfinished or all (-a flag)" << std::endl + << " new Task text create a new task with the text 'Task text'" << std::endl + << " done ID mark task with the given ID as done" << std::endl + << " exit close the program" << std::endl + << " help display this help" << std::endl; + } + + uint64_t millisSinceEpoch() { + auto time = std::chrono::system_clock::now().time_since_epoch(); + return static_cast(std::chrono::duration_cast(time).count()); + } + + /// Formats the given UNIX timestamp as a human-readable time + std::string fmtTime(uint64_t timestamp) { + // NOTE: implement your fancy time formatting here... + return std::to_string(timestamp); + } +}; \ No newline at end of file diff --git a/examples/cpp-autogen/tasklist.fbs b/examples/cpp-autogen/tasklist.fbs new file mode 100644 index 0000000..06a91c4 --- /dev/null +++ b/examples/cpp-autogen/tasklist.fbs @@ -0,0 +1,11 @@ +table Task { + id: ulong; + text: string; + + /// objectbox:date + date_created: long; + + /// objectbox:date + date_finished: long; + +} diff --git a/examples/cpp-gen-sync/tasklist.obx.cpp b/examples/cpp-gen-sync/tasklist.obx.cpp index e221b15..59c3646 100644 --- a/examples/cpp-gen-sync/tasklist.obx.cpp +++ b/examples/cpp-gen-sync/tasklist.obx.cpp @@ -11,10 +11,10 @@ void Task::_OBX_MetaInfo::toFlatBuffer(flatbuffers::FlatBufferBuilder& fbb, cons fbb.Clear(); auto offsettext = fbb.CreateString(object.text); flatbuffers::uoffset_t fbStart = fbb.StartTable(); - fbb.TrackField(4, fbb.PushElement(object.id)); + fbb.AddElement(4, object.id); fbb.AddOffset(6, offsettext); - fbb.TrackField(10, fbb.PushElement(object.date_created)); - fbb.TrackField(12, fbb.PushElement(object.date_finished)); + fbb.AddElement(10, object.date_created); + fbb.AddElement(12, object.date_finished); flatbuffers::Offset offset; offset.o = fbb.EndTable(fbStart); fbb.Finish(offset); @@ -38,10 +38,13 @@ void Task::_OBX_MetaInfo::fromFlatBuffer(const void* data, size_t, Task& outObje outObject.id = table->GetField(4, 0); { auto* ptr = table->GetPointer(6); - if (ptr) outObject.text.assign(ptr->c_str()); + if (ptr) { + outObject.text.assign(ptr->c_str(), ptr->size()); + } else { + outObject.text.clear(); + } } outObject.date_created = table->GetField(10, 0); outObject.date_finished = table->GetField(12, 0); - } diff --git a/examples/cpp-gen/tasklist.obx.cpp b/examples/cpp-gen/tasklist.obx.cpp index e221b15..59c3646 100644 --- a/examples/cpp-gen/tasklist.obx.cpp +++ b/examples/cpp-gen/tasklist.obx.cpp @@ -11,10 +11,10 @@ void Task::_OBX_MetaInfo::toFlatBuffer(flatbuffers::FlatBufferBuilder& fbb, cons fbb.Clear(); auto offsettext = fbb.CreateString(object.text); flatbuffers::uoffset_t fbStart = fbb.StartTable(); - fbb.TrackField(4, fbb.PushElement(object.id)); + fbb.AddElement(4, object.id); fbb.AddOffset(6, offsettext); - fbb.TrackField(10, fbb.PushElement(object.date_created)); - fbb.TrackField(12, fbb.PushElement(object.date_finished)); + fbb.AddElement(10, object.date_created); + fbb.AddElement(12, object.date_finished); flatbuffers::Offset offset; offset.o = fbb.EndTable(fbStart); fbb.Finish(offset); @@ -38,10 +38,13 @@ void Task::_OBX_MetaInfo::fromFlatBuffer(const void* data, size_t, Task& outObje outObject.id = table->GetField(4, 0); { auto* ptr = table->GetPointer(6); - if (ptr) outObject.text.assign(ptr->c_str()); + if (ptr) { + outObject.text.assign(ptr->c_str(), ptr->size()); + } else { + outObject.text.clear(); + } } outObject.date_created = table->GetField(10, 0); outObject.date_finished = table->GetField(12, 0); - } diff --git a/examples/regenerate-schemas.sh b/examples/regenerate-schemas.sh index 3f21b9c..14ead6c 100755 --- a/examples/regenerate-schemas.sh +++ b/examples/regenerate-schemas.sh @@ -25,10 +25,15 @@ ${obxgen} -version ( cd cpp-gen - ${obxgen} -cpp tasklist.fbs + ${obxgen} -cpp11 tasklist.fbs ) ( cd cpp-gen-sync - ${obxgen} -cpp tasklist.fbs -) \ No newline at end of file + ${obxgen} -cpp11 tasklist.fbs +) + +( + cd vectorsearch-cities + ${obxgen} -cpp city.fbs +) diff --git a/examples/vectorsearch-cities/CMakeLists.txt b/examples/vectorsearch-cities/CMakeLists.txt new file mode 100644 index 0000000..49d45c0 --- /dev/null +++ b/examples/vectorsearch-cities/CMakeLists.txt @@ -0,0 +1,18 @@ +# C++ local-only example +cmake_minimum_required(VERSION 3.5) +set(PROJECT_NAME objectbox-c-examples-vectorsearch-cities) +project(${PROJECT_NAME} CXX) +add_executable(${PROJECT_NAME} + main.cpp + city.obx.cpp + ) +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED YES + ) +target_link_libraries(${PROJECT_NAME} objectbox) +target_include_directories(${PROJECT_NAME} PRIVATE ../../include ../../external) + +configure_file(cities.csv ${CMAKE_CURRENT_BINARY_DIR}/cities.csv COPYONLY) +add_custom_target(copy_csv_file ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cities.csv) +add_dependencies(${PROJECT_NAME} copy_csv_file) diff --git a/examples/vectorsearch-cities/README.md b/examples/vectorsearch-cities/README.md new file mode 100644 index 0000000..1847541 --- /dev/null +++ b/examples/vectorsearch-cities/README.md @@ -0,0 +1,83 @@ +# C++ Example App: VectorSearch-Cities + +This is a sample command-line C++ application that +illustrates conducting vector-search query operations in ObjectBox. + +## Prerequisites + +- Download ObjectBox + +## Build + +``` +cmake -S . -B build +cmake --build build +``` + +## Run + +``` +cd build +./objectbox-c-examples-vectorsearch-cities +``` + +``` +Welcome to the ObjectBox vectorsearch-cities app example +Available commands are: + import import cities database + ls [] list cities (with common if set) + name [,] search nearest neighbor cities to ( defaults to 5) + geo ,[,] search nearest neighbor cities to geo-location ( defaults to 5) + add ,, add location + exit close the program + help display this help +``` + +If you start this example for the first time, import some capital cities: + +``` +import ../cities.csv +Imported 211 entries from ../cities.csv +``` + +List all entries: + +``` +ls +[..] +207 Willemstad 12.11 -68.93 +208 Windhoek -22.57 17.08 +209 Yamoussoukro 6.83 -5.29 +210 Yaoundé 3.85 11.50 +211 Yaren -0.55 166.92 +212 Yerevan 40.19 44.52 +``` + +Search nearest neighbors to Berlin: + +``` +name Berlin, 10 + ID Name Location Score + 28 Berlin 52.52 13.40 0.00 +147 Prague 50.08 14.44 7.04 + 49 Copenhagen 55.68 12.57 10.66 +200 Vienna 48.21 16.37 27.41 + 34 Bratislava 48.15 17.11 32.82 + 89 Ljubljana 46.06 14.51 42.98 +196 Vaduz 47.14 9.52 44.02 + 39 Budapest 47.50 19.04 56.98 +203 Warsaw 52.23 21.01 57.95 + 94 Luxembourg City 49.61 6.13 61.36 +``` + +Search nearest neighbors to Area 51: + +``` +geo 37.23, -115.80 + ID Name Location Score +107 Mexico City 19.43 -99.13 594.53 + 27 Belmopan 17.25 -88.76 1130.38 + 64 Guatemala City 14.63 -90.51 1150.28 +164 San Salvador 13.69 -89.22 1260.59 + 67 Havana 23.11 -82.37 1317.07 +``` diff --git a/examples/vectorsearch-cities/VectorSearchCitiesApp.hpp b/examples/vectorsearch-cities/VectorSearchCitiesApp.hpp new file mode 100644 index 0000000..057a47d --- /dev/null +++ b/examples/vectorsearch-cities/VectorSearchCitiesApp.hpp @@ -0,0 +1,307 @@ +/* + * Copyright 2018-2024 ObjectBox Ltd. All rights reserved. + * + * Licensed 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 +#include +#include +#include +#include + +#include "city.obx.hpp" +#include "objectbox-model.h" +#include "objectbox.hpp" + +int processArgs(int argc, char* argv[], obx::Options& outOptions) { + // Remember, argv[0] is application path + + const char* directory = nullptr; + if (argc == 2) { + directory = argv[1]; + } else if (argc == 3) { + std::string paramName = argv[1]; + if (paramName == "-d" || paramName == "--directory") { + directory = argv[2]; + } else { + std::cerr << "Unknown argument " << paramName << ". Expected -d or --directory." << std::endl; + return 1; + } + } else if (argc > 3) { + std::cerr << "This app only takes zero, one or two arguments" << std::endl; + return 1; + } + + if (directory) { + outOptions.directory(directory); + std::cout << "Using DB directory " << directory << std::endl; + } + + return 0; +} + +class VectorSearchCitiesApp { + // for string delimiter + enum class Command { Import, Add, SearchByName, SearchByGeoLocation, RemoveAll, Exit, List, Help, Unknown }; + + obx::Store& store; + obx::Box cityBox; + obx::Query queryCityByName_; + obx::Query queryCityByLocation_; + +public: + VectorSearchCitiesApp(obx::Store& obxStore) + : store(obxStore), + cityBox(obxStore), + queryCityByName_(cityBox.query(City_::name.startsWith("", false)).build()), + queryCityByLocation_(cityBox.query(City_::location.nearestNeighbors({}, 1)).build()) {} + + /// If no cities are present in the DB, tries to import from cities.csv + void checkImportData() { + uint64_t count = cityBox.count(); + if (count == 0) { + bool ok = importData("cities.csv"); + if (!ok) { + std::cout << "NOTE: The initial import from cities.csv failed.\n" + "Maybe try to locate the files and import it manually?\n"; + } + } else { + std::cout << "Will not load cities.csv; we already have " << count << " cities" << std::endl; + } + } + + int run() { + std::cout << "Welcome to the ObjectBox VectorSearch Cities app example" << std::endl; + printHelp(); + + std::string input; + std::vector args; + while (std::getline(std::cin, input)) { // quit the program with ctrl-d + if (input.empty()) continue; + + splitInput(input, args); + try { + Command command = getCommand(args[0]); + if (command == Command::Exit) return 0; + processCommand(command, args); + } catch (const std::exception& e) { + std::cerr << "Error executing " << input << std::endl << e.what(); + return 1; + } + } + + return 0; + } + +protected: + void processCommand(Command command, const std::vector& args) { + switch (command) { + case Command::Import: { + if (args.size() == 2) { + bool success = importData(args[1].c_str()); + if (!success) { + std::cerr << "Error: CVS file not found: " << args[1] << std::endl; + } + } else { + std::cerr << "Missing arguments for import " << std::endl; + printHelp(); + } + break; + } + case Command::Add: { + if (args.size() == 4) { + City object{}; + object.name = args[1]; + object.location = toLocation(args[2], args[3]); + cityBox.put(object); + std::cout << "Added city: " << object.id << " - " << object.name << std::endl; + } else { + std::cerr << "Missing arguments for new " << std::endl; + printHelp(); + } + break; + } + case Command::List: { + if (args.size() >= 1 && args.size() <= 2) { + if (args.size() == 1) { + dump(cityBox.getAll()); + } else { + dump(cityBox.query(City_::name.startsWith(args[1])).build().find()); + } + } else { + std::cerr << "Unknown ls arguments." << std::endl; + printHelp(); + break; + } + break; + } + case Command::SearchByName: { + if (args.size() >= 2 && args.size() <= 3) { + int64_t numResults = (args.size() == 3) ? atoi(args[2].c_str()) : 5; + queryCityByName_.setParameter(City_::name, args[1]); + std::unique_ptr result = queryCityByName_.findFirst(); + // Occasionally, users might import cities.csv multiple time; we just take the first match + if (result) { + queryCityByLocation_.setParameter(City_::location, result->location); + queryCityByLocation_.setParameterMaxNeighbors(City_::location, numResults); + std::vector> citiesAndScores = queryCityByLocation_.findWithScores(); + dump(citiesAndScores); + } else { + std::cerr << "Unknown City " << args[1] << std::endl; + } + } else { + std::cerr << "city-neighbors: wrong arguments." << std::endl; + printHelp(); + break; + } + break; + } + case Command::SearchByGeoLocation: { + if (args.size() >= 3 && args.size() <= 4) { + std::vector location = toLocation(args[1], args[2]); + int64_t numResults = (args.size() == 4) ? atoi(args[3].c_str()) : 5; + queryCityByLocation_.setParameter(City_::location, location); + queryCityByLocation_.setParameterMaxNeighbors(City_::location, numResults); + std::vector> citiesAndScores = queryCityByLocation_.findWithScores(); + dump(citiesAndScores); + } else { + std::cerr << "neighbors: syntax error" << std::endl; + printHelp(); + break; + } + break; + } + case Command::RemoveAll: + if (args.size() == 1) { + uint64_t removedCount = cityBox.removeAll(); + std::cout << "removeAll removed " << removedCount << " cities" << std::endl; + } else { + std::cerr << "removeAll does not take any parameters" << std::endl; + } + break; + case Command::Exit: + assert(false); // Should be checked by the caller already + break; + case Command::Help: + printHelp(); + break; + case Command::Unknown: + default: + std::cerr << "Unknown command " << args.at(0) << std::endl; + fflush(stderr); + printHelp(); + break; + } + } + + static void splitInput(const std::string& input, std::vector& outArgs, char delim = ' ') { + size_t delimIndex = 0; + outArgs.clear(); + std::string::size_type pos, pos_start = 0; + while ((pos = input.find(delim, pos_start)) != std::string::npos) { + std::string arg = input.substr(pos_start, pos - pos_start); + pos_start = pos + 1; + outArgs.push_back(arg); + delim = ','; + } + outArgs.push_back(input.substr(pos_start)); + } + + Command getCommand(const std::string& cmd) { + if (cmd == "import") return Command::Import; + if (cmd == "add") return Command::Add; + if (cmd == "name") return Command::SearchByName; + if (cmd == "geo") return Command::SearchByGeoLocation; + if (cmd == "removeAll") return Command::RemoveAll; + if (cmd == "exit" || cmd == "quit") return Command::Exit; + if (cmd == "ls" || cmd == "list") return Command::List; + if (cmd == "help" || cmd == "?") return Command::Help; + return Command::Unknown; + } + + void printHelp() { + std::cout << "Available commands are:\n" + << " import Import CSV data (try cities.csv)\n" + << " ls [] List cities (with common if set)\n" + << " name [,] Search cities to nearest to the given name/prefix\n" + << " ( defaults to 5; try `name Berlin` or `name berl`)\n" + << " geo ,[,] Search cities nearest to the given geo location\n" + << " ( defaults to 5; try `geo 50,10`)\n" + << " add ,, add location\n" + << " removeAll remove all existing data\n" + << " exit close the program\n" + << " help display this help" << std::endl; + } + + static std::vector toLocation(const std::string& latitude, const std::string& longitude) { + return {float(std::atof(latitude.c_str())), float(std::atof(longitude.c_str()))}; + } + + static void dump(const City& city) { + printf("%3" PRIu64 " %-18s %-9.2f %-9.2f\n", city.id, city.name.c_str(), city.location[0], city.location[1]); + } + + static void dump(const std::vector>&& list) { + printf("%3s %-18s %-18s \n", "ID", "Name", "Location"); + for (const auto& city : list) { + dump(*city); + } + } + + static void dump(const std::vector& list) { + printf("%3s %-18s %-18s \n", "ID", "Name", "Location"); + for (const auto& city : list) { + dump(city); + } + } + + static void dump(const std::pair& pair) { + printf("%3" PRIu64 " %-18s %-9.2f %-9.2f %5.2f\n", pair.first.id, pair.first.name.c_str(), + pair.first.location[0], pair.first.location[1], pair.second); + } + + static void dump(const std::vector>& list) { + printf("%3s %-18s %-19s %-10s\n", "ID", "Name", "Location", "Score"); + for (const auto& city : list) { + dump(city); + } + } + + bool importData(const char* path) { + std::ifstream ifs(path); + if (!ifs.good()) { + return false; + } + obx::Transaction tx = store.tx(obx::TxMode::WRITE); + size_t count = 0; + for (;;) { + std::string line; + std::vector cols; + std::getline(ifs, line); + if (!ifs.good()) break; + splitInput(line, cols, ','); + City object; + object.id = 0; + object.name = cols[0]; + object.location = toLocation(cols[1], cols[2]); + cityBox.put(object); + count++; + } + tx.success(); + std::cout << "Imported " << count << " entries from " << path << std::endl; + return true; + } +}; \ No newline at end of file diff --git a/examples/vectorsearch-cities/cities.csv b/examples/vectorsearch-cities/cities.csv new file mode 100644 index 0000000..d0a609d --- /dev/null +++ b/examples/vectorsearch-cities/cities.csv @@ -0,0 +1,213 @@ +Abuja, 9.0765, 7.3986 +Accra, 5.6037, -0.1870 +Addis Ababa, 9.0084, 38.7813 +Algiers, 36.7529, 3.0420 +Amman, 31.9632, 35.9306 +Amsterdam, 52.3667, 4.8945 +Ankara, 39.9334, 32.8597 +Antananarivo, -18.8792, 47.5079 +Apia, -13.8330, -171.7667 +Ashgabat, 37.9601, 58.3261 +Asmara, 15.3229, 38.9251 +Astana, 51.1796, 71.4475 +Asunción, -25.2637, -57.5759 +Athens, 37.9795, 23.7162 +Avarua, -21.2079, -159.7750 +Baghdad, 33.3152, 44.3661 +Baku, 40.4093, 49.8671 +Bamako, 12.6530, -7.9864 +Bandar Seri Begawan, 4.9031, 114.9398 +Bangkok, 13.7563, 100.5018 +Bangui, 4.3947, 18.5582 +Banjul, 13.4549, -16.5790 +Basseterre, 17.3026, -62.7177 +Beijing, 39.9042, 116.4074 +Beirut, 33.8889, 35.4944 +Belgrade, 44.7866, 20.4489 +Belmopan, 17.2510, -88.7590 +Berlin, 52.5200, 13.4050 +Bern, 46.9480, 7.4474 +Bishkek, 42.8746, 74.5698 +Bissau, 11.8636, -15.5842 +Bogotá, 4.7109, -74.0721 +Brasília, -15.8267, -47.9218 +Bratislava, 48.1486, 17.1077 +Brazzaville, -4.2634, 15.2429 +Bridgetown, 13.1132, -59.5988 +Brussels, 50.8503, 4.3517 +Bucharest, 44.4268, 26.1025 +Budapest, 47.4979, 19.0402 +Buenos Aires, -34.6037, -58.3816 +Bujumbura, -3.3818, 29.3622 +Cairo, 30.0444, 31.2357 +Canberra, -35.2809, 149.1300 +Caracas, 10.4806, -66.9036 +Castries, 14.0101, -60.9874 +Chisinau, 47.0105, 28.8638 +Colombo, 6.9271, 79.8612 +Conakry, 9.6412, -13.5784 +Copenhagen, 55.6761, 12.5683 +Dakar, 14.7167, -17.4677 +Damascus, 33.5131, 36.2919 +Dhaka, 23.8103, 90.4125 +Dili, -8.5569, 125.5603 +Djibouti, 11.5890, 43.1456 +Dodoma, -6.1748, 35.7469 +Doha, 25.2854, 51.5310 +Dublin, 53.3498, -6.2603 +Dushanbe, 38.5868, 68.7841 +Freetown, 8.4840, -13.2299 +Funafuti, -8.5210, 179.1962 +Gaborone, -24.6282, 25.9231 +Georgetown, 6.8013, -58.1550 +Gibraltar, 36.1408, -5.3536 +Guatemala City, 14.6349, -90.5069 +Hanoi, 21.0278, 105.8342 +Harare, -17.8252, 31.0335 +Havana, 23.1136, -82.3666 +Helsinki, 60.1699, 24.9384 +Honiara, -9.4376, 159.9720 +Islamabad, 33.6844, 73.0479 +Jakarta, -6.2088, 106.8456 +Juba, 4.8594, 31.5713 +Kabul, 34.5553, 69.2075 +Kampala, 0.3476, 32.5825 +Kathmandu, 27.7172, 85.3240 +Khartoum, 15.5007, 32.5599 +Kiev, 50.4501, 30.5234 +Kigali, -1.9441, 30.0619 +Kingston, 17.9710, -76.7924 +Kingstown, 13.1467, -61.2121 +Kinshasa, -4.4419, 15.2663 +Kuala Lumpur, 3.1390, 101.6869 +Kuwait City, 29.3759, 47.9774 +La Paz, -16.4897, -68.1193 +Libreville, 0.4162, 9.4673 +Lilongwe, -13.9626, 33.7741 +Lima, -12.0464, -77.0428 +Lisbon, 38.7223, -9.1393 +Ljubljana, 46.0569, 14.5058 +Lomé, 6.1319, 1.2228 +London, 51.5072, -0.1276 +Luanda, -8.8399, 13.2894 +Lusaka, -15.3875, 28.3228 +Luxembourg City, 49.6116, 6.1319 +Madrid, 40.4168, -3.7038 +Majuro, 7.1164, 171.1859 +Malabo, 3.7508, 8.7839 +Male, 4.1755, 73.5093 +Mamoudzou, -12.7871, 45.2750 +Managua, 12.1364, -86.2514 +Manama, 26.2285, 50.5860 +Manila, 14.5995, 120.9842 +Maputo, -25.8918, 32.6051 +Maseru, -29.2976, 27.4854 +Mbabane, -26.3054, 31.1367 +Melekeok, 7.4874, 134.6265 +Mexico City, 19.4326, -99.1332 +Minsk, 53.9045, 27.5615 +Mogadishu, 2.0469, 45.3182 +Monaco, 43.7325, 7.4189 +Monrovia, 6.3005, -10.7974 +Montevideo, -34.9011, -56.1645 +Moroni, -11.7022, 43.2551 +Moscow, 55.7558, 37.6173 +Muscat, 23.5859, 58.4059 +Nairobi, -1.2921, 36.8219 +Nassau, 25.0478, -77.3554 +Naypyidaw, 19.7633, 96.0785 +New Delhi, 28.6139, 77.2090 +Ngerulmud, 7.5004, 134.6249 +Niamey, 13.5122, 2.1254 +Nicosia, 35.1725, 33.365 +Nicosia Northern Cyprus, 35.19, 33.363611 +Nouakchott, 18.0735, -15.9582 +Nuku'alofa, -21.1393, -175.2049 +Nuuk, 64.1836, -51.7214 +Oranjestad, 12.5092, -70.0086 +Oslo, 59.9139, 10.7522 +Ottawa, 45.4215, -75.6972 +Ouagadougou, 12.3714, -1.5197 +Pago Pago, -14.2794, -170.7004 +Palikir, 6.9248, 158.1614 +Panama City, 8.9824, -79.5199 +Papeete, -17.5350, -149.5699 +Paramaribo, 5.8520, -55.2038 +Paris, 48.8566, 2.3522 +Philipsburg, 18.0255, -63.0450 +Phnom Penh, 11.5564, 104.9282 +Plymouth, 16.7056, -62.2126 +Podgorica, 42.4304, 19.2594 +Port Louis, -20.1619, 57.4989 +Port Moresby, -9.4438, 147.1803 +Port Vila, -17.7416, 168.3213 +Port-au-Prince, 18.5944, -72.3074 +Port of Spain, 10.6596, -61.4789 +Porto-Novo, 6.4968, 2.6283 +Prague, 50.0755, 14.4378 +Praia, 14.9195, -23.5087 +Pretoria, -25.7463, 28.1876 +Pristina, 42.6629, 21.1655 +Pyongyang, 39.0392, 125.7625 +Quito, -0.1807, -78.4678 +Rabat, 33.9693, -6.9275 +Reykjavik, 64.1466, -21.9426 +Riga, 56.9496, 24.1052 +Riyadh, 24.7136, 46.6753 +Road Town, 18.4207, -64.6399 +Rome, 41.9028, 12.4964 +Roseau, 15.3092, -61.3794 +Saipan, 15.1833, 145.7500 +San José, 9.9281, -84.0907 +San Juan, 18.4655, -66.1057 +San Marino, 43.9424, 12.4578 +San Salvador, 13.6929, -89.2182 +Sana'a, 15.3694, 44.1910 +Santiago, -33.4489, -70.6693 +Santo Domingo, 18.4861, -69.9312 +Sarajevo, 43.8564, 18.4131 +Seoul, 37.5665, 126.9780 +Singapore, 1.3521, 103.8198 +Skopje, 41.9973, 21.4279 +Sofia, 42.6975, 23.3241 +Sri Jayawardenepura Kotte, 6.8928, 79.9277 +St. George's, 12.0561, -61.7485 +St. Helier, 49.1839, -2.1064 +St. John's, 17.1171, -61.8456 +St. Peter Port, 49.4599, -2.5352 +Stanley, -51.7020, -57.8517 +Stockholm, 59.3293, 18.0686 +Sucre, -19.0421, -65.2559 +Sukhumi, 43.0004, 41.0234 +Suva, -18.1416, 178.4419 +Taipei, 25.0330, 121.5654 +Tallinn, 59.4370, 24.7536 +Tarawa, 1.4170, 173.0000 +Tashkent, 41.2995, 69.2401 +Tbilisi, 41.7151, 44.8271 +Tegucigalpa, 14.0818, -87.2068 +Tehran, 35.6892, 51.3890 +Thimphu, 27.4728, 89.6390 +Tirana, 41.3275, 19.8187 +Tokyo, 35.6762, 139.6503 +Tripoli, 32.8867, 13.1910 +Tunis, 36.8065, 10.1815 +Ulaanbaatar, 47.8864, 106.9057 +Vaduz, 47.1410, 9.5215 +Valletta, 35.9042, 14.5189 +Vatican City, 41.9029, 12.4534 +Victoria, -4.6182, 55.4515 +Vienna, 48.2082, 16.3738 +Vientiane, 17.9757, 102.6331 +Vilnius, 54.6872, 25.2797 +Warsaw, 52.2297, 21.0122 +Washington D.C., 38.9072, -77.0369 +Wellington, -41.2865, 174.7762 +West Island, -12.1880, 96.8292 +Willemstad, 12.1091, -68.9319 +Windhoek, -22.5749, 17.0805 +Yamoussoukro, 6.8276, -5.2893 +Yaoundé, 3.8480, 11.5021 +Yaren, -0.5467, 166.9209 +Yerevan, 40.1872, 44.5152 +Zagreb, 45.8150, 15.9819 \ No newline at end of file diff --git a/examples/vectorsearch-cities/city.fbs b/examples/vectorsearch-cities/city.fbs new file mode 100644 index 0000000..7386116 --- /dev/null +++ b/examples/vectorsearch-cities/city.fbs @@ -0,0 +1,7 @@ +table City { + id: ulong; + name: string; + /// objectbox: index=hnsw, hnsw-dimensions=2 + /// objectbox: hnsw-distance-type=Euclidean + location: [float]; +} diff --git a/examples/vectorsearch-cities/city.obx.cpp b/examples/vectorsearch-cities/city.obx.cpp new file mode 100644 index 0000000..7ef7bff --- /dev/null +++ b/examples/vectorsearch-cities/city.obx.cpp @@ -0,0 +1,55 @@ +// Code generated by ObjectBox; DO NOT EDIT. + +#include "city.obx.hpp" + +const obx::Property City_::id(1); +const obx::Property City_::name(2); +const obx::Property City_::location(3); + +void City::_OBX_MetaInfo::toFlatBuffer(flatbuffers::FlatBufferBuilder& fbb, const City& object) { + fbb.Clear(); + auto offsetname = fbb.CreateString(object.name); + auto offsetlocation = fbb.CreateVector(object.location); + flatbuffers::uoffset_t fbStart = fbb.StartTable(); + fbb.AddElement(4, object.id); + fbb.AddOffset(6, offsetname); + fbb.AddOffset(8, offsetlocation); + flatbuffers::Offset offset; + offset.o = fbb.EndTable(fbStart); + fbb.Finish(offset); +} + +City City::_OBX_MetaInfo::fromFlatBuffer(const void* data, size_t size) { + City object; + fromFlatBuffer(data, size, object); + return object; +} + +std::unique_ptr City::_OBX_MetaInfo::newFromFlatBuffer(const void* data, size_t size) { + auto object = std::make_unique(); + fromFlatBuffer(data, size, *object); + return object; +} + +void City::_OBX_MetaInfo::fromFlatBuffer(const void* data, size_t, City& outObject) { + const auto* table = flatbuffers::GetRoot(data); + assert(table); + outObject.id = table->GetField(4, 0); + { + auto* ptr = table->GetPointer(6); + if (ptr) { + outObject.name.assign(ptr->c_str(), ptr->size()); + } else { + outObject.name.clear(); + } + } + { + auto* ptr = table->GetPointer*>(8); + if (ptr) { + outObject.location.assign(ptr->begin(), ptr->end()); + } else { + outObject.location.clear(); + } + } +} + diff --git a/examples/vectorsearch-cities/city.obx.hpp b/examples/vectorsearch-cities/city.obx.hpp new file mode 100644 index 0000000..bf6ea61 --- /dev/null +++ b/examples/vectorsearch-cities/city.obx.hpp @@ -0,0 +1,44 @@ +// Code generated by ObjectBox; DO NOT EDIT. + +#pragma once + +#include +#include + +#include "flatbuffers/flatbuffers.h" +#include "objectbox.h" +#include "objectbox.hpp" + + +struct City_; + +struct City { + obx_id id; + std::string name; + std::vector location; + + struct _OBX_MetaInfo { + static constexpr obx_schema_id entityId() { return 1; } + + static void setObjectId(City& object, obx_id newId) { object.id = newId; } + + /// Write given object to the FlatBufferBuilder + static void toFlatBuffer(flatbuffers::FlatBufferBuilder& fbb, const City& object); + + /// Read an object from a valid FlatBuffer + static City fromFlatBuffer(const void* data, size_t size); + + /// Read an object from a valid FlatBuffer + static std::unique_ptr newFromFlatBuffer(const void* data, size_t size); + + /// Read an object from a valid FlatBuffer + static void fromFlatBuffer(const void* data, size_t size, City& outObject); + }; +}; + +struct City_ { + static const obx::Property id; + static const obx::Property name; + static const obx::Property location; +}; + diff --git a/examples/vectorsearch-cities/main.cpp b/examples/vectorsearch-cities/main.cpp new file mode 100644 index 0000000..b6d6df2 --- /dev/null +++ b/examples/vectorsearch-cities/main.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2018-2024 ObjectBox Ltd. All rights reserved. + * + * Licensed 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. + */ + +#define OBX_CPP_FILE // Signals objectbox.hpp to add function definitions + +#include "VectorSearchCitiesApp.hpp" +#include "objectbox.hpp" + +using namespace obx; + +int main(int argc, char* argv[]) { + if (!obx_has_feature(OBXFeature_VectorSearch)) { + std::cerr << "Vector search is not supported in this edition.\n" + "Please ensure to get ObjectBox with vector search enabled." + << std::endl; + return 1; + } + + // Hint: create_obx_model() is provided by objectbox-model.h, which is a (pre)generated source file + Options options(create_obx_model()); + + if (int err = processArgs(argc, argv, options)) { + return err; + } + + Store store(options); + VectorSearchCitiesApp app(store); + app.checkImportData(); + return app.run(); +} diff --git a/examples/vectorsearch-cities/objectbox-model.h b/examples/vectorsearch-cities/objectbox-model.h new file mode 100644 index 0000000..95c59ae --- /dev/null +++ b/examples/vectorsearch-cities/objectbox-model.h @@ -0,0 +1,44 @@ +// Code generated by ObjectBox; DO NOT EDIT. + +#pragma once + +#ifdef __cplusplus +#include +#include +extern "C" { +#else +#include +#include +#endif +#include "objectbox.h" + +/// Initializes an ObjectBox model for all entities. +/// The returned pointer may be NULL if the allocation failed. If the returned model is not NULL, you should check if +/// any error occurred by calling obx_model_error_code() and/or obx_model_error_message(). If an error occurred, you're +/// responsible for freeing the resources by calling obx_model_free(). +/// In case there was no error when setting the model up (i.e. obx_model_error_code() returned 0), you may configure +/// OBX_store_options with the model by calling obx_opt_model() and subsequently opening a store with obx_store_open(). +/// As soon as you call obx_store_open(), the model pointer is consumed and MUST NOT be freed manually. +static inline OBX_model* create_obx_model() { + OBX_model* model = obx_model(); + if (!model) return NULL; + + obx_model_entity(model, "City", 1, 607353604830599953); + obx_model_property(model, "id", OBXPropertyType_Long, 1, 3289333370985535139); + obx_model_property_flags(model, OBXPropertyFlags_ID); + obx_model_property(model, "name", OBXPropertyType_String, 2, 8840693632700366740); + obx_model_property(model, "location", OBXPropertyType_FloatVector, 3, 6028438376669885699); + obx_model_property_flags(model, OBXPropertyFlags_INDEXED); + obx_model_property_index_hnsw_dimensions(model, 2); + obx_model_property_index_hnsw_distance_type(model, OBXVectorDistanceType_Euclidean); + obx_model_property_index_id(model, 1, 4616869581890712534); + obx_model_entity_last_property_id(model, 3, 6028438376669885699); + + obx_model_last_entity_id(model, 1, 607353604830599953); + obx_model_last_index_id(model, 1, 4616869581890712534); + return model; // NOTE: the returned model will contain error information if an error occurred. +} + +#ifdef __cplusplus +} +#endif diff --git a/examples/vectorsearch-cities/objectbox-model.json b/examples/vectorsearch-cities/objectbox-model.json new file mode 100644 index 0000000..aa161c4 --- /dev/null +++ b/examples/vectorsearch-cities/objectbox-model.json @@ -0,0 +1,46 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:607353604830599953", + "lastPropertyId": "3:6028438376669885699", + "name": "City", + "properties": [ + { + "id": "1:3289333370985535139", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:8840693632700366740", + "name": "name", + "type": 9 + }, + { + "id": "3:6028438376669885699", + "name": "location", + "indexId": "1:4616869581890712534", + "type": 28, + "flags": 8, + "hnswParams": { + "dimensions": 2, + "distance-type": "Euclidean" + } + } + ] + } + ], + "lastEntityId": "1:607353604830599953", + "lastIndexId": "1:4616869581890712534", + "lastRelationId": "", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/include/objectbox-sync.h b/include/objectbox-sync.h index f620bc4..08f4aba 100644 --- a/include/objectbox-sync.h +++ b/include/objectbox-sync.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 ObjectBox Ltd. All rights reserved. + * Copyright 2018-2024 ObjectBox Ltd. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ #include "objectbox.h" #if defined(static_assert) || defined(__cplusplus) -static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 0 && OBX_VERSION_PATCH == 1, // NOLINT +static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 0 && OBX_VERSION_PATCH == 2, // NOLINT "Versions of objectbox.h and objectbox-sync.h files do not match, please update"); #endif @@ -52,7 +52,7 @@ struct OBX_sync; typedef struct OBX_sync OBX_sync; /// Specifies user-side credential types as well as server-side authenticator types. -/// Some credentail types do not make sense as authenticators such as OBXSyncCredentialsType_USER_PASSWORD which +/// Some credential types do not make sense as authenticators such as OBXSyncCredentialsType_USER_PASSWORD which /// specifies a generic client-side credential type. typedef enum { OBXSyncCredentialsType_NONE = 1, @@ -444,6 +444,12 @@ typedef struct OBX_sync_server OBX_sync_server; /// @returns NULL if server could not be created (e.g. the store could not be opened, bad URL, etc.) OBX_C_API OBX_sync_server* obx_sync_server(OBX_store_options* store_options, const char* url); +/// Like obx_sync_server(), but retrieves its options for the Sync Server from the given FlatBuffers options. +/// @param flat_options FlatBuffers serialized options for the server (start of the bytes buffer, not the "table"). +/// @param flat_options_size Size of the FlatBuffers serialized options. +OBX_C_API OBX_sync_server* obx_sync_server_from_flat_options(OBX_store_options* store_options, const void* flat_options, + size_t flat_options_size); + /// Stops and closes (deletes) the sync server, freeing its resources. /// This includes the store associated with the server; it gets closed and must not be used anymore after this call. OBX_C_API obx_err obx_sync_server_close(OBX_sync_server* server); @@ -479,6 +485,20 @@ OBX_C_API obx_err obx_sync_server_worker_threads(OBX_sync_server* server, int th OBX_C_API obx_err obx_sync_server_history_max_size_in_kb(OBX_sync_server* server, uint64_t max_in_kb, uint64_t target_in_kb); +/// Configures the cluster ID for the given embedded server (the cluster feature must be enabled). +/// @param id A user defined string to identify the cluster (all cluster peer must share the same ID). +OBX_C_API obx_err obx_sync_server_cluster_id(OBX_sync_server* server, const char* id); + +/// Adds a remote cluster peer that can be connected to using the given URL and credentials. +/// Call this method multiple times to add multiple peers (at least 2 times for a cluster of 3). +/// @param url URL to the remote cluster peer used to connect it. +/// @param flags For now, always pass 0. +/// @param credentials the credentials provided to the remote peer to login (it must match the remote's configuration). +/// May be NULL in combination with OBXSyncCredentialsType_NONE. +OBX_C_API obx_err obx_sync_server_add_cluster_peer(OBX_sync_server* server, const char* url, + OBXSyncCredentialsType credentials_type, const void* credentials, + size_t credentials_size, uint32_t flags); + /// Set or overwrite a previously set 'change' listener - provides information about incoming changes. /// @param listener set NULL to reset /// @param listener_arg is a pass-through argument passed to the listener diff --git a/include/objectbox-sync.hpp b/include/objectbox-sync.hpp index 3dba2a4..6e2341c 100644 --- a/include/objectbox-sync.hpp +++ b/include/objectbox-sync.hpp @@ -19,7 +19,7 @@ #include "objectbox-sync.h" #include "objectbox.hpp" -static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 0 && OBX_VERSION_PATCH == 1, // NOLINT +static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 0 && OBX_VERSION_PATCH == 2, // NOLINT "Versions of objectbox.h and objectbox-sync.hpp files do not match, please update"); namespace obx { @@ -747,6 +747,18 @@ class SyncServer : public Closable { using Guard = std::lock_guard; + explicit SyncServer(OBX_sync_server* cSyncServer) : cPtr_(cSyncServer) { + internal::checkPtrOrThrow(cPtr_, "Could not create SyncServer"); + try { + OBX_store* cStore = obx_sync_server_store(cPtr_); + internal::checkPtrOrThrow(cStore, "Could not get SyncServer's store"); + store_.reset(new Store(cStore, false)); + } catch (...) { + close(); + throw; + } + } + public: static bool isAvailable() { return obx_has_feature(OBXFeature_SyncServer); } @@ -770,22 +782,20 @@ class SyncServer : public Closable { /// was started. \b Examples: "ws://0.0.0.0:9999" could be used during development (no certificate config /// needed), while in a production system, you may want to use wss and a specific IP for security reasons. explicit SyncServer(Options& storeOptions, const std::string& url) - : cPtr_(obx_sync_server(storeOptions.release(), url.c_str())) { - internal::checkPtrOrThrow(cPtr_, "Could not create SyncServer"); - try { - OBX_store* cStore = obx_sync_server_store(cPtr_); - internal::checkPtrOrThrow(cStore, "Could not get SyncServer's store"); - store_.reset(new Store(cStore, false)); - } catch (...) { - close(); - throw; - } - } + : SyncServer(obx_sync_server(storeOptions.release(), url.c_str())) {} /// Rvalue variant of SyncServer(Options& storeOptions, const std::string& url) that works equivalently. explicit SyncServer(Options&& storeOptions, const std::string& url) : SyncServer(static_cast(storeOptions), url) {} + explicit SyncServer(Options& storeOptions, const void* flatOptions, size_t flatOptionsSize) + : SyncServer(obx_sync_server_from_flat_options(storeOptions.release(), flatOptions, flatOptionsSize)) {} + + /// Rvalue variant of SyncServer(Options& storeOptions, const void* flatOptions, size_t flatOptionsSize) + /// that works equivalently. + explicit SyncServer(Options&& storeOptions, const void* flatOptions, size_t flatOptionsSize) + : SyncServer(static_cast(storeOptions), flatOptions, flatOptionsSize) {} + SyncServer(SyncServer&& source) noexcept : cPtr_(source.cPtr_) { source.cPtr_ = nullptr; Guard lock(source.listeners_.mutex); diff --git a/include/objectbox.h b/include/objectbox.h index 813c2d3..5a2cacb 100644 --- a/include/objectbox.h +++ b/include/objectbox.h @@ -53,7 +53,7 @@ extern "C" { /// obx_version() or obx_version_is_at_least(). #define OBX_VERSION_MAJOR 4 #define OBX_VERSION_MINOR 0 -#define OBX_VERSION_PATCH 1 // values >= 100 are reserved for dev releases leading to the next minor/major increase +#define OBX_VERSION_PATCH 2 // values >= 100 are reserved for dev releases leading to the next minor/major increase //---------------------------------------------- // Common types @@ -86,6 +86,8 @@ typedef int obx_err; /// @return The visitor returns true to keep going or false to cancel. typedef bool obx_data_visitor(const void* data, size_t size, void* user_data); +struct OBX_bytes_score; // Forward declaration for the obx_data_score_visitor typedef + /// The callback for reading data (i.e. object bytes) with a search score one-by-one. /// @param data contains the current data with score element /// @param user_data is a pass-through argument passed to the called API @@ -165,6 +167,13 @@ typedef enum { /// Vector search functionality; enables indexing for nearest neighbor search. OBXFeature_VectorSearch = 14, + /// WAL (write-ahead logging). + OBXFeature_Wal = 15, + + /// Sync connector to integrate MongoDB with SyncServer. + OBXFeature_SyncMongoDb = 16, + + } OBXFeature; /// Checks whether the given feature is available in the currently loaded library. diff --git a/include/objectbox.hpp b/include/objectbox.hpp index 57e3d99..de48103 100644 --- a/include/objectbox.hpp +++ b/include/objectbox.hpp @@ -36,7 +36,7 @@ #include #endif -static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 0 && OBX_VERSION_PATCH == 1, // NOLINT +static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 0 && OBX_VERSION_PATCH == 2, // NOLINT "Versions of objectbox.h and objectbox.hpp files do not match, please update"); #ifdef __clang__ @@ -122,6 +122,15 @@ class DbException : public Exception { int code() const override { return code_; } }; +/// A functionality was invoked that is not part in this edition of ObjectBox. +class FeatureNotAvailableException : public Exception { +public: + using Exception::Exception; + + /// Always OBX_ERROR_FEATURE_NOT_AVAILABLE + int code() const override { return OBX_ERROR_FEATURE_NOT_AVAILABLE; } +}; + namespace internal { [[noreturn]] void throwIllegalArgumentException(const char* text1, const char* text2); @@ -210,6 +219,8 @@ void appendLastErrorText(obx_err err, std::string& outMessage) { throw ShuttingDownException(message); } else if (err == OBX_ERROR_MAX_DATA_SIZE_EXCEEDED) { throw MaxDataSizeExceededException(message); + } else if (err == OBX_ERROR_FEATURE_NOT_AVAILABLE) { + throw FeatureNotAvailableException(message); } else { throw DbException(message, err); } @@ -1274,7 +1285,8 @@ enum class QueryOp { In, NotIn, Null, - NotNull + NotNull, + NearestNeighbors }; // Internal base class for all the condition containers. Each container starts with `QC` and ends with the type of the @@ -1510,6 +1522,33 @@ class QCBytes : public QC { } }; +class QCVectorF32 : public QC { + const std::vector value_; + const float* valuePtr_ = nullptr; + const size_t maxResultCount_ = 0; + +public: + /// @param value the vector with an element count that matches the dimension given for the property. + QCVectorF32(obx_schema_id propId, QueryOp op, std::vector&& value, size_t maxResultCount) + : QC(propId, op), value_(std::move(value)), maxResultCount_(maxResultCount) {} + + /// Does not copy the value; uses the reference + /// @param value the vector with an element count that matches the dimension given for the property. + QCVectorF32(obx_schema_id propId, QueryOp op, const float* value, size_t maxResultCount) + : QC(propId, op), valuePtr_(value), maxResultCount_(maxResultCount) {} + +protected: + std::unique_ptr copyAsPtr() const override { return QueryCondition::copyAsPtr(*this); }; + + obx_qb_cond applyTo(OBX_query_builder* cqb, bool /*isRoot*/) const override { + if (op_ == QueryOp::NearestNeighbors) { + const float* data = valuePtr_ ? valuePtr_ : value_.data(); + return obx_qb_nearest_neighbors_f32(cqb, propId_, data, maxResultCount_); + } + throwInvalidOperation(); + } +}; + // enable_if_t missing in c++11 so let's have a shorthand here template using enable_if_t = typename std::enable_if::type; @@ -1810,6 +1849,33 @@ class Property : public PropertyTypeless } }; +/// Carries property information when used in the entity-meta ("underscore") class +template +class Property : public PropertyTypeless { +public: + explicit constexpr Property(obx_schema_id id) noexcept : PropertyTypeless(id) {} + + /// @param value the vector with an element count that matches the dimension given for the property. + /// @param maxNeighborCount Maximal number of nearest neighbors to search for. + QCVectorF32 nearestNeighbors(std::vector&& value, size_t maxNeighborCount) const { + return {this->id_, QueryOp::NearestNeighbors, std::move(value), maxNeighborCount}; + } + + /// Note: The vector data is NOT copied but referenced, so keep it until the Query is built. + /// @param value the vector with an element count that matches the dimension given for the property. + /// @param maxNeighborCount Maximal number of nearest neighbors to search for. + QCVectorF32 nearestNeighbors(const std::vector& value, size_t maxNeighborCount) const { + return {this->id_, QueryOp::NearestNeighbors, value.data(), maxNeighborCount}; + } + + /// Note: The vector data is NOT copied but referenced, so keep it until the Query is built. + /// @param value the vector with an element count that matches the dimension given for the property. + /// @param maxNeighborCount Maximal number of nearest neighbors to search for. + QCVectorF32 nearestNeighbors(const float* value, size_t maxNeighborCount) const { + return {this->id_, QueryOp::NearestNeighbors, value, maxNeighborCount}; + } +}; + /// Carries property-based to-one relation information when used in the entity-meta ("underscore") class template // NOLINT TargetEntityT may be used in the future class RelationProperty : public Property { @@ -2077,6 +2143,13 @@ class QueryBuilder : public QueryBuilderBase { return *this; } + /// Overload for a std::vector; for details see the pointer-based nearestNeighborsFloat32(). + QueryBuilder& nearestNeighborsFloat32(Property vectorProperty, + const std::vector& queryVector, size_t maxResultCount) { + QueryBuilderBase::nearestNeighborsFloat32(vectorProperty.id(), queryVector.data(), maxResultCount); + return *this; + } + /// Once all conditions have been applied, build the query using this method to actually run it. Query build(); @@ -2431,6 +2504,15 @@ class Query : public QueryBase { return *this; } + /// Only for HNSW-enabled properties that are used for a nearest neighbor search: + /// sets the maximum of neighbors to search for. + template + Query& setParameterMaxNeighbors(Property property, + int64_t maxNeighborCount) { + QueryBase::setParameter(entityId(), property.id(), maxNeighborCount); + return *this; + } + private: template RET findSingle(obx_err nativeFn(OBX_query*, const void**, size_t*), T fromFlatBuffer(const void*, size_t)) { diff --git a/test.sh b/test.sh index 1289df5..c76553a 100755 --- a/test.sh +++ b/test.sh @@ -34,14 +34,7 @@ echo "Building into \"${buildDir}\"..." mkdir -p ${buildDir} cd ${buildDir} cmake .. - -# Visual Studio CMake generators don't know targets at top-level, so change to sub-directories here. -# Others can use 'cmake --build --target ' directly. -(cd src-test && cmake --build . --target objectbox-c-test) -(cd src-test-gen && cmake --build . --target objectbox-c-gen-test) -(cd examples/c-gen && cmake --build . --target objectbox-c-examples-tasks-c-gen) -(cd examples/cpp-gen && cmake --build . --target objectbox-c-examples-tasks-cpp-gen) -(cd examples/cpp-gen-sync && cmake --build . --target objectbox-c-examples-tasks-cpp-gen-sync) +cmake --build . (cd src-test/${buildSubDir} && ${testPrepCmd} && ./objectbox-c-test) (cd src-test-gen/${buildSubDir} && ${testPrepCmd} && ./objectbox-c-gen-test)