Tło
Operacje na potokach to nowy interfejs zapytań dla Cloud Firestore.
Ten interfejs udostępnia zaawansowane funkcje zapytań, w tym złożone wyrażenia. Dodaje też obsługę wielu nowych funkcji, takich jak min, max, substring, regex_match i array_contains_all.
W przypadku operacji potoku tworzenie indeksu jest też całkowicie opcjonalne, co usprawnia proces opracowywania nowych zapytań. Operacje potoku usuwają też wiele ograniczeń dotyczących kształtu zapytania, co pozwala określać duże zapytania in lub or.
Pierwsze kroki
Aby zainstalować i zainicjować pakiety SDK klienta, postępuj zgodnie z instrukcjami w tych przewodnikach:
- Pierwsze kroki z pakietami SDK na urządzenia mobilne i do internetu
- Pierwsze kroki z bibliotekami klienta serwera
Składnia
W sekcjach poniżej znajdziesz omówienie składni operacji potoku.
Pojęcia
Jedną z najważniejszych różnic w przypadku operacji Pipeline jest wprowadzenie jawnego porządkowania „etapów”. Umożliwia to wyrażanie bardziej złożonych zapytań. Jest to jednak znaczące odstępstwo od istniejącego interfejsu zapytań (operacje podstawowe), w którym kolejność etapów była domyślna. Oto przykład operacji Pipeline:
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) )
Zdarzenie inicjujące
Operacje potoku mają bardzo znaną składnię pochodzącą z dotychczasowych zapytań Cloud Firestore. Aby rozpocząć, zainicjuj zapytanie, wpisując:
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()
Struktura
Podczas tworzenia operacji potoku warto znać kilka ważnych pojęć: etapy, wyrażenia i funkcje.

Etapy: potok może składać się z co najmniej 1 etapu. Logicznie reprezentują one serię kroków (lub etapów) wykonywanych w celu przetworzenia zapytania. Uwaga: w praktyce etapy mogą być wykonywane w innej kolejności, aby zwiększyć wydajność. Nie zmienia to jednak intencji ani poprawności zapytania.
Wyrażenia: etapy często akceptują wyrażenia, które umożliwiają tworzenie bardziej złożonych zapytań. Wyrażenie może być proste i składać się z jednej funkcji, np. eq("a", 1). Możesz też tworzyć bardziej złożone wyrażenia, zagnieżdżając wyrażenia, np. and(eq("a", 1), eq("b", 2)).
Odwołania do pól i stałych
Operacje potoku obsługują złożone wyrażenia. Dlatego może być konieczne rozróżnienie, czy wartość reprezentuje pole czy stałą. Przyjrzyj się temu przykładowi:
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"))) )
Etapy
Etapy wprowadzania
Etap wprowadzania to pierwszy etap zapytania. Określa początkowy zestaw dokumentów, w których wyszukujesz informacje. W przypadku operacji potoku jest to w dużej mierze podobne do istniejących zapytań, w których większość zapytań zaczyna się od etapu collection(...) lub collection_group(...). Dwa nowe etapy wejściowe to database() i documents(...). Etap database() umożliwia zwracanie wszystkich dokumentów w bazie danych, a etap documents(...) działa identycznie jak odczyt wsadowy.
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() )
Podobnie jak w przypadku wszystkich innych etapów kolejność wyników z tych etapów wejściowych nie jest stabilna. Jeśli chcesz uzyskać konkretną kolejność, zawsze dodawaj operatora sort(...).
Gdzie
Etap where(...) działa jak tradycyjna operacja filtrowania dokumentów
wygenerowanych na poprzednim etapie i w większości odzwierciedla istniejącą składnię „where”
w przypadku dotychczasowych zapytań. Wszystkie dokumenty, w których dane wyrażenie przyjmuje wartość inną niż true, są odfiltrowywane z zwracanych dokumentów.
Kilka instrukcji where(...) można połączyć w łańcuch i używać jako wyrażenia and(...). Na przykład te 2 zapytania są logicznie równoważne i można ich używać zamiennie.
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() )
Wybieranie, dodawanie i usuwanie pól
Funkcje select(...), add_fields(...) i remove_fields(...) umożliwiają modyfikowanie pól zwracanych z poprzedniego etapu. Te 3 rodzaje scen są zwykle nazywane scenami projekcyjnymi.
Symbole select(...) i add_fields(...) umożliwiają przypisanie wyniku wyrażenia do nazwy pola podanej przez użytkownika. Wyrażenie, które powoduje błąd, zwróci wartość null. Operator select(...) zwraca tylko dokumenty z określonymi nazwami pól, a operator add_fields(...) rozszerza schemat poprzedniego etapu (potencjalnie zastępując wartości o identycznych nazwach pól).
Symbol remove_fields(...) umożliwia określenie zestawu pól do usunięcia z poprzedniego etapu. Określanie nazw pól, które nie istnieją, nie ma żadnego efektu.
Zapoznaj się z sekcją Ograniczanie zwracanych pól poniżej. Ogólnie rzecz biorąc, użycie takiego etapu do ograniczenia wyniku tylko do pól potrzebnych w kliencie pomaga zmniejszyć koszt i opóźnienie w przypadku większości zapytań.
Agregacja / Niepowtarzalne
Etap aggregate(...) umożliwia przeprowadzenie serii agregacji na dokumentach wejściowych. Domyślnie wszystkie dokumenty są agregowane razem, ale można podać opcjonalny argument grouping, który umożliwia agregowanie dokumentów wejściowych w różnych zasobnikach.
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() )
Jeśli nie podasz wartości groupings, na tym etapie powstanie tylko 1 dokument. W przeciwnym razie dokument zostanie wygenerowany dla każdej niepowtarzalnej kombinacji wartości groupings.
Etap distinct(...) to uproszczony operator agregacji, który umożliwia generowanie tylko unikalnych wartości groupings bez żadnych akumulatorów. Pod każdym innym względem zachowuje się identycznie jak aggregate(...). Przykład znajdziesz poniżej:
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() )
Funkcje
Funkcje są elementem składowym służącym do tworzenia wyrażeń i złożonych zapytań. Pełną listę funkcji wraz z przykładami znajdziesz w dokumentacji funkcji. Przypomnijmy sobie strukturę typowego zapytania:

