يتيح 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
متابعة
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
متابعة
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
تعذُّر إكمال المعاملة
يمكن أن تفشل المعاملة للأسباب التالية:
- تحتوي المعاملة على عمليات قراءة بعد عمليات الكتابة. يجب دائمًا تنفيذ عمليات القراءة قبل أي عمليات كتابة.
- قرأت المعاملة مستندًا تم تعديله خارج المعاملة. في هذه الحالة، يتم تنفيذ المعاملة تلقائيًا مرة أخرى. تتم إعادة محاولة المعاملة لعدد محدود من المرات.
تجاوزت المعاملة الحد الأقصى لحجم الطلب البالغ 10 ميغابايت.
يعتمد حجم المعاملة على أحجام المستندات وإدخالات الفهرس التي تم تعديلها بواسطة المعاملة. بالنسبة إلى عملية الحذف، يشمل ذلك حجم المستند المستهدَف وأحجام إدخالات الفهرس المحذوفة استجابةً للعملية.
تجاوزت المعاملة الموعد النهائي للقفل (20 ثانية). يحرّر Cloud Firestore الأقفال تلقائيًا إذا تعذّر إكمال المعاملة في الوقت المحدّد.
تتجاوز المعاملة الحدّ الأقصى للوقت الذي يبلغ 270 ثانية أو وقت انتهاء صلاحية عدم النشاط الذي يبلغ 60 ثانية. إذا لم يحدث أي نشاط (قراءة أو كتابة) ضمن المعاملة، ستنتهي مهلتها وتتعذّر.
تُرجع المعاملة الفاشلة خطأً ولا تكتب أي شيء في قاعدة البيانات. ليس عليك التراجع عن المعاملة، لأنّ 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
متابعة
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 لتتبُّع آخر مرة تم فيها تعديل أي مدينة مرتبطة بهذا البلد. The
following security rules require that an update to a city document must also
atomically update the related country's last_updated field:
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();
لمزيد من المعلومات حول كيفية حلّ مشاكل وقت الاستجابة الناتجة عن عمليات الكتابة الكبيرة وعمليات الكتابة المجمّعة، والأخطاء الناتجة عن التعارض من المعاملات المتداخلة، والمشاكل الأخرى، يُرجى الاطّلاع على صفحة تحديد المشاكل وحلّها.