Platform-agnostic reference shared by all server-connection-* and sdk-patterns-* skills.
Sub-document operations target specific paths within a JSON document. They avoid the cost of fetching or transmitting the full document — critical for large documents or high-frequency partial updates.
- When to Use Sub-Document vs Full-Document
- Lookup Operations (Read)
- Mutation Operations (Write)
- Array Operations
- Counters
- Multi-Operation Batching
- Language Examples
| Scenario | Use |
|---|---|
| Read the whole document | collection.get() |
| Read 1–3 fields from a large document | lookup_in |
| Update 1–2 fields without changing others | mutate_in |
| Increment a counter | mutate_in + SD.increment |
| Append to a bounded array | mutate_in + SD.array_append |
| Replace the entire document | collection.replace() / upsert() |
| Atomic compare-and-swap | collection.replace() with CAS |
Sub-document operations are atomic per-path. Multiple paths in a single mutate_in call are applied atomically together.
from couchbase.collection import Collection
import couchbase.subdocument as SD
# Fetch specific fields
result = collection.lookup_in("user_alice", [
SD.get("email"),
SD.get("address.city"),
SD.get("preferences.theme")
])
email = result.content_as[str](0)
city = result.content_as[str](1)
theme = result.content_as[str](2)
# Check field existence without fetching value
result = collection.lookup_in("user_alice", [
SD.exists("premiumSubscription")
])
has_premium = result.exists(0)
# Get document count (array length)
result = collection.lookup_in("order_1001", [
SD.count("lineItems")
])
item_count = result.content_as[int](0)Paths use dot notation for nested fields and [n] for array indices:
"address.city" → doc.address.city
"tags[0]" → doc.tags[0]
"events[-1]" → doc.events (last element)
"metadata.tags[2].name" → doc.metadata.tags[2].name
from couchbase.options import MutateInOptions
from datetime import timedelta
# Upsert a field (create or replace)
collection.mutate_in("user_alice", [
SD.upsert("status", "active")
])
# Insert a field (fails if field already exists)
collection.mutate_in("user_alice", [
SD.insert("createdAt", "2024-01-15T10:00:00Z")
])
# Replace a field (fails if field does not exist)
collection.mutate_in("user_alice", [
SD.replace("email", "alice@newdomain.com")
])
# Remove a field
collection.mutate_in("user_alice", [
SD.remove("temporaryToken")
])
# Multiple mutations in one atomic operation
collection.mutate_in("user_alice", [
SD.upsert("status", "active"),
SD.upsert("updatedAt", "2024-06-01T10:00:00Z"),
SD.remove("resetToken"),
SD.increment("loginCount", 1)
])
# With durability and expiry
collection.mutate_in("session_abc", [
SD.upsert("lastSeen", "2024-06-01T10:00:00Z")
], MutateInOptions(
expiry=timedelta(hours=1),
durability=ServerDurability(DurabilityLevel.Majority)
))# Create the document if it doesn't exist, then apply mutations
collection.mutate_in("user_new", [
SD.upsert("name", "Bob"),
SD.upsert("status", "pending")
], MutateInOptions(store_semantics=StoreSemantics.UPSERT))StoreSemantics:
REPLACE(default) — document must existUPSERT— create if missing, update if existsINSERT— fail if document already exists
# Append to end of array
collection.mutate_in("order_1001", [
SD.array_append("events", {"type": "shipped", "ts": "2024-06-01"})
])
# Prepend to start of array
collection.mutate_in("feed_alice", [
SD.array_prepend("items", {"postId": "post_999", "ts": "2024-06-01"})
])
# Insert at a specific index
collection.mutate_in("playlist_1", [
SD.array_insert("tracks[2]", {"id": "track_42", "title": "New Song"})
])
# Add to array only if value is unique (set semantics)
collection.mutate_in("user_alice", [
SD.array_addunique("tags", "premium")
])
# Raises PathExistsException if "premium" is already in tags
# Append multiple elements at once
collection.mutate_in("order_1001", [
SD.array_append("events", [
{"type": "packed", "ts": "2024-06-01T09:00:00Z"},
{"type": "shipped", "ts": "2024-06-01T10:00:00Z"}
], multi=True)
])increment and decrement are atomic — safe for concurrent updates without read-modify-write:
# Increment by 1
collection.mutate_in("stats_global", [
SD.increment("pageViews", 1)
])
# Increment by arbitrary delta
collection.mutate_in("product_42", [
SD.increment("viewCount", 5)
])
# Decrement
collection.mutate_in("inventory_42", [
SD.decrement("stock", 1)
])
# Initialize counter to 0 if missing, then increment
collection.mutate_in("stats_global", [
SD.upsert("newUsers", 0),
SD.increment("newUsers", 1)
])The result of increment/decrement is the new value after the operation:
result = collection.mutate_in("stats", [SD.increment("hits", 1)])
new_count = result.content_as[int](0)Combine lookups and mutations across multiple documents using the SDK's bulk API:
# Bulk lookup_in
from couchbase.result import MultiMutationResult
keys = ["user_alice", "user_bob", "user_carol"]
results = {}
for key in keys:
results[key] = collection.lookup_in(key, [SD.get("email"), SD.get("status")])For high-throughput scenarios, use async SDK methods (acollection.lookup_in) to pipeline operations.
const { MutateInSpec, LookupInSpec } = require('couchbase');
// Lookup
const result = await collection.lookupIn("user_alice", [
LookupInSpec.get("email"),
LookupInSpec.exists("premiumSubscription")
]);
const email = result.content[0].value;
const hasPremium = result.content[1].value;
// Mutate
await collection.mutateIn("user_alice", [
MutateInSpec.upsert("status", "active"),
MutateInSpec.increment("loginCount", 1),
MutateInSpec.arrayAppend("recentLogins", new Date().toISOString())
]);import com.couchbase.client.java.kv.*;
// Lookup
LookupInResult result = collection.lookupIn("user_alice", List.of(
LookupInSpec.get("email"),
LookupInSpec.exists("premiumSubscription")
));
String email = result.contentAs(0, String.class);
boolean hasPremium = result.exists(1);
// Mutate
collection.mutateIn("user_alice", List.of(
MutateInSpec.upsert("status", "active"),
MutateInSpec.increment("loginCount", 1L),
MutateInSpec.arrayAppend("recentLogins", List.of(Instant.now().toString()))
));ops := []gocb.LookupInSpec{
gocb.GetSpec("email", nil),
gocb.ExistsSpec("premiumSubscription", nil),
}
result, _ := collection.LookupIn("user_alice", ops, nil)
var email string
result.ContentAt(0, &email)
mutOps := []gocb.MutateInSpec{
gocb.UpsertSpec("status", "active", nil),
gocb.IncrementSpec("loginCount", 1, nil),
}
collection.MutateIn("user_alice", mutOps, nil)from couchbase.subdocument import get, exists, upsert, increment, array_append
# Lookup
result = collection.lookup_in("user_alice", [
get("email"),
exists("premiumSubscription")
])
email = result.content_as[str](0)
has_premium = result.exists(1)
# Mutate
collection.mutate_in("user_alice", [
upsert("status", "active"),
increment("loginCount", 1),
array_append("recentLogins", ["2024-01-01T00:00:00Z"])
])using Couchbase.KeyValue;
// Lookup
var result = await collection.LookupInAsync("user_alice", specs =>
specs.Get("email").Exists("premiumSubscription"));
var email = result.ContentAs<string>(0);
var hasPremium = result.Exists(1);
// Mutate
await collection.MutateInAsync("user_alice", specs =>
specs.Upsert("status", "active")
.Increment("loginCount", 1)
.ArrayAppend("recentLogins", new[] { DateTime.UtcNow.ToString("o") }));use couchbase::subdoc::{LookupInSpec, MutateInSpec};
// Lookup
let result = collection.lookup_in(
"user_alice",
vec![
LookupInSpec::get("email", None),
LookupInSpec::exists("premiumSubscription", None),
],
None,
).await?;
let email: String = result.content_as(0)?;
let has_premium: bool = result.exists(1);
// Mutate
collection.mutate_in(
"user_alice",
vec![
MutateInSpec::upsert("status", "active", None)?,
MutateInSpec::increment("loginCount", 1, None)?,
],
None,
).await?;import com.couchbase.client.scala.kv._
// Lookup
val result = collection.lookupIn("user_alice", Seq(
LookupInSpec.get("email"),
LookupInSpec.exists("premiumSubscription")
)).get
val email = result.contentAs[String](0).get
val hasPremium = result.exists(1)
// Mutate
collection.mutateIn("user_alice", Seq(
MutateInSpec.upsert("status", "active"),
MutateInSpec.increment("loginCount", 1),
MutateInSpec.arrayAppend("recentLogins", Seq("2024-01-01T00:00:00Z"))
)).getuse Couchbase\LookupGetSpec;
use Couchbase\LookupExistsSpec;
use Couchbase\MutateUpsertSpec;
use Couchbase\MutateIncrementSpec;
use Couchbase\MutateArrayAppendSpec;
// Lookup
$result = $collection->lookupIn("user_alice", [
new LookupGetSpec("email"),
new LookupExistsSpec("premiumSubscription")
]);
$email = $result->content(0);
$hasPremium = $result->exists(1);
// Mutate
$collection->mutateIn("user_alice", [
new MutateUpsertSpec("status", "active"),
new MutateIncrementSpec("loginCount", 1),
new MutateArrayAppendSpec("recentLogins", ["2024-01-01T00:00:00Z"])
]);# Lookup
result = collection.lookup_in("user_alice", [
Couchbase::LookupInSpec.get("email"),
Couchbase::LookupInSpec.exists("premiumSubscription")
])
email = result.content(0)
has_premium = result.exists?(1)
# Mutate
collection.mutate_in("user_alice", [
Couchbase::MutateInSpec.upsert("status", "active"),
Couchbase::MutateInSpec.increment("loginCount", 1),
Couchbase::MutateInSpec.array_append("recentLogins", ["2024-01-01T00:00:00Z"])
])