Modyfikowanie danych za pomocą operacji potoku Firestore

Użyj etapów języka manipulacji danymi (DML) update(...) i delete(...), aby utworzyć potoki danych, które mogą wysyłać zapytania o dokumenty, a następnie usuwać lub modyfikować dane.

Wymagania dotyczące wersji

Operacje opisane na tej stronie wymagają wersji Firestore Enterprise.

Zanim zaczniesz

Musisz wiedzieć, jak wysyłać zapytania do bazy danych za pomocą operacji potoku.

Aktualizowanie dokumentów

Użyj etapu DML update(...), aby utworzyć potoki danych, które mogą wysyłać zapytania o dokumenty, a następnie dodawać lub modyfikować dane.

Wszystkie etapy DML muszą znajdować się na końcu potoku. Dokumenty, które trafiają na ten etap, muszą zawierać pole __name__, aby można było określić, które dokumenty należy zaktualizować. Operacja się nie powiedzie, jeśli którykolwiek z dokumentów, które próbujesz zaktualizować, nie istnieje.

Na przykład ta operacja uzupełnia zmianę modelu danych we wszystkich dokumentach w grupie kolekcji. Potok dodaje pole preferences.color do wszystkich dokumentów w grupie kolekcji users, w których brakuje tego pola.

Node.js
const snapshot = await db.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant(null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Constant, Field, Not

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Not(Field.of("preferences.color").exists()))
    .add_fields(Constant.of(None).as_("preferences.color"))
    .remove_fields("color")
    .update()
    .execute()
)
Java
Pipeline.Snapshot snapshot = firestore.pipeline()
   .collectionGroup("users")
   .where(not(exists(field("preferences.color"))))
   .addFields(constant((String) null).as("preferences.color"))
   .removeFields("color")
   .update()
   .execute().get();
Go
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.Not(firestore.FieldExists(firestore.FieldOf("preferences.color")))).
	AddFields(firestore.Selectables(
		firestore.ConstantOfNull().As("preferences.color"),
	)).
	RemoveFields(firestore.Fields("color")).
	Update().
	Execute(ctx)

Usuń dokumenty

Użyj etapu DML delete(...), aby utworzyć potoki danych, które mogą wysyłać zapytania o dokumenty, a następnie usuwać dane. Aby zapobiec przypadkowemu usuwaniu zbiorczemu, potoki kończące się na delete(...) powinny zawierać co najmniej 1 etap where(...). Wszystkie etapy DML muszą znajdować się na końcu potoku.

Na przykład ten potok usuwa wszystkie dokumenty users, w których pole address.users ma wartość USA, a pole __create_time__ ma wartość mniejszą niż 10 dni:

Node.js
const pipeline = db.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").timestampAdd("day", 10).lessThan(currentTimestamp()))
  .delete();
await pipeline.execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import CurrentTimestamp, Field

snapshot = (
    client.pipeline()
    .collection_group("users")
    .where(Field.of("address.country").equal("USA"))
    .where(
        Field.of("__create_time__")
        .timestamp_add("day", 10)
        .less_than(CurrentTimestamp())
    )
    .delete()
    .execute()
)
Java
Pipeline.Snapshot deleteResults = firestore.pipeline()
  .collectionGroup("users")
  .where(field("address.country").equal("USA"))
  .where(field("__create_time__").add(constant(10)).lessThan(currentTimestamp()))
  .delete()
  .execute().get();
Go
snapshot := client.Pipeline().
	CollectionGroup("users").
	Where(firestore.FieldOf("address.country").Equal("USA")).
	Where(firestore.FieldOf("__create_time__").Add(firestore.ConstantOf(10)).LessThan(firestore.CurrentTimestamp())).
	Delete().
	Execute(ctx)

Tryby wykonywania i spójność

Etapy DML można wykonywać w ramach transakcji jawnej lub poza transakcją:

Wykonywanie transakcyjne

Transakcyjny język DML zapewnia niepodzielną spójność typu „wszystko albo nic” w dokumentach i obsługuje pełny zestaw etapów zapytań potoku. Dzięki temu idealnie nadaje się do zbiorów zadań, które wymagają ścisłych gwarancji spójności, gdzie częściowe aktualizacje są niedopuszczalne, lub które opierają się na złożonych etapach zapytania, pod warunkiem że modyfikacje mieszczą się w limitach rozmiaru transakcji.

