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); });
سویفت
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)") }
هدف-سی
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"), );
جاوا
پایتون
Python
سی++
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; } });
نود جی اس
برو
پی اچ پی
وحدت
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); }); });
سی شارپ
روبی
انتقال اطلاعات از تراکنشها
وضعیت برنامه را درون توابع تراکنش خود تغییر ندهید. انجام این کار باعث ایجاد مشکلات همزمانی میشود، زیرا توابع تراکنش میتوانند چندین بار اجرا شوند و تضمینی برای اجرای آنها در نخ رابط کاربری وجود ندارد. در عوض، اطلاعات مورد نیاز خود را از توابع تراکنش خود منتقل کنید. مثال زیر بر اساس مثال قبلی ساخته شده است تا نحوه انتقال اطلاعات از یک تراکنش را نشان دهد:
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); });
سویفت
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)") }
هدف-سی
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"), );
جاوا
پایتون
Python
سی++
// This is not yet supported.
نود جی اس
برو
پی اچ پی
وحدت
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."); } });
سی شارپ
روبی
شکست تراکنش
یک تراکنش میتواند به دلایل زیر ناموفق باشد:
- تراکنش شامل عملیات خواندن پس از عملیات نوشتن است. عملیات خواندن همیشه باید قبل از هرگونه عملیات نوشتن اجرا شود.
- تراکنش سندی را میخواند که خارج از تراکنش اصلاح شده است. در این حالت، تراکنش به طور خودکار دوباره اجرا میشود. تراکنش به تعداد محدودی بار تکرار میشود.
حجم تراکنش از حداکثر اندازه درخواست ۱۰ مگابایت فراتر رفت.
اندازه تراکنش به اندازه اسناد و ورودیهای فهرست اصلاحشده توسط تراکنش بستگی دارد. برای عملیات حذف، این شامل اندازه سند هدف و اندازه ورودیهای فهرست حذفشده در پاسخ به عملیات میشود.
تراکنش از مهلت قفل (20 ثانیه) فراتر رفت. اگر تراکنشی نتواند به موقع تکمیل شود، Cloud Firestore به طور خودکار قفلها را آزاد میکند.
تراکنش از محدودیت زمانی ۲۷۰ ثانیه یا زمان انقضای ۶۰ ثانیهایِ حالت بیکاری عبور کند. اگر هیچ فعالیتی (خواندن یا نوشتن) در داخل تراکنش انجام نشود، تراکنش با اتمام زمان مواجه شده و ناموفق خواهد بود.
یک تراکنش ناموفق، خطا برمیگرداند و چیزی در پایگاه داده نمینویسد. نیازی به بازگرداندن تراکنش نیست؛ 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(() => { // ... });
سویفت
// 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)") }
هدف-سی
// 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((_) { // ... });
جاوا
پایتون
Python
سی++
// 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; } });
نود جی اس
برو
پی اچ پی
وحدت
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();
سی شارپ
روبی
مانند تراکنشها، نوشتنهای دستهای اتمیک هستند. برخلاف تراکنشها، نوشتنهای دستهای نیازی به اطمینان از عدم تغییر اسناد خوانده شده ندارند که منجر به موارد خرابی کمتری میشود. آنها در معرض تلاش مجدد یا خرابی ناشی از تلاشهای مجدد زیاد قرار نمیگیرند. نوشتنهای دستهای حتی زمانی که دستگاه کاربر آفلاین است نیز اجرا میشوند.
یک نوشتن دستهای با صدها سند ممکن است به بهروزرسانیهای شاخص زیادی نیاز داشته باشد و ممکن است از محدودیت اندازه تراکنش فراتر رود. در این حالت، تعداد اسناد در هر دسته را کاهش دهید. برای نوشتن تعداد زیادی سند، به جای آن از یک نویسنده دستهای یا نوشتنهای موازی تکی استفاده کنید.
اعتبارسنجی دادهها برای عملیات اتمی
برای کتابخانههای کلاینت موبایل/وب، میتوانید دادهها را با استفاده از 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; } } }
محدودیتهای قوانین امنیتی
در قوانین امنیتی برای تراکنشها یا نوشتنهای دستهای، علاوه بر محدودیت معمول ۱۰ فراخوانی برای هر عملیات سند در دسته، محدودیت ۲۰ فراخوانی دسترسی به سند برای کل عملیات اتمیک وجود دارد.
برای مثال، قوانین زیر را برای یک برنامه چت در نظر بگیرید:
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();
برای اطلاعات بیشتر در مورد چگونگی حل مشکلات تأخیر ناشی از نوشتنهای بزرگ و نوشتنهای دستهای، خطاهای ناشی از تداخل تراکنشهای همپوشانی و سایر مشکلات، صفحه عیبیابی را بررسی کنید.