Как изменить данные с помощью операций конвейера Firestore

Используйте этапы языка управления данными (DML) update(...) и delete(...), чтобы создавать конвейеры данных, которые могут запрашивать документы, а затем удалять или изменять данные.

Требования к версиям

Для выполнения описанных на этой странице операций требуется Firestore Enterprise.

Подготовка

Вы должны уметь запрашивать данные из базы данных с помощью операций конвейера.

Обновить документы

Используйте этап DML update(...), чтобы создавать конвейеры данных, которые могут запрашивать документы, а затем добавлять или изменять данные.

Все этапы DML должны находиться в конце конвейера. Документы, поступающие на этот этап, должны содержать поле __name__, чтобы определить, какие документы нужно обновить. Если хотя бы один из документов, которые вы пытаетесь обновить, не существует, операция завершится ошибкой.

Например, следующая операция заполняет изменение модели данных для всех документов в группе коллекций. Конвейер добавляет поле preferences.color во все документы в группе коллекций users, в которых оно отсутствует.

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();
Проложить маршрут
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)

Удалить документы

Используйте этап DML delete(...), чтобы создать конвейеры данных, которые могут запрашивать документы, а затем удалять данные. Чтобы предотвратить случайное массовое удаление, конвейеры, заканчивающиеся на delete(...), должны включать хотя бы один этап where(...). Все этапы DML должны находиться в конце конвейера.

Например, следующий конвейер удаляет все документы users, у которых для поля address.users задано значение USA, а для поля __create_time__ – значение меньше 10 дней:

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();
Проложить маршрут
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)

Режимы выполнения и согласованность

Этапы DML могут выполняться как внутри явной транзакции, так и вне ее:

Транзакционное выполнение

Транзакционный DML обеспечивает атомарную согласованность по принципу "все или ничего" для всех документов и поддерживает полный набор этапов конвейера запросов. Это делает его идеальным для рабочих нагрузок, требующих строгих гарантий согласованности, когда частичные обновления неприемлемы, или зависящих от сложных этапов запроса, при условии, что изменения укладываются в ограничения на размер транзакции.

При запуске конвейера DML в явной транзакции (с использованием runTransaction/transaction.execute(pipeline) или параметра atomic: true) происходит следующее:

  • Все изменения либо фиксируются вместе, либо полностью откатываются в случае сбоя.
  • Перед этапом DML поддерживаются все этапы запроса конвейера (включая подзапросы where, add_fields, union, find_nearest и aggregate).
  • В транзакции все операции чтения должны выполняться до операций записи. Поскольку конвейер DML выполняет запись, вы не можете выполнять чтение после запуска конвейера DML в той же транзакции. Например, вы не можете вызвать конвейер DML несколько раз в одной транзакции или прочитать документы после выполнения конвейера DML. Попытка выполнить чтение после запуска конвейера DML завершается ошибкой FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • На операцию распространяются ограничения на транзакции Firestore Enterprise.

Выполнение без транзакций

Нетранзакционный DML предназначен для массовых изменений в большом количестве документов без ограничений на размер транзакции. Блокировка на уровне документа, а не всей операции, позволяет выполнять другие параллельные операции при одновременном обновлении документов.

Если этапы DML выполняются вне явной транзакции:

  • Независимые обновления и частичный успех. Каждый документ обновляется независимо от других, и операции не являются атомарными для всех документов. Конвейер останавливается при первой ошибке, а ранее измененные документы остаются записанными в базу данных, что позволяет добиться частичного успеха.
  • Поддержка сцены.
    • Расширенные этапы и этапы с несколькими запросами (например, aggregate(...), distinct(...), unnest(...), find_nearest(...), union(...), объединения и подзапросы) не поддерживаются перед DML вне транзакции. Если в вашей воронке есть эти этапы, используйте транзакцию.
  • Ограничение на 20 поисков в правилах безопасности. Каждый пакет документов выполняется в собственной транзакции и повторно оценивает правила безопасности. Если в правилах безопасности используются запросы get() или exists(), каждый пакет выполняет запрос, достигая лимита в 20 запросов на запрос после записи примерно 200 документов и завершаясь с ошибкой PERMISSION_DENIED. Чтобы избежать этого ограничения, используйте специальные утверждения вместо поиска get() или exists().

Ограничения

Обратите внимание на следующие ограничения для этапов DML:

  • Этапы DML должны быть последними в определении конвейера перед вызовом .execute().
  • Если запустить метод вне транзакции, каждый документ будет обновляться независимо от других, что может привести к частичному успеху.
  • Операции DML, не связанные с транзакциями, на которые распространяются правила безопасности с поиском get() или exists(), ограничены примерно 200 документами из-за лимита в 20 поисков.
  • Транзакционные операции DML ограничены лимитами транзакций Firestore Enterprise. Поскольку транзакции Firestore не позволяют выполнять чтение после первой записи, вы не можете вызывать конвейер DML несколько раз в рамках одной транзакции, а также не можете читать документы после выполнения конвейера DML. Попытка выполнить чтение после записи DML завершается ошибкой FAILED_PRECONDITION: Cannot have reads after the first write in a Firestore transaction.
  • Если на этапе, предшествующем этапу DML, создается несколько документов с одинаковым значением __name__ или вычисляется один и тот же идентификатор целевого документа, каждое изменение обрабатывается последовательно:
    • Для upsert(...) и update(...): если несколько изменений нацелены на один и тот же документ, применяется правило "последний побеждает", то есть последнее обработанное изменение определяет окончательное состояние документа.
    • Для delete(...): при первой попытке документ удаляется, а последующие попытки для того же документа не выполняются.
    • Для insert(...): при первой попытке создается документ, а последующие попытки, нацеленные на тот же идентификатор документа, завершаются ошибкой ALREADY_EXISTS.