Contexto
As consultas de pipeline são uma nova interface de consulta para o Firestore. Ele oferece funcionalidades avançadas de consulta, incluindo expressões complexas. Ele também adiciona compatibilidade com muitas
funções novas, como min, max, substring, regex_match e array_contains_all.
Com as consultas de pipeline, a criação de índice também é totalmente opcional, simplificando o processo de desenvolvimento de novas consultas. As consultas de pipeline também removem muitas limitações no formato da consulta, permitindo que você especifique consultas grandes de in ou or.
Vamos começar
Para instalar e inicializar SDKs de cliente, consulte as instruções no Guia de início rápido.
Sintaxe
As seções a seguir oferecem uma visão geral da sintaxe das consultas de pipeline.
Conceitos
Uma diferença notável nas consultas de pipeline é a introdução da ordenação explícita de "etapas". Isso permite expressar consultas mais complexas. No entanto, é um desvio notável da interface de consulta atual, em que a ordenação das etapas era implícita. Considere o exemplo de consulta de pipeline a seguir:
Web
const pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort(field("name").ascending()) // Step 4: Return the top 10. Note applying the limit earlier in the // pipeline would have unintentional results. .limit(10);
Swift
let pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(Field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort([Field("name").ascending()]) // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have // unintentional results. .limit(10)
Kotlin
val pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort(field("name").ascending()) // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have // unintentional results. .limit(10)
Java
Pipeline pipeline = db.pipeline() // Step 1: Start a query with collection scope .collection("cities") // Step 2: Filter the collection .where(field("population").greaterThan(100000)) // Step 3: Sort the remaining documents .sort(field("name").ascending()) // Step 4: Return the top 10. Note applying the limit earlier in the pipeline would have // unintentional results. .limit(10);
Python
from google.cloud.firestore_v1.pipeline_expressions import Field pipeline = ( client.pipeline() .collection("cities") .where(Field.of("population").greater_than(100_000)) .sort(Field.of("name").ascending()) .limit(10) )
Inicialização
As consultas de pipeline têm uma sintaxe muito familiar das consultas Cloud Firestore atuais. Para começar, inicialize uma consulta escrevendo o seguinte:
Web
const { getFirestore } = require("firebase/firestore"); const { execute } = require("firebase/firestore/pipelines"); const database = getFirestore(app, "enterprise"); const pipeline = database.pipeline();
Swift
let firestore = Firestore.firestore(database: "enterprise") let pipeline = firestore.pipeline()
Kotlin
val firestore = Firebase.firestore("enterprise") val pipeline = firestore.pipeline()
Java
FirebaseFirestore firestore = FirebaseFirestore.getInstance("enterprise"); PipelineSource pipeline = firestore.pipeline();
Python
firestore_client = firestore.client(default_app, "your-new-enterprise-database") pipeline = firestore_client.pipeline()
Estrutura
Há alguns termos importantes para entender ao criar consultas de pipeline: etapas, expressões e funções.

Etapas: um pipeline pode consistir em uma ou mais etapas. Logicamente, eles representam a série de etapas (ou estágios) realizadas para executar a consulta. Observação: na prática, as etapas podem ser executadas fora de ordem para melhorar o desempenho. No entanto, isso não muda a intenção ou a correção da consulta.
Expressões: as etapas geralmente aceitam uma expressão, permitindo que você expresse consultas mais complexas. A expressão pode ser simples e consistir em uma única função, como eq("a", 1). Você também pode expressar expressões mais complexas aninhando expressões como and(eq("a", 1), eq("b", 2)).
Referências de campo x constantes
As consultas de pipeline aceitam expressões complexas. Assim, pode ser necessário diferenciar se um valor representa um campo ou uma constante. Confira o exemplo a seguir:
Web
const pipeline = db.pipeline() .collection("cities") .where(field("name").equal(constant("Toronto")));
Swift
let pipeline = db.pipeline() .collection("cities") .where(Field("name").equal(Constant("Toronto")))
Kotlin
val pipeline = db.pipeline() .collection("cities") .where(field("name").equal(constant("Toronto")))
Java
Pipeline pipeline = db.pipeline() .collection("cities") .where(field("name").equal(constant("Toronto")));
Python
from google.cloud.firestore_v1.pipeline_expressions import Field, Constant pipeline = ( client.pipeline() .collection("cities") .where(Field.of("name").equal(Constant.of("Toronto"))) )
Fases
Estágios de entrada
A etapa de entrada representa a primeira etapa de uma consulta. Ele define o conjunto inicial de documentos que você está consultando. Para consultas de pipeline, isso é muito parecido com as consultas atuais, em que a maioria começa com um estágio collection(...) ou collection_group(...). Dois novos estágios de entrada são database() e documents(...), em que database() permite retornar todos os documentos no banco de dados, enquanto documents(...) age de maneira idêntica a uma leitura em lote.
Web
let results; // Return all restaurants in San Francisco results = await execute(db.pipeline().collection("cities/sf/restaurants")); // Return all restaurants results = await execute(db.pipeline().collectionGroup("restaurants")); // Return all documents across all collections in the database (the entire database) results = await execute(db.pipeline().database()); // Batch read of 3 documents results = await execute(db.pipeline().documents([ doc(db, "cities", "SF"), doc(db, "cities", "DC"), doc(db, "cities", "NY") ]));
Swift
var results: Pipeline.Snapshot // Return all restaurants in San Francisco results = try await db.pipeline().collection("cities/sf/restaurants").execute() // Return all restaurants results = try await db.pipeline().collectionGroup("restaurants").execute() // Return all documents across all collections in the database (the entire database) results = try await db.pipeline().database().execute() // Batch read of 3 documents results = try await db.pipeline().documents([ db.collection("cities").document("SF"), db.collection("cities").document("DC"), db.collection("cities").document("NY") ]).execute()
Kotlin
var results: Task<Pipeline.Snapshot> // Return all restaurants in San Francisco results = db.pipeline().collection("cities/sf/restaurants").execute() // Return all restaurants results = db.pipeline().collectionGroup("restaurants").execute() // Return all documents across all collections in the database (the entire database) results = db.pipeline().database().execute() // Batch read of 3 documents results = db.pipeline().documents( db.collection("cities").document("SF"), db.collection("cities").document("DC"), db.collection("cities").document("NY") ).execute()
Java
Task<Pipeline.Snapshot> results; // Return all restaurants in San Francisco results = db.pipeline().collection("cities/sf/restaurants").execute(); // Return all restaurants results = db.pipeline().collectionGroup("restaurants").execute(); // Return all documents across all collections in the database (the entire database) results = db.pipeline().database().execute(); // Batch read of 3 documents results = db.pipeline().documents( db.collection("cities").document("SF"), db.collection("cities").document("DC"), db.collection("cities").document("NY") ).execute();
Python
# Return all restaurants in San Francisco results = client.pipeline().collection("cities/sf/restaurants").execute() # Return all restaurants results = client.pipeline().collection_group("restaurants").execute() # Return all documents across all collections in the database (the entire database) results = client.pipeline().database().execute() # Batch read of 3 documents results = ( client.pipeline() .documents( client.collection("cities").document("SF"), client.collection("cities").document("DC"), client.collection("cities").document("NY"), ) .execute() )
Assim como em todas as outras etapas, a ordem dos resultados dessas etapas de entrada não é estável. Um operador sort(...) sempre deve ser adicionado se uma ordenação específica for desejada.
Onde
A etapa where(...) funciona como uma operação de filtro tradicional em documentos gerados na etapa anterior e reflete principalmente a sintaxe "where" das consultas atuais. Qualquer documento em que uma determinada expressão seja avaliada como um valor diferente de true é filtrado dos documentos retornados.
Várias instruções where(...) podem ser encadeadas e agir como uma expressão and(...). Por exemplo, as duas consultas a seguir são logicamente equivalentes e podem ser usadas de forma intercambiável.
Web
let results; results = await execute(db.pipeline().collection("books") .where(field("rating").equal(5)) .where(field("published").lessThan(1900)) ); results = await execute(db.pipeline().collection("books") .where(and(field("rating").equal(5), field("published").lessThan(1900))) );
Swift
var results: Pipeline.Snapshot results = try await db.pipeline().collection("books") .where(Field("rating").equal(5)) .where(Field("published").lessThan(1900)) .execute() results = try await db.pipeline().collection("books") .where(Field("rating").equal(5) && Field("published").lessThan(1900)) .execute()
Kotlin
var results: Task<Pipeline.Snapshot> results = db.pipeline().collection("books") .where(field("rating").equal(5)) .where(field("published").lessThan(1900)) .execute() results = db.pipeline().collection("books") .where(Expression.and(field("rating").equal(5), field("published").lessThan(1900))) .execute()
Java
Task<Pipeline.Snapshot> results; results = db.pipeline().collection("books") .where(field("rating").equal(5)) .where(field("published").lessThan(1900)) .execute(); results = db.pipeline().collection("books") .where(Expression.and( field("rating").equal(5), field("published").lessThan(1900) )) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import And, Field results = ( client.pipeline() .collection("books") .where(Field.of("rating").equal(5)) .where(Field.of("published").less_than(1900)) .execute() ) results = ( client.pipeline() .collection("books") .where(And(Field.of("rating").equal(5), Field.of("published").less_than(1900))) .execute() )
Selecionar / Adicionar e remover campos
Os comandos select(...), add_fields(...) e remove_fields(...) permitem modificar os campos retornados de uma etapa anterior. Esses três são geralmente chamados de estágios de estilo de projeção.
As funções select(...) e add_fields(...) permitem especificar o resultado de uma expressão para um nome de campo fornecido pelo usuário. Uma expressão que resulta em um erro
vai gerar um valor null. O select(...) retorna apenas os documentos com os nomes de campo especificados, enquanto o add_fields(...) estende o esquema da etapa anterior, podendo substituir valores com nomes de campo idênticos.
O remove_fields(...) permite especificar um conjunto de campos a serem removidos da
etapa anterior. Especificar nomes de campos que não existem não faz nada.
Consulte a seção Restringir os campos a serem retornados abaixo, mas, em geral, usar essa etapa para restringir o resultado apenas aos campos necessários no cliente ajuda a reduzir o custo e a latência da maioria das consultas.
Agregar / Distinto
O estágio aggregate(...) permite realizar uma série de agregações nos documentos de entrada. Por padrão, todos os documentos são agregados, mas um argumento grouping opcional pode ser fornecido, permitindo que os documentos de entrada sejam agregados em diferentes intervalos.
Web
const results = await execute(db.pipeline() .collection("books") .aggregate( field("rating").average().as("avg_rating") ) .distinct(field("genre")) );
Swift
let results = try await db.pipeline() .collection("books") .aggregate([ Field("rating").average().as("avg_rating") ], groups: [ Field("genre") ]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .aggregate( AggregateStage .withAccumulators(AggregateFunction.average("rating").alias("avg_rating")) .withGroups(field("genre")) ) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .aggregate(AggregateStage .withAccumulators( AggregateFunction.average("rating").alias("avg_rating")) .withGroups(field("genre"))) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .aggregate( Field.of("rating").average().as_("avg_rating"), groups=[Field.of("genre")] ) .execute() )
Quando groupings não é especificado, essa etapa produz apenas um documento. Caso contrário, um documento é gerado para cada combinação exclusiva de valores de groupings.
A etapa distinct(...) é um operador de agregação simplificado que permite gerar apenas o groupings exclusivo sem acumuladores. Ele se comporta de maneira idêntica ao de aggregate(...) em todos os outros aspectos. Confira um exemplo abaixo.
Web
const results = await execute(db.pipeline() .collection("books") .distinct( field("author").toUpper().as("author"), field("genre") ) );
Swift
let results = try await db.pipeline() .collection("books") .distinct([ Field("author").toUpper().as("author"), Field("genre") ]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .distinct( field("author").toUpper().alias("author"), field("genre") ) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .distinct( field("author").toUpper().alias("author"), field("genre") ) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .distinct(Field.of("author").to_upper().as_("author"), "genre") .execute() )
Funções
As funções são um elemento básico para criar expressões e consultas complexas. Para uma lista completa de funções com exemplos, consulte a Referência de funções. Como um lembrete rápido, considere a estrutura de uma consulta típica:

Muitos estágios aceitam expressões que contêm uma ou mais funções. O uso mais comum de funções é encontrado nas etapas where(...) e select(...). Há dois tipos principais de funções que você precisa conhecer:
Web
let results; // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = await execute(db.pipeline().collection("books") .select(field("current").logicalMinimum(field("updated")).as("price_min")) ); // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = await execute(db.pipeline().collection("books") .aggregate(field("price").minimum().as("min_price")) );
Swift
var results: Pipeline.Snapshot // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = try await db.pipeline().collection("books") .select([ Field("current").logicalMinimum(["updated"]).as("price_min") ]) .execute() // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = try await db.pipeline().collection("books") .aggregate([Field("price").minimum().as("min_price")]) .execute()
Kotlin
var results: Task<Pipeline.Snapshot> // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = db.pipeline().collection("books") .select( field("current").logicalMinimum("updated").alias("price_min") ) .execute() // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = db.pipeline().collection("books") .aggregate(AggregateFunction.minimum("price").alias("min_price")) .execute()
Java
Task<Pipeline.Snapshot> results; // Type 1: Scalar (for use in non-aggregation stages) // Example: Return the min store price for each book. results = db.pipeline().collection("books") .select( field("current").logicalMinimum("updated").alias("price_min") ) .execute(); // Type 2: Aggregation (for use in aggregate stages) // Example: Return the min price of all books. results = db.pipeline().collection("books") .aggregate(AggregateFunction.minimum("price").alias("min_price")) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field # Type 1: Scalar (for use in non-aggregation stages) # Example: Return the min store price for each book. results = ( client.pipeline() .collection("books") .select( Field.of("current").logical_minimum(Field.of("updated")).as_("price_min") ) .execute() ) # Type 2: Aggregation (for use in aggregate stages) # Example: Return the min price of all books. results = ( client.pipeline() .collection("books") .aggregate(Field.of("price").minimum().as_("min_price")) .execute() )
Limites
Na maioria das vezes, a Enterprise Edition não impõe limites ao formato da consulta. Em outras palavras, você não está limitado a um pequeno número de valores em uma consulta IN ou OR. Em vez disso, há dois limites principais que você precisa conhecer:
- Prazo: 60 segundos (igual à Standard Edition).
- Uso de memória: limite de 128 MiB na quantidade de dados materializados durante a execução da consulta.
Erros
Você pode encontrar consultas com falha por vários motivos. Confira um link para erros comuns e a ação associada que você pode realizar:
| Código do erro | Ação |
DEADLINE_EXCEEDED
|
A consulta que você está executando excede um prazo de 60 segundos e requer mais otimização. Consulte a seção de performance para dicas. Se não for possível identificar a causa principal do problema, entre em contato com a equipe. |
RESOURCE_EXHAUSTED
|
A consulta que você está executando excede os limites de memória e exige mais otimização. Consulte a seção de performance para dicas. Se não for possível identificar a causa principal do problema, entre em contato com a equipe. |
INTERNAL
|
Entre em contato com a equipe para receber ajuda. |
Desempenho
Ao contrário das consultas atuais, as consultas de pipeline não exigem que um índice esteja sempre presente. Isso significa que uma consulta pode apresentar maior latência em comparação com as consultas atuais, que falhariam imediatamente com um erro de índice ausente FAILED_PRECONDITION. Para melhorar o desempenho das consultas de pipeline, siga algumas etapas.
Criar índices
Índice usado
Com a explicação da consulta, é possível identificar se ela está sendo atendida por um índice ou voltando para uma operação menos eficiente, como uma verificação de tabela. Se a consulta não estiver sendo totalmente veiculada de um índice, crie um seguindo as instruções.
Como criar índices
Siga a documentação de gerenciamento de índices para criar índices. Antes de criar um índice, conheça as práticas recomendadas gerais com índices no Firestore. Para garantir que sua consulta possa usar índices, siga as práticas recomendadas para criar índices com campos na seguinte ordem:
- Todos os campos que serão usados em filtros de igualdade (em qualquer ordem)
- Todos os campos que serão classificados (na mesma ordem)
- Campos que serão usados em filtros de intervalo ou desigualdade em ordem decrescente de seletividade de restrição de consulta
Por exemplo, para a seguinte consulta:
Web
const results = await execute(db.pipeline() .collection("books") .where(field("published").lessThan(1900)) .where(field("genre").equal("Science Fiction")) .where(field("rating").greaterThan(4.3)) .sort(field("published").descending()) );
Swift
let results = try await db.pipeline() .collection("books") .where(Field("published").lessThan(1900)) .where(Field("genre").equal("Science Fiction")) .where(Field("rating").greaterThan(4.3)) .sort([Field("published").descending()]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .where(field("published").lessThan(1900)) .where(field("genre").equal("Science Fiction")) .where(field("rating").greaterThan(4.3)) .sort(field("published").descending()) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .where(field("published").lessThan(1900)) .where(field("genre").equal("Science Fiction")) .where(field("rating").greaterThan(4.3)) .sort(field("published").descending()) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .where(Field.of("published").less_than(1900)) .where(Field.of("genre").equal("Science Fiction")) .where(Field.of("rating").greater_than(4.3)) .sort(Field.of("published").descending()) .execute() )
O índice recomendado é um índice de escopo de coleção em books para (genre [...], published DESC, avg_rating DESC).
Densidade de índice
O Cloud Firestore é compatível com índices esparsos e não esparsos. Para mais informações, consulte Densidade de índice.
Consultas cobertas + índices secundários
O Firestore pode pular a busca do documento completo e retornar apenas os resultados do índice se todos os campos retornados estiverem presentes em um índice secundário. Isso normalmente leva a uma melhoria significativa na latência (e no custo). Usando o exemplo de consulta abaixo:
Web
const results = await execute(db.pipeline() .collection("books") .where(field("category").like("%fantasy%")) .where(field("title").exists()) .where(field("author").exists()) .select(field("title"), field("author")) );
Swift
let results = try await db.pipeline() .collection("books") .where(Field("category").like("%fantasy%")) .where(Field("title").exists()) .where(Field("author").exists()) .select([Field("title"), Field("author")]) .execute()
Kotlin
val results = db.pipeline() .collection("books") .where(field("category").like("%fantasy%")) .where(field("title").exists()) .where(field("author").exists()) .select(field("title"), field("author")) .execute()
Java
Task<Pipeline.Snapshot> results = db.pipeline() .collection("books") .where(field("category").like("%fantasy%")) .where(field("title").exists()) .where(field("author").exists()) .select(field("title"), field("author")) .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field results = ( client.pipeline() .collection("books") .where(Field.of("category").like("%fantasy%")) .where(Field.of("title").exists()) .where(Field.of("author").exists()) .select("title", "author") .execute() )
Se o banco de dados já tiver um índice de escopo de coleção em books para (category [...], title [...], author [...]), ele poderá evitar buscar qualquer coisa dos próprios documentos principais. Nesse caso, a ordem no índice não importa, e [...] é usado para indicar isso.
Restringir os campos a serem retornados
Por padrão, uma consulta do Firestore retorna todos os campos em um documento, semelhante a um SELECT * em sistemas tradicionais. No entanto, se o aplicativo precisar apenas de um subconjunto dos campos, as etapas select(...) ou restrict(...) poderão ser usadas para enviar essa filtragem do lado do servidor. Isso vai diminuir o tamanho da resposta (reduzindo o custo de saída da rede) e melhorar a latência.
Ferramentas de solução de problemas
Query Explain
Com o Query Explain, você tem visibilidade nas métricas de execução e detalhes sobre os índices usados.
Métricas
As consultas de pipeline são totalmente integradas às métricas do Firestore.
Limitações e problemas conhecidos
Índices especializados
As consultas de pipeline ainda não são compatíveis com os tipos de índice array-contains e vector atuais. Em vez de apenas rejeitar essas consultas, o Firestore tentará usar outros índices ascending e descending existentes. É esperado que, durante a prévia particular, as consultas de pipeline com essas expressões array_contains ou find_nearest sejam mais lentas do que as equivalentes atuais devido a isso.
Paginação
O suporte para paginação fácil em um conjunto de resultados não está disponível durante a prévia particular. Isso pode ser evitado encadeando estágios equivalentes de where(...) e sort(...), conforme mostrado abaixo.
Web
// Existing pagination via `startAt()` const q = query(collection(db, "cities"), orderBy("population"), startAt(1000000)); // Private preview workaround using pipelines const pageSize = 2; const pipeline = db.pipeline() .collection("cities") .select("name", "population", "__name__") .sort(field("population").descending(), field("__name__").ascending()); // Page 1 results let snapshot = await execute(pipeline.limit(pageSize)); // End of page marker const lastDoc = snapshot.results[snapshot.results.length - 1]; // Page 2 results snapshot = await execute( pipeline .where( or( and( field("population").equal(lastDoc.get("population")), field("__name__").greaterThan(lastDoc.ref) ), field("population").lessThan(lastDoc.get("population")) ) ) .limit(pageSize) );
Swift
// Existing pagination via `start(at:)` let query = db.collection("cities").order(by: "population").start(at: [1000000]) // Private preview workaround using pipelines let pipeline = db.pipeline() .collection("cities") .where(Field("population").greaterThanOrEqual(1000000)) .sort([Field("population").descending()])
Kotlin
// Existing pagination via `startAt()` val query = db.collection("cities").orderBy("population").startAt(1000000) // Private preview workaround using pipelines val pipeline = db.pipeline() .collection("cities") .where(field("population").greaterThanOrEqual(1000000)) .sort(field("population").descending())
Java
// Existing pagination via `startAt()` Query query = db.collection("cities").orderBy("population").startAt(1000000); // Private preview workaround using pipelines Pipeline pipeline = db.pipeline() .collection("cities") .where(field("population").greaterThanOrEqual(1000000)) .sort(field("population").descending());
Python
from google.cloud.firestore_v1.pipeline_expressions import Field # Existing pagination via `start_at()` query = ( client.collection("cities") .order_by("population") .start_at({"population": 1_000_000}) ) # Private preview workaround using pipelines pipeline = ( client.pipeline() .collection("cities") .where(Field.of("population").greater_than_or_equal(1_000_000)) .sort(Field.of("population").descending()) )
Compatiblidade com o emulador
O emulador ainda não é compatível com consultas de pipeline.
Suporte em tempo real e off-line
As consultas de pipeline ainda não têm recursos em tempo real e off-line.
A seguir
- Comece a explorar a documentação de referência de funções e etapas.