Tło
Zapytania potoków to nowy interfejs zapytań w Firestore. Zapewnia 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 zapytań potoku tworzenie indeksu jest też całkowicie opcjonalne, co upraszcza proces opracowywania nowych zapytań. Zapytania potokowe 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 przewodniku dla początkujących.
Składnia
W sekcjach poniżej znajdziesz omówienie składni zapytań potokowych.
Pojęcia
Jedną z najważniejszych różnic w przypadku zapytań potokowych 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 dotychczasowego interfejsu zapytań, w którym kolejność etapów była domyślna. Oto przykładowe zapytanie do potoku:
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
Zapytania potoku mają bardzo podobną składnię do istniejących 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 zapytań potoku warto znać kilka terminów: 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ż wyrażać 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
Zapytania 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 danych to pierwszy etap zapytania. Określa początkowy zestaw dokumentów, w których wyszukujesz informacje. W przypadku zapytań potokowych jest to w dużej mierze podobne do istniejących zapytań, w których większość 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.
Można połączyć ze sobą kilka instrukcji where(...), które będą działać jak wyrażenie 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 zapytania.
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 60-sekundowy termin 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ń zapytania 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ść zapytań potokowych, 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ę ze sprawdzonymi metodami dotyczącymi indeksów w 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 tabela (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 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
Jeśli wszystkie zwracane pola znajdują się w indeksie dodatkowym, Firestore może pominąć pobieranie pełnego dokumentu i zwrócić tylko wyniki z indeksu. Zwykle prowadzi to do znacznego skrócenia czasu oczekiwania (i obniżenia kosztów). Użyj 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, co oznacza symbol [...].
Ograniczanie zwracanych pól
Domyślnie zapytanie Firestore zwraca wszystkie pola w dokumencie, podobnie jak SELECT * w tradycyjnych systemach. Jeśli jednak aplikacja potrzebuje tylko podzbioru pól, do przesłania tego filtrowania po stronie serwera można użyć etapów select(...) lub restrict(...). 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
Zapytania potoku są w pełni zintegrowane z dotychczasowymi danymi Firestore.
Znane problemy i ograniczenia
Indeksy specjalistyczne
Zapytania w potoku nie obsługują jeszcze istniejących array-contains i vector typów indeksów. Zamiast odrzucać takie zapytania, Firestore spróbuje użyć innych istniejących indeksów ascending i descending. Oczekuje się, że w trakcie prywatnej wersji testowej zapytania Pipeline z takimi wyrażeniami array_contains lub find_nearest będą wolniejsze od ich dotychczasowych odpowiedników.
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 zapytań potokowych.
Obsługa w czasie rzeczywistym i offline
Zapytania potokowe nie mają jeszcze możliwości działania w czasie rzeczywistym ani offline.
Co dalej?
- Zacznij od zapoznania się z dokumentacją referencyjną dotyczącą funkcji i etapów.