Skip to content

Commit 96416c9

Browse files
authored
feat: add Collection.mutateIn (#29)
1 parent 457123b commit 96416c9

File tree

8 files changed

+1177
-70
lines changed

8 files changed

+1177
-70
lines changed

init-couchbase-server.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ docker compose up -d
99

1010
MAX_ATTEMPTS=60
1111
ATTEMPTS=0
12-
until curl http://localhost:8091/pools/default >/dev/null 2>1; do
12+
until curl http://localhost:8091/pools/default >/dev/null 2>&1; do
1313
if [ $ATTEMPTS -eq $MAX_ATTEMPTS ]; then
1414
echo "Couchbase Sever is unavailable after $MAX_ATTEMPTS attempts - exiting"
1515
exit 1

packages/couchbase/lib/couchbase.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export 'src/collection.dart'
1010
GetOptions,
1111
InsertOptions,
1212
LookupInOptions,
13+
MutateInOptions,
1314
RemoveOptions,
1415
ReplaceOptions,
1516
UpsertOptions;
@@ -19,6 +20,8 @@ export 'src/crud_operation.dart'
1920
GetResult,
2021
LookupInResult,
2122
LookupInResultEntry,
23+
MutateInResult,
24+
MutateInResultEntry,
2225
MutationResult;
2326
export 'src/diagnostics.dart'
2427
show
@@ -131,7 +134,12 @@ export 'src/general.dart'
131134
ServiceType,
132135
TranscoderOptions;
133136
export 'src/message.g.dart'
134-
show DurabilityLevel, KeyValueStatusCode, PersistTo, ReplicateTo;
137+
show
138+
DurabilityLevel,
139+
KeyValueStatusCode,
140+
PersistTo,
141+
ReplicateTo,
142+
StoreSemantics;
135143
export 'src/mutation_state.dart' show MutationState, MutationToken;
136144
export 'src/query.dart'
137145
show
@@ -144,6 +152,7 @@ export 'src/query.dart'
144152
QueryStatus,
145153
QueryWarning;
146154
export 'src/scope.dart' show Scope;
147-
export 'src/sub_document_spec.dart' show LookupInMacro, LookupInSpec;
155+
export 'src/sub_document_spec.dart'
156+
show LookupInMacro, LookupInSpec, MutateInMacro, MutateInSpec;
148157
export 'src/transcoder.dart'
149158
show DefaultTranscoder, EncodedDocumentData, Transcoder;

packages/couchbase/lib/src/collection.dart

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,43 @@ class LookupInOptions extends CommonOptions {
180180
const LookupInOptions({super.timeout});
181181
}
182182

183+
/// Options for [Collection.mutateIn].
184+
///
185+
/// {@category Key-Value}
186+
class MutateInOptions extends CommonDurabilityOptions {
187+
const MutateInOptions({
188+
this.expiry,
189+
this.preserveExpiry = false,
190+
this.cas,
191+
this.storeSemantics = StoreSemantics.replace,
192+
super.durabilityLevel,
193+
super.timeout,
194+
});
195+
196+
const MutateInOptions.legacyDurability({
197+
this.expiry,
198+
this.preserveExpiry = false,
199+
this.cas,
200+
this.storeSemantics = StoreSemantics.replace,
201+
super.persistTo,
202+
super.replicateTo,
203+
super.timeout,
204+
}) : super.legacyDurability();
205+
206+
/// The expiry time for this document.
207+
final Duration? expiry;
208+
209+
/// Whether any existing expiry on the document should be preserved.
210+
final bool preserveExpiry;
211+
212+
/// If specified, indicates that operation should be failed if the [Cas]
213+
/// has changed from this value, indicating that the document has changed.
214+
final Cas? cas;
215+
216+
/// The store semantics to use for this operation.
217+
final StoreSemantics storeSemantics;
218+
}
219+
183220
/// Exposes the operations which are available to be performed against a
184221
/// collection.
185222
///
@@ -514,6 +551,87 @@ class Collection {
514551
);
515552
}
516553

