Premiers pas avec les requêtes de pipeline

Arrière-plan

Les opérations de pipeline sont une nouvelle interface de requête pour Firestore. Cette interface fournit des fonctionnalités de requête avancées qui incluent des expressions complexes. Il ajoute également la prise en charge de nombreuses nouvelles fonctions telles que min, max, substring, regex_match et array_contains_all.

Avec les opérations de pipeline, la création d'index est également entièrement facultative, ce qui simplifie le processus de développement de nouvelles requêtes. Les opérations de pipeline suppriment également de nombreuses limites concernant la forme des requêtes, ce qui vous permet de spécifier de grandes requêtes in ou or.

Premiers pas

Pour installer et initialiser les SDK clients, consultez les instructions des guides suivants :

Syntaxe

Les sections suivantes présentent la syntaxe des opérations de pipeline.

Concepts

Une différence notable avec les opérations de pipeline est l'introduction d'un ordre de "phase" explicite. Cela permet d'exprimer des requêtes plus complexes. Toutefois, il s'agit d'une déviation notable par rapport à l'interface de requête existante (opérations de base) où l'ordre des étapes était implicite. Prenons l'exemple d'opération de pipeline suivant :

Node.js
  db.pipeline()
    .collection() // Step 1 (start a query with 'collection' scope)
    .where()      // Step 2 (filter collection)
    .sort()       // Step 3 (order results)
    .limit()      // Step 4 (limit results)

  // Note: Applying a limit before a sort can yield unintended
  // results (as the limit would be applied before sorting).
    

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
Android
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
Android
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)
)

Initialisation

Les opérations de pipeline ont une syntaxe très familière qui provient des requêtes Firestore existantes. Pour commencer, initialisez une requête en écrivant ce qui suit :

Node.js
  const db = new Firestore({ projectId: '', databaseId: databaseId'})
  db.pipeline()
    
Java
  Firestore db = FirestoreOptions.newBuilder().build().getService();
  db.pipeline()
    

Structure

Il est important de comprendre certains termes lorsque vous créez des opérations de pipeline : étapes, expressions et fonctions.

Exemple illustrant les étapes et les expressions d'une requête

Étapes : un pipeline peut comporter une ou plusieurs étapes. Logiquement, ils représentent la série d'étapes (ou de phases) nécessaires à l'exécution de la requête. Remarque : Dans la pratique, les étapes peuvent être exécutées dans le désordre pour améliorer les performances. Toutefois, cela ne modifie pas l'intention ni la pertinence de la requête.

Expressions : les étapes acceptent souvent une expression qui vous permet de formuler des requêtes plus complexes. L'expression peut être simple et se composer d'une seule fonction, comme eq("a", 1). Vous pouvez également exprimer des expressions plus complexes en imbriquant des expressions comme and(eq("a", 1), eq("b", 2))..

Références de champ et références constantes

Les opérations de pipeline sont compatibles avec les expressions complexes. Il peut donc être nécessaire de faire la différence entre une valeur de champ et une valeur constante. Prenons l'exemple suivant :

Node.js
  // Here the two parameters "name" and "toronto" could represent fields or constants.

  db.pipeline()
    .collection("cities")
    .where(eq("name","toronto"))

  // In many cases, it's not necessary to differentiate between the two. In the
  // case of equality, we will implicit treat the first parameter as a field 
  // reference and the second as a constant. However if you need to override you 
  // can always be explicit with the value types
  
  db.pipeline()
    .collection("cities")
    .where(eq(Field.of("name"), Constant.of("toronto")))

  // In some cases, being explicit is always required. However, it should be
  // enough to look at the type signature of the expressions to know what
  //parameters can be used with implicit types, and what should be explicitly specified. 
    

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
Android
val pipeline = db.pipeline()
    .collection("cities")
    .where(field("name").equal(constant("Toronto")))
Java
Android
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")))
)

Étapes

Étapes d'entrée

L'étape d'entrée représente la première étape d'une requête. Il définit l'ensemble initial de documents sur lesquels vous effectuez des requêtes. Pour les opérations de pipeline, cela ressemble en grande partie aux requêtes existantes, où la plupart des requêtes commencent par une étape collection(...) ou collection_group(...). Deux nouvelles étapes d'entrée sont database() et documents(...), où database() permet de renvoyer tous les documents de la base de données, tandis que documents(...) agit de la même manière qu'une lecture par lot.

Node.js
  // Return all restaurants in San Francisco
  const results = await db.pipeline()
    .collection("cities/sf/restaurants")
    .execute();

  // Return all restaurants
  const results = await db.pipeline()
    .collectionGroup("restaurants")
    .execute();

  // Return all documents across all collections in the database (huge result!)
  const results = await db.pipeline()
    .database()
    .execute();

  // Batch read of 3 documents
  const results = await db.pipeline()
    .documents(
      db.collection("cities").doc("SF"),
      db.collection("cities").doc("DC"),
      db.collection("cities").doc("NY"))
    .execute();
    

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
Android
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
Android
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()
)

