Premiers pas avec l'opération Firestore Pipelines

Arrière-plan

Les requêtes de pipeline sont une nouvelle interface de requête pour Firestore. Il fournit des fonctionnalités de requête avancées, y compris 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 requêtes de pipeline, la création d'index est également totalement facultative, ce qui simplifie le processus de développement de nouvelles requêtes. Les requêtes de pipeline suppriment également de nombreuses limites concernant la forme des requêtes, ce qui vous permet de spécifier des requêtes in ou or volumineuses.

Premiers pas

Pour installer et initialiser les SDK clients, consultez les instructions du guide de démarrage.

Syntaxe

Les sections suivantes présentent la syntaxe des requêtes de pipeline.

Concepts

Une différence notable avec les requêtes 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'un écart notable par rapport à l'interface de requête existante, où l'ordre des étapes était implicite. Prenons l'exemple de requête de pipeline suivant :

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

Initialisation

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

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

Structure

Lorsque vous créez des requêtes de pipeline, il est important de comprendre certains termes : é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 requêtes 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 :

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

É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 requêtes 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.

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

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é.

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.

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

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.

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

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 :

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

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 :

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

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 requêtes de pipeline ne nécessitent pas 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 requêtes 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 :

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

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

Densité de l'index

Cloud Firestore est compatible avec les index denses et clairsemés. 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 :

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

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 de la requête

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

Métriques

Les requêtes de pipeline sont entièrement intégrées aux métriques Firestore existantes.

Problèmes et limites connus

Index spécialisés

Les requêtes 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, pendant l'aperçu privé, les requêtes de pipeline avec de telles expressions array_contains ou find_nearest soient plus lentes que leurs équivalents existants.

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.

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

Prise en charge des émulateurs

L'émulateur n'est pas encore compatible avec les requêtes de pipeline.

Assistance en temps réel et hors connexion

Les requêtes de pipeline ne disposent pas encore de fonctionnalités en temps réel et hors connexion.

Étape suivante