Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

MongoDB のクエリ - Swift SDK

項目一覧

  • Overview
  • ユースケース
  • 前提条件
  • サンプルデータ
  • Async/Await クエリ MongoDB
  • ドキュメントの作成
  • 単一ドキュメントのインサート
  • 複数のドキュメントの挿入
  • ドキュメントを読む
  • 単一ドキュメントの検索
  • 複数ドキュメントの検索
  • ドキュメントの検索とソート
  • コレクション内のドキュメントをカウント
  • Update Documents
  • 単一ドキュメントの更新
  • 複数のドキュメントの更新
  • ドキュメントをアップサートする
  • Delete Documents
  • 単一ドキュメントの削除
  • 複数のドキュメントの削除
  • 変更の監視
  • コレクションの変更の監視
  • 特定の ID へのコレクション内の変更の監視
  • フィルターでコレクションの変更を監視
  • 非同期シーケンスとしてコレクションを監視
  • ドキュメントの集計
  • フィルター ドキュメント
  • グループ ドキュメント
  • プロジェクト ドキュメント フィールド
  • ドキュメントへのフィールドの追加
  • Unwind Array Values

Realm Swift SDK の MongoClientQuery APIを使用して、クライアント アプリケーション コードから MongoDB に保存されているデータを直接クエリできます。 Atlas App Servicesは、ログインしたユーザーまたは各ドキュメントの内容に基づいて結果を安全に取得するためのコレクションにデータ アクセスルールを提供します。

Tip

以下も参照してください。

このページでは、MongoDB データソースを直接クエリする方法について説明します。 Realm から取得したデータをフィルタリングするには、「データのフィルタリング 」を参照してください。

MongoDB データソースをクエリする理由はさまざまあります。 Atlas Device Sync を介してクライアント内のデータを操作することは、必ずしも現実的ではないか、可能な限りありません。 次の場合には、MongoDB をクエリすることをお勧めします。

  • データセットが大きいか、クライアント デバイスにデータセット全体のロードに対する制約がある

  • カスタム ユーザー データを作成または更新している場合

  • Realm でモデル化されていないドキュメントを取得している

  • アプリは厳密なスキーマを持たないコレクションにアクセスする必要があります

  • 非 Realm サービスは、アクセスしたいコレクションを生成します。

網羅的なものではありませんが、これらは MongoDB を直接クエリする一般的なユースケースです。

クライアント アプリケーションから MongoDB をクエリする前に、App Services App で MongoDB Data Access を設定する必要があります。 バックエンド アプリを設定して Realm SDK クエリ Atlas を使用できるようにする方法については、App Services ドキュメントの「 MongoDB データアクセスの設定 」を参照してください。

これらの例は、フード ストアのチェーンで読み取りを説明する MongoDB コレクションで動作します。 ドキュメントは、次のプロパティを持つオブジェクトを表します。

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
}

各例の完全なコードには、各操作を完了する前にログインと MongoDB コレクション ハンドルのインスタンス化が含まれています。 簡潔にするために、これらの例ではログインとコレクション処理コードが省略されています。 ただし、完全な各例は次のようになります。

appClient.login(credentials: Credentials.anonymous) { (result) in
// Remember to dispatch back to the main thread in completion handlers
// if you want to do anything on the UI.
DispatchQueue.main.async {
switch result {
case .failure(let error):
print("Login failed: \(error)")
case .success(let user):
print("Login as \(user) succeeded!")
// mongodb-atlas is the cluster service name
let mongoClient = user.mongoClient("mongodb-atlas")
// Select the database
let database = mongoClient.database(named: "ios")
// Select the collection
let collection = database.collection(withName: "CoffeeDrinks")
// This document represents a CoffeeDrink object
let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43]
// Insert the document into the collection
collection.insertOne(drink) { result in
switch result {
case .failure(let error):
print("Call to MongoDB failed: \(error.localizedDescription)")
return
case .success(let objectId):
// Success returns the objectId for the inserted document
print("Successfully inserted a document with id: \(objectId)")
}
}
}
}
}

バージョン 10.16.0 の新機能

Realm Swift SDK は、MongoCollection メソッドの async/await バージョンを提供します。

このページのすべてのメソッドは async/await 構文と互換性があります。 この例では、 collection.insertOne()メソッドのその構文を示します。 完了ハンドラーのバージョンは、「 単一ドキュメントの挿入 」で確認できます。