Comme pour toutes les autres étapes, l'ordre des résultats de ces étapes d'entrée n'est pas stable. Un opérateur sort(...) doit toujours être ajouté si un ordre spécifique est souhaité.

Lieu

L'étape where(...) agit comme une opération de filtrage traditionnelle sur les documents générés à partir de l'étape précédente et reflète en grande partie la syntaxe "where" existante pour les requêtes existantes. Tout document pour lequel une expression donnée est évaluée à une valeur non true est filtré des documents renvoyés.

Plusieurs instructions where(...) peuvent être enchaînées et agir comme une expression and(...). Par exemple, les deux requêtes suivantes sont logiquement équivalentes et peuvent être utilisées de manière interchangeable.

Node.js
  const results = await db.pipeline()
    .collection("books")
    .where(eq("rating", 5.0))
    .where(lt('published', 1900))
    .execute();
  
  const results = await db.pipeline()
    .collection("books")
    .where(and(
      eq("rating", 5.0), 
      lt('published', 1900)))
    .execute();
    

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
Android
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
Android
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()
)

Sélectionner / Ajouter et supprimer des champs

select(...), add_fields(...) et remove_fields(...) vous permettent de modifier les champs renvoyés à partir d'une étape précédente. Ces trois types de mises en scène sont généralement appelés "mises en scène de type projection".

select(...) et add_fields(...) vous permettent de spécifier le résultat d'une expression dans un nom de champ fourni par l'utilisateur. Une expression qui génère une erreur renvoie une valeur null. select(...) ne renvoie que les documents avec les noms de champs spécifiés, tandis que add_fields(...) étend le schéma de l'étape précédente (en écrasant potentiellement les valeurs avec des noms de champs identiques).

remove_fields(...) permet de spécifier un ensemble de champs à supprimer de l'étape précédente. La spécification de noms de champs inexistants n'a aucun effet.

Consultez la section Restreindre les champs à renvoyer ci-dessous. En général, l'utilisation d'une telle étape pour limiter le résultat aux seuls champs nécessaires dans le client est utile pour réduire le coût et la latence de la plupart des requêtes.

Agréger / Distinct

L'étape aggregate(...) vous permet d'effectuer une série d'agrégations sur les documents d'entrée. Par défaut, tous les documents sont agrégés, mais un argument grouping facultatif peut être fourni, ce qui permet d'agréger les documents d'entrée dans différents buckets.

Node.js
  const results = await db.pipeline()
    .collection("books")
    .aggregate({
        accumulators: [avg('rating').as('avg_rating')],
        groups: ['genre'],
      })
    .execute();
    

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
Android
val results = db.pipeline()
    .collection("books")
    .aggregate(
        AggregateStage
            .withAccumulators(AggregateFunction.average("rating").alias("avg_rating"))
            .withGroups(field("genre"))
    )
    .execute()
Java
Android
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()
)

