Cloud Firestore תומך בפעולות אטומיות לקריאה ולכתיבה של נתונים. בסדרה של פעולות אטומיות, או שכל הפעולות מצליחות או שאף אחת מהן לא מבוצעת. יש שני סוגים של פעולות אטומיות ב-Cloud Firestore:
- עסקאות: עסקה היא קבוצה של פעולות קריאה וכתיבה במסמך אחד או יותר.
- כתיבה באצווה: כתיבה באצווה היא קבוצה של פעולות כתיבה במסמך אחד או יותר.
עדכון נתונים באמצעות עסקאות
באמצעות ספריות הלקוח של Cloud Firestore, אפשר לקבץ כמה פעולות לעסקה אחת. הפעולות האלה שימושיות כשרוצים לעדכן את הערך של שדה מסוים על סמך הערך הנוכחי שלו או על סמך הערך של שדה אחר.
עסקה מורכבת ממספר כלשהו של פעולות get()
ואחריהן מספר כלשהו של פעולות כתיבה כמו set()
, update()
או delete()
. במקרה של עריכה בו-זמנית,
Cloud Firestore מריץ את כל העסקה מחדש. לדוגמה, אם עסקה קוראת מסמכים ולקוח אחר משנה אחד מהמסמכים האלה, Cloud Firestore העסקה תנסה שוב. התכונה הזו מבטיחה שהעסקה תתבצע על סמך נתונים עדכניים ועקביים.
העסקאות אף פעם לא מבצעות כתיבה חלקית. כל פעולות הכתיבה מבוצעות בסוף עסקה מוצלחת.
כשמשתמשים בעסקאות, חשוב לשים לב לדברים הבאים:
- פעולות קריאה צריכות להופיע לפני פעולות כתיבה.
- פונקציה שמפעילה טרנזקציה (פונקציית טרנזקציה) עשויה לפעול יותר מפעם אחת אם עריכה מקבילה משפיעה על מסמך שהטרנזקציה קוראת.
- פונקציות של טרנזקציות לא צריכות לשנות ישירות את מצב האפליקציה.
- העסקאות ייכשלו כשהלקוח יהיה במצב אופליין.
בדוגמה הבאה אפשר לראות איך ליצור ולהריץ טרנזקציה:
Web
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
// 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); });
Swift
let sfReference = db.collection("cities").document("SF") do { let _ = try await 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 }) print("Transaction successfully committed!") } catch { print("Transaction failed: \(error)") }
Objective-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
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
Python
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
Go
PHP
Unity
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#
Ruby
העברת מידע מחוץ לעסקאות
אל תשנו את מצב האפליקציה בתוך פונקציות העסקה. הפעולה הזו תגרום לבעיות של ביצוע מקביל, כי פונקציות של טרנזקציות יכולות לפעול כמה פעמים, ואין ערובה שהן יפעלו בשרשור של ממשק המשתמש. במקום זאת, מעבירים את המידע שצריך מתוך פונקציות העסקה. בדוגמה הבאה אנחנו ממשיכים את הדוגמה הקודמת ומראים איך להעביר מידע מחוץ לעסקה:
Web
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
// 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); });
Swift
let sfReference = db.collection("cities").document("SF") do { let object = try await 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 }) print("Population increased to \(object!)") } catch { print("Error updating population: \(error)") }
Objective-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
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
Python
Python
C++
// This is not yet supported.
Node.js
Go
PHP
Unity
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#
Ruby
העסקה נכשלה
יכול להיות שטרנזקציה תיכשל מהסיבות הבאות:
- העסקה מכילה פעולות קריאה אחרי פעולות כתיבה. פעולות קריאה תמיד צריכות להיות לפני פעולות כתיבה.
- העסקה קראה מסמך ששונה מחוץ לעסקה. במקרה כזה, העסקה מופעלת שוב באופן אוטומטי. המערכת מנסה לבצע את העסקה מספר סופי של פעמים.
העסקה חרגה מגודל הבקשה המקסימלי של 10MiB.
גודל העסקה תלוי בגודל המסמכים ורשומות האינדקס ששונו על ידי העסקה. במקרה של פעולת מחיקה, השדה הזה כולל את הגודל של מסמך היעד ואת הגדלים של רשומות האינדקס שנמחקו בתגובה לפעולה.
אם העסקה נכשלת, מוחזרת שגיאה ולא נכתב דבר במסד הנתונים. אין צורך לבטל את העסקה, כי Cloud Firestore עושה את זה באופן אוטומטי.
כתיבה בקבוצות
אם לא צריך לקרוא מסמכים במערך הפעולות, אפשר להריץ כמה פעולות כתיבה כקבוצה אחת שמכילה כל שילוב של פעולות set()
, update()
או delete()
.
כל פעולה בחבילה נספרת בנפרד בשימוש שלכם ב-Cloud Firestore. קבוצת פעולות כתיבה מסתיימת באופן אטומי ויכולה לכתוב למספר מסמכים. בדוגמה הבאה אפשר לראות איך ליצור ולבצע קומיט של קבוצת פעולות כתיבה:
Web
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
// 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(() => { // ... });
Swift
// 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 do { try await batch.commit() print("Batch write succeeded.") } catch { print("Error writing batch: \(error)") }
Objective-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
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
Python
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
Go
PHP
Unity
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#
Ruby
בדומה לעסקאות, פעולות כתיבה באצווה הן אטומיות. בניגוד לעסקאות, פעולות כתיבה באצווה לא צריכות לוודא שהמסמכים שנקראו לא ישתנו, ולכן יש פחות מקרים של כשלים. הם לא כפופים לניסיונות חוזרים או לכשלים כתוצאה מניסיונות חוזרים רבים מדי. הפעולות של כתיבת נתונים בקבוצות מתבצעות גם כשהמכשיר של המשתמש במצב אופליין.
יכול להיות שפעולת כתיבה באצווה עם מאות מסמכים תדרוש הרבה עדכונים של האינדקס, ותגרום לחריגה מהמגבלה על גודל העסקה. במקרה כזה, צריך להקטין את מספר המסמכים בכל אצווה. כדי לכתוב מספר גדול של מסמכים, כדאי להשתמש בכלי לכתיבה בכמות גדולה או בכתיבה מקבילית של מסמכים בודדים.
אימות נתונים לפעולות אטומיות
בספריות לקוח לנייד או לאינטרנט, אפשר לאמת נתונים באמצעות Cloud Firestore Security Rules. אתם יכולים לוודא שהעדכון של מסמכים קשורים תמיד מתבצע באופן אטומי, ותמיד כחלק מעסקה או כתיבה באצווה.
משתמשים בפונקציית כלל האבטחה getAfter()
כדי לגשת למצב של מסמך ולאמת אותו אחרי שקבוצת פעולות מסתיימת, אבל לפני ש-Cloud Firestore מבצע את הפעולות.
לדוגמה, נניח שמסד הנתונים של הדוגמה cities
מכיל גם אוסף countries
. בכל מסמך country
נעשה שימוש בשדה last_updated
כדי לעקוב אחרי הפעם האחרונה שבה עודכנה עיר כלשהי שקשורה למדינה הזו. כללי האבטחה הבאים מחייבים שעדכון של מסמך city
יעדכן גם את השדה last_updated
של המדינה הרלוונטית, באופן אטומי:
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; } } }
מגבלות על כללי אבטחה
בכללי האבטחה של טרנזקציות או פעולות כתיבה באצווה, יש מגבלה של 20 קריאות לגישה למסמכים לכל הפעולה האטומית, בנוסף למגבלה הרגילה של 10 קריאות לכל פעולת מסמך בודדת באצווה.
לדוגמה, נניח שיש לכם כללים כאלה באפליקציית צ'אט:
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)); } } }
קטעי הקוד שבהמשך ממחישים את מספר הקריאות לגישה למסמך שנעשה בהן שימוש בכמה דפוסי גישה לנתונים:
// 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();
למידע נוסף על פתרון בעיות של זמן אחזור שנגרמות בגלל כתיבות גדולות וכתיבות באצווה, שגיאות שנגרמות בגלל התנגשות מטרנזקציות חופפות ובעיות אחרות, מומלץ לעיין בדף לפתרון בעיות.