// This document represents a CoffeeDrink object
let drink: Document = [ "name": "Bean of the Day", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43]
do {
// Use the async collection method to insert the document
let objectId = try await collection.insertOne(drink)
print("Successfully inserted a document with id: \(objectId)")
} catch {
print("Call to MongoDB failed: \(error.localizedDescription)")
}

Realm Swift SDK バージョン 10.15.0 および 10.16.0 以降では、Realm API の多くが Swift async/await 構文をサポートしています。 プロジェクトは、次の要件を満たしている必要があります。

Swift SDK バージョン
Swift バージョン要件
サポートされている OS

10.25.0

Swift 5.6

iOS 13.x

10.15.0 または 10.16.0

Swift 5.5

iOS 15.x

アプリがasync/awaitコンテキストで Realm にアクセスする場合は、スレッド関連のクラッシュを回避するためにコードを@MainActorでマークします。

これらのコード スニペットは、モバイル アプリケーションから MongoDB コレクションに 1 つ以上のドキュメントを挿入する方法を示しています。 これらのメソッドは 1 つ以上のドキュメントを受け取り、結果を返します。 [] に成功すると、挿入されたドキュメントのObjectIdが返されます。または、複数のドキュメントを挿入している場合は、ObjectId の配列が順番に返されます。

collection.insertOne() を使用して単一のドキュメントを挿入できます。

このスニペットは、「Collectionbian受け入れ」を説明する単一のドキュメントを、店舗のグループで販売する機能を説明するドキュメントのコレクションに挿入します

// This document represents a CoffeeDrink object
let drink: Document = [ "name": "Colombian Blend", "beanRegion": "Timbio, Colombia", "containsDairy": false, "storeNumber": 43]
// Insert the document into the collection
collection.insertOne(drink) { result in
switch result {
case .failure(let error):
print("Call to MongoDB failed: \(error.localizedDescription)")
return
case .success(let objectId):
// Success returns the objectId for the inserted document
print("Successfully inserted a document with id: \(objectId)")
}
}
Successfully inserted a document with id: objectId(64e50cab7765243942cd04ce)

collection.insertMany() を使用して複数のドキュメントを挿入できます。

このスニペットは、グループ化された店舗で販売されるミルクバーを説明するドキュメントのコレクションに、コピー リンクを説明する 3 つのドキュメントを挿入します。

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]
// Insert the documents into the collection
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.

これらのコード スニペットは、モバイル アプリケーションから MongoDB コレクションに保存されているデータを読み取る方法を示しています。 読み取り操作では、標準のクエリ構文を使用して、データベースから返されるドキュメントを指定します。 読み取り操作では、一致した単一ドキュメント( findOneDocument()の場合)、 long数値( count()の場合)、または一致したドキュメントの配列(次の場合)のいずれかに解決される結果が返されます。 find() )。

collection.findOneDocument() を使用して単一のドキュメントを検索できます。

このスニペットは、グループ化された店舗で販売されているお茶を説明するドキュメントのコレクションから 1 つのドキュメントを検索します。ドキュメントのnameフィールドには string 値「Colombian Express」が含まれます。

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))
])

collection.find() を使用して複数のドキュメントを見つけることができます。

このスニペットは、グループ化された店舗で販売されているお茶を説明するドキュメントのコレクション内のすべてのドキュメントを検索します。ドキュメントのnameフィールドには値「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 ...

コレクション内のドキュメントをソートするには、希望するソート オプションでFindOptionsのインスタンスを初期化します。 FindOptionsには、制限プロジェクション並べ替えの 3 つのパラメータがあります。 sorting引数は、各キーがフィールドを表すキーと値のペアの配列にすることができます。 各キーで、値1は降順でソートされ、 -1は昇順でソートされます。

次に、クエリの実行時にFindOptionsインスタンスをcollection.find()メソッドに渡します。

