Cloud Firestore hỗ trợ các hoạt động nguyên tử để đọc và ghi dữ liệu. Trong một tập hợp các thao tác nguyên tử, tất cả các thao tác đều thành công hoặc không thao tác nào được áp dụng. Có hai loại hoạt động nguyên tử trong Cloud Firestore:
- Giao dịch : giao dịch là một tập hợp các thao tác đọc và ghi trên một hoặc nhiều tài liệu.
- Ghi theo đợt : ghi theo đợt là một tập hợp các thao tác ghi trên một hoặc nhiều tài liệu.
Mỗi giao dịch hoặc lô ghi có thể ghi tối đa 500 tài liệu. Để biết các giới hạn bổ sung liên quan đến việc ghi, hãy xem Hạn ngạch và Giới hạn .
Cập nhật dữ liệu với các giao dịch
Bằng cách sử dụng thư viện máy khách Cloud Firestore, bạn có thể nhóm nhiều hoạt động thành một giao dịch. Giao dịch hữu ích khi bạn muốn cập nhật giá trị của trường dựa trên giá trị hiện tại của trường hoặc giá trị của một số trường khác.
Một giao dịch bao gồm bất kỳ số lượng thao tác get()
nào, theo sau là bất kỳ số lượng thao tác ghi nào, chẳng hạn như set()
, update()
hoặc delete()
. Trong trường hợp chỉnh sửa đồng thời, Cloud Firestore sẽ chạy lại toàn bộ giao dịch. Ví dụ: nếu một giao dịch đọc tài liệu và một khách hàng khác sửa đổi bất kỳ tài liệu nào trong số đó, thì Cloud Firestore sẽ thử lại giao dịch. Tính năng này đảm bảo rằng giao dịch chạy trên dữ liệu cập nhật và nhất quán.
Giao dịch không bao giờ áp dụng một phần ghi. Tất cả các ghi thực hiện khi kết thúc một giao dịch thành công.
Khi sử dụng các giao dịch, lưu ý rằng:
- Thao tác đọc phải đến trước thao tác ghi.
- Một chức năng gọi một giao dịch (hàm giao dịch) có thể chạy nhiều lần nếu một chỉnh sửa đồng thời ảnh hưởng đến tài liệu mà giao dịch đó đọc.
- Các chức năng giao dịch không nên sửa đổi trực tiếp trạng thái ứng dụng.
- Giao dịch sẽ không thành công khi khách hàng ngoại tuyến.
Ví dụ sau đây cho thấy cách tạo và chạy một giao dịch:
Web version 9
import { runTransaction } from "firebase/firestore"; try { await runTransaction(db, async (transaction) => { const sfDoc = await transaction.get(sfDocRef); if (!sfDoc.exists()) { throw "Document does not exist!"; } const newPopulation = sfDoc.data().population + 1; transaction.update(sfDocRef, { population: newPopulation }); }); console.log("Transaction successfully committed!"); } catch (e) { console.log("Transaction failed: ", e); }
Web version 8
// Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); // Uncomment to initialize the doc. // sfDocRef.set({ population: 0 }); return db.runTransaction((transaction) => { // This code may get re-run multiple times if there are conflicts. return transaction.get(sfDocRef).then((sfDoc) => { if (!sfDoc.exists) { throw "Document does not exist!"; } // Add one person to the city population. // Note: this could be done without a transaction // by updating the population using FieldValue.increment() var newPopulation = sfDoc.data().population + 1; transaction.update(sfDocRef, { population: newPopulation }); }); }).then(() => { console.log("Transaction successfully committed!"); }).catch((error) => { console.log("Transaction failed: ", error); });
Nhanh
let sfReference = db.collection("cities").document("SF") db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)" ] ) errorPointer?.pointee = error return nil } // Note: this could be done without a transaction // by updating the population using FieldValue.increment() transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference) return nil }) { (object, error) in if let error = error { print("Transaction failed: \(error)") } else { print("Transaction successfully committed!") } }
Mục tiêu-C
FIRDocumentReference *sfReference = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) { FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer]; if (*errorPointer != nil) { return nil; } if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot" }]; return nil; } NSInteger oldPopulation = [sfDocument.data[@"population"] integerValue]; // Note: this could be done without a transaction // by updating the population using FieldValue.increment() [transaction updateData:@{ @"population": @(oldPopulation + 1) } forDocument:sfReference]; return nil; } completion:^(id result, NSError *error) { if (error != nil) { NSLog(@"Transaction failed: %@", error); } else { NSLog(@"Transaction successfully committed!"); } }];
Kotlin+KTX
val sfDocRef = db.collection("cities").document("SF") db.runTransaction { transaction -> val snapshot = transaction.get(sfDocRef) // Note: this could be done without a transaction // by updating the population using FieldValue.increment() val newPopulation = snapshot.getDouble("population")!! + 1 transaction.update(sfDocRef, "population", newPopulation) // Success null }.addOnSuccessListener { Log.d(TAG, "Transaction success!") } .addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new Transaction.Function<Void>() { @Override public Void apply(@NonNull Transaction transaction) throws FirebaseFirestoreException { DocumentSnapshot snapshot = transaction.get(sfDocRef); // Note: this could be done without a transaction // by updating the population using FieldValue.increment() double newPopulation = snapshot.getDouble("population") + 1; transaction.update(sfDocRef, "population", newPopulation); // Success return null; } }).addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Log.d(TAG, "Transaction success!"); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Transaction failure.", e); } });
Dart
final sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) async { final snapshot = await transaction.get(sfDocRef); // Note: this could be done without a transaction // by updating the population using FieldValue.increment() final newPopulation = snapshot.get("population") + 1; transaction.update(sfDocRef, {"population": newPopulation}); }).then( (value) => print("DocumentSnapshot successfully updated!"), onError: (e) => print("Error updating document $e"), );
Java
con trăn
Python
C++
DocumentReference sf_doc_ref = db->Collection("cities").Document("SF"); db->RunTransaction([sf_doc_ref](Transaction& transaction, std::string& out_error_message) -> Error { Error error = Error::kErrorOk; DocumentSnapshot snapshot = transaction.Get(sf_doc_ref, &error, &out_error_message); // Note: this could be done without a transaction by updating the // population using FieldValue::Increment(). std::int64_t new_population = snapshot.Get("population").integer_value() + 1; transaction.Update( sf_doc_ref, {{"population", FieldValue::Integer(new_population)}}); return Error::kErrorOk; }).OnCompletion([](const Future<void>& future) { if (future.error() == Error::kErrorOk) { std::cout << "Transaction success!" << std::endl; } else { std::cout << "Transaction failure: " << future.error_message() << std::endl; } });
Node.js
Đi
PHP
Đoàn kết
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactionAsync(transaction => { return transaction.GetSnapshotAsync(cityRef).ContinueWith((snapshotTask) => { DocumentSnapshot snapshot = snapshotTask.Result; long newPopulation = snapshot.GetValue<long>("Population") + 1; Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); }); });
C#
hồng ngọc
Chuyển thông tin ra khỏi giao dịch
Không sửa đổi trạng thái ứng dụng bên trong các chức năng giao dịch của bạn. Làm như vậy sẽ gây ra sự cố tương tranh vì các chức năng giao dịch có thể chạy nhiều lần và không được đảm bảo chạy trên chuỗi giao diện người dùng. Thay vào đó, hãy chuyển thông tin bạn cần ra khỏi chức năng giao dịch của mình. Ví dụ sau dựa trên ví dụ trước để chỉ ra cách chuyển thông tin ra khỏi giao dịch:
Web version 9
import { doc, runTransaction } from "firebase/firestore"; // Create a reference to the SF doc. const sfDocRef = doc(db, "cities", "SF"); try { const newPopulation = await runTransaction(db, async (transaction) => { const sfDoc = await transaction.get(sfDocRef); if (!sfDoc.exists()) { throw "Document does not exist!"; } const newPop = sfDoc.data().population + 1; if (newPop <= 1000000) { transaction.update(sfDocRef, { population: newPop }); return newPop; } else { return Promise.reject("Sorry! Population is too big"); } }); console.log("Population increased to ", newPopulation); } catch (e) { // This will be a "population is too big" error. console.error(e); }
Web version 8
// Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) => { return transaction.get(sfDocRef).then((sfDoc) => { if (!sfDoc.exists) { throw "Document does not exist!"; } var newPopulation = sfDoc.data().population + 1; if (newPopulation <= 1000000) { transaction.update(sfDocRef, { population: newPopulation }); return newPopulation; } else { return Promise.reject("Sorry! Population is too big."); } }); }).then((newPopulation) => { console.log("Population increased to ", newPopulation); }).catch((err) => { // This will be an "population is too big" error. console.error(err); });
Nhanh
let sfReference = db.collection("cities").document("SF") db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)" ] ) errorPointer?.pointee = error return nil } // Note: this could be done without a transaction // by updating the population using FieldValue.increment() let newPopulation = oldPopulation + 1 guard newPopulation <= 1000000 else { let error = NSError( domain: "AppErrorDomain", code: -2, userInfo: [NSLocalizedDescriptionKey: "Population \(newPopulation) too big"] ) errorPointer?.pointee = error return nil } transaction.updateData(["population": newPopulation], forDocument: sfReference) return newPopulation }) { (object, error) in if let error = error { print("Error updating population: \(error)") } else { print("Population increased to \(object!)") } }
Mục tiêu-C
FIRDocumentReference *sfReference = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) { FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer]; if (*errorPointer != nil) { return nil; } if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot" }]; return nil; } NSInteger population = [sfDocument.data[@"population"] integerValue]; population++; if (population >= 1000000) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-2 userInfo:@{ NSLocalizedDescriptionKey: @"Population too big" }]; return @(population); } [transaction updateData:@{ @"population": @(population) } forDocument:sfReference]; return nil; } completion:^(id result, NSError *error) { if (error != nil) { NSLog(@"Transaction failed: %@", error); } else { NSLog(@"Population increased to %@", result); } }];
Kotlin+KTX
val sfDocRef = db.collection("cities").document("SF") db.runTransaction { transaction -> val snapshot = transaction.get(sfDocRef) val newPopulation = snapshot.getDouble("population")!! + 1 if (newPopulation <= 1000000) { transaction.update(sfDocRef, "population", newPopulation) newPopulation } else { throw FirebaseFirestoreException("Population too high", FirebaseFirestoreException.Code.ABORTED) } }.addOnSuccessListener { result -> Log.d(TAG, "Transaction success: $result") }.addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new Transaction.Function<Double>() { @Override public Double apply(@NonNull Transaction transaction) throws FirebaseFirestoreException { DocumentSnapshot snapshot = transaction.get(sfDocRef); double newPopulation = snapshot.getDouble("population") + 1; if (newPopulation <= 1000000) { transaction.update(sfDocRef, "population", newPopulation); return newPopulation; } else { throw new FirebaseFirestoreException("Population too high", FirebaseFirestoreException.Code.ABORTED); } } }).addOnSuccessListener(new OnSuccessListener<Double>() { @Override public void onSuccess(Double result) { Log.d(TAG, "Transaction success: " + result); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Transaction failure.", e); } });
Dart
final sfDocRef = db.collection("cities").doc("SF"); db.runTransaction((transaction) { return transaction.get(sfDocRef).then((sfDoc) { final newPopulation = sfDoc.get("population") + 1; transaction.update(sfDocRef, {"population": newPopulation}); return newPopulation; }); }).then( (newPopulation) => print("Population increased to $newPopulation"), onError: (e) => print("Error updating document $e"), );
Java
con trăn
Python
C++
// This is not yet supported.
Node.js
Đi
PHP
Đoàn kết
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactionAsync(transaction => { return transaction.GetSnapshotAsync(cityRef).ContinueWith((task) => { long newPopulation = task.Result.GetValue<long>("Population") + 1; if (newPopulation <= 1000000) { Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); return true; } else { return false; } }); }).ContinueWith((transactionResultTask) => { if (transactionResultTask.Result) { Console.WriteLine("Population updated successfully."); } else { Console.WriteLine("Sorry! Population is too big."); } });
C#
hồng ngọc
Giao dịch thất bại
Một giao dịch có thể thất bại vì những lý do sau:
- Giao dịch chứa các thao tác đọc sau các thao tác ghi. Thao tác đọc phải luôn đi trước bất kỳ thao tác ghi nào.
- Giao dịch đọc một tài liệu đã được sửa đổi bên ngoài giao dịch. Trong trường hợp này, giao dịch sẽ tự động chạy lại. Giao dịch được thử lại một số lần hữu hạn.
Giao dịch đã vượt quá kích thước yêu cầu tối đa là 10 MiB.
Kích thước giao dịch phụ thuộc vào kích thước của tài liệu và mục nhập chỉ mục được sửa đổi bởi giao dịch. Đối với thao tác xóa, điều này bao gồm kích thước của tài liệu đích và kích thước của các mục nhập chỉ mục đã bị xóa để đáp ứng với thao tác.
Một giao dịch không thành công sẽ trả về lỗi và không ghi gì vào cơ sở dữ liệu. Bạn không cần phải hoàn nguyên giao dịch; Cloud Firestore tự động thực hiện việc này.
ghi hàng loạt
Nếu bạn không cần đọc bất kỳ tài liệu nào trong bộ thao tác của mình, bạn có thể thực hiện nhiều thao tác ghi dưới dạng một lô duy nhất chứa bất kỳ sự kết hợp nào của các thao tác set()
, update()
hoặc delete()
. Một lô ghi hoàn thành nguyên tử và có thể ghi vào nhiều tài liệu. Ví dụ sau đây cho thấy cách xây dựng và thực hiện một đợt ghi:
Web version 9
import { writeBatch, doc } from "firebase/firestore"; // Get a new write batch const batch = writeBatch(db); // Set the value of 'NYC' const nycRef = doc(db, "cities", "NYC"); batch.set(nycRef, {name: "New York City"}); // Update the population of 'SF' const sfRef = doc(db, "cities", "SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' const laRef = doc(db, "cities", "LA"); batch.delete(laRef); // Commit the batch await batch.commit();
Web version 8
// Get a new write batch var batch = db.batch(); // Set the value of 'NYC' var nycRef = db.collection("cities").doc("NYC"); batch.set(nycRef, {name: "New York City"}); // Update the population of 'SF' var sfRef = db.collection("cities").doc("SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' var laRef = db.collection("cities").doc("LA"); batch.delete(laRef); // Commit the batch batch.commit().then(() => { // ... });
Nhanh
// Get new write batch let batch = db.batch() // Set the value of 'NYC' let nycRef = db.collection("cities").document("NYC") batch.setData([:], forDocument: nycRef) // Update the population of 'SF' let sfRef = db.collection("cities").document("SF") batch.updateData(["population": 1000000 ], forDocument: sfRef) // Delete the city 'LA' let laRef = db.collection("cities").document("LA") batch.deleteDocument(laRef) // Commit the batch batch.commit() { err in if let err = err { print("Error writing batch \(err)") } else { print("Batch write succeeded.") } }
Mục tiêu-C
// Get new write batch FIRWriteBatch *batch = [self.db batch]; // Set the value of 'NYC' FIRDocumentReference *nycRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"NYC"]; [batch setData:@{} forDocument:nycRef]; // Update the population of 'SF' FIRDocumentReference *sfRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [batch updateData:@{ @"population": @1000000 } forDocument:sfRef]; // Delete the city 'LA' FIRDocumentReference *laRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"LA"]; [batch deleteDocument:laRef]; // Commit the batch [batch commitWithCompletion:^(NSError * _Nullable error) { if (error != nil) { NSLog(@"Error writing batch %@", error); } else { NSLog(@"Batch write succeeded."); } }];
Kotlin+KTX
val nycRef = db.collection("cities").document("NYC") val sfRef = db.collection("cities").document("SF") val laRef = db.collection("cities").document("LA") // Get a new write batch and commit all write operations db.runBatch { batch -> // Set the value of 'NYC' batch.set(nycRef, City()) // Update the population of 'SF' batch.update(sfRef, "population", 1000000L) // Delete the city 'LA' batch.delete(laRef) }.addOnCompleteListener { // ... }
Java
// Get a new write batch WriteBatch batch = db.batch(); // Set the value of 'NYC' DocumentReference nycRef = db.collection("cities").document("NYC"); batch.set(nycRef, new City()); // Update the population of 'SF' DocumentReference sfRef = db.collection("cities").document("SF"); batch.update(sfRef, "population", 1000000L); // Delete the city 'LA' DocumentReference laRef = db.collection("cities").document("LA"); batch.delete(laRef); // Commit the batch batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { // ... } });
Dart
// Get a new write batch final batch = db.batch(); // Set the value of 'NYC' var nycRef = db.collection("cities").doc("NYC"); batch.set(nycRef, {"name": "New York City"}); // Update the population of 'SF' var sfRef = db.collection("cities").doc("SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' var laRef = db.collection("cities").doc("LA"); batch.delete(laRef); // Commit the batch batch.commit().then((_) { // ... });
Java
con trăn
Python
C++
// Get a new write batch WriteBatch batch = db->batch(); // Set the value of 'NYC' DocumentReference nyc_ref = db->Collection("cities").Document("NYC"); batch.Set(nyc_ref, {}); // Update the population of 'SF' DocumentReference sf_ref = db->Collection("cities").Document("SF"); batch.Update(sf_ref, {{"population", FieldValue::Integer(1000000)}}); // Delete the city 'LA' DocumentReference la_ref = db->Collection("cities").Document("LA"); batch.Delete(la_ref); // Commit the batch batch.Commit().OnCompletion([](const Future<void>& future) { if (future.error() == Error::kErrorOk) { std::cout << "Write batch success!" << std::endl; } else { std::cout << "Write batch failure: " << future.error_message() << std::endl; } });
Node.js
Đi
PHP
Đoàn kết
WriteBatch batch = db.StartBatch(); // Set the data for NYC DocumentReference nycRef = db.Collection("cities").Document("NYC"); Dictionary<string, object> nycData = new Dictionary<string, object> { { "name", "New York City" } }; batch.Set(nycRef, nycData); // Update the population for SF DocumentReference sfRef = db.Collection("cities").Document("SF"); Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", 1000000} }; batch.Update(sfRef, updates); // Delete LA DocumentReference laRef = db.Collection("cities").Document("LA"); batch.Delete(laRef); // Commit the batch batch.CommitAsync();
C#
hồng ngọc
Một đợt ghi có thể chứa tới 500 thao tác. Mỗi thao tác trong lô được tính riêng vào việc sử dụng Cloud Firestore của bạn.
Giống như các giao dịch, ghi hàng loạt là nguyên tử. Không giống như các giao dịch, ghi theo đợt không cần đảm bảo rằng các tài liệu đã đọc vẫn không bị sửa đổi, điều này dẫn đến ít trường hợp lỗi hơn. Chúng không phải thử lại hoặc thất bại do thử lại quá nhiều lần. Ghi theo lô thực thi ngay cả khi thiết bị của người dùng ngoại tuyến.
Xác thực dữ liệu cho các hoạt động nguyên tử
Đối với thư viện máy khách trên thiết bị di động/web, bạn có thể xác thực dữ liệu bằng Quy tắc bảo mật của Cloud Firestore . Bạn có thể đảm bảo rằng các tài liệu liên quan luôn được cập nhật nguyên tử và luôn là một phần của giao dịch hoặc ghi theo đợt. Sử dụng chức năng quy tắc bảo mật getAfter()
để truy cập và xác thực trạng thái của tài liệu sau khi một nhóm thao tác hoàn tất nhưng trước khi Cloud Firestore thực hiện các thao tác.
Ví dụ, hãy tưởng tượng rằng cơ sở dữ liệu cho ví dụ về cities
cũng chứa một bộ sưu tập countries
. Mỗi tài liệu country
sử dụng trường last_updated
để theo dõi lần cuối cùng bất kỳ thành phố nào liên quan đến quốc gia đó được cập nhật. Các quy tắc bảo mật sau đây yêu cầu bản cập nhật cho tài liệu city
cũng phải cập nhật nguyên bản trường last_updated
của quốc gia có liên quan:
service cloud.firestore { match /databases/{database}/documents { // If you update a city doc, you must also // update the related country's last_updated field. match /cities/{city} { allow write: if request.auth != null && getAfter( /databases/$(database)/documents/countries/$(request.resource.data.country) ).data.last_updated == request.time; } match /countries/{country} { allow write: if request.auth != null; } } }
Giới hạn quy tắc bảo mật
Trong các quy tắc bảo mật cho các giao dịch hoặc ghi theo lô, có giới hạn 20 lệnh gọi truy cập tài liệu cho toàn bộ hoạt động nguyên tử ngoài giới hạn 10 lệnh gọi thông thường cho mỗi thao tác tài liệu đơn lẻ trong lô.
Ví dụ: hãy xem xét các quy tắc sau cho ứng dụng trò chuyện:
service cloud.firestore { match /databases/{db}/documents { function prefix() { return /databases/{db}/documents; } match /chatroom/{roomId} { allow read, write: if request.auth != null && roomId in get(/$(prefix())/users/$(request.auth.uid)).data.chats || exists(/$(prefix())/admins/$(request.auth.uid)); } match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId || exists(/$(prefix())/admins/$(request.auth.uid)); } match /admins/{userId} { allow read, write: if request.auth != null && exists(/$(prefix())/admins/$(request.auth.uid)); } } }
Đoạn mã dưới đây minh họa số lượng lệnh gọi truy cập tài liệu được sử dụng cho một số mẫu truy cập dữ liệu:
// 0 document access calls used, because the rules evaluation short-circuits // before the exists() call is invoked. db.collection('user').doc('myuid').get(...); // 1 document access call used. The maximum total allowed for this call // is 10, because it is a single document request. db.collection('chatroom').doc('mygroup').get(...); // Initializing a write batch... var batch = db.batch(); // 2 document access calls used, 10 allowed. var group1Ref = db.collection("chatroom").doc("group1"); batch.set(group1Ref, {msg: "Hello, from Admin!"}); // 1 document access call used, 10 allowed. var newUserRef = db.collection("users").doc("newuser"); batch.update(newUserRef, {"lastSignedIn": new Date()}); // 1 document access call used, 10 allowed. var removedAdminRef = db.collection("admin").doc("otheruser"); batch.delete(removedAdminRef); // The batch used a total of 2 + 1 + 1 = 4 document access calls, out of a total // 20 allowed. batch.commit();