Uruchomienie potoku DML w ramach jawnej transakcji (za pomocą runTransaction / transaction.execute(pipeline) lub opcji atomic: true) powoduje następujące zachowanie:

  • Wszystkie mutacje są zatwierdzane razem lub całkowicie wycofywane w przypadku niepowodzenia.
  • Przed etapem DML obsługiwane są wszystkie etapy zapytania potoku (w tym podzapytania where, add_fields, union, find_nearest i aggregate).
  • W transakcji wszystkie operacje odczytu muszą zostać wykonane przed operacjami zapisu. Potok DML wykonuje operacje zapisu, więc po uruchomieniu potoku DML nie można wykonywać operacji odczytu w tej samej transakcji. Nie możesz na przykład wywołać potoku DML wielokrotnie w ramach jednej transakcji ani odczytać dokumentów po wykonaniu potoku DML. Próba wykonania odczytu po nieudanym uruchomieniu potoku DML kończy się błędem FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • Operacja podlega limitom transakcji w wersji Firestore Enterprise.

Wykonanie nietransakcyjne

Nietransakcyjny język DML jest przeznaczony do zbiorczych modyfikacji dużej liczby dokumentów bez przekraczania limitów rozmiaru transakcji. Dzięki blokowaniu na poziomie dokumentu, a nie całej operacji, inne równoczesne operacje mogą być wykonywane podczas aktualizowania dokumentów na dużą skalę.

Gdy etapy DML są wykonywane poza jawną transakcją:

  • Niezależne aktualizacje i częściowe powodzenie: każdy dokument jest aktualizowany niezależnie od innych, a operacje nie są niepodzielne w różnych dokumentach. Potok zatrzymuje się przy pierwszym błędzie, a wcześniej zmodyfikowane dokumenty pozostają zapisane w bazie danych, co pozwala na częściowe powodzenie.
  • Pomoc na etapie:
    • Zaawansowane i wielozapytaniowe etapy (takie jak aggregate(...), distinct(...), unnest(...), find_nearest(...), union(...), złączenia i podzapytania) nie są obsługiwane przed DML poza transakcją. Jeśli Twój potok wymaga tych etapów, użyj transakcji.
  • Limit 20 wyszukiwań w regułach zabezpieczeń: każda partia dokumentów jest wykonywana we własnej transakcji i ponownie ocenia reguły zabezpieczeń. Jeśli Twoje reguły zabezpieczeń używają wyszukiwania get() lub exists(), każda grupa wykonuje wyszukiwanie, osiągając limit 20 wyszukiwań na żądanie po zapisaniu około 200 dokumentów i kończąc się błędem PERMISSION_DENIED. Aby uniknąć tego limitu, zamiast wyszukiwań get() lub exists() używaj twierdzeń niestandardowych.

Ograniczenia

Pamiętaj o tych ograniczeniach dotyczących etapów DML:

  • Etapy DML muszą być ostatnim etapem w definicji potoku przed wywołaniem .execute().
  • Gdy operacja jest wykonywana poza transakcją, każdy dokument jest aktualizowany niezależnie od innych, co umożliwia częściowe powodzenie.
  • Nietransakcyjne operacje DML podlegające regułom zabezpieczeń z wyszukiwaniami get() lub exists() są ograniczone do około 200 dokumentów ze względu na limit 20 wyszukiwań.
  • Transakcyjne operacje DML są ograniczone przez limity transakcji Firestore Enterprise. Transakcje Firestore nie zezwalają na odczyty po pierwszym zapisie, dlatego nie można wielokrotnie wywoływać potoku DML w ramach jednej transakcji ani odczytywać dokumentów po wykonaniu potoku DML. Próba wykonania odczytu po nieudanym zapisie DML kończy się błędem FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • Jeśli etap poprzedzający etap DML generuje wiele dokumentów z tym samym __name__ lub zwraca ten sam identyfikator dokumentu docelowego, każda mutacja jest przetwarzana sekwencyjnie:
    • W przypadku upsert(...) i update(...): wiele mutacji dotyczących tego samego dokumentu jest zgodnych z semantyką „ostatnia wygrywa”, gdzie ostatnia przetworzona mutacja określa ostateczny stan dokumentu.
    • W przypadku delete(...): pierwsza próba powoduje usunięcie dokumentu, a kolejne próby dotyczące tego samego dokumentu nie powodują żadnych działań.
    • W przypadku insert(...): pierwsza próba powoduje utworzenie dokumentu, a kolejne próby dotyczące tego samego identyfikatora dokumentu kończą się błędem ALREADY_EXISTS.