554+
/// Performs a Mutate-In operation against a document.
555+
///
556+
/// Allows atomic modification of specific fields within a document. Also
557+
/// enables access to document extended-attributes.
558+
///
559+
/// [key] is the key of the document to mutate.
560+
///
561+
/// [specs] is a list of [MutateInSpec]s describing the operations to perform
562+
Future<MutateInResult> mutateIn(
563+
String key,
564+
List<MutateInSpec> specs, [
565+
MutateInOptions? options,
566+
]) async {
567+
options ??= const MutateInOptions();
568+
569+
if (specs.isEmpty) {
570+
throw ArgumentError.value(
571+
specs,
572+
'specs',
573+
'must not be empty',
574+
);
575+
}
576+
577+
final id = _documentId(key);
578+
final expiry = options.expiry ?? Duration.zero;
579+
final cas = options.cas ?? InternalCas.zero;
580+
final timeout = _mutationTimeout(options);
581+
final implSpecs = List.generate(
582+
specs.length,
583+
(index) => specs[index].toMessage(index),
584+
);
585+
586+
final response = options.usesLegacyDurability
587+
? await _connection.mutateInWithLegacyDurability(
588+
MutateInWithLegacyDurability(
589+
id: id,
590+
cas: cas,
591+
specs: implSpecs,
592+
storeSemantics: options.storeSemantics,
593+
timeout: timeout,
594+
expiry: expiry.inSeconds,
595+
preserveExpiry: options.preserveExpiry,
596+
persistTo: options.durabilityPersistTo,
597+
replicateTo: options.durabilityReplicateTo,
598+
partition: 0,
599+
opaque: 0,
600+
accessDeleted: false,
601+
createAsDeleted: false,
602+
),
603+
)
604+
: await _connection.mutateIn(
605+
MutateInRequest(
606+
id: id,
607+
cas: cas,
608+
specs: implSpecs,
609+
storeSemantics: options.storeSemantics,
610+
timeout: timeout,
611+
expiry: expiry.inSeconds,
612+
preserveExpiry: options.preserveExpiry,
613+
durabilityLevel: options.durabilityLevel,
614+
partition: 0,
615+
opaque: 0,
616+
accessDeleted: false,
617+
createAsDeleted: false,
618+
),
619+
);
620+
621+
final results = response.fields.map((entry) {
622+
return MutateInResultEntry(
623+
value:
624+
entry.value.isEmpty ? null : _decodeSubDocumentValue(entry.value),
625+
);
626+
}).toList();
627+
628+
return MutateInResult(
629+
content: results,
630+
cas: response.cas,
631+
token: response.token,
632+
);
633+
}
634+
517635
DocumentId _documentId(String key) {
518636
return DocumentId(
519637
bucket: _scope.bucket.name,
@@ -618,7 +736,7 @@ class Collection {
618736
.decode(EncodedDocumentData(flags: flags, bytes: bytes));
619737
for (final projectionPath in paths) {
620738
final value = SubDocumentUtils.getByPath(fullDocument, projectionPath);
621-
if (value != SubDocumentUtils.notFoundSentinel) {
739+
if (!identical(value, SubDocumentUtils.notFoundSentinel)) {
622740
content =
623741
SubDocumentUtils.insertByPath(content, projectionPath, value);
624742
}

packages/couchbase/lib/src/crud_operation.dart

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class MutationResult {
5656
final MutationToken? token;
5757
}
5858

59-
/// The results of a specific sub-operation within a Lookup-In operation.
59+
/// The result of a specific sub-operation within a Lookup-In operation.
6060
///
6161
/// {@category Key-Value}
6262
class LookupInResultEntry {
@@ -82,9 +82,44 @@ class LookupInResult {
8282
required this.cas,
8383
});
8484

85-
/// A list of result entries for each sub-operation performed.
85+
/// The list of result entries for each sub-operation performed.
8686
final List<LookupInResultEntry> content;
8787

8888
/// The cas of the document.
8989
final Cas cas;
9090
}
91+
92+
/// The result of a specific sub-operation within a Mutate-In operation.
93+
///
94+
/// {@category Key-Value}
95+
class MutateInResultEntry {
96+
const MutateInResultEntry({
97+
this.value,
98+
});
99+
100+
/// The resulting value after the completion of the sub-operation.
101+
///
102+
/// This is only returned in the case of a counter operation
103+
/// (increment/decrement) and is not included for general operations.
104+
final Object? value;
105+
}
106+
107+
/// Contains the results of a Mutate-In operation.
108+
///
109+
/// {@category Key-Value}
110+
class MutateInResult {
111+
const MutateInResult({
112+
required this.content,
113+
required this.cas,
114+
this.token,
115+
});
116+
117+
/// The list of result entries for each sub-operation performed.
118+
final List<MutateInResultEntry> content;
119+
120+
/// The updated [Cas] for the document.
121+
final Cas cas;
122+
123+
/// The token representing the mutation performed.
124+
final MutationToken? token;
125+
}

packages/couchbase/lib/src/exception.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,8 @@ message_errors.ErrorCode _resolveErrorCode(Object error) {
14891489
return error;
14901490
} else if (error is message_errors.KeyValueErrorContext) {
14911491
return error.code;
1492+
} else if (error is message_errors.SubdocumentErrorContext) {
1493+
return error.code;
14921494
} else if (error is message_errors.ViewErrorContext) {
14931495
return error.code;
14941496
} else if (error is message_errors.QueryErrorContext) {

0 commit comments

Comments
 (0)