Wiele etapów akceptuje wyrażenia zawierające co najmniej 1 funkcję. Najczęstsze zastosowania funkcji znajdziesz na etapach where(...) i select(...). Warto znać 2 główne typy funkcji:
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() )
Limity
Wersja Enterprise w większości przypadków nie nakłada ograniczeń na kształt zapytania. Innymi słowy, w zapytaniu IN lub OR nie musisz ograniczać się do niewielkiej liczby wartości. Zamiast tego istnieją 2 główne limity, o których musisz pamiętać:
- Termin: 60 sekund (identyczny jak w przypadku wersji Standard).
- Wykorzystanie pamięci: limit 128 MiB na ilość zmaterializowanych danych podczas wykonywania zapytań.
Błędy
Nieudane zapytania mogą mieć różne przyczyny. Kliknij ten link, aby zobaczyć częste błędy i powiązane z nimi działania, które możesz podjąć:
| Kod błędu | Czynność |
DEADLINE_EXCEEDED
|
Wykonywane zapytanie przekracza limit 60 sekund i wymaga dodatkowej optymalizacji. Wskazówki znajdziesz w sekcji dotyczącej skuteczności. Jeśli nie możesz znaleźć przyczyny problemu, skontaktuj się z zespołem. |
RESOURCE_EXHAUSTED
|
Wykonywane zapytanie przekracza limity pamięci i wymaga dodatkowej optymalizacji. Wskazówki znajdziesz w sekcji dotyczącej skuteczności. Jeśli nie możesz znaleźć przyczyny problemu, skontaktuj się z zespołem. |
INTERNAL
|
Skontaktuj się z zespołem, aby uzyskać pomoc. |
Wydajność
W przeciwieństwie do dotychczasowych zapytań operacje potoku nie wymagają, aby indeks był zawsze obecny. Oznacza to, że zapytanie może mieć większe opóźnienie w porównaniu z dotychczasowymi zapytaniami, które od razu kończyły się niepowodzeniem z powodu błędu FAILED_PRECONDITION braku indeksu. Aby zwiększyć wydajność operacji potoku, możesz wykonać kilka czynności.
Tworzenie indeksów
Użyty indeks
Funkcja wyjaśniania zapytań umożliwia sprawdzenie, czy zapytanie jest obsługiwane przez indeks, czy też korzysta z mniej wydajnej operacji, takiej jak skanowanie tabeli. Jeśli zapytanie nie jest w pełni obsługiwane przez indeks, możesz go utworzyć, wykonując te instrukcje.
Tworzenie indeksów
Aby utworzyć indeksy, możesz skorzystać z dokumentacji zarządzania indeksami. Zanim utworzysz indeks, zapoznaj się z ogólnymi sprawdzonymi metodami dotyczącymi indeksów w Cloud Firestore. Aby mieć pewność, że zapytanie może korzystać z indeksów, postępuj zgodnie ze sprawdzonymi metodami tworzenia indeksów z polami w tej kolejności:
- Wszystkie pola, które będą używane w filtrach równości (w dowolnej kolejności)
- Wszystkie pola, według których będzie sortowana lista (w tej samej kolejności).
- Pola, które będą używane w filtrach zakresu lub nierówności, w kolejności malejącej selektywności ograniczeń zapytania.
Na przykład w przypadku tego zapytania:
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() )
Zalecany indeks to indeks zakresu kolekcji w przypadku books dla (genre [...], published DESC, avg_rating DESC).
Gęstość indeksu
Cloud Firestore obsługuje indeksy rzadkie i nierzadkie. Więcej informacji znajdziesz w sekcji Gęstość indeksu.
Zapytania pokryte + indeksy dodatkowe
Cloud Firestore może pominąć pobieranie pełnego dokumentu i zwrócić tylko wyniki z indeksu, jeśli wszystkie zwracane pola znajdują się w indeksie dodatkowym. Zwykle prowadzi to do znacznego skrócenia czasu oczekiwania (i obniżenia kosztów). Korzystając z przykładowego zapytania poniżej:
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() )
Jeśli w bazie danych istnieje już indeks zakresu kolekcji dla books w przypadku (category [...], title [...], author [...]), można uniknąć pobierania czegokolwiek z samych dokumentów głównych. W tym przypadku kolejność w indeksie nie ma znaczenia. Oznacza to symbol [...].
Ograniczanie zwracanych pól
Domyślnie zapytanie Cloud Firestore zwraca wszystkie pola w dokumencie, podobnie jak SELECT * w tradycyjnych systemach. Jeśli jednak aplikacja potrzebuje tylko podzbioru pól, etapy select(...) lub restrict(...) można wykorzystać do przesłania tego filtrowania po stronie serwera. Zmniejszy to zarówno rozmiar odpowiedzi (obniżając koszt ruchu wychodzącego z sieci), jak i opóźnienie.
Narzędzia rozwiązywania problemów
Wyjaśnienie zapytania
Funkcja Query Explain umożliwia wgląd w dane o wykonywaniu i szczegóły dotyczące używanych indeksów.
Dane
Operacje potoku są w pełni zintegrowane z dotychczasowymi Cloud Firestore danymi.
Znane problemy i ograniczenia
Indeksy specjalistyczne
Operacje potoku nie obsługują jeszcze istniejących array-contains i vector typów indeksów. Zamiast odrzucać takie zapytania, Cloud Firestore spróbuje użyć innych istniejących indeksów ascending i descending. Oczekuje się, że w trakcie prywatnego podglądu operacje potoku z takimi wyrażeniami array_contains lub find_nearest będą wolniejsze niż ich odpowiedniki, które są obecnie dostępne.
Podział na strony
W prywatnej wersji testowej nie jest obsługiwane łatwe stronicowanie zestawu wyników. Można to obejść, łącząc ze sobą równoważne etapy where(...) i sort(...), jak pokazano poniżej.
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()) )
Pomoc dotycząca emulatora
Emulator nie obsługuje jeszcze operacji potokowych.
Obsługa w czasie rzeczywistym i offline
Operacje potoku nie mają jeszcze funkcji czasu rzeczywistego i offline.
Co dalej?
- Zacznij od zapoznania się z dokumentacją referencyjną dotyczącą funkcji i etapów.