Wprowadzenie do operacji Firestore Pipelines

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

Przykład pokazujący etapy i wyrażenia w zapytaniu

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()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(...)remove_fields(...) umożliwiają modyfikowanie pól zwracanych z poprzedniego etapu. Te 3 rodzaje scen są zwykle nazywane scenami projekcyjnymi.

Symbole select(...)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:

Przykład pokazujący etapy i funkcje w zapytaniu

Wiele etapów akceptuje wyrażenia zawierające co najmniej 1 funkcję. Najczęstsze zastosowania funkcji znajdziesz na etapach where(...)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:

  1. Wszystkie pola, które będą używane w filtrach równości (w dowolnej kolejności)
  2. wszystkie pola, według których będzie sortowana tabela (w tej samej kolejności);
  3. 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-containsvector typów indeksów. Zamiast odrzucać takie zapytania, Firestore spróbuje użyć innych istniejących indeksów ascendingdescending. 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(...)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?