Modifier des données avec des opérations de pipeline

Utilisez les étapes de langage de manipulation de données (LMD) update(...) et delete(...) pour créer des pipelines de données capables d'interroger des documents, puis de supprimer ou de modifier des données.

Édition requise

Les opérations décrites sur cette page nécessitent l'édition Enterprise de Firestore.

Avant de commencer

Vous devez savoir comment interroger une base de données avec des opérations de pipeline.

Mettre à jour des documents

Utilisez l'étape LMD update(...) pour créer des pipelines de données capables d'interroger des documents, puis d'ajouter ou de modifier des données.

Toutes les étapes LMD doivent se trouver à la fin du pipeline. Les documents entrant dans cette étape doivent inclure le champ __name__ pour identifier les documents à mettre à jour. L'opération échoue si l'un des documents que vous tentez de mettre à jour n'existe pas.

Par exemple, l'opération suivante effectue un backfill d'une modification de modèle de données sur tous les documents d'un groupe de collections. Le pipeline ajoute un champ preferences.color à tous les documents du groupe de collections users qui ne comportent pas ce champ.

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)

Supprimer des documents

Utilisez l'étape LMD delete(...) pour créer des pipelines de données capables d'interroger des documents, puis de supprimer des données. Pour éviter les suppressions groupées accidentelles, les pipelines se terminant par delete(...) doivent inclure au moins une étape where(...). Toutes les étapes LMD doivent se trouver à la fin du pipeline.

Par exemple, le pipeline suivant supprime tous les documents users dont le champ address.users est défini sur USA et dont le champ __create_time__ est inférieur à 10 jours :

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)

Modes d'exécution et cohérence

Les étapes LMD peuvent être exécutées dans une transaction explicite ou en dehors d'une transaction :

Exécution transactionnelle

Le LMD transactionnel offre une cohérence atomique de type "tout ou rien" entre les documents et accepte l'ensemble des étapes de requête du pipeline. Il est donc idéal pour les charges de travail qui nécessitent des garanties de cohérence strictes (où les mises à jour partielles sont inacceptables) ou qui reposent sur des étapes de requête complexes, à condition que les modifications respectent les limites de taille des transactions.

L'exécution d'un pipeline LMD dans une transaction explicite (à l'aide de runTransaction / transaction.execute(pipeline) ou de l'option atomic: true) présente le comportement suivant :

  • Toutes les mutations sont validées ensemble ou annulées complètement en cas d'échec.
  • Toutes les étapes de requête de pipeline sont acceptées avant une étape LMD (y compris les sous-requêtes where, add_fields, union, find_nearest et aggregate).
  • Dans une transaction, toutes les opérations de lecture doivent être exécutées avant les opérations d'écriture. Étant donné qu'un pipeline LMD effectue des écritures, vous ne pouvez pas exécuter de lectures après avoir exécuté un pipeline LMD dans la même transaction. Par exemple, vous ne pouvez pas appeler un pipeline LMD plusieurs fois dans une même transaction, ni lire des documents après l'exécution d'un pipeline LMD. La tentative d'exécution d'une lecture après l'exécution d'un pipeline LMD échoue avec une erreur FAILED_PRECONDITION : Cannot have reads after the first write in a Firestore transaction.
  • L'opération est soumise aux limites de transactions de l'édition Enterprise de Firestore.

Exécution non transactionnelle

Le LMD non transactionnel est conçu pour les modifications groupées sur un grand nombre de documents sans rencontrer de limites de taille de transaction. En verrouillant au niveau du document plutôt que sur l'ensemble de l'opération, il permet à d'autres opérations simultanées de se poursuivre tout en mettant à jour les documents à grande échelle.

Lorsque les étapes LMD s'exécutent en dehors d'une transaction explicite :

  • Mises à jour indépendantes et réussite partielle : chaque document est mis à jour indépendamment des autres, et les opérations ne sont pas atomiques entre les documents. Le pipeline s'arrête à la première erreur, et les documents précédemment modifiés restent écrits dans la base de données, ce qui permet une réussite partielle.
  • Compatibilité avec les étapes :
    • Les étapes avancées et à requêtes multiples (telles que aggregate(...), distinct(...), unnest(...), find_nearest(...), union(...), les jointures et les sous-requêtes) ne sont pas acceptées avant le LMD en dehors d'une transaction. Utilisez une transaction si votre pipeline nécessite ces étapes.
  • Limite de 20 recherches pour les règles de sécurité : chaque lot de documents s'exécute dans sa propre transaction et réévalue les règles de sécurité. Si vos règles de sécurité utilisent des recherches get() ou exists(), chaque lot exécute une recherche, atteignant la limite de 20 recherches par requête après l'écriture d'environ 200 documents et l'échec avec PERMISSION_DENIED. Utilisez des revendications personnalisées au lieu des recherches get() ou exists() pour éviter cette limite.

Limites

Notez les limites suivantes pour les étapes LMD :

  • Les étapes LMD doivent être les dernières étapes d'une définition de pipeline avant d'appeler .execute().
  • Lorsqu'ils sont exécutés en dehors d'une transaction, chaque document est mis à jour indépendamment des autres, ce qui permet une réussite partielle.
  • Les opérations LMD non transactionnelles soumises à des règles de sécurité avec des recherches get() ou exists() sont limitées à environ 200 documents en raison de la limite de 20 recherches.
  • Les opérations LMD transactionnelles sont limitées par les limites de transaction de l'édition Enterprise de Firestore. Étant donné que les transactions Firestore n'autorisent pas les lectures après la première écriture, vous ne pouvez pas appeler un pipeline LMD plusieurs fois au cours d'une même transaction, ni lire des documents après l'exécution d'un pipeline LMD. La tentative d'exécution d'une lecture après l'échec d'une écriture LMD renvoie une erreur FAILED_PRECONDITION : Cannot have reads after the first write in a Firestore transaction.
  • Si l'étape précédant l'étape LMD produit plusieurs documents avec le même __name__ ou est évaluée avec le même ID de document de destination, chaque mutation est traitée de manière séquentielle :
    • Pour upsert(...) et update(...) : plusieurs mutations ciblant le même document suivent la sémantique "le dernier l'emporte", où la dernière mutation traitée détermine l'état final du document.
    • Pour delete(...) : la première tentative supprime le document, et les tentatives suivantes pour le même document sont des opérations "no-op".
    • Pour insert(...) : la première tentative crée le document, et les tentatives suivantes ciblant le même ID de document échouent avec une erreur ALREADY_EXISTS.