diff --git a/dev-doc/updating-c-library.md b/dev-doc/updating-c-library.md index a08abc9d..f5ae9dcc 100644 --- a/dev-doc/updating-c-library.md +++ b/dev-doc/updating-c-library.md @@ -5,40 +5,96 @@ Dart won't error on C function signature mismatch, leading to obscure memory bug ## C libraries +For each: + +- Update versions below +- Run update script +- Add [CHANGELOG](../objectbox/CHANGELOG.md) entry +- Commit changes + +### Desktop, Scripts + For Dart Native and unit tests ([install.sh](../install.sh)), for the binding update script (see below) and -for Flutter (`flutter_libs` and `sync_flutter_libs` plugins) on Linux and Windows: +for Flutter (`flutter_libs` and `sync_flutter_libs` plugins) on Linux and Windows: + +```bash +./tool/set-c-version.sh 4.1.0 ``` -./tool/set-c-version.sh 4.0.2 + +```text +* Flutter for Linux/Windows, Dart Native: update to [objectbox-c 4.1.0](https://github.com/objectbox/objectbox-c/releases/tag/v4.1.0). +``` + +```text +Update C library [4.0.2 -> 4.1.0] ``` +### Android + For the Flutter plugins on Android ([view releases](https://github.com/objectbox/objectbox-java/releases)): + +```bash +./tool/set-android-version.sh 4.1.0 +``` + +```text +* Flutter for Android: update to [objectbox-android 4.1.0](https://github.com/objectbox/objectbox-java/releases/tag/V4.1.0). + If your project is [using Admin](https://docs.objectbox.io/data-browser#admin-for-android), make sure to + update to `io.objectbox:objectbox-android-objectbrowser:4.1.0` in `android/app/build.gradle`. ``` -./tool/set-android-version.sh 4.0.3 + +```text +Update objectbox-android [4.0.3 -> 4.1.0] + +Bundled with C API 4.1.0 and ObjectBox 4.1.0-2025-01-28 ``` +Note: the embedded C API and ObjectBox version can be looked up +from the relevant objectbox repository release tag (like `java-4.1.0`). + +### Apple OSs + For the Flutter plugins on iOS/macOS ([view releases](https://github.com/objectbox/objectbox-swift/releases)) + +```bash +./tool/set-swift-version.sh 4.1.0 ``` -./tool/set-swift-version.sh 4.0.1 + +```text +* Flutter for iOS/macOS: update to [objectbox-swift 4.1.0](https://github.com/objectbox/objectbox-swift/releases/tag/v4.1.0). + For existing projects, run `pod repo update` and `pod update ObjectBox` in the `ios` or `macos` directories. +``` + +```text +Update ObjectBox Swift [4.0.0 -> 4.0.1] + +Bundled with C API 4.1.0 and ObjectBox 4.1.0-2025-01-30 ``` -For each, add an entry (see previous releases) to the [CHANGELOG](../objectbox/CHANGELOG.md). +Note: the embedded C API and ObjectBox version can be looked up +from the objectbox-swift release tag (like `v4.1.0`) and +the objectbox commit it points to (see `external/objectbox`). ## Dart C API bindings + To download the C library header files and generate bindings with ffigen (requires LLVM libraries, see [ffigen docs](https://pub.dev/packages/ffigen#installing-llvm) and the ffigen section in [pubspec.yaml](../objectbox/pubspec.yaml)): -``` + +```bash ./tool/update-c-binding.sh ``` Then manually: + - Copy/update enums that need to be exposed to users from [objectbox_c.dart](../objectbox/lib/src/native/bindings/objectbox_c.dart) to [enums.dart](../objectbox/lib/src/modelinfo/enums.dart). - Check the changed files, make any required changes in the Dart library (like method signature changes). - ⚠️ Update minimum C API and core version and notes as needed in [bindings.dart](../objectbox/lib/src/native/bindings/bindings.dart). - - Note: the embedded C API and core version can be looked up - for Android from the relevant core repository release tag and - for Swift from its repos release tag and the core commit it points to. +- Commit as + +```text +Update C-API [4.0.2 -> 4.1.0] +``` diff --git a/flutter_libs/android/build.gradle b/flutter_libs/android/build.gradle index 160dde47..79bf4f15 100644 --- a/flutter_libs/android/build.gradle +++ b/flutter_libs/android/build.gradle @@ -45,13 +45,13 @@ android { } defaultConfig { - minSdkVersion 19 // ObjectBox Android requires Android 4.4 (API level 19) + minSdkVersion 21 // ObjectBox Android requires Android 5.0 (API level 21) } dependencies { // ObjectBox Android library that includes an ObjectBox C library version compatible with // the C API binding of the ObjectBox Dart package. // https://central.sonatype.com/search?q=g:io.objectbox%20objectbox-android - implementation "io.objectbox:objectbox-android:4.0.3" + implementation "io.objectbox:objectbox-android:4.1.0" } } diff --git a/flutter_libs/ios/objectbox_flutter_libs.podspec b/flutter_libs/ios/objectbox_flutter_libs.podspec index c2243b0d..3a24c744 100644 --- a/flutter_libs/ios/objectbox_flutter_libs.podspec +++ b/flutter_libs/ios/objectbox_flutter_libs.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'ObjectBox', '4.0.1' + s.dependency 'ObjectBox', '4.1.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/flutter_libs/linux/CMakeLists.txt b/flutter_libs/linux/CMakeLists.txt index 6abf6625..aca438e2 100644 --- a/flutter_libs/linux/CMakeLists.txt +++ b/flutter_libs/linux/CMakeLists.txt @@ -44,7 +44,7 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) # ---------------------------------------------------------------------- # Download and add objectbox-c prebuilt library. -set(OBJECTBOX_VERSION 4.0.2) +set(OBJECTBOX_VERSION 4.1.0) set(OBJECTBOX_ARCH ${CMAKE_SYSTEM_PROCESSOR}) if (${OBJECTBOX_ARCH} MATCHES "x86_64") diff --git a/flutter_libs/macos/objectbox_flutter_libs.podspec b/flutter_libs/macos/objectbox_flutter_libs.podspec index b3340ff5..5dcbcf81 100644 --- a/flutter_libs/macos/objectbox_flutter_libs.podspec +++ b/flutter_libs/macos/objectbox_flutter_libs.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.dependency 'ObjectBox', '4.0.1' + s.dependency 'ObjectBox', '4.1.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.3' diff --git a/flutter_libs/pubspec.yaml b/flutter_libs/pubspec.yaml index cd23368d..57a25122 100644 --- a/flutter_libs/pubspec.yaml +++ b/flutter_libs/pubspec.yaml @@ -3,7 +3,7 @@ description: Superfast NoSQL Flutter / Dart database. This package contains Flut # Link to actual directory in repository so file links on pub.dev work. repository: https://github.com/objectbox/objectbox-dart/tree/main/flutter_libs homepage: https://objectbox.io -version: 4.0.3 +version: 4.1.0 environment: sdk: '>=2.18.0 <4.0.0' @@ -14,7 +14,7 @@ dependencies: sdk: flutter # This is here just to ensure compatibility between objectbox-dart code and the libraries used # You should still depend on objectbox directly in your Flutter application. - objectbox: 4.0.3 + objectbox: 4.1.0 path_provider: ^2.0.0 dev_dependencies: diff --git a/flutter_libs/windows/CMakeLists.txt b/flutter_libs/windows/CMakeLists.txt index a699fde5..77595b41 100644 --- a/flutter_libs/windows/CMakeLists.txt +++ b/flutter_libs/windows/CMakeLists.txt @@ -50,7 +50,7 @@ set(objectbox_flutter_libs_bundled_libraries # ---------------------------------------------------------------------- # Download and add objectbox-c prebuilt library. -set(OBJECTBOX_VERSION 4.0.2) +set(OBJECTBOX_VERSION 4.1.0) set(OBJECTBOX_ARCH ${CMAKE_SYSTEM_PROCESSOR}) if (${OBJECTBOX_ARCH} MATCHES "AMD64") diff --git a/generator/lib/src/version.dart b/generator/lib/src/version.dart index deb429e6..6c73c95d 100644 --- a/generator/lib/src/version.dart +++ b/generator/lib/src/version.dart @@ -4,5 +4,5 @@ class Version { /// /// This string is updated by the /tool/set-version.sh script /// as part of the release process. - static const String current = "4.0.3"; + static const String current = "4.1.0"; } diff --git a/generator/pubspec.yaml b/generator/pubspec.yaml index d755efd7..3ea269b7 100644 --- a/generator/pubspec.yaml +++ b/generator/pubspec.yaml @@ -3,13 +3,13 @@ description: ObjectBox Flutter / Dart database binding code generator - finds an # Link to actual directory in repository so file links on pub.dev work. repository: https://github.com/objectbox/objectbox-dart/tree/main/generator homepage: https://objectbox.io -version: 4.0.3 +version: 4.1.0 environment: sdk: '>=2.18.0 <4.0.0' dependencies: - objectbox: 4.0.3 + objectbox: 4.1.0 analyzer: '>=5.2.0 <7.0.0' # 5.1.0 has a bug where DartType.element has been removed. build: ^2.0.0 collection: ^1.15.0 diff --git a/install.sh b/install.sh index 0feeb1ae..2707c1c6 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,7 @@ set -eu # It's important that the generated dart bindings and the c-api library version match. Dart won't error on C function # signature mismatch, leading to obscure memory bugs. # For how to upgrade the version see dev-doc/updating-c-library.md -cLibVersion=4.0.2 +cLibVersion=4.1.0 os=$(uname) cLibArgs="$*" diff --git a/objectbox/CHANGELOG.md b/objectbox/CHANGELOG.md index 4bb8b311..910addf0 100644 --- a/objectbox/CHANGELOG.md +++ b/objectbox/CHANGELOG.md @@ -1,5 +1,24 @@ ## latest + + +## 4.1.0 (2025-02-04) + +* Flutter for Android: requires Android 5.0 (API level 21). +* Vector Search: You can now use the new `VectorDistanceType.GEO` distance-type to perform vector searches on + geographical coordinates. This is particularly useful for location-based applications. +* Flutter for Linux/Windows, Dart Native: update to [objectbox-c 4.1.0](https://github.com/objectbox/objectbox-c/releases/tag/v4.1.0). +* Flutter for Android: update to [objectbox-android 4.1.0](https://github.com/objectbox/objectbox-java/releases/tag/V4.1.0). + If your project is [using Admin](https://docs.objectbox.io/data-browser#admin-for-android), make sure to + update to `io.objectbox:objectbox-android-objectbrowser:4.1.0` in `android/app/build.gradle`. +* Flutter for iOS/macOS: update to [objectbox-swift 4.1.0](https://github.com/objectbox/objectbox-swift/releases/tag/v4.1.0). + For existing projects, run `pod repo update` and `pod update ObjectBox` in the `ios` or `macos` directories. + +### Sync + +* Add [JWT authentication](https://sync.objectbox.io/sync-server-configuration/jwt-authentication). +* Sync clients can send multiple credentials for login. + ## 4.0.3 (2024-10-17) * Generator: replace cryptography library, allows to use newer versions of the transitive `js` dependency. [#638](https://github.com/objectbox/objectbox-dart/issues/638) diff --git a/objectbox/example/dart-native/vectorsearch_cities/lib/model.dart b/objectbox/example/dart-native/vectorsearch_cities/lib/model.dart index 0910694e..9e65611a 100644 --- a/objectbox/example/dart-native/vectorsearch_cities/lib/model.dart +++ b/objectbox/example/dart-native/vectorsearch_cities/lib/model.dart @@ -7,7 +7,7 @@ class City { String? name; - @HnswIndex(dimensions: 2) + @HnswIndex(dimensions: 2, distanceType: VectorDistanceType.geo) @Property(type: PropertyType.floatVector) List? location; diff --git a/objectbox/example/dart-native/vectorsearch_cities/pubspec.yaml b/objectbox/example/dart-native/vectorsearch_cities/pubspec.yaml index 87394443..f1ec8fe3 100644 --- a/objectbox/example/dart-native/vectorsearch_cities/pubspec.yaml +++ b/objectbox/example/dart-native/vectorsearch_cities/pubspec.yaml @@ -6,7 +6,7 @@ environment: sdk: ^2.18.6 dependencies: - objectbox: ^4.0.3 + objectbox: ^4.1.0 dev_dependencies: build_runner: ^2.4.9 diff --git a/objectbox/example/flutter/event_management_tutorial/event_manager/pubspec.yaml b/objectbox/example/flutter/event_management_tutorial/event_manager/pubspec.yaml index 42d128e6..88470a1f 100644 --- a/objectbox/example/flutter/event_management_tutorial/event_manager/pubspec.yaml +++ b/objectbox/example/flutter/event_management_tutorial/event_manager/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - objectbox: ^4.0.3 + objectbox: ^4.1.0 objectbox_flutter_libs: any intl: any diff --git a/objectbox/example/flutter/event_management_tutorial/many_to_many/pubspec.yaml b/objectbox/example/flutter/event_management_tutorial/many_to_many/pubspec.yaml index 42d128e6..88470a1f 100644 --- a/objectbox/example/flutter/event_management_tutorial/many_to_many/pubspec.yaml +++ b/objectbox/example/flutter/event_management_tutorial/many_to_many/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - objectbox: ^4.0.3 + objectbox: ^4.1.0 objectbox_flutter_libs: any intl: any diff --git a/objectbox/example/flutter/objectbox_demo/android/app/build.gradle b/objectbox/example/flutter/objectbox_demo/android/app/build.gradle index 136b9f09..cb51c6ca 100644 --- a/objectbox/example/flutter/objectbox_demo/android/app/build.gradle +++ b/objectbox/example/flutter/objectbox_demo/android/app/build.gradle @@ -47,7 +47,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // minSdkVersion flutter.minSdkVersion - minSdkVersion 19 // ObjectBox requires at least SDK 19 (Android 4.4) + minSdkVersion 21 // ObjectBox Android requires Android 5.0 (API level 21) targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/objectbox/example/flutter/objectbox_demo/pubspec.yaml b/objectbox/example/flutter/objectbox_demo/pubspec.yaml index 276eba60..418146e2 100644 --- a/objectbox/example/flutter/objectbox_demo/pubspec.yaml +++ b/objectbox/example/flutter/objectbox_demo/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - objectbox: ^4.0.3 + objectbox: ^4.1.0 objectbox_flutter_libs: any intl: any path_provider: ^2.0.10 diff --git a/objectbox/example/flutter/objectbox_demo_relations/android/app/build.gradle b/objectbox/example/flutter/objectbox_demo_relations/android/app/build.gradle index dfcc7549..2f620725 100644 --- a/objectbox/example/flutter/objectbox_demo_relations/android/app/build.gradle +++ b/objectbox/example/flutter/objectbox_demo_relations/android/app/build.gradle @@ -47,7 +47,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // minSdkVersion flutter.minSdkVersion - minSdkVersion 19 // ObjectBox requires at least SDK 19 (Android 4.4) + minSdkVersion 21 // ObjectBox Android requires Android 5.0 (API level 21) targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -82,5 +82,5 @@ dependencies { // Add objectbox-android-objectbrowser only for debug builds. // Warning: when objectbox_flutter_libs updates check if version // needs update, e.g. check https://github.com/objectbox/objectbox-dart/releases. - debugImplementation("io.objectbox:objectbox-android-objectbrowser:4.0.3") + debugImplementation("io.objectbox:objectbox-android-objectbrowser:4.1.0") } diff --git a/objectbox/example/flutter/objectbox_demo_relations/pubspec.yaml b/objectbox/example/flutter/objectbox_demo_relations/pubspec.yaml index bacccdad..f464a0e5 100644 --- a/objectbox/example/flutter/objectbox_demo_relations/pubspec.yaml +++ b/objectbox/example/flutter/objectbox_demo_relations/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - objectbox: ^4.0.3 + objectbox: ^4.1.0 objectbox_flutter_libs: any intl: any path_provider: ^2.0.10 # 2.0.11+ requires Flutter 2.8.0 diff --git a/objectbox/example/flutter/objectbox_demo_sync/pubspec.yaml b/objectbox/example/flutter/objectbox_demo_sync/pubspec.yaml index 3b631515..1d20088d 100644 --- a/objectbox/example/flutter/objectbox_demo_sync/pubspec.yaml +++ b/objectbox/example/flutter/objectbox_demo_sync/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - objectbox: ^4.0.3 + objectbox: ^4.1.0 objectbox_sync_flutter_libs: any # For Sync support use this instead of objectbox_flutter_libs. intl: any path_provider: ^2.0.10 diff --git a/objectbox/lib/src/annotations.dart b/objectbox/lib/src/annotations.dart index b924960d..562f567f 100644 --- a/objectbox/lib/src/annotations.dart +++ b/objectbox/lib/src/annotations.dart @@ -341,7 +341,18 @@ enum VectorDistanceType { /// The more negative the dot product, the higher the distance is (the farther the vectors are). /// /// Value range: 0.0 - 2.0 (nonlinear; 0.0: nearest, 1.0: orthogonal, 2.0: farthest) - dotProductNonNormalized + dotProductNonNormalized, + + /// For geospatial coordinates aka latitude/longitude pairs. + /// + /// Note, that the vector dimension must be 2, with the latitude being the first element and longitude the second. + /// If the vector has more than 2 dimensions, the first 2 dimensions are used. + /// If the vector has fewer than 2 dimensions, the distance is zero. + /// + /// Internally, this uses haversine distance. + /// + /// Value range: 0 km - 6371 * π km (approx. 20015.09 km; half the Earth's circumference) + geo } /// Flags as a part of the [HnswIndex] configuration. diff --git a/objectbox/lib/src/modelinfo/model_hnsw_params.dart b/objectbox/lib/src/modelinfo/model_hnsw_params.dart index f621e97d..dee5c1e6 100644 --- a/objectbox/lib/src/modelinfo/model_hnsw_params.dart +++ b/objectbox/lib/src/modelinfo/model_hnsw_params.dart @@ -189,6 +189,8 @@ extension ModelVectorDistanceType on VectorDistanceType { return OBXVectorDistanceType.DotProduct; } else if (this == VectorDistanceType.dotProductNonNormalized) { return OBXVectorDistanceType.DotProductNonNormalized; + } else if (this == VectorDistanceType.geo) { + return OBXVectorDistanceType.Geo; } else { throw ArgumentError.value(this, "distanceType"); } diff --git a/objectbox/lib/src/native/bindings/bindings.dart b/objectbox/lib/src/native/bindings/bindings.dart index 71861aaa..5f526954 100644 --- a/objectbox/lib/src/native/bindings/bindings.dart +++ b/objectbox/lib/src/native/bindings/bindings.dart @@ -94,15 +94,16 @@ ObjectBoxC? _tryObjectBoxLibFile() { // Require the minimum C API version of all supported platform-specific // libraries. -// Library | C API version | Core version -// objectbox-c | 4.0.2 | 4.0.2-2024-10-15 -// ObjectBox Swift 4.0.1 | 4.0.2 | 4.0.2-2024-10-15 -// objectbox-android 4.0.3 | 4.0.1 | 4.0.2-2024-10-15 +// Library | C API | Core +// ------------------------|-------|----------------- +// objectbox-c | 4.1.0 | 4.1.0-2025-01-28 +// ObjectBox Swift 4.1.0 | 4.1.0 | 4.1.0-2025-01-30 +// objectbox-android 4.1.0 | 4.1.0 | 4.1.0-2025-01-28 var _obxCminMajor = 4; -var _obxCminMinor = 0; -var _obxCminPatch = 1; +var _obxCminMinor = 1; +var _obxCminPatch = 0; // Require minimum core version guaranteeing actual C API availability. -var _obxCoreMinVersion = "4.0.2-2024-10-15"; +var _obxCoreMinVersion = "4.1.0-2025-01-28"; bool _isSupportedVersion(ObjectBoxC obxc) { if (!obxc.version_is_at_least(_obxCminMajor, _obxCminMinor, _obxCminPatch)) { diff --git a/objectbox/lib/src/native/bindings/objectbox-sync.h b/objectbox/lib/src/native/bindings/objectbox-sync.h index bb697092..2a154f39 100644 --- a/objectbox/lib/src/native/bindings/objectbox-sync.h +++ b/objectbox/lib/src/native/bindings/objectbox-sync.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2024 ObjectBox Ltd. All rights reserved. + * Copyright 2018-2025 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 == 2, // NOLINT +static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 1 && OBX_VERSION_PATCH == 0, // NOLINT "Versions of objectbox.h and objectbox-sync.h files do not match, please update"); #endif @@ -56,11 +56,19 @@ typedef struct OBX_sync OBX_sync; /// specifies a generic client-side credential type. typedef enum { OBXSyncCredentialsType_NONE = 1, - OBXSyncCredentialsType_SHARED_SECRET = 2, + OBXSyncCredentialsType_SHARED_SECRET = 2, ///< Deprecated, replaced by SHARED_SECRET_SIPPED OBXSyncCredentialsType_GOOGLE_AUTH = 3, - OBXSyncCredentialsType_SHARED_SECRET_SIPPED = 4, - OBXSyncCredentialsType_OBX_ADMIN_USER = 5, - OBXSyncCredentialsType_USER_PASSWORD = 6, + OBXSyncCredentialsType_SHARED_SECRET_SIPPED = 4, ///< Uses shared secret to create a hashed credential. + OBXSyncCredentialsType_OBX_ADMIN_USER = 5, ///< ObjectBox admin users (username/password) + OBXSyncCredentialsType_USER_PASSWORD = 6, ///< Generic credential type suitable for ObjectBox admin + ///< (and possibly others in the future) + OBXSyncCredentialsType_JWT_ID = 7, ///< JSON Web Token (JWT): an ID token that typically provides identity + ///< information about the authenticated user. + OBXSyncCredentialsType_JWT_ACCESS = 8, ///< JSON Web Token (JWT): an access token that is used to access resources. + OBXSyncCredentialsType_JWT_REFRESH = 9, ///< JSON Web Token (JWT): a refresh token that is used to obtain a new + ///< access token. + OBXSyncCredentialsType_JWT_CUSTOM = 10, ///< JSON Web Token (JWT): a token that is neither an ID, access, + ///< nor refresh token. } OBXSyncCredentialsType; // TODO sync prefix @@ -194,8 +202,11 @@ OBX_C_API OBX_sync* obx_sync_urls(OBX_store* store, const char* server_urls[], s OBX_C_API obx_err obx_sync_close(OBX_sync* sync); /// Sets credentials to authenticate the client with the server. -/// See OBXSyncCredentialsType for available options. -/// The accepted OBXSyncCredentials type depends on your sync-server configuration. +/// Any credentials that were set before are replaced; +/// if you want to pass multiple credentials, use obx_sync_credentials_add() instead. +/// If the client was waiting for credentials, this can trigger a reconnection/login attempt. +/// @param type See OBXSyncCredentialsType for available options. +/// The accepted OBXSyncCredentials type depends on your sync-server configuration. /// @param data may be NULL in combination with OBXSyncCredentialsType_NONE OBX_C_API obx_err obx_sync_credentials(OBX_sync* sync, OBXSyncCredentialsType type, const uint8_t* data, size_t size); @@ -207,6 +218,29 @@ OBX_C_API obx_err obx_sync_credentials(OBX_sync* sync, OBXSyncCredentialsType ty OBX_C_API obx_err obx_sync_credentials_user_password(OBX_sync* sync, OBXSyncCredentialsType type, const char* username, const char* password); +/// For authentication with multiple credentials, collect credentials by calling this function multiple times. +/// When adding the last credentials element, the "complete" flag must be set to true. +/// When completed, it will "activate" the collected credentials and replace any previously set credentials and +/// potentially trigger a reconnection/login attempt. +/// @param type See OBXSyncCredentialsType for available options. +/// The accepted OBXSyncCredentials type depends on your sync-server configuration. +/// @param data non-NULL (OBXSyncCredentialsType_NONE is not allowed) +/// @param complete set to true when adding the last credentials element to activate the set of credentials +OBX_C_API obx_err obx_sync_credentials_add(OBX_sync* sync, OBXSyncCredentialsType type, const uint8_t* data, size_t size, + bool complete); + +/// For authentication with multiple credentials, collect credentials by calling this function multiple times. +/// When adding the last credentials element, the "complete" flag must be set to true. +/// When completed, it will "activate" the collected credentials and replace any previously set credentials and +/// potentially trigger a reconnection/login attempt. +/// @param type See OBXSyncCredentialsType for available options. +/// The accepted OBXSyncCredentials type depends on your sync-server configuration. +/// @param username non-NULL +/// @param password non-NULL +/// @param complete set to true when adding the last credentials element to activate the set of credentials +OBX_C_API obx_err obx_sync_credentials_add_user_password(OBX_sync* sync, OBXSyncCredentialsType type, + const char* username, const char* password, bool complete); + /// Configures the maximum number of outgoing TX messages that can be sent without an ACK from the server. /// @returns OBX_ERROR_ILLEGAL_ARGUMENT if value is not in the range 1-20 OBX_C_API obx_err obx_sync_max_messages_in_flight(OBX_sync* sync, int value); @@ -374,9 +408,9 @@ OBX_C_API void obx_sync_listener_msg_objects(OBX_sync* sync, OBX_sync_listener_m void* listener_arg); /// Set or overwrite a previously set 'error' listener - provides information about occurred sync-level errors. -/// @param listener set NULL to reset +/// @param listener The callback to receive sync errors. Set to NULL to reset. /// @param listener_arg is a pass-through argument passed to the listener -OBX_C_API void obx_sync_listener_error(OBX_sync* sync, OBX_sync_listener_error* error, void* listener_arg); +OBX_C_API void obx_sync_listener_error(OBX_sync* sync, OBX_sync_listener_error* listener, void* listener_arg); //---------------------------------------------- // Sync Stats @@ -702,7 +736,7 @@ typedef enum { /// Get u64 value for sync server statistics. /// @param counter_type the counter value to be read (make sure to choose a uint64_t (u64) metric value type). -/// @param out_count receives the counter value. +/// @param out_value receives the counter value. /// @return OBX_SUCCESS if the counter has been successfully retrieved. /// @return OBX_ERROR_ILLEGAL_ARGUMENT if counter_type is undefined (this also happens if the wrong type is requested) /// @return OBX_ERROR_ILLEGAL_STATE if the server is not started. @@ -711,7 +745,7 @@ OBX_C_API obx_err obx_sync_server_stats_u64(OBX_sync_server* server, OBXSyncServ /// Get double value for sync server statistics. /// @param counter_type the counter value to be read (make sure to use a double (f64) metric value type). -/// @param out_count receives the counter value. +/// @param out_value receives the counter value. /// @return OBX_SUCCESS if the counter has been successfully retrieved. /// @return OBX_ERROR_ILLEGAL_ARGUMENT if counter_type is undefined (this also happens if the wrong type is requested) /// @return OBX_ERROR_ILLEGAL_STATE if the server is not started. @@ -785,7 +819,7 @@ typedef void OBX_custom_msg_server_func_client_connection_close(void* server_use /// Callback to shutdown and free all resources associated with the sync client connection to the custom server. /// Note that the custom server may already have been shutdown at this point (e.g. no server user data is supplied). /// Must be provided to implement a custom server. See notes on OBX_custom_msg_server_functions for more details. -/// @param server_user_data User supplied data returned by the function that created the server +/// @param connection_user_data User supplied data returned by the function that created the server typedef void OBX_custom_msg_server_func_client_connection_shutdown(void* connection_user_data); /// Struct of the custom server function callbacks. In order to implement the custom server, you must provide diff --git a/objectbox/lib/src/native/bindings/objectbox.h b/objectbox/lib/src/native/bindings/objectbox.h index c859454b..6973625c 100644 --- a/objectbox/lib/src/native/bindings/objectbox.h +++ b/objectbox/lib/src/native/bindings/objectbox.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 ObjectBox Ltd. All rights reserved. + * Copyright 2018-2025 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. @@ -52,8 +52,8 @@ extern "C" { /// When using ObjectBox as a dynamic library, you should verify that a compatible version was linked using /// obx_version() or obx_version_is_at_least(). #define OBX_VERSION_MAJOR 4 -#define OBX_VERSION_MINOR 0 -#define OBX_VERSION_PATCH 2 // values >= 100 are reserved for dev releases leading to the next minor/major increase +#define OBX_VERSION_MINOR 1 +#define OBX_VERSION_PATCH 0 // values >= 100 are reserved for dev releases leading to the next minor/major increase //---------------------------------------------- // Common types @@ -173,6 +173,8 @@ typedef enum { /// Sync connector to integrate MongoDB with SyncServer. OBXFeature_SyncMongoDb = 16, + /// Enables additional authentication/authorization methods for sync login, e.g. JWT based methods. + OBXFeature_Auth = 17, } OBXFeature; @@ -480,6 +482,11 @@ typedef enum { OBXVectorDistanceType_Manhattan = 4, OBXVectorDistanceType_Hamming = 5, + /// For geospatial coordinates aka latitude/longitude pairs. + /// Note, that the vector dimension must be 2, with the latitude being the first element and longitude the second. + /// Internally, this uses haversine distance. + OBXVectorDistanceType_Geo = 6, + /// A custom dot product similarity measure that does not require the vectors to be normalized. /// Note: this is no replacement for cosine similarity (like DotProduct for normalized vectors is). /// The non-linear conversion provides a high precision over the entire float range (for the raw dot product). @@ -487,6 +494,7 @@ typedef enum { /// The more negative the dot product, the higher the distance is (the farther the vectors are). /// Value range: 0.0 - 2.0 (nonlinear; 0.0: nearest, 1.0: orthogonal, 2.0: farthest) OBXVectorDistanceType_DotProductNonNormalized = 10, + } OBXVectorDistanceType; /// Utility function to calculate the distance of two given vectors. @@ -1857,10 +1865,120 @@ OBX_C_API obx_qb_cond obx_qb_contains_string(OBX_query_builder* builder, obx_sch OBX_C_API obx_qb_cond obx_qb_contains_element_string(OBX_query_builder* builder, obx_schema_id property_id, const char* value, bool case_sensitive); -/// For flex properties that have a map as root value, this looks for matching key/value pair. +/// @Deprecated use obx_qb_equals_key_value_string instead OBX_C_API obx_qb_cond obx_qb_contains_key_value_string(OBX_query_builder* builder, obx_schema_id property_id, const char* key, const char* value, bool case_sensitive); +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must equal this one. +/// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. +OBX_C_API obx_qb_cond obx_qb_equals_key_value_string(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, const char* value, bool case_sensitive); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must equal this one. +OBX_C_API obx_qb_cond obx_qb_equals_key_value_int(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, int64_t value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must equal this one. +OBX_C_API obx_qb_cond obx_qb_equals_key_value_double(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, double value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being greater than the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be greater than this one. +/// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. +OBX_C_API obx_qb_cond obx_qb_greater_key_value_string(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, const char* value, bool case_sensitive); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being greater than the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be greater than this one. +OBX_C_API obx_qb_cond obx_qb_greater_key_value_int(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, int64_t value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being greater than the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be greater than this one. +OBX_C_API obx_qb_cond obx_qb_greater_key_value_double(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, double value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being greater than or equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be greater than or equal to this one. +/// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. +OBX_C_API obx_qb_cond obx_qb_greater_or_equal_key_value_string(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, const char* value, bool case_sensitive); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being greater than or equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be greater than or equal to this one. +OBX_C_API obx_qb_cond obx_qb_greater_or_equal_key_value_int(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, int64_t value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being greater than or equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be greater than or equal to this one. +OBX_C_API obx_qb_cond obx_qb_greater_or_equal_key_value_double(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, double value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being lesser than the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be lesser than this one. +/// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. +OBX_C_API obx_qb_cond obx_qb_less_than_key_value_string(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, const char* value, bool case_sensitive); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being lesser than the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be lesser than this one. +OBX_C_API obx_qb_cond obx_qb_less_than_key_value_int(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, int64_t value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being lesser than the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be lesser than this one. +OBX_C_API obx_qb_cond obx_qb_less_than_key_value_double(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, double value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being lesser than or equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be lesser than or equal to this one. +/// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. +OBX_C_API obx_qb_cond obx_qb_less_or_equal_key_value_string(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, const char* value, bool case_sensitive); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being lesser than or equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be lesser than or equal to this one. +OBX_C_API obx_qb_cond obx_qb_less_or_equal_key_value_int(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, int64_t value); + +/// For flex properties that have a map as root value, this looks for a matching key/value pair, +/// with the map value being lesser than or equal to the given one. +/// @param key must be an exact match exactly (case-sensitive) +/// @param value the map's value must be lesser than or equal to this one. +OBX_C_API obx_qb_cond obx_qb_less_or_equal_key_value_double(OBX_query_builder* builder, obx_schema_id property_id, + const char* key, double value); + OBX_C_API obx_qb_cond obx_qb_starts_with_string(OBX_query_builder* builder, obx_schema_id property_id, const char* value, bool case_sensitive); diff --git a/objectbox/lib/src/native/bindings/objectbox_c.dart b/objectbox/lib/src/native/bindings/objectbox_c.dart index 27a08d62..702343fd 100644 --- a/objectbox/lib/src/native/bindings/objectbox_c.dart +++ b/objectbox/lib/src/native/bindings/objectbox_c.dart @@ -4152,7 +4152,7 @@ class ObjectBoxC { int Function(ffi.Pointer, int, ffi.Pointer, bool)>(); - /// For flex properties that have a map as root value, this looks for matching key/value pair. + /// @Deprecated use obx_qb_equals_key_value_string instead int qb_contains_key_value_string( ffi.Pointer builder, int property_id, @@ -4182,6 +4182,475 @@ class ObjectBoxC { int Function(ffi.Pointer, int, ffi.Pointer, ffi.Pointer, bool)>(); + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must equal this one. + /// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. + int qb_equals_key_value_string( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + ffi.Pointer value, + bool case_sensitive, + ) { + return _qb_equals_key_value_string( + builder, + property_id, + key, + value, + case_sensitive, + ); + } + + late final _qb_equals_key_value_stringPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Pointer, + ffi.Bool)>>('obx_qb_equals_key_value_string'); + late final _qb_equals_key_value_string = + _qb_equals_key_value_stringPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, ffi.Pointer, bool)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must equal this one. + int qb_equals_key_value_int( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + int value, + ) { + return _qb_equals_key_value_int( + builder, + property_id, + key, + value, + ); + } + + late final _qb_equals_key_value_intPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Int64)>>('obx_qb_equals_key_value_int'); + late final _qb_equals_key_value_int = _qb_equals_key_value_intPtr.asFunction< + int Function( + ffi.Pointer, int, ffi.Pointer, int)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must equal this one. + int qb_equals_key_value_double( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + double value, + ) { + return _qb_equals_key_value_double( + builder, + property_id, + key, + value, + ); + } + + late final _qb_equals_key_value_doublePtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Double)>>('obx_qb_equals_key_value_double'); + late final _qb_equals_key_value_double = + _qb_equals_key_value_doublePtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, double)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being greater than the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be greater than this one. + /// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. + int qb_greater_key_value_string( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + ffi.Pointer value, + bool case_sensitive, + ) { + return _qb_greater_key_value_string( + builder, + property_id, + key, + value, + case_sensitive, + ); + } + + late final _qb_greater_key_value_stringPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Pointer, + ffi.Bool)>>('obx_qb_greater_key_value_string'); + late final _qb_greater_key_value_string = + _qb_greater_key_value_stringPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, ffi.Pointer, bool)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being greater than the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be greater than this one. + int qb_greater_key_value_int( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + int value, + ) { + return _qb_greater_key_value_int( + builder, + property_id, + key, + value, + ); + } + + late final _qb_greater_key_value_intPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Int64)>>('obx_qb_greater_key_value_int'); + late final _qb_greater_key_value_int = + _qb_greater_key_value_intPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, int)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being greater than the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be greater than this one. + int qb_greater_key_value_double( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + double value, + ) { + return _qb_greater_key_value_double( + builder, + property_id, + key, + value, + ); + } + + late final _qb_greater_key_value_doublePtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Double)>>('obx_qb_greater_key_value_double'); + late final _qb_greater_key_value_double = + _qb_greater_key_value_doublePtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, double)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being greater than or equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be greater than or equal to this one. + /// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. + int qb_greater_or_equal_key_value_string( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + ffi.Pointer value, + bool case_sensitive, + ) { + return _qb_greater_or_equal_key_value_string( + builder, + property_id, + key, + value, + case_sensitive, + ); + } + + late final _qb_greater_or_equal_key_value_stringPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Pointer, + ffi.Bool)>>('obx_qb_greater_or_equal_key_value_string'); + late final _qb_greater_or_equal_key_value_string = + _qb_greater_or_equal_key_value_stringPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, ffi.Pointer, bool)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being greater than or equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be greater than or equal to this one. + int qb_greater_or_equal_key_value_int( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + int value, + ) { + return _qb_greater_or_equal_key_value_int( + builder, + property_id, + key, + value, + ); + } + + late final _qb_greater_or_equal_key_value_intPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Int64)>>('obx_qb_greater_or_equal_key_value_int'); + late final _qb_greater_or_equal_key_value_int = + _qb_greater_or_equal_key_value_intPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, int)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being greater than or equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be greater than or equal to this one. + int qb_greater_or_equal_key_value_double( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + double value, + ) { + return _qb_greater_or_equal_key_value_double( + builder, + property_id, + key, + value, + ); + } + + late final _qb_greater_or_equal_key_value_doublePtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Double)>>('obx_qb_greater_or_equal_key_value_double'); + late final _qb_greater_or_equal_key_value_double = + _qb_greater_or_equal_key_value_doublePtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, double)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being lesser than the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be lesser than this one. + /// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. + int qb_less_than_key_value_string( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + ffi.Pointer value, + bool case_sensitive, + ) { + return _qb_less_than_key_value_string( + builder, + property_id, + key, + value, + case_sensitive, + ); + } + + late final _qb_less_than_key_value_stringPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Pointer, + ffi.Bool)>>('obx_qb_less_than_key_value_string'); + late final _qb_less_than_key_value_string = + _qb_less_than_key_value_stringPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, ffi.Pointer, bool)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being lesser than the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be lesser than this one. + int qb_less_than_key_value_int( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + int value, + ) { + return _qb_less_than_key_value_int( + builder, + property_id, + key, + value, + ); + } + + late final _qb_less_than_key_value_intPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Int64)>>('obx_qb_less_than_key_value_int'); + late final _qb_less_than_key_value_int = + _qb_less_than_key_value_intPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, int)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being lesser than the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be lesser than this one. + int qb_less_than_key_value_double( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + double value, + ) { + return _qb_less_than_key_value_double( + builder, + property_id, + key, + value, + ); + } + + late final _qb_less_than_key_value_doublePtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Double)>>('obx_qb_less_than_key_value_double'); + late final _qb_less_than_key_value_double = + _qb_less_than_key_value_doublePtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, double)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being lesser than or equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be lesser than or equal to this one. + /// @param case_sensitive if true, the value's match is case-sensitive, otherwise case-insensitive. + int qb_less_or_equal_key_value_string( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + ffi.Pointer value, + bool case_sensitive, + ) { + return _qb_less_or_equal_key_value_string( + builder, + property_id, + key, + value, + case_sensitive, + ); + } + + late final _qb_less_or_equal_key_value_stringPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Pointer, + ffi.Bool)>>('obx_qb_less_or_equal_key_value_string'); + late final _qb_less_or_equal_key_value_string = + _qb_less_or_equal_key_value_stringPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, ffi.Pointer, bool)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being lesser than or equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be lesser than or equal to this one. + int qb_less_or_equal_key_value_int( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + int value, + ) { + return _qb_less_or_equal_key_value_int( + builder, + property_id, + key, + value, + ); + } + + late final _qb_less_or_equal_key_value_intPtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Int64)>>('obx_qb_less_or_equal_key_value_int'); + late final _qb_less_or_equal_key_value_int = + _qb_less_or_equal_key_value_intPtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, int)>(); + + /// For flex properties that have a map as root value, this looks for a matching key/value pair, + /// with the map value being lesser than or equal to the given one. + /// @param key must be an exact match exactly (case-sensitive) + /// @param value the map's value must be lesser than or equal to this one. + int qb_less_or_equal_key_value_double( + ffi.Pointer builder, + int property_id, + ffi.Pointer key, + double value, + ) { + return _qb_less_or_equal_key_value_double( + builder, + property_id, + key, + value, + ); + } + + late final _qb_less_or_equal_key_value_doublePtr = _lookup< + ffi.NativeFunction< + obx_qb_cond Function( + ffi.Pointer, + obx_schema_id, + ffi.Pointer, + ffi.Double)>>('obx_qb_less_or_equal_key_value_double'); + late final _qb_less_or_equal_key_value_double = + _qb_less_or_equal_key_value_doublePtr.asFunction< + int Function(ffi.Pointer, int, + ffi.Pointer, double)>(); + int qb_starts_with_string( ffi.Pointer builder, int property_id, @@ -7812,7 +8281,10 @@ class ObjectBoxC { _sync_closePtr.asFunction)>(); /// Sets credentials to authenticate the client with the server. - /// See OBXSyncCredentialsType for available options. + /// Any credentials that were set before are replaced; + /// if you want to pass multiple credentials, use obx_sync_credentials_add() instead. + /// If the client was waiting for credentials, this can trigger a reconnection/login attempt. + /// @param type See OBXSyncCredentialsType for available options. /// The accepted OBXSyncCredentials type depends on your sync-server configuration. /// @param data may be NULL in combination with OBXSyncCredentialsType_NONE int sync_credentials( @@ -7867,6 +8339,80 @@ class ObjectBoxC { int Function(ffi.Pointer, int, ffi.Pointer, ffi.Pointer)>(); + /// For authentication with multiple credentials, collect credentials by calling this function multiple times. + /// When adding the last credentials element, the "complete" flag must be set to true. + /// When completed, it will "activate" the collected credentials and replace any previously set credentials and + /// potentially trigger a reconnection/login attempt. + /// @param type See OBXSyncCredentialsType for available options. + /// The accepted OBXSyncCredentials type depends on your sync-server configuration. + /// @param data non-NULL (OBXSyncCredentialsType_NONE is not allowed) + /// @param complete set to true when adding the last credentials element to activate the set of credentials + int sync_credentials_add( + ffi.Pointer sync1, + int type, + ffi.Pointer data, + int size, + bool complete, + ) { + return _sync_credentials_add( + sync1, + type, + data, + size, + complete, + ); + } + + late final _sync_credentials_addPtr = _lookup< + ffi.NativeFunction< + obx_err Function( + ffi.Pointer, + ffi.Int32, + ffi.Pointer, + ffi.Size, + ffi.Bool)>>('obx_sync_credentials_add'); + late final _sync_credentials_add = _sync_credentials_addPtr.asFunction< + int Function( + ffi.Pointer, int, ffi.Pointer, int, bool)>(); + + /// For authentication with multiple credentials, collect credentials by calling this function multiple times. + /// When adding the last credentials element, the "complete" flag must be set to true. + /// When completed, it will "activate" the collected credentials and replace any previously set credentials and + /// potentially trigger a reconnection/login attempt. + /// @param type See OBXSyncCredentialsType for available options. + /// The accepted OBXSyncCredentials type depends on your sync-server configuration. + /// @param username non-NULL + /// @param password non-NULL + /// @param complete set to true when adding the last credentials element to activate the set of credentials + int sync_credentials_add_user_password( + ffi.Pointer sync1, + int type, + ffi.Pointer username, + ffi.Pointer password, + bool complete, + ) { + return _sync_credentials_add_user_password( + sync1, + type, + username, + password, + complete, + ); + } + + late final _sync_credentials_add_user_passwordPtr = _lookup< + ffi.NativeFunction< + obx_err Function( + ffi.Pointer, + ffi.Int32, + ffi.Pointer, + ffi.Pointer, + ffi.Bool)>>('obx_sync_credentials_add_user_password'); + late final _sync_credentials_add_user_password = + _sync_credentials_add_user_passwordPtr.asFunction< + int Function(ffi.Pointer, int, ffi.Pointer, + ffi.Pointer, bool)>(); + /// Configures the maximum number of outgoing TX messages that can be sent without an ACK from the server. /// @returns OBX_ERROR_ILLEGAL_ARGUMENT if value is not in the range 1-20 int sync_max_messages_in_flight( @@ -8500,16 +9046,16 @@ class ObjectBoxC { ffi.Pointer)>(); /// Set or overwrite a previously set 'error' listener - provides information about occurred sync-level errors. - /// @param listener set NULL to reset + /// @param listener The callback to receive sync errors. Set to NULL to reset. /// @param listener_arg is a pass-through argument passed to the listener void sync_listener_error( ffi.Pointer sync1, - ffi.Pointer error, + ffi.Pointer listener, ffi.Pointer listener_arg, ) { return _sync_listener_error( sync1, - error, + listener, listener_arg, ); } @@ -8968,7 +9514,7 @@ class ObjectBoxC { /// Get u64 value for sync server statistics. /// @param counter_type the counter value to be read (make sure to choose a uint64_t (u64) metric value type). - /// @param out_count receives the counter value. + /// @param out_value receives the counter value. /// @return OBX_SUCCESS if the counter has been successfully retrieved. /// @return OBX_ERROR_ILLEGAL_ARGUMENT if counter_type is undefined (this also happens if the wrong type is requested) /// @return OBX_ERROR_ILLEGAL_STATE if the server is not started. @@ -8994,7 +9540,7 @@ class ObjectBoxC { /// Get double value for sync server statistics. /// @param counter_type the counter value to be read (make sure to use a double (f64) metric value type). - /// @param out_count receives the counter value. + /// @param out_value receives the counter value. /// @return OBX_SUCCESS if the counter has been successfully retrieved. /// @return OBX_ERROR_ILLEGAL_ARGUMENT if counter_type is undefined (this also happens if the wrong type is requested) /// @return OBX_ERROR_ILLEGAL_STATE if the server is not started. @@ -9729,6 +10275,9 @@ abstract class OBXFeature { /// Sync connector to integrate MongoDB with SyncServer. static const int SyncMongoDb = 16; + + /// Enables additional authentication/authorization methods for sync login, e.g. JWT based methods. + static const int Auth = 17; } /// Log level as passed to obx_log_callback. @@ -9850,6 +10399,11 @@ abstract class OBXVectorDistanceType { static const int Manhattan = 4; static const int Hamming = 5; + /// For geospatial coordinates aka latitude/longitude pairs. + /// Note, that the vector dimension must be 2, with the latitude being the first element and longitude the second. + /// Internally, this uses haversine distance. + static const int Geo = 6; + /// A custom dot product similarity measure that does not require the vectors to be normalized. /// Note: this is no replacement for cosine similarity (like DotProduct for normalized vectors is). /// The non-linear conversion provides a high precision over the entire float range (for the raw dot product). @@ -10333,11 +10887,35 @@ class OBX_sync extends ffi.Opaque {} /// specifies a generic client-side credential type. abstract class OBXSyncCredentialsType { static const int NONE = 1; + + /// < Deprecated, replaced by SHARED_SECRET_SIPPED static const int SHARED_SECRET = 2; static const int GOOGLE_AUTH = 3; + + /// < Uses shared secret to create a hashed credential. static const int SHARED_SECRET_SIPPED = 4; + + /// < ObjectBox admin users (username/password) static const int OBX_ADMIN_USER = 5; + + /// < Generic credential type suitable for ObjectBox admin + /// < (and possibly others in the future) static const int USER_PASSWORD = 6; + + /// < JSON Web Token (JWT): an ID token that typically provides identity + /// < information about the authenticated user. + static const int JWT_ID = 7; + + /// < JSON Web Token (JWT): an access token that is used to access resources. + static const int JWT_ACCESS = 8; + + /// < JSON Web Token (JWT): a refresh token that is used to obtain a new + /// < access token. + static const int JWT_REFRESH = 9; + + /// < JSON Web Token (JWT): a token that is neither an ID, access, + /// < nor refresh token. + static const int JWT_CUSTOM = 10; } abstract class OBXRequestUpdatesMode { @@ -10761,7 +11339,7 @@ typedef OBX_custom_msg_server_func_client_connection_close = ffi.NativeFunction< /// Callback to shutdown and free all resources associated with the sync client connection to the custom server. /// Note that the custom server may already have been shutdown at this point (e.g. no server user data is supplied). /// Must be provided to implement a custom server. See notes on OBX_custom_msg_server_functions for more details. -/// @param server_user_data User supplied data returned by the function that created the server +/// @param connection_user_data User supplied data returned by the function that created the server typedef OBX_custom_msg_server_func_client_connection_shutdown = ffi.NativeFunction< ffi.Void Function(ffi.Pointer connection_user_data)>; @@ -10881,9 +11459,9 @@ typedef obx_dart_closer const int OBX_VERSION_MAJOR = 4; -const int OBX_VERSION_MINOR = 0; +const int OBX_VERSION_MINOR = 1; -const int OBX_VERSION_PATCH = 2; +const int OBX_VERSION_PATCH = 0; const int OBX_ID_NEW = -1; diff --git a/objectbox/lib/src/native/sync.dart b/objectbox/lib/src/native/sync.dart index e09985aa..a6ddc0ba 100644 --- a/objectbox/lib/src/native/sync.dart +++ b/objectbox/lib/src/native/sync.dart @@ -44,6 +44,28 @@ class SyncCredentials { static SyncCredentials userAndPassword(String user, String password) => _SyncCredentialsUserPassword._( OBXSyncCredentialsType.USER_PASSWORD, user, password); + + /// JSON Web Token (JWT): an ID token that typically provides identity + /// information about the authenticated user. + static SyncCredentials jwtIdToken(String jwtIdToken) => + SyncCredentialsSecret._encode(OBXSyncCredentialsType.JWT_ID, jwtIdToken); + + /// JSON Web Token (JWT): an access token that is used to access resources. + static SyncCredentials jwtAccessToken(String jwtAccessToken) => + SyncCredentialsSecret._encode( + OBXSyncCredentialsType.JWT_ACCESS, jwtAccessToken); + + /// JSON Web Token (JWT): a refresh token that is used to obtain a new + /// access token. + static SyncCredentials jwtRefreshToken(String jwtRefreshToken) => + SyncCredentialsSecret._encode( + OBXSyncCredentialsType.JWT_REFRESH, jwtRefreshToken); + + /// JSON Web Token (JWT): a token that is neither an ID, access, + /// nor refresh token. + static SyncCredentials jwtCustomToken(String jwtCustomToken) => + SyncCredentialsSecret._encode( + OBXSyncCredentialsType.JWT_CUSTOM, jwtCustomToken); } class _SyncCredentialsNone extends SyncCredentials { @@ -169,10 +191,10 @@ class SyncClient { /// Creates a Sync client associated with the given store and options. /// This does not initiate any connection attempts yet: call start() to do so. SyncClient._( - this._store, List serverUrls, SyncCredentials credentials) { + this._store, List serverUrls, List credentials) { if (serverUrls.isEmpty) { throw ArgumentError.value( - serverUrls, "serverUrls", "must contain at least one server URL"); + serverUrls, "serverUrls", "Provide at least one server URL"); } if (!Sync.isAvailable()) { @@ -187,7 +209,12 @@ class SyncClient { C.sync_urls(InternalStoreAccess.ptr(_store), ptr, size), 'failed to create Sync client')); - setCredentials(credentials); + if (credentials.length == 1) { + setCredentials(credentials[0]); + } else { + // also covers the length == 0 case + setMultipleCredentials(credentials); + } } /// Closes and cleans up all resources used by this sync client. @@ -240,8 +267,8 @@ class SyncClient { creds._user, (userCStr) => withNativeString( creds._password, - (passwordCStr) => C.sync_credentials_user_password( - _ptr, creds._type, userCStr, passwordCStr))); + (passwordCStr) => checkObx(C.sync_credentials_user_password( + _ptr, creds._type, userCStr, passwordCStr)))); } else if (creds is SyncCredentialsSecret) { withNativeBytes( creds.data, @@ -250,6 +277,54 @@ class SyncClient { } } + /// Like [setCredentials], but accepts multiple credentials. + /// + /// However, does **not** support [SyncCredentials.none()]. + void setMultipleCredentials(List credentials) { + if (credentials.isEmpty) { + throw ArgumentError.value( + credentials, "credentials", "Provide at least one credential"); + } + + var length = credentials.length; + for (int i = 0; i < length; i++) { + final isLast = i + 1 == length; + var credential = credentials[i]; + + if (credential is _SyncCredentialsNone) { + throw ArgumentError.value(credentials, "credentials", + "SyncCredentials.none() is not supported, use setCredentials() instead"); + } + + try { + if (credential is _SyncCredentialsUserPassword) { + withNativeString( + credential._user, + (userCStr) => withNativeString( + credential._password, + (passwordCStr) => checkObx( + C.sync_credentials_add_user_password(_ptr, + credential._type, userCStr, passwordCStr, isLast)))); + } else if (credential is SyncCredentialsSecret) { + withNativeBytes( + credential.data, + (Pointer credsPtr, int credsSize) => checkObx( + C.sync_credentials_add( + _ptr, credential._type, credsPtr, credsSize, isLast))); + } + } catch (e) { + // To make exceptions related to a credential easier to attribute, + // wrap in an ArgumentError and give the position in the list. + if (e is StateError) { + // State errors should not be specific to a credential, so rethrow + rethrow; + } else { + throw ArgumentError.value(credential, "credentials[$i]", "$e"); + } + } + } + } + /// Configures how sync updates are received from the server. If automatic /// updates are turned off, they will need to be requested manually. void setRequestUpdatesMode(SyncRequestUpdatesMode mode) { @@ -626,9 +701,26 @@ class Sync { Store store, String serverUrl, SyncCredentials credentials) => clientMultiUrls(store, [serverUrl], credentials); + /// Like [client], but accepts a list of credentials. + /// + /// When passing multiple credentials, does **not** support + /// [SyncCredentials.none()]. + static SyncClient clientMultiCredentials( + Store store, String serverUrl, List credentials) => + clientMultiCredentialsMultiUrls(store, [serverUrl], credentials); + /// Like [client], but accepts a list of URLs to work with multiple servers. static SyncClient clientMultiUrls( - Store store, List serverUrls, SyncCredentials credentials) { + Store store, List serverUrls, SyncCredentials credentials) => + clientMultiCredentialsMultiUrls(store, serverUrls, [credentials]); + + /// Like [client], but accepts a list of credentials and a list of URLs to + /// work with multiple servers. + /// + /// When passing multiple credentials, does **not** support + /// [SyncCredentials.none()]. + static SyncClient clientMultiCredentialsMultiUrls( + Store store, List serverUrls, List credentials) { if (syncClientsStorage.containsKey(store)) { throw StateError('Only one sync client can be active for a store'); } diff --git a/objectbox/pubspec.yaml b/objectbox/pubspec.yaml index bfea4c36..aec52b70 100644 --- a/objectbox/pubspec.yaml +++ b/objectbox/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://objectbox.io # Link to actual directory in repository so file links on pub.dev work. repository: https://github.com/objectbox/objectbox-dart/tree/main/objectbox documentation: https://docs.objectbox.io -version: 4.0.3 +version: 4.1.0 environment: # minimum Dart SDK (also see generator and flutter_libs) diff --git a/objectbox_test/test/annotations_test.dart b/objectbox_test/test/annotations_test.dart index 9fdf2bf1..fc1c7241 100644 --- a/objectbox_test/test/annotations_test.dart +++ b/objectbox_test/test/annotations_test.dart @@ -22,6 +22,7 @@ void main() { OBXVectorDistanceType.DotProduct); expect(VectorDistanceType.dotProductNonNormalized.toConstant(), OBXVectorDistanceType.DotProductNonNormalized); + expect(VectorDistanceType.geo.toConstant(), OBXVectorDistanceType.Geo); }); test("ModelHnswParams maps values", () { diff --git a/objectbox_test/test/entity.dart b/objectbox_test/test/entity.dart index 9ffbff5d..5712476d 100644 --- a/objectbox_test/test/entity.dart +++ b/objectbox_test/test/entity.dart @@ -465,6 +465,10 @@ class HnswObject { @HnswIndex(dimensions: 2) List? floatVector; + @Property(type: PropertyType.floatVector) + @HnswIndex(dimensions: 2, distanceType: VectorDistanceType.geo) + List? floatVectorGeoCoordinates; + final rel = ToOne(); } diff --git a/objectbox_test/test/hnsw_test.dart b/objectbox_test/test/hnsw_test.dart index 0d1447a7..8291a858 100644 --- a/objectbox_test/test/hnsw_test.dart +++ b/objectbox_test/test/hnsw_test.dart @@ -72,6 +72,41 @@ void main() { expect(closest2.name, "node8"); }); + test('vectorSearchCitiesGeo', () { + // capital cities across Europe + List cities = ["Berlin", "Paris", "Rome", "Madrid", "London"]; + List> coordinates = [ + [52.5200, 13.4050], + [48.8566, 2.3522], + [41.9028, 12.4964], + [40.4168, -3.7038], + [51.5074, -0.1278] + ]; + + box.putMany(List.generate(cities.length, (i) { + return HnswObject() + ..name = cities[i] + ..floatVectorGeoCoordinates = coordinates[i]; + })); + + // lat/lng for Munich + final List searchVector = [48.1371, 11.5754]; + + final query = box + .query(HnswObject_.floatVectorGeoCoordinates + .nearestNeighborsF32(searchVector, 5)) + .build(); + addTearDown(() => query.close()); + + final nearestCities = query.find(); + expect(nearestCities.length, 5); + expect(nearestCities[0].name, "Berlin"); + expect(nearestCities[1].name, "Paris"); + expect(nearestCities[2].name, "Rome"); + expect(nearestCities[3].name, "Madrid"); + expect(nearestCities[4].name, "London"); + }); + test('find offset limit', () { box.putMany(List.generate(15, (index) { final i = index + 1; // start at 1 diff --git a/objectbox_test/test/objectbox-model.json b/objectbox_test/test/objectbox-model.json index 23803d53..99635f56 100644 --- a/objectbox_test/test/objectbox-model.json +++ b/objectbox_test/test/objectbox-model.json @@ -667,7 +667,7 @@ }, { "id": "14:880388751413233760", - "lastPropertyId": "4:2308158275756661586", + "lastPropertyId": "5:1476994841170736323", "name": "HnswObject", "properties": [ { @@ -695,6 +695,13 @@ "flags": 520, "indexId": "22:6527315700526716999", "relationTarget": "RelatedNamedEntity" + }, + { + "id": "5:1476994841170736323", + "name": "floatVectorGeoCoordinates", + "type": 28, + "flags": 8, + "indexId": "23:6649884639373473085" } ], "relations": [] @@ -720,7 +727,7 @@ } ], "lastEntityId": "15:4803284427984871569", - "lastIndexId": "22:6527315700526716999", + "lastIndexId": "23:6649884639373473085", "lastRelationId": "1:2155747579134420981", "lastSequenceId": "0:0", "modelVersion": 5, diff --git a/objectbox_test/test/sync_test.dart b/objectbox_test/test/sync_test.dart index b21b0b7b..3e0138e0 100644 --- a/objectbox_test/test/sync_test.dart +++ b/objectbox_test/test/sync_test.dart @@ -37,9 +37,13 @@ void main() { expect(waitUntil(() => client.state() == SyncState.loggedIn), isTrue); } - // lambda to easily create clients in the test below + // lambda to easily create clients in the tests below + SyncClient createAuthenticatedClient( + Store s, List credentials) => + Sync.clientMultiCredentials(s, 'ws://127.0.0.1:$serverPort', credentials); + SyncClient createClient(Store s) => - Sync.client(s, 'ws://127.0.0.1:$serverPort', SyncCredentials.none()); + createAuthenticatedClient(s, [SyncCredentials.none()]); // lambda to easily create clients in the test below SyncClient loggedInClient(Store s) { @@ -57,10 +61,18 @@ void main() { }); test('Sync.clientMulti throws if empty URL list', () { + // Note: this test works with a library that does not have the Sync + // feature, because the URLs are checked before checking for the feature. expect( () => Sync.clientMultiUrls(store, [], SyncCredentials.none()), throwsA(isArgumentError.having((e) => e.message, 'message', - contains('must contain at least one server URL')))); + contains('Provide at least one server URL')))); + + expect( + () => Sync.clientMultiCredentialsMultiUrls( + store, [], [SyncCredentials.none()]), + throwsA(isArgumentError.having((e) => e.message, 'message', + contains('Provide at least one server URL')))); }); test('SyncCredentials string encoding', () { @@ -114,6 +126,18 @@ void main() { expect(store.syncClient(), isNull); }); + test('Sync.clientMulti throws if empty credential list', () { + expect( + () => Sync.clientMultiCredentials(store, 'test-url', []), + throwsA(isArgumentError.having((e) => e.message, 'message', + contains('Provide at least one credential')))); + + expect( + () => Sync.clientMultiCredentialsMultiUrls(store, ['test-url'], []), + throwsA(isArgumentError.having((e) => e.message, 'message', + contains('Provide at least one credential')))); + }); + test('SyncClient is closed when a store is closed', () { final client = createClient(env2.store); env2.closeAndDelete(); @@ -150,13 +174,30 @@ void main() { expect(() => c.cancelUpdates(), error); expect(() => c.requestUpdates(subscribeForFuturePushes: true), error); expect(() => c.outgoingMessageCount(), error); + expect(() => c.setCredentials(SyncCredentials.none()), error); + expect( + () => c.setCredentials(SyncCredentials.sharedSecretString('secret')), + error); + expect( + () => c + .setCredentials(SyncCredentials.userAndPassword('obx', 'secret')), + error); + + expect( + () => c.setMultipleCredentials([ + SyncCredentials.sharedSecretString('secret'), + SyncCredentials.userAndPassword('obx', 'secret') + ]), + error); + expect(() => c.setRequestUpdatesMode(SyncRequestUpdatesMode.auto), error); }); test('SyncClient simple coverage (no server available)', () { SyncClient c = createClient(store); expect(c.isClosed(), isFalse); + c.setCredentials(SyncCredentials.none()); c.setCredentials(SyncCredentials.googleAuthString('secret')); c.setCredentials(SyncCredentials.sharedSecretString('secret')); @@ -165,6 +206,11 @@ void main() { c.setCredentials(SyncCredentials.sharedSecretUint8List( Uint8List.fromList([13, 0, 25]))); c.setCredentials(SyncCredentials.userAndPassword('obx', 'secret')); + c.setCredentials(SyncCredentials.jwtIdToken('id-token')); + c.setCredentials(SyncCredentials.jwtAccessToken('access-token')); + c.setCredentials(SyncCredentials.jwtRefreshToken('refresh-token')); + c.setCredentials(SyncCredentials.jwtCustomToken('custom-token')); + c.setCredentials(SyncCredentials.none()); c.setRequestUpdatesMode(SyncRequestUpdatesMode.manual); c.start(); @@ -176,7 +222,33 @@ void main() { expect(c.state(), equals(SyncState.stopped)); }); - group('Sync tests with server', () { + test('SyncClient setMultipleCredentials', () { + SyncClient c = createClient(store); + + expect( + () => c.setMultipleCredentials([]), + throwsA(isA() + .having((e) => e.name, "name", "credentials"))); + + // none() not supported + expect( + () => c.setMultipleCredentials([SyncCredentials.none()]), + throwsA(isA() + .having((e) => e.name, "name", "credentials"))); + + // Not throwing in Dart for any supported type + c.setMultipleCredentials([ + SyncCredentials.googleAuthString('secret'), + SyncCredentials.sharedSecretString('secret'), + SyncCredentials.userAndPassword('obx', 'secret'), + SyncCredentials.jwtIdToken('id-token'), + SyncCredentials.jwtAccessToken('access-token'), + SyncCredentials.jwtRefreshToken('refresh-token'), + SyncCredentials.jwtCustomToken('custom-token') + ]); + }); + + group('Server tests using sync-server in PATH', () { late SyncServer server; setUp(() async { @@ -389,6 +461,57 @@ void main() { skip: SyncServer.isAvailable() ? null : 'sync-server executable is not available in PATH - tests requiring it are skipped'); + + group('Server tests expecting running Sync server', () { + final String testJwtToken = "INSERT_VALID_JWT"; + final String testInvalidJwtToken = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJzeW5jLXNlcnZlciIsImlzcyI6Im9iamVjdGJveC1hdXRoIiwiZXhwIjoxNzM4MjE1NjAwLCJpYXQiOjE3MzgyMTc0MDN9.3auqtgaSEqpFqXhuCyoDM-LbfTOIEGGF6X0AjCcykJ2Nv1WN6LaVbuMDjMf-tKSLyeqFkzQbIckP4FvLHh7wQJ6rafDiT4H2pb6xhouU1QH3szK2S_7VDl_4BhxRbW5pEUt9086HXaVFHEZVS0417pxomlPHxrc1n4Z_A4QxZM5_xh5xcHV8PiGgXWb6_2basjBj5z6POTrazRs67IOQ-ob6ROIsOUGu3om6b8i0h_QSMmeJbujfr2EZqhYWTKijeyidbjRWZ97NFxtGRYN_jPOvy-T3gANXs2a32Er8XvgZTjr_-O8tl_1fHPo2kDE-UCNdwUfBQFhTokDUdJ81bg"; + + /// NOTE Unlike the other tests, this test assumes a Sync server is + /// already running on [serverPort] and has JWT auth configured. + /// Then obtain a JWT and insert it in [testJwtToken] above. + /// + /// Background: Sync server needs to run in a supported environment to + /// enable JWT authentication. So this test can not interact with a + /// sync-server binary like the other tests. + test('Auth with JSON Web Token (JWT)', () async { + // Note: the objectbox project covers all cases, this test just + // ensures the Dart parts work as expected. + + expect(testJwtToken, isNot("INSERT_VALID_JWT"), + reason: + "Paste a valid JWT into testJwtToken before running this test"); + + // Using an already running server, at least check it's available + await SyncServer.onlineAt(serverPort); + + // invalid token should fail to log in + var client = createAuthenticatedClient( + env.store, [SyncCredentials.jwtIdToken(testInvalidJwtToken)]); + + final events = []; + client.loginEvents.listen(events.add); + client.start(); + addTearDown(() => client.close()); + + expect( + await client.loginEvents.first.timeout(defaultTimeout, + onTimeout: () => throw TimeoutException( + "Did not receive login event within $defaultTimeout")), + equals(SyncLoginEvent.credentialsRejected)); + + // valid token should succeed to log in + client.setCredentials(SyncCredentials.jwtIdToken(testJwtToken)); + + waitUntilLoggedIn(client); + await yieldExecution(); + + expect( + events, + equals( + [SyncLoginEvent.credentialsRejected, SyncLoginEvent.loggedIn])); + }); + }, skip: "Test requires to manually run Sync server"); } else { // TESTS to run when SYNC is NOT available @@ -405,7 +528,7 @@ void main() { class SyncServer { Directory? _dir; int? _port; - Future? _process; + Process? _process; static bool isAvailable() { // Note: this causes an additional valgrind summary output with a leak. @@ -423,31 +546,72 @@ class SyncServer { } } - Future start({bool keepDb = false}) async { - _port ??= await _getUnusedPort(); + static Future _writeConfFile( + Directory directory, String contents) async { + if (!await directory.exists()) { + await directory.create(recursive: true); + } + + final configFile = File('${directory.path}/sync-server-conf-test.json'); + await configFile.writeAsString(contents); // overwrites by default - _dir ??= Directory('testdata-sync-server-$_port'); + return configFile; + } + + /// By default uses `--unsecured-no-authentication`. To use a custom + /// authentication configuration, pass [configContents]. + /// It will be used to create a config file that is passed using `--conf`. + /// + /// Set [keepDb] to not delete an existing database before starting the + /// server. + Future start({bool keepDb = false, String? configContents}) async { + final port = _port ??= await _getUnusedPort(); + _port = port; + + final dir = _dir ??= Directory('testdata-sync-server-$port'); + _dir = dir; if (!keepDb) _deleteDb(); - _process = Process.start('sync-server', [ - '--unsecured-no-authentication', - '--db-directory=${_dir!.path}', + // Note: add arguments using the '--arg=value' syntax (unlike at the + // command line, which uses '--arg value')! + final arguments = [ + '--db-directory=${dir.path}', '--model=${Directory.current.path}/test/objectbox-model.json', - '--bind=ws://127.0.0.1:$_port', + '--bind=ws://127.0.0.1:$port', '--admin-bind=http://127.0.0.1:${await _getUnusedPort()}' - ]); + ]; + if (configContents != null && configContents.isNotEmpty) { + // Note: command line arguments overwrite values in a conf file + final configFile = await _writeConfFile(dir, configContents); + arguments.add("--conf=${configFile.absolute.path}"); + } else { + arguments.add('--unsecured-no-authentication'); + } + + print("Starting Sync server with arguments: $arguments"); + final process = await Process.start('sync-server', arguments); + _process = process; + + // Make log output visible when running tests + stdout.addStream(process.stdout); + stderr.addStream(process.stderr); + + return port; + } - return _port!; + /// Like [onlineAt], but assuming the [_port] of this server. + Future online() async { + return onlineAt(_port!); } /// Wait for the server to respond to a simple http request. /// This simple check speeds up test by only trying to log in after the server /// has started, avoiding the reconnect backoff intervals altogether. - Future online() async => Future(() async { + static Future onlineAt(int port) async => Future(() async { final httpClient = HttpClient(); while (true) { try { - await httpClient.get('127.0.0.1', _port!, ''); + await httpClient.get('127.0.0.1', port, ''); break; } on SocketException catch (e) { // Only retry if "Connection refused" (not using error codes as they @@ -460,17 +624,17 @@ class SyncServer { } } httpClient.close(force: true); - }).timeout(defaultTimeout); + }).timeout(defaultTimeout, + onTimeout: () => throw TimeoutException( + "Server did not come online within $defaultTimeout ms")); Future stop({bool keepDb = false}) async { - if (_process == null) return; - final proc = await _process!; + final proc = _process; + if (proc == null) return; _process = null; proc.kill(ProcessSignal.sigint); final exitCode = await proc.exitCode; if (exitCode != 0) { - await stdout.addStream(proc.stdout); - await stderr.addStream(proc.stderr); expect(await proc.exitCode, isZero); } if (!keepDb) _deleteDb(); diff --git a/sync_flutter_libs/android/build.gradle b/sync_flutter_libs/android/build.gradle index 024e51c0..95ae6c0f 100644 --- a/sync_flutter_libs/android/build.gradle +++ b/sync_flutter_libs/android/build.gradle @@ -52,6 +52,6 @@ android { // ObjectBox Android library that includes an ObjectBox C library version compatible with // the C API binding of the ObjectBox Dart package. // https://central.sonatype.com/search?q=g:io.objectbox%20objectbox-sync-android - implementation "io.objectbox:objectbox-sync-android:4.0.3" + implementation "io.objectbox:objectbox-sync-android:4.1.0" } } diff --git a/sync_flutter_libs/ios/objectbox_sync_flutter_libs.podspec b/sync_flutter_libs/ios/objectbox_sync_flutter_libs.podspec index 777b7b11..d452b7b9 100644 --- a/sync_flutter_libs/ios/objectbox_sync_flutter_libs.podspec +++ b/sync_flutter_libs/ios/objectbox_sync_flutter_libs.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'ObjectBox', '4.0.1-sync' + s.dependency 'ObjectBox', '4.1.0-sync' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/sync_flutter_libs/linux/CMakeLists.txt b/sync_flutter_libs/linux/CMakeLists.txt index 2738fbb3..4958308b 100644 --- a/sync_flutter_libs/linux/CMakeLists.txt +++ b/sync_flutter_libs/linux/CMakeLists.txt @@ -44,7 +44,7 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) # ---------------------------------------------------------------------- # Download and add objectbox-c prebuilt library. -set(OBJECTBOX_VERSION 4.0.2) +set(OBJECTBOX_VERSION 4.1.0) set(OBJECTBOX_ARCH ${CMAKE_SYSTEM_PROCESSOR}) if (${OBJECTBOX_ARCH} MATCHES "x86_64") diff --git a/sync_flutter_libs/macos/objectbox_sync_flutter_libs.podspec b/sync_flutter_libs/macos/objectbox_sync_flutter_libs.podspec index f347e954..7c39f43e 100644 --- a/sync_flutter_libs/macos/objectbox_sync_flutter_libs.podspec +++ b/sync_flutter_libs/macos/objectbox_sync_flutter_libs.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.dependency 'ObjectBox', '4.0.1-sync' + s.dependency 'ObjectBox', '4.1.0-sync' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.3' diff --git a/sync_flutter_libs/pubspec.yaml b/sync_flutter_libs/pubspec.yaml index 21e15d07..46544410 100644 --- a/sync_flutter_libs/pubspec.yaml +++ b/sync_flutter_libs/pubspec.yaml @@ -3,7 +3,7 @@ description: Fast Flutter database for persisting Dart objects. This package con # Link to actual directory in repository so file links on pub.dev work. repository: https://github.com/objectbox/objectbox-dart/tree/main/sync_flutter_libs homepage: https://objectbox.io -version: 4.0.3 +version: 4.1.0 environment: sdk: '>=2.18.0 <4.0.0' @@ -14,7 +14,7 @@ dependencies: sdk: flutter # This is here just to ensure compatibility between objectbox-dart code and the libraries used # You should still depend on objectbox directly in your Flutter application. - objectbox: 4.0.3 + objectbox: 4.1.0 path_provider: ^2.0.0 dev_dependencies: diff --git a/sync_flutter_libs/windows/CMakeLists.txt b/sync_flutter_libs/windows/CMakeLists.txt index fd1cdd64..75e92965 100644 --- a/sync_flutter_libs/windows/CMakeLists.txt +++ b/sync_flutter_libs/windows/CMakeLists.txt @@ -50,7 +50,7 @@ set(objectbox_sync_flutter_libs_bundled_libraries # ---------------------------------------------------------------------- # Download and add objectbox-c prebuilt library. -set(OBJECTBOX_VERSION 4.0.2) +set(OBJECTBOX_VERSION 4.1.0) set(OBJECTBOX_ARCH ${CMAKE_SYSTEM_PROCESSOR}) if (${OBJECTBOX_ARCH} MATCHES "AMD64") diff --git a/tool/update-c-binding.sh b/tool/update-c-binding.sh index 16476af0..c8b3a2bd 100755 --- a/tool/update-c-binding.sh +++ b/tool/update-c-binding.sh @@ -5,7 +5,7 @@ # copies the header files, makes some required modifications # and runs the ffigen binding generator on them. -cLibVersion=4.0.2 +cLibVersion=4.1.0 echo "Downloading C library source files from GitHub..." # Note: the release archives do not contain objectbox-dart.h, so get the full sources.