diff --git a/dev-doc/updating-c-library.md b/dev-doc/updating-c-library.md index f5ae9dccd..b54c2d7f6 100644 --- a/dev-doc/updating-c-library.md +++ b/dev-doc/updating-c-library.md @@ -19,15 +19,15 @@ for the binding update script (see below) and 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.2.0 ``` ```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). +* Update ObjectBox database for Flutter Linux/Windows, Dart Native apps to [4.2.0](https://github.com/objectbox/objectbox-c/releases/tag/v4.2.0). ``` ```text -Update C library [4.0.2 -> 4.1.0] +Update C library [4.1.0 -> 4.2.0] ``` ### Android @@ -35,19 +35,19 @@ Update C library [4.0.2 -> 4.1.0] For the Flutter plugins on Android ([view releases](https://github.com/objectbox/objectbox-java/releases)): ```bash -./tool/set-android-version.sh 4.1.0 +./tool/set-android-version.sh 4.2.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`. +* Update ObjectBox database for Flutter Android apps to 4.2.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.2.0` in `android/app/build.gradle`. ``` ```text -Update objectbox-android [4.0.3 -> 4.1.0] +Update objectbox-android [4.1.0 -> 4.2.0] -Bundled with C API 4.1.0 and ObjectBox 4.1.0-2025-01-28 +Bundled with C API 4.2.0 and ObjectBox 4.2.0-2025-03-04 ``` Note: the embedded C API and ObjectBox version can be looked up @@ -58,18 +58,18 @@ from the relevant objectbox repository release tag (like `java-4.1.0`). 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.2.0 ``` ```text -* Flutter for iOS/macOS: update to [objectbox-swift 4.1.0](https://github.com/objectbox/objectbox-swift/releases/tag/v4.1.0). +* Update ObjectBox database for Flutter iOS/macOS apps to 4.2.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] +Update ObjectBox Swift [4.1.0 -> 4.2.0] -Bundled with C API 4.1.0 and ObjectBox 4.1.0-2025-01-30 +Bundled with C API 4.2.0 and ObjectBox 4.2.0-2025-03-04 ``` Note: the embedded C API and ObjectBox version can be looked up @@ -96,5 +96,5 @@ Then manually: - Commit as ```text -Update C-API [4.0.2 -> 4.1.0] +Update C-API [4.1.0 -> 4.2.0] ``` diff --git a/flutter_libs/android/build.gradle b/flutter_libs/android/build.gradle index 79bf4f154..788a1a9ad 100644 --- a/flutter_libs/android/build.gradle +++ b/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-android - implementation "io.objectbox:objectbox-android:4.1.0" + implementation "io.objectbox:objectbox-android:4.2.0" } } diff --git a/flutter_libs/ios/objectbox_flutter_libs.podspec b/flutter_libs/ios/objectbox_flutter_libs.podspec index 3a24c744b..a26696e9d 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.1.0' + s.dependency 'ObjectBox', '4.2.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 aca438e23..cbce9d6c1 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.1.0) +set(OBJECTBOX_VERSION 4.2.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 5dcbcf81d..e6ef1d9af 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.1.0' + s.dependency 'ObjectBox', '4.2.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.3' diff --git a/flutter_libs/windows/CMakeLists.txt b/flutter_libs/windows/CMakeLists.txt index 77595b411..eed019cbf 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.1.0) +set(OBJECTBOX_VERSION 4.2.0) set(OBJECTBOX_ARCH ${CMAKE_SYSTEM_PROCESSOR}) if (${OBJECTBOX_ARCH} MATCHES "AMD64") diff --git a/generator/lib/src/code_builder.dart b/generator/lib/src/code_builder.dart index f735df13e..f447004b9 100644 --- a/generator/lib/src/code_builder.dart +++ b/generator/lib/src/code_builder.dart @@ -1,21 +1,21 @@ import 'dart:async'; -import 'dart:io'; import 'dart:convert'; +import 'dart:io'; import 'package:build/build.dart'; import 'package:collection/collection.dart'; +import 'package:dart_style/dart_style.dart'; import 'package:glob/glob.dart'; +import 'package:objectbox/internal.dart'; import 'package:objectbox_generator/src/analysis/analysis.dart'; import 'package:objectbox_generator/src/builder_dirs.dart'; import 'package:path/path.dart' as path; -import 'package:objectbox/internal.dart'; -import 'package:dart_style/dart_style.dart'; -import 'package:source_gen/source_gen.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; +import 'package:source_gen/source_gen.dart'; +import 'code_chunks.dart'; import 'config.dart'; import 'entity_resolver.dart'; -import 'code_chunks.dart'; /// CodeBuilder collects all '.objectbox.info' files created by EntityResolver and generates objectbox-model.json and /// objectbox_model.dart @@ -213,6 +213,8 @@ class CodeBuilder extends Builder { propInModel.dartFieldType = prop.dartFieldType; propInModel.relationTarget = prop.relationTarget; propInModel.hnswParams = prop.hnswParams; + propInModel.externalType = prop.externalType; + propInModel.externalName = prop.externalName; if (!prop.hasIndexFlag()) { propInModel.removeIndex(); @@ -242,6 +244,8 @@ class CodeBuilder extends Builder { relInModel.name = rel.name; relInModel.targetName = rel.targetName; + relInModel.externalType = rel.externalType; + relInModel.externalName = rel.externalName; } IdUid mergeEntity(ModelInfo modelInfo, ModelEntity entity) { @@ -267,6 +271,7 @@ class CodeBuilder extends Builder { entityInModel.name = entity.name; entityInModel.flags = entity.flags; + entityInModel.externalName = entity.externalName; entityInModel.constructorParams = entity.constructorParams; // here, the entity was found already and entityInModel and entity might differ, i.e. conflicts need to be resolved, so merge all properties first diff --git a/generator/lib/src/code_chunks.dart b/generator/lib/src/code_chunks.dart index 336b4bdf5..2fbce137e 100644 --- a/generator/lib/src/code_chunks.dart +++ b/generator/lib/src/code_chunks.dart @@ -122,12 +122,16 @@ class CodeChunks { } static String createModelEntity(ModelEntity entity) { + var additionalArgs = ''; + if (entity.externalName != null) { + additionalArgs += " externalName: '${entity.externalName}',"; + } return ''' $obxInt.ModelEntity( id: ${createIdUid(entity.id)}, name: '${entity.name}', lastPropertyId: ${createIdUid(entity.lastPropertyId)}, - flags: ${entity.flags}, + flags: ${entity.flags},$additionalArgs properties: <$obxInt.ModelProperty>[ ${entity.properties.map(createModelProperty).join(',')} ], @@ -154,6 +158,12 @@ class CodeChunks { additionalArgs += ", hnswParams: ${property.hnswParams!.toCodeString(obxInt)}"; } + if (property.externalType != null) { + additionalArgs += ", externalType: ${property.externalType!}"; + } + if (property.externalName != null) { + additionalArgs += ", externalName: '${property.externalName!}'"; + } return ''' $obxInt.ModelProperty( id: ${createIdUid(property.id)}, @@ -166,11 +176,19 @@ class CodeChunks { } static String createModelRelation(ModelRelation relation) { + var additionalArgs = ''; + if (relation.externalType != null) { + additionalArgs += ", externalType: ${relation.externalType!}"; + } + if (relation.externalName != null) { + additionalArgs += ", externalName: '${relation.externalName!}'"; + } return ''' $obxInt.ModelRelation( id: ${createIdUid(relation.id)}, name: '${relation.name}', targetId: ${createIdUid(relation.targetId)} + $additionalArgs ) '''; } diff --git a/generator/lib/src/entity_resolver.dart b/generator/lib/src/entity_resolver.dart index eb688718f..03355b962 100644 --- a/generator/lib/src/entity_resolver.dart +++ b/generator/lib/src/entity_resolver.dart @@ -28,6 +28,8 @@ class EntityResolver extends Builder { final _indexChecker = const TypeChecker.fromRuntime(Index); final _backlinkChecker = const TypeChecker.fromRuntime(Backlink); final _hnswChecker = const TypeChecker.fromRuntime(HnswIndex); + final _externalTypeChecker = const TypeChecker.fromRuntime(ExternalType); + final _externalNameChecker = const TypeChecker.fromRuntime(ExternalName); @override FutureOr build(BuildStep buildStep) async { @@ -68,6 +70,11 @@ class EntityResolver extends Builder { null, uidRequest: !entityUid.isNull && entityUid.intValue == 0); + // @ExternalName + _externalNameChecker.runIfMatches(classElement, (annotation) { + entity.externalName = _readExternalNameParams(annotation); + }); + // Sync: check if enabled and options _syncChecker.runIfMatches(classElement, (annotation) { entity.flags |= OBXEntityFlags.SYNC_ENABLED; @@ -171,6 +178,7 @@ class EntityResolver extends Builder { final backlinkAnnotations = _backlinkChecker.annotationsOfExact(annotated); if (backlinkAnnotations.isNotEmpty) { + // Handles ToMany based on other ToOne or ToMany relation (backlink) if (!isToManyRel) { log.severe( " Skipping property '${f.name}': @Backlink() may only be used with ToMany."); @@ -183,14 +191,33 @@ class EntityResolver extends Builder { entity.backlinks.add(backlink); log.info(' $backlink'); } else if (isToManyRel) { + // Handles standalone (non backlink) ToMany relation + + // @ExternalType + int? externalType; + _externalTypeChecker.runIfMatches(annotated, (annotation) { + final externalTypeId = _readExternalTypeParams(annotation); + externalType = externalTypeId; + }); + + // @ExternalName + String? externalName; + _externalNameChecker.runIfMatches(annotated, (annotation) { + externalName = _readExternalNameParams(annotation); + }); + // create relation final rel = ModelRelation.create(IdUid(0, propUid ?? 0), f.name, targetName: relTargetName, - uidRequest: propUid != null && propUid == 0); + uidRequest: propUid != null && propUid == 0, + externalName: externalName, + externalType: externalType); + entity.relations.add(rel); log.info(' $rel'); } else { + // Handles regular properties // create property (do not use readEntity.createProperty in order to avoid generating new ids) final prop = ModelProperty.create( IdUid(0, propUid ?? 0), f.name, fieldType, @@ -226,6 +253,17 @@ class EntityResolver extends Builder { _readHnswIndexParams(annotation, prop); }); + // @ExternalType + _externalTypeChecker.runIfMatches(annotated, (annotation) { + final externalTypeId = _readExternalTypeParams(annotation); + prop.externalType = externalTypeId; + }); + + // @ExternalName + _externalNameChecker.runIfMatches(annotated, (annotation) { + prop.externalName = _readExternalNameParams(annotation); + }); + // for code generation prop.dartFieldType = f.type.element!.name! + (isNullable(f.type) ? '?' : ''); @@ -555,6 +593,27 @@ class EntityResolver extends Builder { annotation.getField('vectorCacheHintSizeKB')!.toIntValue()); property.hnswParams = ModelHnswParams.fromAnnotation(hnswRestored); } + + int _readExternalTypeParams(DartObject annotation) { + final typeIndex = + _enumValueIndex(annotation.getField('type')!, "ExternalType.type"); + final type = + typeIndex != null ? ExternalPropertyType.values[typeIndex] : null; + if (type == null) { + throw InvalidGenerationSourceError( + "'type' attribute not specified in @ExternalType annotation"); + } + return externalTypeToOBXExternalType(type); + } + + String _readExternalNameParams(DartObject annotation) { + final name = annotation.getField('name')!.toStringValue(); + if (name == null) { + throw InvalidGenerationSourceError( + "'name' attribute not specified in @ExternalName annotation"); + } + return name; + } } extension _TypeCheckerExtensions on TypeChecker { diff --git a/generator/test/code_builder_test.dart b/generator/test/code_builder_test.dart index 8bfd7b0e6..29e5cb44d 100644 --- a/generator/test/code_builder_test.dart +++ b/generator/test/code_builder_test.dart @@ -378,6 +378,92 @@ void main() { expect(idProperty.flags & OBXPropertyFlags.ID_SELF_ASSIGNABLE != 0, true); }); }); + + group("ExternalType and ExternalName annotations", () { + test('annotations work on @Entity', () async { + final source = r''' + library example; + import 'package:objectbox/objectbox.dart'; + + @Entity() + @ExternalName(name: 'my-mongo-entity') + class Example { + @Id() + int id = 0; + } + '''; + + final testEnv = GeneratorTestEnv(); + await testEnv.run(source); + + final entity = testEnv.model.entities[0]; + expect(entity.externalName, "my-mongo-entity"); + }); + + test('annotations work on properties', () async { + final source = r''' + library example; + import 'package:objectbox/objectbox.dart'; + + @Entity() + class Example { + @Id() + int id = 0; + + @Property(type: PropertyType.byteVector) + @ExternalType(type: ExternalPropertyType.mongoId) + List? mongoId; + + @ExternalType(type: ExternalPropertyType.uuid) + @ExternalName(name: 'my-mongo-uuid') + List? mongoUuid; + } + '''; + + final testEnv = GeneratorTestEnv(); + await testEnv.run(source); + + final property1 = testEnv.model.entities[0].properties + .firstWhere((element) => element.name == "mongoId"); + expect(property1.externalType, OBXExternalPropertyType.MongoId); + + final property2 = testEnv.model.entities[0].properties + .firstWhere((element) => element.name == "mongoUuid"); + expect(property2.externalType, OBXExternalPropertyType.Uuid); + expect(property2.externalName, "my-mongo-uuid"); + }); + + test('annotations work on ToMany (standalone) relations', () async { + final source = r''' + library example; + import 'package:objectbox/objectbox.dart'; + + @Entity() + class Student{ + int id; + + @ExternalType(type: ExternalPropertyType.mongoId) + final rel1 = ToMany(); + + @ExternalType(type: ExternalPropertyType.uuid) + @ExternalName(name: 'my-courses-rel') + final rel2 = ToMany(); + } + '''; + + final testEnv = GeneratorTestEnv(); + await testEnv.run(source); + + final relation1 = testEnv.model.entities[0].relations + .firstWhere((element) => element.name == "rel1"); + expect(relation1.externalType, OBXExternalPropertyType.MongoId); + + final relation2 = testEnv.model.entities[0].relations + .firstWhere((element) => element.name == "rel2"); + expect(relation2.externalType, OBXExternalPropertyType.Uuid); + expect(relation2.externalName, "my-courses-rel"); + }); + }); } Future _unsupported() { diff --git a/install.sh b/install.sh index 2707c1c6c..bac35583d 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.1.0 +cLibVersion=4.2.0 os=$(uname) cLibArgs="$*" diff --git a/objectbox/CHANGELOG.md b/objectbox/CHANGELOG.md index f94cf4bac..cc1de67c7 100644 --- a/objectbox/CHANGELOG.md +++ b/objectbox/CHANGELOG.md @@ -4,6 +4,12 @@ * Examples: demos are compatible with JDK 21 included with Android Studio Ladybug or later, require Flutter SDK 3.24 (with Dart SDK 3.5) or newer. * Requires at least Dart SDK 3.4 or Flutter SDK 3.22. +* Update ObjectBox database for Flutter Linux/Windows, Dart Native apps to [4.2.0](https://github.com/objectbox/objectbox-c/releases/tag/v4.2.0). +* Update ObjectBox database for Flutter Android apps to 4.2.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.2.0` in `android/app/build.gradle`. +* Update ObjectBox database for Flutter iOS/macOS apps to 4.2.0. + For existing projects, run `pod repo update` and `pod update ObjectBox` in the `ios` or `macos` directories. ## 4.1.0 (2025-02-04) 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 56fc22b7e..5da6c3569 100644 --- a/objectbox/example/flutter/objectbox_demo_relations/android/app/build.gradle +++ b/objectbox/example/flutter/objectbox_demo_relations/android/app/build.gradle @@ -83,5 +83,5 @@ dependencies { // Add the Android library with ObjectBox Admin only for debug builds. // Note: when the objectbox package updates, check if the Android // library below needs to be updated as well. - debugImplementation("io.objectbox:objectbox-android-objectbrowser:4.1.0") + debugImplementation("io.objectbox:objectbox-android-objectbrowser:4.2.0") } diff --git a/objectbox/lib/src/annotations.dart b/objectbox/lib/src/annotations.dart index 44c20582d..690e58105 100644 --- a/objectbox/lib/src/annotations.dart +++ b/objectbox/lib/src/annotations.dart @@ -487,3 +487,124 @@ class HnswIndex { this.reparationBacklinkProbability, this.vectorCacheHintSizeKB}); } + +/// A property type of an external system (e.g. another database) that has no +/// default mapping to an ObjectBox type. +/// +/// Use with [ExternalType]. +enum ExternalPropertyType { + /// Representing type: ByteVector + /// + /// Encoding: 1:1 binary representation, little endian (16 bytes) + int128, + + /// Representing type: ByteVector + /// + /// Encoding: 1:1 binary representation (16 bytes) + uuid, + + /// IEEE 754 decimal128 type, e.g. supported by MongoDB. + /// + /// Representing type: ByteVector + /// + /// Encoding: 1:1 binary representation (16 bytes) + decimal128, + + /// A key/value map; e.g. corresponds to a JSON object or a MongoDB document + /// (although not keeping the key order). + /// + /// Unlike the Flex type, this must contain a map value (e.g. not a vector or + /// a scalar). + /// + /// Representing type: Flex + /// + /// Encoding: Flex + flexMap, + + /// A vector (aka list or array) of flexible elements; e.g. corresponds to a + /// JSON array or a MongoDB array. + /// + /// Unlike the Flex type, this must contain a vector value (e.g. not a map or + /// a scalar). + /// + /// Representing type: Flex + /// + /// Encoding: Flex + flexVector, + + /// Placeholder (not yet used) for a JSON document. + /// + /// Representing type: String + json, + + /// Placeholder (not yet used) for a BSON document. + /// + /// Representing type: ByteVector + bson, + + /// JavaScript source code. + /// + /// Representing type: String + javaScript, + + /// A vector (array) of Int128 values. + int128Vector, + + /// A vector (array) of Int128 values + uuidVector, + + /// The 12-byte ObjectId type in MongoDB. + /// + /// Representing type: ByteVector + /// + /// Encoding: 1:1 binary representation (12 bytes) + mongoId, + + /// A vector (array) of MongoId values. + mongoIdVector, + + /// Representing type: Long + /// + /// Encoding: Two unsigned 32-bit integers merged into a 64-bit integer. + mongoTimestamp, + + /// Representing type: ByteVector + /// + /// Encoding: 3 zero bytes (reserved, functions as padding), fourth byte is + /// the sub-type, followed by the binary data. + mongoBinary, + + /// Representing type: string vector with 2 elements (index 0: pattern, + /// index 1: options) + /// + /// Encoding: 1:1 string representation + mongoRegex +} + +/// See the constructor documentation. +class ExternalType { + /// The type of the property in the external system. + /// + /// See [ExternalPropertyType] for possible values. + final ExternalPropertyType type; + + /// Sets the type of a property or the type of object IDs of a [ToMany] in an + /// external system (like another database). + /// + /// This is useful if there is no default mapping of the ObjectBox type to the + /// type in the external system. + /// + /// Carefully look at the documentation of the external type to ensure it is + /// compatible with the ObjectBox type. + const ExternalType({required this.type}); +} + +/// See the constructor documentation. +class ExternalName { + /// The name assigned to the property in the external system. + final String name; + + /// Sets the name of an @Entity, a property or a [ToMany] in an external + /// system (like another database). + const ExternalName({required this.name}); +} diff --git a/objectbox/lib/src/modelinfo/enums.dart b/objectbox/lib/src/modelinfo/enums.dart index 2ebb7067d..d422dcebb 100644 --- a/objectbox/lib/src/modelinfo/enums.dart +++ b/objectbox/lib/src/modelinfo/enums.dart @@ -239,3 +239,112 @@ abstract class OBXPropertyType { /// < Variable sized vector of Date values (high precision 64-bit timestamp). static const int DateNanoVector = 32; } + +int externalTypeToOBXExternalType(ExternalPropertyType type) { + switch (type) { + case ExternalPropertyType.int128: + return OBXExternalPropertyType.Int128; + case ExternalPropertyType.uuid: + return OBXExternalPropertyType.Uuid; + case ExternalPropertyType.decimal128: + return OBXExternalPropertyType.Decimal128; + case ExternalPropertyType.flexMap: + return OBXExternalPropertyType.FlexMap; + case ExternalPropertyType.flexVector: + return OBXExternalPropertyType.FlexVector; + case ExternalPropertyType.json: + return OBXExternalPropertyType.Json; + case ExternalPropertyType.bson: + return OBXExternalPropertyType.Bson; + case ExternalPropertyType.javaScript: + return OBXExternalPropertyType.JavaScript; + case ExternalPropertyType.int128Vector: + return OBXExternalPropertyType.Int128Vector; + case ExternalPropertyType.uuidVector: + return OBXExternalPropertyType.UuidVector; + case ExternalPropertyType.mongoId: + return OBXExternalPropertyType.MongoId; + case ExternalPropertyType.mongoIdVector: + return OBXExternalPropertyType.MongoIdVector; + case ExternalPropertyType.mongoTimestamp: + return OBXExternalPropertyType.MongoTimestamp; + case ExternalPropertyType.mongoBinary: + return OBXExternalPropertyType.MongoBinary; + case ExternalPropertyType.mongoRegex: + return OBXExternalPropertyType.MongoRegex; + default: + throw ArgumentError.value(type, 'type', 'Invalid ExternalType'); + } +} + +/// A property type of an external system (e.g. another database) that has no default mapping to an ObjectBox type. +/// External property types numeric values start at 100 to avoid overlaps with ObjectBox's PropertyType. +/// (And if we ever support one of these as a primary type, we could share the numeric value?) +abstract class OBXExternalPropertyType { + /// Not a real type: represents uninitialized state and can be used for forward compatibility. + static const int Unknown = 0; + + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation, little endian (16 bytes) + static const int Int128 = 100; + + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (16 bytes) + static const int Uuid = 102; + + /// IEEE 754 decimal128 type, e.g. supported by MongoDB + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (16 bytes) + static const int Decimal128 = 103; + + /// A key/value map; e.g. corresponds to a JSON object or a MongoDB document (although not keeping the key order). + /// Unlike the Flex type, this must contain a map value (e.g. not a vector or a scalar). + /// Representing type: Flex + /// Encoding: Flex + static const int FlexMap = 107; + + /// A vector (aka list or array) of flexible elements; e.g. corresponds to a JSON array or a MongoDB array. + /// Unlike the Flex type, this must contain a vector value (e.g. not a map or a scalar). + /// Representing type: Flex + /// Encoding: Flex + static const int FlexVector = 108; + + /// Placeholder (not yet used) for a JSON document. + /// Representing type: String + static const int Json = 109; + + /// Placeholder (not yet used) for a BSON document. + /// Representing type: ByteVector + static const int Bson = 110; + + /// JavaScript source code + /// Representing type: String + static const int JavaScript = 111; + + /// A vector (array) of Int128 values + static const int Int128Vector = 116; + + /// A vector (array) of Int128 values + static const int UuidVector = 118; + + /// The 12-byte ObjectId type in MongoDB + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (12 bytes) + static const int MongoId = 123; + + /// A vector (array) of MongoId values + static const int MongoIdVector = 124; + + /// Representing type: Long + /// Encoding: Two unsigned 32-bit integers merged into a 64-bit integer. + static const int MongoTimestamp = 125; + + /// Representing type: ByteVector + /// Encoding: 3 zero bytes (reserved, functions as padding), fourth byte is the sub-type, + /// followed by the binary data. + static const int MongoBinary = 126; + + /// Representing type: string vector with 2 elements (index 0: pattern, index 1: options) + /// Encoding: 1:1 string representation + static const int MongoRegex = 127; +} diff --git a/objectbox/lib/src/modelinfo/modelentity.dart b/objectbox/lib/src/modelinfo/modelentity.dart index e15ac526d..e5c0f0346 100644 --- a/objectbox/lib/src/modelinfo/modelentity.dart +++ b/objectbox/lib/src/modelinfo/modelentity.dart @@ -14,6 +14,7 @@ class ModelEntity { late String _name; IdUid lastPropertyId = const IdUid.empty(); int _flags = 0; + String? externalName; final List _properties; final List _relations; final List _backlinks; @@ -72,6 +73,7 @@ class ModelEntity { required String name, required this.lastPropertyId, required int flags, + this.externalName, required List properties, required List relations, required List backlinks}) @@ -94,6 +96,7 @@ class ModelEntity { _backlinks = [] { name = data['name'] as String?; flags = data['flags'] as int? ?? 0; + externalName = data['externalName'] as String?; final properties = data['properties'] as List; for (final p in properties) { @@ -179,6 +182,7 @@ class ModelEntity { ret['lastPropertyId'] = lastPropertyId.toString(); ret['name'] = name; if (flags != 0) ret['flags'] = flags; + if (externalName != null) ret['externalName'] = externalName; ret['properties'] = properties.map((p) => p.toMap(forModelJson: forModelJson)).toList(); ret['relations'] = diff --git a/objectbox/lib/src/modelinfo/modelproperty.dart b/objectbox/lib/src/modelinfo/modelproperty.dart index 2ff57c6cc..30c720d8f 100644 --- a/objectbox/lib/src/modelinfo/modelproperty.dart +++ b/objectbox/lib/src/modelinfo/modelproperty.dart @@ -16,6 +16,8 @@ class ModelProperty { ModelEntity? entity; String? relationTarget; ModelHnswParams? hnswParams; + int? externalType; + String? externalName; /// Type used in the source dart code - used by the code generator. /// Starts with [_fieldReadOnlyPrefix] if the field (currently IDs only) is @@ -114,7 +116,9 @@ class ModelProperty { required int flags, IdUid? indexId, this.relationTarget, - this.hnswParams}) + this.hnswParams, + this.externalName, + this.externalType}) : _name = name, _type = type, _flags = flags, @@ -127,7 +131,9 @@ class ModelProperty { _dartFieldType = data['dartFieldType'] as String?, uidRequest = data['uidRequest'] as bool? ?? false, hnswParams = ModelHnswParams.fromMap( - data['hnswParams'] as Map?) { + data['hnswParams'] as Map?), + externalType = data['externalType'] as int?, + externalName = data['externalName'] as String? { name = data['name'] as String?; type = data['type'] as int?; flags = data['flags'] as int? ?? 0; @@ -140,6 +146,8 @@ class ModelProperty { ret['id'] = id.toString(); ret['name'] = name; ret['type'] = type; + if (externalType != null) ret['externalType'] = externalType; + if (externalName != null) ret['externalName'] = externalName; if (flags != 0) ret['flags'] = flags; if (indexId != null) ret['indexId'] = indexId!.toString(); if (relationTarget != null) ret['relationTarget'] = relationTarget; diff --git a/objectbox/lib/src/modelinfo/modelrelation.dart b/objectbox/lib/src/modelinfo/modelrelation.dart index f8c275004..cad53f851 100644 --- a/objectbox/lib/src/modelinfo/modelrelation.dart +++ b/objectbox/lib/src/modelinfo/modelrelation.dart @@ -12,6 +12,9 @@ class ModelRelation { String? _targetName; + int? externalType; + String? externalName; + // whether the user requested UID information (started a rename process) final bool uidRequest; @@ -44,15 +47,29 @@ class ModelRelation { // used in code generator ModelRelation.create(this.id, String? name, - {String? targetId, String? targetName, this.uidRequest = false}) { + {String? targetId, + String? targetName, + int? externalType, + String? externalName, + this.uidRequest = false}) { this.name = name; if (targetId != null) this.targetId = IdUid.fromString(targetId); if (targetName != null) this.targetName = targetName; + if (externalType != null) { + this.externalType = externalType; + } + if (externalName != null) { + this.externalName = externalName; + } } // used in generated code ModelRelation( - {required this.id, required String name, required IdUid targetId}) + {required this.id, + required String name, + required IdUid targetId, + this.externalName, + this.externalType}) : _name = name, _targetId = targetId, uidRequest = false; @@ -62,13 +79,21 @@ class ModelRelation { IdUid.fromString(data['id'] as String?), data['name'] as String?, targetId: data['targetId'] as String?, targetName: data['targetName'] as String?, - uidRequest: data['uidRequest'] as bool? ?? false); + uidRequest: data['uidRequest'] as bool? ?? false, + externalType: data['externalType'] as int?, + externalName: data['externalName'] as String?); Map toMap({bool forModelJson = false}) { final ret = {}; ret['id'] = id.toString(); ret['name'] = name; if (_targetId != null) ret['targetId'] = _targetId.toString(); + if (externalType != null) { + ret['externalType'] = externalType; + } + if (externalName != null) { + ret['externalName'] = externalName; + } if (!forModelJson) { ret['targetName'] = _targetName; ret['uidRequest'] = uidRequest; diff --git a/objectbox/lib/src/native/bindings/objectbox-sync.h b/objectbox/lib/src/native/bindings/objectbox-sync.h index 2a154f39d..109bc8a3f 100644 --- a/objectbox/lib/src/native/bindings/objectbox-sync.h +++ b/objectbox/lib/src/native/bindings/objectbox-sync.h @@ -34,7 +34,7 @@ #include "objectbox.h" #if defined(static_assert) || defined(__cplusplus) -static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 1 && OBX_VERSION_PATCH == 0, // NOLINT +static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 2 && OBX_VERSION_PATCH == 0, // NOLINT "Versions of objectbox.h and objectbox-sync.h files do not match, please update"); #endif diff --git a/objectbox/lib/src/native/bindings/objectbox.h b/objectbox/lib/src/native/bindings/objectbox.h index 6973625cd..22a9602f3 100644 --- a/objectbox/lib/src/native/bindings/objectbox.h +++ b/objectbox/lib/src/native/bindings/objectbox.h @@ -52,7 +52,7 @@ 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 1 +#define OBX_VERSION_MINOR 2 #define OBX_VERSION_PATCH 0 // values >= 100 are reserved for dev releases leading to the next minor/major increase //---------------------------------------------- @@ -615,6 +615,76 @@ typedef enum { OBXPropertyFlags_EXPIRATION_TIME = 65536, } OBXPropertyFlags; +/// A property type of an external system (e.g. another database) that has no default mapping to an ObjectBox type. +/// External property types numeric values start at 100 to avoid overlaps with ObjectBox's PropertyType. +/// (And if we ever support one of these as a primary type, we could share the numeric value?) +typedef enum { + /// Not a real type: represents uninitialized state and can be used for forward compatibility. + OBXExternalPropertyType_Unknown = 0, + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation, little endian (16 bytes) + OBXExternalPropertyType_Int128 = 100, + // OBXExternalPropertyType_Reserved1 = 101, + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (16 bytes) + OBXExternalPropertyType_Uuid = 102, + /// IEEE 754 decimal128 type, e.g. supported by MongoDB + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (16 bytes) + OBXExternalPropertyType_Decimal128 = 103, + // OBXExternalPropertyType_Reserved2 = 104, + // OBXExternalPropertyType_Reserved3 = 105, + // OBXExternalPropertyType_Reserved4 = 106, + /// A key/value map; e.g. corresponds to a JSON object or a MongoDB document (although not keeping the key order). + /// Unlike the Flex type, this must contain a map value (e.g. not a vector or a scalar). + /// Representing type: Flex + /// Encoding: Flex + OBXExternalPropertyType_FlexMap = 107, + /// A vector (aka list or array) of flexible elements; e.g. corresponds to a JSON array or a MongoDB array. + /// Unlike the Flex type, this must contain a vector value (e.g. not a map or a scalar). + /// Representing type: Flex + /// Encoding: Flex + OBXExternalPropertyType_FlexVector = 108, + /// Placeholder (not yet used) for a JSON document. + /// Representing type: String + OBXExternalPropertyType_Json = 109, + /// Placeholder (not yet used) for a BSON document. + /// Representing type: ByteVector + OBXExternalPropertyType_Bson = 110, + /// JavaScript source code + /// Representing type: String + OBXExternalPropertyType_JavaScript = 111, + // OBXExternalPropertyType_Reserved5 = 112, + // OBXExternalPropertyType_Reserved6 = 113, + // OBXExternalPropertyType_Reserved7 = 114, + // OBXExternalPropertyType_Reserved8 = 115, + /// A vector (array) of Int128 values + OBXExternalPropertyType_Int128Vector = 116, + // OBXExternalPropertyType_Reserved9 = 117, + /// A vector (array) of Int128 values + OBXExternalPropertyType_UuidVector = 118, + // OBXExternalPropertyType_Reserved10 = 119, + // OBXExternalPropertyType_Reserved11 = 120, + // OBXExternalPropertyType_Reserved12 = 121, + // OBXExternalPropertyType_Reserved13 = 122, + /// The 12-byte ObjectId type in MongoDB + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (12 bytes) + OBXExternalPropertyType_MongoId = 123, + /// A vector (array) of MongoId values + OBXExternalPropertyType_MongoIdVector = 124, + /// Representing type: Long + /// Encoding: Two unsigned 32-bit integers merged into a 64-bit integer. + OBXExternalPropertyType_MongoTimestamp = 125, + /// Representing type: ByteVector + /// Encoding: 3 zero bytes (reserved, functions as padding), fourth byte is the sub-type, + /// followed by the binary data. + OBXExternalPropertyType_MongoBinary = 126, + /// Representing type: string vector with 2 elements (index 0: pattern, index 1: options) + /// Encoding: 1:1 string representation + OBXExternalPropertyType_MongoRegex = 127, +} OBXExternalPropertyType; + struct OBX_model; // doxygen (only) picks up the typedef struct below /// Model represents a database schema and must be provided when opening the store. @@ -657,6 +727,14 @@ OBX_C_API obx_err obx_model_entity(OBX_model* model, const char* name, obx_schem /// @param flags See OBXEntityFlags for values (use bitwise OR to combine multiple flags) OBX_C_API obx_err obx_model_entity_flags(OBX_model* model, uint32_t flags); +/// Set the highest ever known property id in the entity. Should always be equal to or higher than the +/// last property id of the previous version of the entity. +OBX_C_API obx_err obx_model_entity_last_property_id(OBX_model* model, obx_schema_id property_id, obx_uid property_uid); + +/// Refine the definition of the entity declared by the most recent obx_model_entity() call: set the external name. +/// This is an optional name used in an external system, e.g. another database that ObjectBox syncs with. +OBX_C_API obx_err obx_model_entity_external_name(OBX_model* model, const char* external_name); + /// Starts the definition of a new property for the entity type of the last obx_model_entity() call. /// @param name A human readable name for the property. Must be unique within the entity /// @param type The type of property required @@ -682,6 +760,19 @@ OBX_C_API obx_err obx_model_property_relation(OBX_model* model, const char* targ /// @param index_uid Used to identify relations between versions of the model. Must be globally unique. OBX_C_API obx_err obx_model_property_index_id(OBX_model* model, obx_schema_id index_id, obx_uid index_uid); +/// Refine the definition of the property declared by the most recent obx_model_property() call: set the external name. +/// This is an optional name used in an external system, e.g. another database that ObjectBox syncs with. +/// @param index_id Must be unique within this version of the model +/// @param index_uid Used to identify relations between versions of the model. Must be globally unique. +OBX_C_API obx_err obx_model_property_external_name(OBX_model* model, const char* external_name); + +/// Refine the definition of the property declared by the most recent obx_model_property() call: set the external type. +/// This is an optional type used in an external system, e.g. another database that ObjectBox syncs with. +/// Note that the supported mappings from ObjectBox types to external types are limited. +/// @param index_id Must be unique within this version of the model +/// @param index_uid Used to identify relations between versions of the model. Must be globally unique. +OBX_C_API obx_err obx_model_property_external_type(OBX_model* model, OBXExternalPropertyType external_type); + /// Sets the vector dimensionality for the HNSW index of the latest property (must be of a supported vector type). /// This a mandatory option for all HNSW indexes. /// Note 1: vectors with higher dimensions than this value are also indexed (ignoring the higher elements). @@ -702,7 +793,7 @@ OBX_C_API obx_err obx_model_property_index_hnsw_neighbors_per_node(OBX_model* mo /// If indexing time is not a major concern, a value of at least 200 is recommended to improve search quality. OBX_C_API obx_err obx_model_property_index_hnsw_indexing_search_count(OBX_model* model, uint32_t value); -/// Sets flags for the HNSW index of the latest property (). +/// Sets flags for the HNSW index of the latest property. /// For details see OBXHnswFlags and its individual values. /// @param flags See OBXHnswFlags for values (use bitwise OR to combine multiple flags) OBX_C_API obx_err obx_model_property_index_hnsw_flags(OBX_model* model, uint32_t flags); @@ -728,6 +819,15 @@ OBX_C_API obx_err obx_model_property_index_hnsw_vector_cache_hint_size_kb(OBX_mo OBX_C_API obx_err obx_model_relation(OBX_model* model, obx_schema_id relation_id, obx_uid relation_uid, obx_schema_id target_id, obx_uid target_uid); +/// Augments the previously defined relation with a name +OBX_C_API obx_err obx_model_relation_name(OBX_model* model, const char* name); + +/// Augments the previously defined relation with an external name (used outside of ObjectBox) +OBX_C_API obx_err obx_model_relation_external_name(OBX_model* model, const char* external_name); + +/// Augments the previously defined relation with an external type (used outside of ObjectBox) +OBX_C_API obx_err obx_model_relation_external_type(OBX_model* model, OBXExternalPropertyType external_type); + /// Set the highest ever known entity id in the model. Should always be equal to or higher than the /// last entity id of the previous version of the model OBX_C_API void obx_model_last_entity_id(OBX_model*, obx_schema_id entity_id, obx_uid entity_uid); @@ -740,10 +840,6 @@ OBX_C_API void obx_model_last_index_id(OBX_model* model, obx_schema_id index_id, /// last relation id of the previous version of the model. OBX_C_API void obx_model_last_relation_id(OBX_model* model, obx_schema_id relation_id, obx_uid relation_uid); -/// Set the highest ever known property id in the entity. Should always be equal to or higher than the -/// last property id of the previous version of the entity. -OBX_C_API obx_err obx_model_entity_last_property_id(OBX_model* model, obx_schema_id property_id, obx_uid property_uid); - //---------------------------------------------- // Store //---------------------------------------------- diff --git a/objectbox/lib/src/native/bindings/objectbox_c.dart b/objectbox/lib/src/native/bindings/objectbox_c.dart index 702343fda..4a26aa2a5 100644 --- a/objectbox/lib/src/native/bindings/objectbox_c.dart +++ b/objectbox/lib/src/native/bindings/objectbox_c.dart @@ -427,6 +427,47 @@ class ObjectBoxC { late final _model_entity_flags = _model_entity_flagsPtr .asFunction, int)>(); + /// Set the highest ever known property id in the entity. Should always be equal to or higher than the + /// last property id of the previous version of the entity. + int model_entity_last_property_id( + ffi.Pointer model, + int property_id, + int property_uid, + ) { + return _model_entity_last_property_id( + model, + property_id, + property_uid, + ); + } + + late final _model_entity_last_property_idPtr = _lookup< + ffi.NativeFunction< + obx_err Function(ffi.Pointer, obx_schema_id, + obx_uid)>>('obx_model_entity_last_property_id'); + late final _model_entity_last_property_id = _model_entity_last_property_idPtr + .asFunction, int, int)>(); + + /// Refine the definition of the entity declared by the most recent obx_model_entity() call: set the external name. + /// This is an optional name used in an external system, e.g. another database that ObjectBox syncs with. + int model_entity_external_name( + ffi.Pointer model, + ffi.Pointer external_name, + ) { + return _model_entity_external_name( + model, + external_name, + ); + } + + late final _model_entity_external_namePtr = _lookup< + ffi.NativeFunction< + obx_err Function(ffi.Pointer, + ffi.Pointer)>>('obx_model_entity_external_name'); + late final _model_entity_external_name = + _model_entity_external_namePtr.asFunction< + int Function(ffi.Pointer, ffi.Pointer)>(); + /// Starts the definition of a new property for the entity type of the last obx_model_entity() call. /// @param name A human readable name for the property. Must be unique within the entity /// @param type The type of property required @@ -523,6 +564,50 @@ class ObjectBoxC { late final _model_property_index_id = _model_property_index_idPtr .asFunction, int, int)>(); + /// Refine the definition of the property declared by the most recent obx_model_property() call: set the external name. + /// This is an optional name used in an external system, e.g. another database that ObjectBox syncs with. + /// @param index_id Must be unique within this version of the model + /// @param index_uid Used to identify relations between versions of the model. Must be globally unique. + int model_property_external_name( + ffi.Pointer model, + ffi.Pointer external_name, + ) { + return _model_property_external_name( + model, + external_name, + ); + } + + late final _model_property_external_namePtr = _lookup< + ffi.NativeFunction< + obx_err Function(ffi.Pointer, + ffi.Pointer)>>('obx_model_property_external_name'); + late final _model_property_external_name = + _model_property_external_namePtr.asFunction< + int Function(ffi.Pointer, ffi.Pointer)>(); + + /// Refine the definition of the property declared by the most recent obx_model_property() call: set the external type. + /// This is an optional type used in an external system, e.g. another database that ObjectBox syncs with. + /// Note that the supported mappings from ObjectBox types to external types are limited. + /// @param index_id Must be unique within this version of the model + /// @param index_uid Used to identify relations between versions of the model. Must be globally unique. + int model_property_external_type( + ffi.Pointer model, + int external_type, + ) { + return _model_property_external_type( + model, + external_type, + ); + } + + late final _model_property_external_typePtr = _lookup< + ffi + .NativeFunction, ffi.Int32)>>( + 'obx_model_property_external_type'); + late final _model_property_external_type = _model_property_external_typePtr + .asFunction, int)>(); + /// Sets the vector dimensionality for the HNSW index of the latest property (must be of a supported vector type). /// This a mandatory option for all HNSW indexes. /// Note 1: vectors with higher dimensions than this value are also indexed (ignoring the higher elements). @@ -591,7 +676,7 @@ class ObjectBoxC { _model_property_index_hnsw_indexing_search_countPtr .asFunction, int)>(); - /// Sets flags for the HNSW index of the latest property (). + /// Sets flags for the HNSW index of the latest property. /// For details see OBXHnswFlags and its individual values. /// @param flags See OBXHnswFlags for values (use bitwise OR to combine multiple flags) int model_property_index_hnsw_flags( @@ -702,6 +787,61 @@ class ObjectBoxC { late final _model_relation = _model_relationPtr .asFunction, int, int, int, int)>(); + /// Augments the previously defined relation with a name + int model_relation_name( + ffi.Pointer model, + ffi.Pointer name, + ) { + return _model_relation_name( + model, + name, + ); + } + + late final _model_relation_namePtr = _lookup< + ffi.NativeFunction< + obx_err Function(ffi.Pointer, + ffi.Pointer)>>('obx_model_relation_name'); + late final _model_relation_name = _model_relation_namePtr.asFunction< + int Function(ffi.Pointer, ffi.Pointer)>(); + + /// Augments the previously defined relation with an external name (used outside of ObjectBox) + int model_relation_external_name( + ffi.Pointer model, + ffi.Pointer external_name, + ) { + return _model_relation_external_name( + model, + external_name, + ); + } + + late final _model_relation_external_namePtr = _lookup< + ffi.NativeFunction< + obx_err Function(ffi.Pointer, + ffi.Pointer)>>('obx_model_relation_external_name'); + late final _model_relation_external_name = + _model_relation_external_namePtr.asFunction< + int Function(ffi.Pointer, ffi.Pointer)>(); + + /// Augments the previously defined relation with an external type (used outside of ObjectBox) + int model_relation_external_type( + ffi.Pointer model, + int external_type, + ) { + return _model_relation_external_type( + model, + external_type, + ); + } + + late final _model_relation_external_typePtr = _lookup< + ffi + .NativeFunction, ffi.Int32)>>( + 'obx_model_relation_external_type'); + late final _model_relation_external_type = _model_relation_external_typePtr + .asFunction, int)>(); + /// Set the highest ever known entity id in the model. Should always be equal to or higher than the /// last entity id of the previous version of the model void model_last_entity_id( @@ -765,27 +905,6 @@ class ObjectBoxC { late final _model_last_relation_id = _model_last_relation_idPtr .asFunction, int, int)>(); - /// Set the highest ever known property id in the entity. Should always be equal to or higher than the - /// last property id of the previous version of the entity. - int model_entity_last_property_id( - ffi.Pointer model, - int property_id, - int property_uid, - ) { - return _model_entity_last_property_id( - model, - property_id, - property_uid, - ); - } - - late final _model_entity_last_property_idPtr = _lookup< - ffi.NativeFunction< - obx_err Function(ffi.Pointer, obx_schema_id, - obx_uid)>>('obx_model_entity_last_property_id'); - late final _model_entity_last_property_id = _model_entity_last_property_idPtr - .asFunction, int, int)>(); - /// Get the actual bytes from the given OBX_bytes_lazy. /// Because of the potential lazy creation of bytes, the given bytes are not const as it may be resolved internally. /// For the same reason, this function is not thread-safe, at least for the first call on a OBX_bytes_lazy instance. @@ -10510,6 +10629,78 @@ abstract class OBXPropertyFlags { static const int EXPIRATION_TIME = 65536; } +/// A property type of an external system (e.g. another database) that has no default mapping to an ObjectBox type. +/// External property types numeric values start at 100 to avoid overlaps with ObjectBox's PropertyType. +/// (And if we ever support one of these as a primary type, we could share the numeric value?) +abstract class OBXExternalPropertyType { + /// Not a real type: represents uninitialized state and can be used for forward compatibility. + static const int Unknown = 0; + + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation, little endian (16 bytes) + static const int Int128 = 100; + + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (16 bytes) + static const int Uuid = 102; + + /// IEEE 754 decimal128 type, e.g. supported by MongoDB + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (16 bytes) + static const int Decimal128 = 103; + + /// A key/value map; e.g. corresponds to a JSON object or a MongoDB document (although not keeping the key order). + /// Unlike the Flex type, this must contain a map value (e.g. not a vector or a scalar). + /// Representing type: Flex + /// Encoding: Flex + static const int FlexMap = 107; + + /// A vector (aka list or array) of flexible elements; e.g. corresponds to a JSON array or a MongoDB array. + /// Unlike the Flex type, this must contain a vector value (e.g. not a map or a scalar). + /// Representing type: Flex + /// Encoding: Flex + static const int FlexVector = 108; + + /// Placeholder (not yet used) for a JSON document. + /// Representing type: String + static const int Json = 109; + + /// Placeholder (not yet used) for a BSON document. + /// Representing type: ByteVector + static const int Bson = 110; + + /// JavaScript source code + /// Representing type: String + static const int JavaScript = 111; + + /// A vector (array) of Int128 values + static const int Int128Vector = 116; + + /// A vector (array) of Int128 values + static const int UuidVector = 118; + + /// The 12-byte ObjectId type in MongoDB + /// Representing type: ByteVector + /// Encoding: 1:1 binary representation (12 bytes) + static const int MongoId = 123; + + /// A vector (array) of MongoId values + static const int MongoIdVector = 124; + + /// Representing type: Long + /// Encoding: Two unsigned 32-bit integers merged into a 64-bit integer. + static const int MongoTimestamp = 125; + + /// Representing type: ByteVector + /// Encoding: 3 zero bytes (reserved, functions as padding), fourth byte is the sub-type, + /// followed by the binary data. + static const int MongoBinary = 126; + + /// Representing type: string vector with 2 elements (index 0: pattern, index 1: options) + /// Encoding: 1:1 string representation + static const int MongoRegex = 127; +} + class OBX_model extends ffi.Opaque {} /// Schema entity & property identifiers @@ -11459,7 +11650,7 @@ typedef obx_dart_closer const int OBX_VERSION_MAJOR = 4; -const int OBX_VERSION_MINOR = 1; +const int OBX_VERSION_MINOR = 2; const int OBX_VERSION_PATCH = 0; diff --git a/objectbox/lib/src/native/model.dart b/objectbox/lib/src/native/model.dart index e6071a28a..35753f9b9 100644 --- a/objectbox/lib/src/native/model.dart +++ b/objectbox/lib/src/native/model.dart @@ -52,6 +52,15 @@ class Model { if (entity.flags != 0) _check(C.model_entity_flags(_cModel, entity.flags)); + final externalName = entity.externalName?.toNativeUtf8(); + if (externalName != null) { + try { + _check(C.model_entity_external_name(_cModel, externalName.cast())); + } finally { + calloc.free(externalName); + } + } + // add all properties entity.properties.forEach(addProperty); @@ -115,10 +124,34 @@ class Model { _cModel, hnswParams.vectorCacheHintSizeKB!)); } } + + final externalType = prop.externalType; + if (externalType != null) { + _check(C.model_property_external_type(_cModel, externalType)); + } + final externalName = prop.externalName?.toNativeUtf8(); + if (externalName != null) { + try { + _check(C.model_property_external_name(_cModel, externalName.cast())); + } finally { + calloc.free(externalName); + } + } } void addRelation(ModelRelation rel) { _check(C.model_relation( _cModel, rel.id.id, rel.id.uid, rel.targetId.id, rel.targetId.uid)); + if (rel.externalType != null) { + _check(C.model_relation_external_type(_cModel, rel.externalType!)); + } + final externalName = rel.externalName?.toNativeUtf8(); + if (externalName != null) { + try { + _check(C.model_relation_external_name(_cModel, externalName.cast())); + } finally { + calloc.free(externalName); + } + } } } diff --git a/objectbox_test/test/box_test.dart b/objectbox_test/test/box_test.dart index f56b41ad7..90c1f9237 100644 --- a/objectbox_test/test/box_test.dart +++ b/objectbox_test/test/box_test.dart @@ -724,6 +724,22 @@ void main() { expect(item2.tFloat64List, [-2000.00001, 2000.00001]); }); + test('@ExternalType and @ExternalName annotations work correctly', () { + // That the database accepts a model with external types and names is tested + // implicitly by creating a Store. This test is only a smoke test to verify + // the annotated properties and ToMany work without issue. + final box = store.box(); + var testObject = EntityWithExternalType([90, 100, 110], [1, 2, 3]); + testObject.mongoIdEntities + .add(EntityWithExternalType([120, 121, 122], null)); + final id = box.put(testObject); + final item = box.get(id)!; + expect(item.id, id); + expect(item.mongoId, [90, 100, 110]); + expect(item.mongoUuid, [1, 2, 3]); + expect(item.mongoIdEntities.first.mongoId, [120, 121, 122]); + }); + test('.count() works', () { expect(box.count(), equals(0)); box.putMany(simpleItems()); diff --git a/objectbox_test/test/entity.dart b/objectbox_test/test/entity.dart index 5712476d6..1bfcf3297 100644 --- a/objectbox_test/test/entity.dart +++ b/objectbox_test/test/entity.dart @@ -472,6 +472,27 @@ class HnswObject { final rel = ToOne(); } +@Entity() +@ExternalName(name: 'my-mongo-entity') +class EntityWithExternalType { + @Id() + int id = 0; + + @Property(type: PropertyType.byteVector) + @ExternalType(type: ExternalPropertyType.mongoId) + List? mongoId; + + @ExternalType(type: ExternalPropertyType.uuid) + @ExternalName(name: 'my-mongo-uuid') + List? mongoUuid; + + @ExternalType(type: ExternalPropertyType.mongoIdVector) + @ExternalName(name: 'my-mongo-rel') + final mongoIdEntities = ToMany(); + + EntityWithExternalType(this.mongoId, this.mongoUuid); +} + @Entity() class RelatedNamedEntity { @Id() diff --git a/objectbox_test/test/objectbox-model.json b/objectbox_test/test/objectbox-model.json index 99635f567..ebb6adf65 100644 --- a/objectbox_test/test/objectbox-model.json +++ b/objectbox_test/test/objectbox-model.json @@ -724,11 +724,47 @@ } ], "relations": [] + }, + { + "id": "16:5931645853908059165", + "lastPropertyId": "3:7670802129899081197", + "name": "EntityWithExternalType", + "externalName": "my-mongo-entity", + "properties": [ + { + "id": "1:3044824951282217899", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:8057333288729755334", + "name": "mongoId", + "type": 23, + "externalType": 123 + }, + { + "id": "3:7670802129899081197", + "name": "mongoUuid", + "type": 27, + "externalType": 102, + "externalName": "my-mongo-uuid" + } + ], + "relations": [ + { + "id": "2:313640065593441165", + "name": "mongoIdEntities", + "targetId": "16:5931645853908059165", + "externalType": 124, + "externalName": "my-mongo-rel" + } + ] } ], - "lastEntityId": "15:4803284427984871569", + "lastEntityId": "16:5931645853908059165", "lastIndexId": "23:6649884639373473085", - "lastRelationId": "1:2155747579134420981", + "lastRelationId": "2:313640065593441165", "lastSequenceId": "0:0", "modelVersion": 5, "modelVersionParserMinimum": 5, diff --git a/sync_flutter_libs/android/build.gradle b/sync_flutter_libs/android/build.gradle index 95ae6c0f1..39ec10d25 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.1.0" + implementation "io.objectbox:objectbox-sync-android:4.2.0" } } diff --git a/sync_flutter_libs/ios/objectbox_sync_flutter_libs.podspec b/sync_flutter_libs/ios/objectbox_sync_flutter_libs.podspec index d452b7b91..3267a9d1a 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.1.0-sync' + s.dependency 'ObjectBox', '4.2.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 4958308b2..246d71b49 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.1.0) +set(OBJECTBOX_VERSION 4.2.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 7c39f43e0..eab6f3d23 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.1.0-sync' + s.dependency 'ObjectBox', '4.2.0-sync' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.3' diff --git a/sync_flutter_libs/windows/CMakeLists.txt b/sync_flutter_libs/windows/CMakeLists.txt index 75e929653..4fcd94c55 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.1.0) +set(OBJECTBOX_VERSION 4.2.0) set(OBJECTBOX_ARCH ${CMAKE_SYSTEM_PROCESSOR}) if (${OBJECTBOX_ARCH} MATCHES "AMD64") diff --git a/tool/common.sh b/tool/common.sh index e8f41ba0c..171f16379 100755 --- a/tool/common.sh +++ b/tool/common.sh @@ -9,11 +9,17 @@ root=$( ) echo "Repo root dir: $root" -# align GNU vs BSD `sed` version handling -i argument +# macOS includes the BSD version of sed, which uses a different syntax than the +# GNU version, which these scripts expect. So require users of this script to +# install gsed. if [[ "$OSTYPE" == "darwin"* ]]; then - sed="sed -i ''" + if ! command -v gsed &>/dev/null; then + echo "Error: gsed is required but not installed. Install it, for example using 'brew install gsed'." + exit 1 + fi + sed="gsed" else - sed="sed -i" + sed="sed" fi function update() { @@ -26,5 +32,5 @@ function update() { expr=${2} echo "Updating ${file} - \"${expr}\"" - $sed "${expr}" "$root/$file" + $sed -i "${expr}" "$root/$file" } diff --git a/tool/update-c-binding.sh b/tool/update-c-binding.sh index c8b3a2bdf..87789a244 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.1.0 +cLibVersion=4.2.0 echo "Downloading C library source files from GitHub..." # Note: the release archives do not contain objectbox-dart.h, so get the full sources.