Skip to content

Commit dde2510

Browse files
Support Unique conflict resolution strategy replace.
1 parent 3654430 commit dde2510

File tree

3 files changed

+62
-12
lines changed

3 files changed

+62
-12
lines changed

generator/lib/src/entity_resolver.dart

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ class EntityResolver extends Builder {
257257
}
258258
}
259259

260+
// Verify there is at most 1 unique property with REPLACE strategy.
261+
ensureSingleUniqueReplace(entity);
262+
// If sync enabled, verify all unique properties use REPLACE strategy.
263+
ifSyncEnsureAllUniqueAreReplace(entity);
264+
260265
entity.properties.forEach((p) => log.info(' $p'));
261266

262267
return entity;
@@ -301,9 +306,9 @@ class EntityResolver extends Builder {
301306
FieldElement f, int? fieldType, Element elementBare, ModelProperty prop) {
302307
IndexType? indexType;
303308

304-
final hasIndexAnnotation = _indexChecker.hasAnnotationOfExact(f);
305-
final hasUniqueAnnotation = _uniqueChecker.hasAnnotationOfExact(f);
306-
if (!hasIndexAnnotation && !hasUniqueAnnotation) return null;
309+
final indexAnnotation = _indexChecker.firstAnnotationOfExact(f);
310+
final uniqueAnnotation = _uniqueChecker.firstAnnotationOfExact(f);
311+
if (indexAnnotation == null && uniqueAnnotation == null) return null;
307312

308313
// Throw if property type does not support any index.
309314
if (fieldType == OBXPropertyType.Float ||
@@ -319,8 +324,6 @@ class EntityResolver extends Builder {
319324
}
320325

321326
// If available use index type from annotation.
322-
final indexAnnotation =
323-
hasIndexAnnotation ? _indexChecker.firstAnnotationOfExact(f) : null;
324327
if (indexAnnotation != null && !indexAnnotation.isNull) {
325328
final enumValItem = enumValueItem(indexAnnotation.getField('type')!);
326329
if (enumValItem != null) indexType = IndexType.values[enumValItem];
@@ -343,8 +346,15 @@ class EntityResolver extends Builder {
343346
"entity ${elementBare.name}: a hash index is not supported for type '${f.type}' of field '${f.name}'");
344347
}
345348

346-
if (hasUniqueAnnotation) {
349+
if (uniqueAnnotation != null && !uniqueAnnotation.isNull) {
347350
prop.flags |= OBXPropertyFlags.UNIQUE;
351+
// Determine unique conflict resolution.
352+
final onConflictVal =
353+
enumValueItem(uniqueAnnotation.getField('onConflict')!);
354+
if (onConflictVal != null &&
355+
ConflictStrategy.values[onConflictVal] == ConflictStrategy.replace) {
356+
prop.flags |= OBXPropertyFlags.UNIQUE_ON_CONFLICT_REPLACE;
357+
}
348358
}
349359

350360
switch (indexType) {
@@ -363,6 +373,27 @@ class EntityResolver extends Builder {
363373
}
364374
}
365375

376+
void ensureSingleUniqueReplace(ModelEntity entity) {
377+
final uniqueReplaceProps = entity.properties
378+
.where((p) => p.hasFlag(OBXPropertyFlags.UNIQUE_ON_CONFLICT_REPLACE));
379+
if (uniqueReplaceProps.length > 1) {
380+
throw InvalidGenerationSourceError(
381+
"ConflictStrategy.replace can only be used on a single property, but found multiple in '${entity.name}':\n ${uniqueReplaceProps.join('\n ')}");
382+
}
383+
}
384+
385+
void ifSyncEnsureAllUniqueAreReplace(ModelEntity entity) {
386+
if (!entity.hasFlag(OBXEntityFlags.SYNC_ENABLED)) return;
387+
final uniqueButNotReplaceProps = entity.properties.where((p) {
388+
return p.hasFlag(OBXPropertyFlags.UNIQUE) &&
389+
!p.hasFlag(OBXPropertyFlags.UNIQUE_ON_CONFLICT_REPLACE);
390+
});
391+
if (uniqueButNotReplaceProps.isNotEmpty) {
392+
throw InvalidGenerationSourceError(
393+
"Synced entities must use @Unique(onConflict: ConflictStrategy.replace) on all unique properties, but found others in '${entity.name}':\n ${uniqueButNotReplaceProps.join('\n ')}");
394+
}
395+
}
396+
366397
int? enumValueItem(DartObject typeField) {
367398
if (!typeField.isNull) {
368399
final enumValues = (typeField.type as InterfaceType)

objectbox/lib/src/annotations.dart

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:objectbox/objectbox.dart';
2+
13
/// Entity annotation is used on a class to let ObjectBox know it should store
24
/// it - making the class a "persistable Entity".
35
///
@@ -190,17 +192,31 @@ enum IndexType {
190192
hash64,
191193
}
192194

193-
/// Unique annotation forces that the value of a property is unique among all
194-
/// objects stored for the given entity.
195+
/// Enforces that the value of a property is unique among all objects in a box
196+
/// before an object can be put.
195197
///
196-
/// Trying to put an Object with offending values will result in an exception.
198+
/// Trying to put an object with offending values will result in a
199+
/// [UniqueViolationException] (see [ConflictStrategy.fail]).
200+
/// Set [onConflict] to change this strategy.
197201
///
198-
/// Unique properties are based on an [Index], so the same restrictions apply.
202+
/// Note: Unique properties are based on an [Index], so the same restrictions apply.
199203
/// It is supported to explicitly add the [Index] annotation to configure the
200-
/// index type.
204+
/// index.
201205
class Unique {
206+
/// The strategy to use when a conflict is detected when an object is put.
207+
final ConflictStrategy onConflict;
208+
202209
/// Create a Unique annotation.
203-
const Unique();
210+
const Unique({this.onConflict = ConflictStrategy.fail});
211+
}
212+
213+
/// Used with [Unique] to specify the conflict resolution strategy.
214+
enum ConflictStrategy {
215+
/// Throws [UniqueViolationException] if any property violates a [Unique] constraint.
216+
fail,
217+
218+
/// Any conflicting objects are deleted before the object is inserted.
219+
replace,
204220
}
205221

206222
/// Backlink annotation specifies a link in a reverse direction of another

objectbox/lib/src/modelinfo/enums.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ abstract class OBXPropertyFlags {
122122
/// ///
123123
/// /// For Time Series IDs, a companion property of type Date or DateNano represents the exact timestamp.
124124
static const int ID_COMPANION = 16384;
125+
126+
/// Unique on-conflict strategy: the object being put replaces any existing conflicting object (deletes it).
127+
static const int UNIQUE_ON_CONFLICT_REPLACE = 32768;
125128
}
126129

127130
abstract class OBXPropertyType {

0 commit comments

Comments
 (0)