このスニペットは、グループ化された店舗で販売されているケーキを説明するドキュメントのコレクション内のすべてのドキュメントを検索します。ドキュメントのnameフィールドには値「Americano」が含まれ、 beanRegionフィールドで降順にソートされます。

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` ...

collection.count()を使用してコレクション内のドキュメントをカウントできます。 カウントするドキュメントを決定するために、オプションのクエリと制限を指定できます。 クエリを指定しない場合、アクションはコレクション内のすべてのドキュメントをカウントします。

このスニペットは、グループ化された店舗で販売されているお茶を説明するドキュメントのコレクション内のドキュメントの数をカウントします。ドキュメントのnameフィールドには「操作の数」が含まれています。

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

これらのコード スニペットは、モバイル アプリケーションから MongoDB コレクションに保存されているデータをアップデートする方法を示しています。 更新操作では、クエリを使用して更新するドキュメントを指定し、更新演算子を使用してクエリに一致するドキュメントを変更する方法を記述します。 更新操作は、 UpdateResultまたはErrorに解決される結果を返します。

collection.updateOneDocument() を使用して単一のドキュメントを更新できます。

このスニペットは、店舗のグループで販売されるケーキのミルクを説明するドキュメントのコレクション内の 1 つのドキュメントを更新します。 このアップデート操作は、 nameフィールドに値「east」が含まれているドキュメントをクエリし、 containsDairyフィールドを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.

コレクション.updateManyDocuments() を使用して複数のドキュメントを更新できます。

このスニペットは、店舗のグループで販売されるケーキのミルクを説明するドキュメントのコレクション内の複数のドキュメントを更新します。 このアップデート操作は、 nameフィールドに値「east」が含まれているドキュメントをクエリし、 containsDairyフィールドを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.

アップデート操作がコレクション内のどのドキュメントにも一致しない場合は、 upsertオプションをtrueに設定することで、アップデート クエリに一致する新しいドキュメントを 1 つ自動的に挿入できます。

次のスニペットは、店舗のグループで販売されるミルクサーバーについて説明するドキュメントのコレクション内の 1 つのドキュメントを更新します。 クエリに一致するドキュメントがない場合、ドキュメントがない場合は新しいドキュメントが挿入されます。 この操作は、 nameフィールドの値が「操作の数」で、かつstoreNumberフィールドの値が55であるドキュメントをクエリします。

このスニペットはupsertオプションをtrueに設定しているため、クエリに一致するドキュメントがない場合、MongoDB はクエリと指定された更新の両方を含む新しいドキュメントを作成します。

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

これらのコード スニペットは、MongoDB コレクションに保存されているドキュメントをモバイル アプリケーションから削除する方法を示しています。 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.

collection.deleteOneDocument() を使用して、コレクションから単一のドキュメントを削除できます。

このスニペットは、店舗のグループで販売されているお茶を説明するドキュメントのコレクション内の 1 つのドキュメントを削除します。 この操作は、 nameフィールドの値が「Mocha」で、かつstoreNumberフィールドの値が17であるドキュメントをクエリして削除します。

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.

collection.deleteManyDocuments() を使用して、コレクションから複数の項目を削除できます。

このスニペットは、 nameフィールドに値「Capped」が含まれるドキュメントのクエリに一致する店舗のグループで販売されているお茶を説明するドキュメントのコレクション内のすべてのドキュメントを削除します。

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.

コレクション内のドキュメントが作成、変更、または削除されるたびに MongoDB が発行する変更通知について、コレクションを監視できます。 各通知では、変更されたドキュメントとその変更方法、およびイベントの原因となった操作後の完全なドキュメントを指定します。

重要

サーバーレスの制限事項

データソースが Atlas サーバーレスインスタンスの場合、変更を監視することはできません。 MongoDB サーバーレスは現在、変更をリッスンするために監視対象コレクションで使用される 変更ストリーム をサポートしていません。

collection.watch()を呼び出すと、コレクションに加えられた変更のストリームを開くことができます。 この関数は、MongoDB コレクションが変更されたときに AnyBSON 変更イベントを発行する発行者を作成します。

オプションで、 collection.watch(filterIds:) を使用してコレクション内の_idsのフィルタリングされたリストを監視するか、 collection.watch(matchFilter:)を使用して受信変更イベントに$matchフィルターを適用できます

.watch()メソッドはChangeEventDelegateを受け取り、ストリームの変更をサブスクライブできます。 たとえば、この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)")
}
}

このコードは、 CoffeeDrinksコレクション内のドキュメントに対する変更を監視します。

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!")
// Continue below
}
// Set up the client, database, and collection.
let client = self.appClient.currentUser!.mongoClient("mongodb-atlas")
let database = client.database(named: "ios")
let collection = database.collection(withName: "CoffeeDrinks")
// Watch the collection. In this example, we use a queue and delegate,
// both of which are optional arguments.
let queue = DispatchQueue(label: "io.realm.watchQueue")
let delegate = MyChangeStreamDelegate()
let changeStream = collection.watch(delegate: delegate, queue: queue)
// Adding a document triggers a change event.
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)")
}
}
// After you're done watching for events, close the change stream.
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

_idを渡すことで、特定のオブジェクト リストに対するコレクション内の変更を監視できます。 ObjectId の配列とともにcollection.watch(filterIds: )を呼び出し、それらのドキュメントに適用される変更イベントのみを受け取ります。

このChangeEventDeleteを使用する例を考えてみます。

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)")
}
}

以下のコードでは、この削除を使用して、 CoffeeDrinksコレクション内の特定のドキュメントに対する変更を監視します。

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!")
// Continue below
}
// Set up the client, database, and collection.
let client = self.appClient.currentUser!.mongoClient("mongodb-atlas")
let database = client.database(named: "ios")
let collection = database.collection(withName: "CoffeeDrinks")
// Watch the collection. In this example, we use a queue and delegate,
// both of which are optional arguments.
// `filterIds` is an array of specific document ObjectIds you want to watch.
let queue = DispatchQueue(label: "io.realm.watchQueue")
let delegate = MyChangeStreamDelegate()
let changeStream = collection.watch(filterIds: [drinkObjectId], delegate: delegate, queue: queue)
// An update to a relevant document triggers a change event.
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")
}
}
// After you're done watching for events, close the change stream.
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

collection.watch(matchFilter: )を呼び出すと、特定の条件を満たすコレクション内のドキュメントに加えられた変更のストリームを開くことができます。 このメソッドは、コレクションの監視中に発生する各Document データベース イベント を処理するために $match 演算子 のクエリとして使用される パラメータを受け入れます。

このChangeEventDeleteを使用する例を考えてみます。

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)")
}
}

以下のコードでは、この削除を使用して、 CoffeeDrinkコレクション内のドキュメントに対する変更を監視します。 これにより、ドキュメントのstoreNumber値が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!")
// Continue below
}
// Set up the client, database, and collection.
let client = self.appClient.currentUser!.mongoClient("mongodb-atlas")
let database = client.database(named: "ios")
let collection = database.collection(withName: "CoffeeDrinks")
// Watch the collection. In this example, we use a queue and delegate,
// both of which are optional arguments.
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)
// An update to a relevant document triggers a change event.
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")
}
}
// After you're done watching for events, close the change stream.
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 document
Change stream closed

バージョン10.37.0の新機能

非同期シーケンスを開いて、コレクションに対する変更を監視できます。 非同期コンテキストでは、コレクションに対してchangeEvents()を呼び出して変更ストリームを開きます。 これにより、MongoDB コレクションの各変更に関する情報を含む任意の BSON 値の非同期シーケンスが提供されます。

オプションで、監視ストリームがサーバー上で初期化されたときに呼び出されるchangeEvents(onOpen: )コールバックを提供できます。

changeEvents() API では、上記の例と同様に、コレクション内のドキュメントのサブセットを監視するには、 filterIdsまたはmatchFilterを使用できます。

次のスニペットは、 CoffeeDrinksコレクション内のドキュメントに対する変更を非同期シーケンスとして監視します。

let user = try await appClient.login(credentials: Credentials.anonymous)
// Set up the client, database, and collection.
let mongoClient = user.mongoClient("mongodb-atlas")
let database = mongoClient.database(named: "ios")
let collection = database.collection(withName: "CoffeeDrinks")
// Set up a task you'll later await to keep the change stream open,
// and you can cancel it when you're done watching for events.
let task = Task {
// Open the change stream.
let changeEvents = collection.changeEvents(onOpen: {
print("Successfully opened change stream")
})
// Await events in the change stream.
for try await event in changeEvents {
let doc = event.documentValue!
print("Received event: \(event.documentValue!)")
}
}
// Updating a document in the collection triggers a change event.
let queryFilter: Document = ["_id": AnyBSON(objectId) ]
let documentUpdate: Document = ["$set": ["containsDairy": true]]
let updateResult = try await collection.updateOneDocument(filter: queryFilter, update: documentUpdate)
// Cancel the task when you're done watching the stream.
task.cancel()
_ = await task.result
Successfully opened change stream
Received 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([]))
]))]

集計操作は、集計パイプラインと呼ばれる一連のデータ集計ステージを通じてコレクション内のすべてのドキュメントを実行します。 集計を使用すると、ドキュメントのフィルタリングと変換、関連するドキュメントのグループに関するサマリー データの収集、その他の複雑なデータ操作が可能になります。

collection.aggregate() を使用して、コレクションに対して集計操作を構成して実行できます。

集計操作は、集計ステージのリストを入力として受け入れ、パイプラインによって処理されたドキュメントのコレクション、またはErrorに解決される結果を返します。

$matchステージは、標準の MongoDBクエリ構文を使用してドキュメントをフィルタリングできます。

この$matchステージでは、ドキュメントをフィルタリングして、 storeNumberフィールドの値が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...)

$groupステージを使用して、1 つ以上のドキュメントのサマリーデータを集計できます。 MongoDB は、 $groupステージの_idフィールドに定義された式に基づいてドキュメントをグループ化します。 フィールド名の前に$を付けることで、特定のドキュメント フィールドを参照できます。

この$groupステージでは、ドキュメントをstoreNumberフィールドの値で配置します。 次に、 フィールドの値にその店舗番号を含むドキュメントの数を計算します。 つまり、店舗番号ごとのケーキの数を計算しています。

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...)

$projectステージを使用して、ドキュメントから特定のフィールドを含めたり省略したり、集計演算子を使用して新しいフィールドを計算したりできます。 プロジェクションは、次の 2 つの方法で機能します。

  • 1を使用して含めるフィールドを指定します。 これには、指定されていないすべてのフィールドを暗黙的に除外するという副作用があります。

  • 0を使用して除外するフィールドを指定します。 これには、指定されていないすべてのフィールドが暗黙的に含まれるという副作用があります。

これらの 2 つのプロジェクション方法は、相互に排他的です。 含めるフィールドを指定した場合は、除外するフィールドも指定できず、その逆も同様です。

注意

_idフィールドは特別なケースで、明示的に指定されない限り、すべてのクエリに常に含まれます。 このため、 0値を持つ_idフィールド除外しながら、同時にstoreNumberなどの他のフィールドを1とともに含めることができます。 _idフィールドを除外するという特別なケースのみ、1 つの$projectステージで除外と包含の両方が許可されます。

この例では、 CoffeeDrinkドキュメントに、次のドキュメントと同様に「Store 42」などの単語を含む string 値であるstoreフィールドがあるとします。

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"]

次の$projectステージでは、 _idフィールドが省略され、 nameフィールドが含まれ、 storeNumberという名前の新しいフィールドが作成されます。 storeNumberは 2 つの集計演算子を使用して生成されます。

  1. $split は、 store string 表現を、スペース文字を囲む 2 つの string セグメントに分割します。 たとえば、この方法で分割された値「Store 42」は、「store」と「42」の 2 つの要素を含む配列を返します。

  2. $arrayElemAt は、2 番目の引数に基づいて配列から特定の要素を選択します。 この場合、値1は、 0の配列インデックス以降、 $split演算子によって生成された配列から 2 番目の要素を選択します。 たとえば、この操作に渡される値 ["store", "42"] は "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...)

$addFieldsステージを使用して、集計演算子を使用して計算された値を持つ新しいフィールドを追加できます。

注意

$addFields$projectに似ていますが、フィールドを含めたり省略したりすることはできません。

この例では、 CoffeeDrinkドキュメントに、次のドキュメントと同様に「Store 42」などの単語を含む string 値であるstoreフィールドがあるとします。

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"]

次の$addFieldsステージでは、 storeNumberという名前の新しいフィールドが作成されます。値はstoreフィールドの値を変換する 2 つの集計演算子の出力です。

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))]

$unwindステージを使用して、配列を含む単一のドキュメントを、その配列の個々の値を含む複数のドキュメントに変換できます。 配列フィールドを展開すると、MongoDB は配列フィールドの要素ごとに各ドキュメントを 1 回コピーしますが、コピーごとに配列値を配列要素に置き換えます。

featuredInPromotions配列を含む次のドキュメントを考えてみましょう。

let drink: Document = [
"name": "Maple Latte",
"beanRegion": "Yirgacheffe, Ethiopia",
"containsDairy": true,
"storeNumber": 42,
"featuredInPromotions": [
"Spring into Spring",
"Tastes of Fall",
"Winter Delights"
]
]

次の$unwindステージでは、各ドキュメントのitems配列の要素ごとに新しいドキュメントが作成されます。 また、元の配列内の要素の位置インデックスを指定するitemIndexというフィールドも新しいドキュメントに追加されます。

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"))]

次に、グループ ドキュメントの例のように、各昇格のキー量をfeaturedInPromotions$sumの値で$groupするか、データに基づいて他の計算や変換を実行することができます。

戻る

関数の呼び出し