Lorsque groupings n'est pas spécifié, cette étape ne produit qu'un seul document. Sinon, un document est généré pour chaque combinaison unique de valeurs groupings.

L'étape distinct(...) est un opérateur d'agrégation simplifié qui permet de générer uniquement les groupings uniques sans aucun accumulateur. Il se comporte de manière identique à celui de aggregate(...) pour tous les autres aspects. Voici un exemple :

Node.js
  const results = await db.pipeline()
    .collection("books")
    .distinct(toUppercase(Field.of("author")).as("author"), Field.of("genre"))
    .execute();
    

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
Android
val results = db.pipeline()
    .collection("books")
    .distinct(
        field("author").toUpper().alias("author"),
        field("genre")
    )
    .execute()
Java
Android
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()
)

Fonctions

Les fonctions sont des éléments de base pour créer des expressions et des requêtes complexes. Pour obtenir une liste complète des fonctions avec des exemples, consultez la documentation de référence sur les fonctions. Pour rappel, voici la structure d'une requête typique :

Exemple illustrant les étapes et les fonctions d&#39;une requête

De nombreuses étapes acceptent les expressions contenant une ou plusieurs fonctions. L'utilisation la plus courante des fonctions se trouve dans les étapes where(...) et select(...). Il existe deux principaux types de fonctions que vous devez connaître :

Node.js
  // Type 1: Scalar (for use in non-aggregation stages)
  // Example: Return the min store price for each book.

  const results = await db.pipeline()
    .collection("books")
    .select(logicalMin(Field.of("current"),Field.of("updated")).as("price_min"))
    .execute();

  // Type 2: Aggregation (for use in aggregate stages)
  // Example: Return the min price of all books.

  const results = await db.pipeline()
    .collection("books")
    .aggregate(min(Field.of("price"))
    .execute();
    

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
Android
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
Android
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

Dans la plupart des cas, Enterprise Edition n'impose pas de limites à la forme de la requête. En d'autres termes, vous n'êtes pas limité à un petit nombre de valeurs dans une requête IN ou OR. En revanche, vous devez connaître deux limites principales :

  • Délai : 60 secondes (identique à l'édition Standard).
  • Utilisation de la mémoire : limite de 128 Mio sur la quantité de données matérialisées lors de l'exécution de la requête.

Erreurs

Plusieurs raisons peuvent expliquer l'échec des requêtes. Voici un lien vers les erreurs courantes et les actions associées que vous pouvez effectuer :

Code d'erreur Action
DEADLINE_EXCEEDED La requête que vous exécutez dépasse le délai de 60 secondes et nécessite une optimisation supplémentaire. Consultez la section sur les performances pour obtenir des conseils. Si vous ne parvenez pas à identifier la cause première du problème, contactez l'équipe.
RESOURCE_EXHAUSTED La requête que vous exécutez dépasse les limites de mémoire et nécessite une optimisation supplémentaire. Consultez la section sur les performances pour obtenir des conseils. Si vous ne parvenez pas à identifier la cause première du problème, contactez l'équipe.
INTERNAL Contactez l'équipe pour obtenir de l'aide.

Performances

Contrairement aux requêtes existantes, les opérations de pipeline ne nécessitent pas toujours la présence d'un index. Cela signifie qu'une requête peut présenter une latence plus élevée que les requêtes existantes, qui auraient échoué immédiatement avec une erreur d'index manquant FAILED_PRECONDITION. Pour améliorer les performances des opérations de pipeline, vous pouvez suivre quelques étapes.

Créer des index

Index utilisé

L'explication des requêtes vous permet de déterminer si votre requête est traitée par un index ou si elle est redirigée vers une opération moins efficace, comme une analyse de table. Si votre requête n'est pas entièrement traitée à partir d'un index, vous pouvez en créer un en suivant les instructions.

Créer des index

Vous pouvez suivre la documentation existante sur la gestion des index pour créer des index. Avant de créer un index, familiarisez-vous avec les bonnes pratiques générales concernant les index dans Firestore. Pour vous assurer que votre requête peut exploiter les index, suivez les bonnes pratiques pour créer des index avec des champs dans l'ordre suivant :

  1. Tous les champs qui seront utilisés dans les filtres d'égalité (dans n'importe quel ordre)
  2. Tous les champs sur lesquels le tri sera effectué (dans le même ordre)
  3. Champs qui seront utilisés dans les filtres de plage ou d'inégalité, par ordre décroissant de sélectivité des contraintes de requête

Par exemple, pour la requête suivante :

Node.js
const results = await db.pipeline()
  .collection('books')
  .where(lt('published', 1900))
  .where(eq('genre', 'Science Fiction'))
  .where(gt('avg_rating', 4.3))
  .sort(Field.of('published').descending())
  .execute();
    

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
Android
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
Android
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()
)

