Modificar dados com operações de pipeline

Use as etapas de linguagem de manipulação de dados (DML) update(...) e delete(...) para criar pipelines de dados que podem consultar documentos e, em seguida, excluir ou modificar dados.

Requisitos da versão

As operações descritas nesta página exigem a edição Enterprise do Firestore.

Antes de começar

Você precisa saber como consultar um banco de dados com operações de pipeline.

Atualizar documentos

Use o estágio de DML update(...) para criar pipelines de dados que podem consultar documentos e adicionar ou modificar dados.

Todas as fases de DML precisam estar no final do pipeline. Os documentos que entram nesse estágio precisam incluir o campo __name__ para identificar quais documentos atualizar. A operação falhará se algum dos documentos que você tentar atualizar não existir.

Por exemplo, a operação a seguir preenche uma alteração de modelo de dados em todos os documentos de um grupo de coleções. O pipeline adiciona um campo preferences.color a todos os documentos no grupo de coleções users que não têm esse campo.

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)

Excluir documentos

Use o estágio DML delete(...) para criar pipelines de dados que podem consultar documentos e excluir dados. Para evitar exclusões em massa acidentais, os pipelines que terminam em delete(...) precisam incluir pelo menos um estágio where(...). Todas as fases de DML precisam estar no final do pipeline.

Por exemplo, o pipeline a seguir exclui todos os documentos users com address.users definido como USA e com __create_time__ menor que 10 dias:

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)

Modos de execução e consistência

As etapas de DML podem ser executadas dentro ou fora de uma transação explícita:

Execução transacional

A DML transacional oferece consistência atômica, do tipo "tudo ou nada", em todos os documentos e é compatível com todo o conjunto de estágios de consulta do pipeline. Isso o torna ideal para cargas de trabalho que exigem garantias de consistência estritas, em que atualizações parciais são inaceitáveis, ou que dependem de etapas de consulta complexas, desde que as modificações se encaixem nos limites de tamanho da transação.

Executar um pipeline de DML dentro de uma transação explícita (usando runTransaction / transaction.execute(pipeline) ou a opção atomic: true) tem o seguinte comportamento:

  • Todas as mutações são confirmadas juntas ou revertidas completamente em caso de falha.
  • Todos os estágios de consulta do pipeline são compatíveis antes de um estágio de DML (incluindo subconsultas where, add_fields, union, find_nearest e aggregate).
  • Em uma transação, todas as operações de leitura precisam ser executadas antes das operações de gravação. Como um pipeline de DML realiza gravações, não é possível executar leituras depois de executar um pipeline de DML na mesma transação. Por exemplo, não é possível chamar um pipeline de DML várias vezes em uma única transação nem ler documentos depois de executar um pipeline de DML. A tentativa de executar uma leitura após a execução de um pipeline de DML falha com um erro FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • A operação está sujeita aos limites de transação da edição Enterprise do Firestore.

Execução não transacional

A DML não transacional foi projetada para modificações em massa em um grande número de documentos sem que as limitações de tamanho da transação sejam atingidas. Ao bloquear no nível do documento, em vez de em toda a operação, ele permite que outras operações simultâneas continuem enquanto atualiza documentos em grande escala.

Quando as etapas de DML são executadas fora de uma transação explícita:

  • Atualizações independentes e sucesso parcial: cada documento é atualizado de forma independente, e as operações não são atômicas entre os documentos. O pipeline é interrompido no primeiro erro, e os documentos modificados anteriormente permanecem gravados no banco de dados, o que permite um sucesso parcial.
  • Suporte para preparo:
    • Estágios avançados e de várias consultas (como aggregate(...), distinct(...), unnest(...), find_nearest(...), union(...), mesclagens e subconsultas) não são compatíveis antes da DML fora de uma transação. Use uma transação se o pipeline exigir essas etapas.
  • Limite de 20 pesquisas das regras de segurança: cada lote de documentos é executado na própria transação e reavalia as regras de segurança. Se as regras de segurança usarem pesquisas get() ou exists(), cada lote vai executar uma pesquisa, atingindo o limite de 20 pesquisas por solicitação depois de gravar aproximadamente 200 documentos e falhar com PERMISSION_DENIED. Use declarações personalizadas em vez de pesquisas get() ou exists() para evitar esse limite.

Limitações

Observe as seguintes limitações para as etapas de DML:

  • Os estágios de DML precisam ser o último estágio em uma definição de pipeline antes de chamar .execute().
  • Quando executado fora de uma transação, cada documento é atualizado de forma independente, o que permite um sucesso parcial.
  • As operações DML não transacionais sujeitas a regras de segurança com pesquisas get() ou exists() são limitadas a aproximadamente 200 documentos devido ao limite de 20 pesquisas.
  • As operações DML transacionais são limitadas pelos limites de transação da edição Enterprise do Firestore. Como as transações do Firestore não permitem leituras após a primeira gravação, não é possível chamar um pipeline de DML várias vezes em uma única transação nem ler documentos após a execução de um pipeline de DML. A tentativa de executar uma leitura após uma gravação de DML falha com um erro FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • Se a etapa anterior à etapa de DML produzir vários documentos com o mesmo __name__ ou for avaliada com o mesmo ID de documento de destino, cada mutação será processada sequencialmente:
    • Para upsert(...) e update(...): várias mutações que visam o mesmo documento seguem a semântica "a última vence", em que a última mutação processada determina o estado final do documento.
    • Para delete(...): a primeira tentativa exclui o documento, e as tentativas subsequentes para o mesmo documento são no-ops.
    • Para insert(...): a primeira tentativa cria o documento, e as tentativas subsequentes que segmentam o mesmo ID de documento falham com um erro ALREADY_EXISTS.