You can query data stored in MongoDB directly from your client application code by using the Realm Swift SDK's MongoClient with the Query API. Atlas App Services provides data access rules on collections to securely retrieve results based on the logged-in user or the content of each document.
TipThis page covers querying a MongoDB data source directly. To filter data you retrieve from a realm, see: Filter Data.
There are a variety of reasons you might want to query a MongoDB data source. Working with data in your client via Atlas Device Sync is not always practical or possible. You might want to query MongoDB when:
The data set is large or the client device has constraints against loading the entire data set
You are retrieving documents that are not modeled in Realm
Your app needs to access collections that don't have strict schemas
A non-Realm service generates collections that you want to access
While not exhaustive, these are some common use cases for querying MongoDB directly.
Before you can query MongoDB from your client application, you must set up MongoDB Data Access in your App Services App. To learn how to set up your backend App to let the Realm SDK query Atlas, refer to Set Up MongoDB Data Access in the App Services documentation.
These examples operate on a MongoDB collection that describes coffee drinks in a chain of coffee shops. The documents represent objects with these properties:
class CoffeeDrink: Object { @Persisted(primaryKey: true) var _id: ObjectId @Persisted var name: String @Persisted var beanRegion: String? @Persisted var containsDairy: Bool @Persisted var storeNumber: Int}
The complete code for each example includes logging in and instantiating a MongoDB collection handle before completing each operation. For brevity, these examples omit the login and collection handle code. However, each complete example looks like this:
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") let mongoClient = user.mongoClient("mongodb-atlas") let database = mongoClient.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43] collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): print("Successfully inserted a document with id: \(objectId)") } } } }}
New in version 10.16.0.
The Realm Swift SDK provides async/await versions of the MongoCollection methods.
All of the methods on this page are compatible with the async/await syntax. This example illustrates that syntax for the collection.insertOne()
method. You can see the completion handler version in Insert a Single Document.
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43]do { let objectId = try await collection.insertOne(drink) print("Successfully inserted a document with id: \(objectId)")} catch { print("Call to MongoDB failed: \(error.localizedDescription)")}
Starting with Realm Swift SDK Versions 10.15.0 and 10.16.0, many of the Realm APIs support the Swift async/await syntax. Projects must meet these requirements:
Swift SDK Version
Swift Version Requirement
Supported OS
10.25.0
Swift 5.6
iOS 13.x
10.15.0 or 10.16.0
Swift 5.5
iOS 15.x
If your app accesses Realm in an async/await
context, mark the code with @MainActor
to avoid threading-related crashes.
These code snippets demonstrate how to insert one or more documents into a MongoDB collection from a mobile application. These methods take one or more documents and return a result. Success returns the objectId of the inserted document, or an array of objectIds in order when inserting multiple documents.
You can insert a single document using collection.insertOne().
This snippet inserts a single document describing a "Colombian Blend" coffee drink into a collection of documents that describe coffee drinks for sale in a group of stores:
let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43]collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): print("Successfully inserted a document with id: \(objectId)") }}
Successfully inserted a document with id: objectId(64e50cab7765243942cd04ce)
You can insert multiple documents using collection.insertMany().
This snippet inserts three documents describing coffee drinks into a collection of documents that describe coffee drinks for sale in a group of stores:
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 42]let drink2: Document = [ "name": "Maple Latte", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "storeNumber": 42]let drink3: Document = [ "name": "Bean of the Day", "beanRegion": "San Marcos, Guatemala", "containsDairy": false, "storeNumber": 47]collection.insertMany([drink, drink2, drink3]) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectIds): print("Successfully inserted \(objectIds.count) new documents.") }}
Successfully inserted 3 new documents.
These code snippets demonstrate how to read data stored in a MongoDB collection from a mobile application. Read operations use a standard query syntax to specify which documents to return from the database. Read operations return a result that that resolves to either a single matched document (in the case of findOneDocument()
), a long
numeric value (in the case of count()
) or an array of matched documents (in the case of find()
).
You can find a single document using collection.findOneDocument().
This snippet finds a single document from a collection of documents that describe coffee drinks for sale in a group of stores, where the document's name
field contains the string value "Colombian Blend":
let queryFilter: Document = ["name": "Colombian Blend"]collection.findOneDocument(filter: queryFilter) { result in switch result { case .failure(let error): print("Did not find matching documents: \(error.localizedDescription)") return case .success(let document): print("Found a matching document: \(String(describing: document))") }}
Found a matching document: Optional([ "name": Optional(RealmSwift.AnyBSON.string("Colombian Blend")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e5014f65796c813bc68274)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(43))])
You can find multiple documents using collection.find().
This snippet finds all documents in a collection of documents that describe coffee drinks for sale in a group of stores, where the document's name
field contains the value "Americano":
let queryFilter: Document = ["name": "Americano"]collection.find(filter: queryFilter) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let bsonDocumentArray): print("Results: ") for bsonDocument in bsonDocumentArray { print("Coffee drink named: \(String(describing: bsonDocument["name"]))") } }}
Results:Coffee drink named: Optional(Optional(RealmSwift.AnyBSON.string("Americano")))... more matching documents ...
You can sort documents in a collection by initializing an instance of FindOptions with your preferred sort options. FindOptions
has three parameters: limit, projection, and sorting. The sorting
argument can be an array of key-value pairs where each key represents a field. For each key, the value 1
sorts in descending order, or -1
sorts in ascending order.
You then pass the FindOptions
instance to the collection.find()
method when running the query.
This snippet finds all documents in a collection of documents that describe coffee drinks for sale in a group of stores, where the document's name
field contains the value "Americano", sorted in descending order by the beanRegion
field:
let queryFilter: Document = ["name": "Americano"]let findOptions = FindOptions(0, nil, [["beanRegion": 1]])collection.find(filter: queryFilter, options: findOptions) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let documents): print("Results: ") for document in documents { print("Coffee drink: \(document)") } }}
Results:Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e521ed6b124fd047534345)), "beanRegion": Optional(RealmSwift.AnyBSON.string("San Marcos, Guatemala")), "name": Optional(RealmSwift.AnyBSON.string("Americano")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42))]Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e521ed6b124fd047534344)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "name": Optional(RealmSwift.AnyBSON.string("Americano")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia"))]... more matching documents, sorted by `beanRegion` ...
You can count documents in a collection using collection.count(). You can specify an optional query and limit to determine which documents to count. If you don't specify a query, the action counts all documents in the collection.
This snippet counts the number of documents in a collection of documents that describe coffee drinks for sale in a group of stores, where the document's name
field contains the value "Bean of the Day":
let queryFilter: Document = ["name": "Bean of the Day"]collection.count(filter: queryFilter) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let count): print("Found this many documents in the collection matching the filter: \(count)") }}
Found this many documents in the collection matching the filter: 24
These code snippets demonstrate how to update data stored in a MongoDB collection from a mobile application. Update operations use queries to specify which documents to update, and update operators to desribe how to mutate documents that match the query. Update operations return a result that resolves to an UpdateResult or Error
.
You can update a single document using collection.updateOneDocument().
This snippet updates a single document in a collection of documents that describe coffee drinks for sale in a group of stores. This update operation queries for a document whose name
field contains the value "Bean of the Day", and sets the containsDairy
field to true
:
let queryFilter: Document = ["name": "Bean of the Day", "storeNumber": 42]let documentUpdate: Document = ["$set": ["containsDairy": true]]collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): if updateResult.matchedCount == 1 && updateResult.modifiedCount == 1 { print("Successfully updated a matching document.") } else { print("Did not update a document") } }
Successfully updated a matching document.
You can update multiple documents using collection.updateManyDocuments().
This snippet updates multiple documents in a collection of documents that describe coffee drinks for sale in a group of stores. This update operation queries for documents where the name
field contains the value "Bean of the Day", and changes the containsDairy
field to true
:
let queryFilter: Document = ["name": "Bean of the Day"]let documentUpdate: Document = ["$set": ["containsDairy": true]]collection.updateManyDocuments(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated \(updateResult.modifiedCount) documents.") }}
Successfully updated 24 documents.
If an update operation does not match any document in the collection, you can automatically insert a single new document into the collection that matches the update query by setting the upsert
option to true
.
The following snippet updates a document in a collection of documents that describe coffee drinks for sale in a group of stores. If no document matches the query, it inserts a new document if no document. This operation queries for documents where the name
field has a value of "Bean of the Day", and the storeNumber
field has a value of 55
.
Because this snippet sets the upsert
option to true
, if no document matches the query, MongoDB creates a new document that includes both the query and specified updates:
let queryFilter: Document = ["name": "Bean of the Day", "storeNumber": 55]let documentUpdate: Document = ["name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": false, "storeNumber": 55]collection.updateOneDocument(filter: queryFilter, update: documentUpdate, upsert: true) { result in switch result { case .failure(let error): print("Failed to update document: \(error.localizedDescription)") return case .success(let updateResult): if let unwrappedDocumentId = updateResult.documentId { print("Successfully upserted a document with id: \(unwrappedDocumentId)") } else { print("Did not upsert a document") } }}
Successfully upserted a document with id: 64e523e37765243942eba44a
These code snippets demonstrate how to delete documents that are stored in a MongoDB collection from a mobile application. Delete operations use a query to specify which documents to delete and return results that resolve to an Int
count of deleted documents or Error
.
You can delete a single document from a collection using collection.deleteOneDocument().
This snippet deletes one document in a collection of documents that describe coffee drinks for sale in a group of stores. This operation queries for a document where the name
field has a value of "Mocha" and the storeNumber
field has a value of 17
, and deletes it.
let queryFilter: Document = ["name": "Mocha", "storeNumber": 17]collection.deleteOneDocument(filter: queryFilter) { deletedResult in switch deletedResult { case .failure(let error): print("Failed to delete a document: \(error.localizedDescription)") return case .success(let deletedResult): print("Successfully deleted a document.") }}
Successfully deleted a document.
You can delete multiple items from a collection using collection.deleteManyDocuments().
This snippet deletes all documents in a collection of documents that describe coffee drinks for sale in a group of stores that match the query for documents whose name
field contains the value "Caramel Latte":
let filter: Document = ["name": "Caramel Latte"]collection.deleteManyDocuments(filter: filter) { deletedResult in switch deletedResult { case .failure(let error): print("Failed to delete a document: \(error.localizedDescription)") return case .success(let deletedResult): print("Successfully deleted \(deletedResult) documents.") }}
Successfully deleted 3 documents.
You can watch a collection for change notifications that MongoDB emits whenever a document in the collection is created, modified, or deleted. Each notification specifies a document that changed, how it changed, and the full document after the operation that caused the event.
Important Serverless LimitationsYou cannot watch for changes if the data source is an Atlas serverless instance. MongoDB serverless currently does not support change streams, which are used on watched collections to listen for changes.
You can open a stream of changes made to a collection by calling collection.watch(). This function creates a publisher that emits a AnyBSON change event when the MongoDB collection changes.
You can optionally watch a filtered list of _ids
in the collection with collection.watch(filterIds:), or apply a $match
filter to incoming change events with collection.watch(matchFilter:).
The .watch()
method can take a ChangeEventDelegate to subscribe to changes on a stream. For example, with this ChangeEventDelegate
:
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") }}
This code watches for changes to documents in the CoffeeDrinks
collection:
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") } let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let changeStream = collection.watch(delegate: delegate, queue: queue) let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 42] collection.insertOne(drink) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let objectId): print("Successfully inserted a document with id: \(objectId)") } } changeStream.close() }}
Login as <RLMUser: 0x600002dfd1a0> succeeded!Change stream opened: <RLMChangeStream: 0x60000182ab80>Successfully inserted a document with id: objectId(64e525665fef1743dedb5aa6)Change event document received: [ "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:15:18 +0000)), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E525660000000B2B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E525665FEF1743DEDB5AA60004" )) ])), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e525665fef1743dedb5aa6)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")), "db": Optional(RealmSwift.AnyBSON.string("ios")) ])), "operationType": Optional(RealmSwift.AnyBSON.string("insert")), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e525665fef1743dedb5aa6)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)) ]))]Change stream closed
You can watch for changes in a collection to a specific list of objects by passing in their _id
. Call collection.watch(filterIds: ) with an array of ObjectIds to receive only change events that apply to those documents.
Consider an example that uses this ChangeEventDelegate:
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") }}
The code below uses this delegate to watch for changes to specific documents in the CoffeeDrinks
collection:
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") } let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let changeStream = collection.watch(filterIds: [drinkObjectId], delegate: delegate, queue: queue) let queryFilter: Document = ["_id": AnyBSON(drinkObjectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated the document") } } changeStream.close() }}
Login as <RLMUser: 0x60000010eb00> succeeded!Successfully inserted a document with id: objectId(64e525ce7765243942ef0a58)Change stream opened: <RLMChangeStream: 0x6000034946c0>Change event document received: [ "fullDocument": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e525ce7765243942ef0a58)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:17:09 +0000)), "operationType": Optional(RealmSwift.AnyBSON.string("update")), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e525ce7765243942ef0a58)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "db": Optional(RealmSwift.AnyBSON.string("ios")), "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")) ])), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "removedFields": Optional(RealmSwift.AnyBSON.array([])), "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true))])) ])), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E525D5000000082B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E525CE7765243942EF0A580004" )) ]))]Change stream closed
You can open a stream of changes made to documents in a collection that fulfill certain criteria by calling collection.watch(matchFilter: ). This method accepts a Document
parameter that is used as the query of a $match operator to process each database event that occurs while watching the collection.
Consider an example that uses this ChangeEventDelegate:
class MyChangeStreamDelegate: ChangeEventDelegate { func changeStreamDidOpen(_ changeStream: RealmSwift.ChangeStream) { print("Change stream opened: \(changeStream)") } func changeStreamDidClose(with error: Error?) { if let anError = error { print("Change stream closed with error: \(anError.localizedDescription)") } else { print("Change stream closed") } } func changeStreamDidReceive(error: Error) { print("Received error: \(error.localizedDescription)") } func changeStreamDidReceive(changeEvent: RealmSwift.AnyBSON?) { guard let changeEvent = changeEvent else { return } guard let document = changeEvent.documentValue else { return } print("Change event document received: \(document)") }}
The code below uses this delegate to watch for changes to documents in the CoffeeDrink
collection. It only triggers the provided callback for events whose documents have a storeNumber
value of 42
:
appClient.login(credentials: Credentials.anonymous) { (result) in DispatchQueue.main.async { switch result { case .failure(let error): print("Login failed: \(error)") case .success(let user): print("Login as \(user) succeeded!") } let client = self.appClient.currentUser!.mongoClient("mongodb-atlas") let database = client.database(named: "ios") let collection = database.collection(withName: "CoffeeDrinks") let queue = DispatchQueue(label: "io.realm.watchQueue") let delegate = MyChangeStreamDelegate() let matchFilter = [ "fullDocument.storeNumber": AnyBSON(42) ] let changeStream = collection.watch(matchFilter: matchFilter, delegate: delegate, queue: queue) let queryFilter: Document = ["_id": AnyBSON(drinkObjectId) ] let documentUpdate: Document = ["$set": ["containsDairy": true]] collection.updateOneDocument(filter: queryFilter, update: documentUpdate) { result in switch result { case .failure(let error): print("Call to MongoDB failed: \(error.localizedDescription)") return case .success(let updateResult): print("Successfully updated the document") } } changeStream.close() }}
Login as <RLMUser: 0x6000026ee640> succeeded!Successfully inserted a document with id: objectId(64e5266731323150716faf13)Change stream opened: <RLMChangeStream: 0x6000013283c0>Change event document received: [ "operationType": Optional(RealmSwift.AnyBSON.string("update")), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "removedFields": Optional(RealmSwift.AnyBSON.array([])), "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)) ])) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:19:44 +0000)), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E526700000000E2B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E5266731323150716FAF130004" )) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "db": Optional(RealmSwift.AnyBSON.string("ios")), "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")) ])), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e5266731323150716faf13)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)) ])), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e5266731323150716faf13)) ]))]Successfully updated the documentChange stream closed
New in version 10.37.0.
You can open an async sequence to watch for changes to a collection. In an async context, call changeEvents() on a collection to open a change stream. This provides an async sequence of AnyBSON values containing information about each change to the MongoDB collection.
You can optionally provide a changeEvents(onOpen: ) callback which is invoked when the watch stream has initialized on the server.
The changeEvents()
API can take filterIds
or a matchFilter
to watch a subset of documents in a collection, similar to the examples above.
The following snippet watches for changes to any documents in the CoffeeDrinks
collection as an async sequence:
let user = try await appClient.login(credentials: Credentials.anonymous)let mongoClient = user.mongoClient("mongodb-atlas")let database = mongoClient.database(named: "ios")let collection = database.collection(withName: "CoffeeDrinks")let task = Task { let changeEvents = collection.changeEvents(onOpen: { print("Successfully opened change stream") }) for try await event in changeEvents { let doc = event.documentValue! print("Received event: \(event.documentValue!)") }}let queryFilter: Document = ["_id": AnyBSON(objectId) ]let documentUpdate: Document = ["$set": ["containsDairy": true]]let updateResult = try await collection.updateOneDocument(filter: queryFilter, update: documentUpdate)task.cancel()_ = await task.result
Successfully opened change streamReceived event: [ "operationType": Optional(RealmSwift.AnyBSON.string("update")), "documentKey": Optional(RealmSwift.AnyBSON.document([ "_id": Optional(RealmSwift.AnyBSON.objectId(64e526d9850b15debe83ff46)) ])), "ns": Optional(RealmSwift.AnyBSON.document([ "coll": Optional(RealmSwift.AnyBSON.string("CoffeeDrinks")), "db": Optional(RealmSwift.AnyBSON.string("ios")) ])), "clusterTime": Optional(RealmSwift.AnyBSON.datetime(2023-08-22 21:21:30 +0000)), "fullDocument": Optional(RealmSwift.AnyBSON.document([ "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(43)), "_id": Optional(RealmSwift.AnyBSON.objectId(64e526d9850b15debe83ff46)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")) ])), "_id": Optional(RealmSwift.AnyBSON.document([ "_data": Optional(RealmSwift.AnyBSON.string( "8264E526DA000000092B022C0100296E5A100464816C3449884434A07AC19F4AAFCB8046645F6964006464E526D9850B15DEBE83FF460004" ) ) ])), "updateDescription": Optional(RealmSwift.AnyBSON.document([ "updatedFields": Optional(RealmSwift.AnyBSON.document([ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)) ])), "removedFields": Optional(RealmSwift.AnyBSON.array([])) ]))]
Aggregation operations run all documents in a collection through a series of data aggregation stages called an aggregation pipeline. Aggregation allows you to filter and transform documents, collect summary data about groups of related documents, and other complex data operations.
You can configure and run aggregation operations on a collection using collection.aggregate().
An aggregation operation accepts a list of aggregation stages as input and returns a result that resolves to a collection of documents processed by the pipeline, or an Error
.
You can use the $match stage to filter documents using standard MongoDB query syntax:
This $match
stage filters documents to include only those where the storeNumber
field has a value equal to 42
:
let pipeline: [Document] = [["$match": ["storeNumber": ["$eq": 42]]]]collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let documents): print("Successfully ran the aggregation:") for document in documents { print("Coffee drink: \(document)") } }}
Successfully ran the aggregation:Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e53171313231507183e7c2)), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42))]Coffee drink: [ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e53171313231507183e7c3)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42))] (...more results...)
You can use the $group stage to aggregate summary data for one or more documents. MongoDB groups documents based on the expression defined in the _id
field of the $group
stage. You can reference a specific document field by prefixing the field name with a $
.
This $group
stage arranges documents by the value of their storeNumber
field. It then calculates the number of coffee drink documents that contain that store number in the field's value. In other words, we're calculating the number of coffee drinks for each store number.
let pipeline: [Document] = [["$group": ["_id": "$storeNumber", "numItems": ["$sum": 1]]]]collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } }}
Successfully ran the aggregation.["numItems": Optional(RealmSwift.AnyBSON.int64(27)), "_id": Optional(RealmSwift.AnyBSON.int64(42))]["numItems": Optional(RealmSwift.AnyBSON.int64(44)), "_id": Optional(RealmSwift.AnyBSON.int64(47))](...more results...)
You can use the $project stage to include or omit specific fields from documents or to calculate new fields using aggregation operators. Projections work in two ways:
Specify that you want to include fields by using a 1
. This has the side-effect of implicitly excluding all unspecified fields.
Specify that you want to exclude fields by using a 0
. This has the side-effect of implicitly including all unspecified fields.
These two methods of projection are mutually exclusive. If you specify fields to include, you cannot also specify fields to exclude, and vice versa.
NoteThe _id
field is a special case: it is always included in every query unless explicitly specified otherwise. For this reason, you can exclude the _id
field with a 0
value while simultaneously including other fields, like storeNumber
, with a 1
. Only the special case of exclusion of the _id
field allows both exclusion and inclusion in one $project
stage.
For this example, assume the CoffeeDrink
document has a store
field that is a string value containing the word "Store" with a number, such as "Store 42", similar to these documents:
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "store": "Store 42"]let drink2: Document = [ "name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "store": "Store 47"]
The following $project
stage omits the _id
field, includes the name
field, and creates a new field named storeNumber
. The storeNumber
is generated using two aggregation operators:
$split
separates the store
string representation into two string segments surrounding the space character. For example, the value "Store 42" split in this way returns an array with two elements: "Store" and "42".
$arrayElemAt
selects a specific element from an array based on the second argument. In this case, the value 1
selects the second element from the array generated by the $split
operator since arrays index from 0
. For example, the value ["Store", "42"] passed to this operation would return a value of "42".
let pipeline: [Document] = [["$project": ["_id": 0, "name": 1, "storeNumber": ["$arrayElemAt": [["$split": ["$store", " "]], 1]]]]]collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } }}
Successfully ran the aggregation.["name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "storeNumber": Optional(RealmSwift.AnyBSON.string("42"))]["storeNumber": Optional(RealmSwift.AnyBSON.string("47")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day"))](...more results...)
You can use the $addFields stage to add new fields with calculated values using aggregation operators.
Note$addFields
is similar to $project but does not allow you to include or omit fields.
For this example, assume the CoffeeDrink
document has a store
field that is a string value containing the word "Store" with a number, such as "Store 42", similar to these documents:
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "store": "Store 42"]let drink2: Document = [ "name": "Bean of the Day", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "store": "Store 47"]
The following $addFields
stage creates a new field named storeNumber
where the value is the output of two aggregate operators that transform the value of the store
field.
let pipeline: [Document] = [["$addFields": ["storeNumber": ["$arrayElemAt": [["$split": ["$store", " "]], 1]]]]]collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print(result) } }}
Successfully ran the aggregation.[ "storeNumber": Optional(RealmSwift.AnyBSON.string("42")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e588ff5fef1743de3559aa)), "store": Optional(RealmSwift.AnyBSON.string("Store 42")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(false)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Timbio, Colombia"))][ "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.string("47")), "store": Optional(RealmSwift.AnyBSON.string("Store 47")), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "name": Optional(RealmSwift.AnyBSON.string("Bean of the Day")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e588ff5fef1743de3559ab))]
You can use the $unwind stage to transform a single document containing an array into multiple documents containing individual values from that array. When you unwind an array field, MongoDB copies each document once for each element of the array field but replaces the array value with the array element in each copy.
Consider this document that includes a featuredInPromotions
array:
let drink: Document = [ "name": "Maple Latte", "beanRegion": "Yirgacheffe, Ethiopia", "containsDairy": true, "storeNumber": 42, "featuredInPromotions": [ "Spring into Spring", "Tastes of Fall", "Winter Delights" ]]
The following $unwind
stage creates a new document for each element of the items
array in each document. It also adds a field called itemIndex
to each new document that specifies the element's position index in the original array:
let pipeline: [Document] = [["$unwind": ["path": "$featuredInPromotions", "includeArrayIndex": "itemIndex"]]]collection.aggregate(pipeline: pipeline) { result in switch result { case .failure(let error): print("Failed to aggregate: \(error.localizedDescription)") return case .success(let results): print("Successfully ran the aggregation.") for result in results { print("Coffee drink: \(result)") } }}
Successfully ran the aggregation.Coffee drink: [ "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Spring into Spring")), "itemIndex": Optional(RealmSwift.AnyBSON.int64(0)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true))]Coffee drink: [ "itemIndex": Optional(RealmSwift.AnyBSON.int64(1)), "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Tastes of Fall")), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia"))]Coffee drink: [ "name": Optional(RealmSwift.AnyBSON.string("Maple Latte")), "_id": Optional(RealmSwift.AnyBSON.objectId(64e58bb4fc901d40e03fde64)), "beanRegion": Optional(RealmSwift.AnyBSON.string("Yirgacheffe, Ethiopia")), "itemIndex": Optional(RealmSwift.AnyBSON.int64(2)), "containsDairy": Optional(RealmSwift.AnyBSON.bool(true)), "storeNumber": Optional(RealmSwift.AnyBSON.int64(42)), "featuredInPromotions": Optional(RealmSwift.AnyBSON.string("Winter Delights"))]
You could then $group
by the value of featuredInPromotions
and $sum
the number of coffee drinks in each promotion as in the group documents example, or perform other calculations or transformations based on your data.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4