L'index recommandé est un index de portée de collection sur books pour (genre [...], published DESC, avg_rating DESC)..

Densité de l'index

Firestore est compatible avec les index denses et creux. Pour en savoir plus, consultez Densité d'index.

Requêtes couvertes + index secondaires

Firestore peut ignorer la récupération du document complet et ne renvoyer que les résultats de l'index si tous les champs renvoyés sont présents dans un index secondaire. Cela permet généralement d'améliorer considérablement la latence (et les coûts). Utilisez l'exemple de requête ci-dessous :

Node.js
const results = await db.pipeline()
  .collection("books")
  .where(like(Field.of("category"), "%fantasy%"))
  .where(exists("title"))
  .where(exists("author"))
  .select("title", "author")
  .execute();
    

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
Android
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
Android
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()
)

Si la base de données dispose déjà d'un index de portée de collection sur books pour (category [...], title [...], author [...]), elle peut éviter de récupérer des données à partir des documents principaux eux-mêmes. Dans ce cas, l'ordre dans l'index n'a pas d'importance. [...] est utilisé pour l'indiquer.

Restreindre les champs à renvoyer

Par défaut, une requête Firestore renvoie tous les champs d'un document, ce qui est analogue à un SELECT * dans les systèmes traditionnels. Toutefois, si votre application n'a besoin que d'un sous-ensemble de champs, les étapes select(...) ou restrict(...) peuvent être utilisées pour effectuer ce filtrage côté serveur. Cela réduira la taille de la réponse (et donc les coûts de sortie réseau) et améliorera la latence.

Outils de dépannage

Explication des requêtes

Query Explain vous permet de visualiser les métriques d'exécution et les détails sur les index utilisés.

Métriques

Les opérations de pipeline sont entièrement intégrées aux métriques Firestore existantes.

Problèmes et limites connus

Index spécialisés

Les opérations de pipeline ne sont pas encore compatibles avec les types d'index array-contains et vector existants. Au lieu de simplement rejeter ces requêtes, Firestore tentera d'utiliser d'autres index ascending et descending existants. Il est prévu que les opérations de pipeline avec de telles expressions array_contains ou find_nearest soient plus lentes que leurs équivalents existants pendant la preview privée.

Pagination

La pagination facile sur un ensemble de résultats n'est pas disponible pendant la preview privée. Pour contourner ce problème, vous pouvez enchaîner des étapes where(...) et sort(...) équivalentes, comme indiqué ci-dessous.

Node.js
  // Existing pagination via 'startAt(...)'.
  db.collection("cities")
    .orderBy("population")
    .startAt(1000000);

  // Near-term work around via Pipeline operations.
  db.pipeline()
    .collection("cities")
    .where(gte("population", 1000000)
    .sort(Field.of("population").descending());
    

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
Android
// 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
Android
// 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())
)

Prise en charge des émulateurs

L'émulateur n'est pas encore compatible avec les opérations de pipeline.

Assistance en temps réel et hors connexion

Les opérations de pipeline ne disposent pas encore de fonctionnalités en temps réel et hors connexion.

Étape suivante