Реализация запросов Data Connect

Firebase Data Connect позволяет создавать коннекторы для ваших экземпляров PostgreSQL, управляемых с помощью Google Cloud SQL. Эти коннекторы представляют собой комбинации запросов и мутаций для использования ваших данных из вашей схемы.

В руководстве по началу работы представлена ​​схема приложения для обзора фильмов для PostgreSQL.

В этом руководстве также были представлены как развертываемые, так и специальные административные операции, включая запросы.

  • Развертываемые запросы — это те, которые вы реализуете для вызова из клиентских приложений с конечными точками API, которые вы определяете. Вы объединяете их в коннектор , развернутый на сервере. Инструментарий Data Connect генерирует клиентские SDK на основе вашего API. Развернутые запросы не защищены политикой IAM, поэтому обязательно защитите их с помощью директивы Data Connect @auth .
  • Административные запросы ad hoc запускаются из привилегированных сред для чтения данных. Вы можете создавать и выполнять их в консоли Firebase или в локальных средах разработки с помощью нашего расширения Data Connect VS Code.

В этом руководстве более подробно рассматриваются развертываемые запросы .

Особенности запросов Data Connect

Data Connect позволяет выполнять базовые запросы всеми способами, которые можно ожидать от базы данных PostgreSQL.

Но с помощью расширений Data Connect для GraphQL вы можете реализовать расширенные запросы для более быстрых и эффективных приложений:

  • Используйте ключевые скаляры, возвращаемые многими операциями, для упрощения повторяющихся операций с записями.
  • Выполняйте запросы в ходе многошаговых операций мутации для поиска данных, экономя строки кода и количество обращений к серверу.

Используйте сгенерированные поля для построения запросов

Ваши операции Data Connect расширят набор полей, автоматически сгенерированных Data Connect на основе типов и отношений типов в вашей схеме. Эти поля генерируются локальными инструментами всякий раз, когда вы редактируете свою схему.

Вы можете использовать сгенерированные поля для реализации все более сложных запросов: от извлечения отдельных записей или нескольких записей из отдельных таблиц до нескольких записей из связанных таблиц.

Предположим, что ваша схема содержит тип Movie и связанный с ним тип Actor . Data Connect генерирует поля movie , movies , actors_on_movies и многое другое.

Запрос с
поле movie

Поле movie представляет собой отдельную запись в таблице Movie .

Используйте это поле для запроса отдельного фильма по его ключу.

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}

Запрос с
поле movies

Поле movies представляет собой список записей в таблице Movie .

Используйте это поле для запроса нескольких фильмов, например, всех фильмов определенного года.

query GetMovies($myYear: Int!) {
  movies(where: { year: { eq: $myYear } }) { title }
}

Запрос с
поле actors_on_movies

Поле actors_on_movies представляет собой список записей, которые связывают таблицы Actor и Movie . Используйте это поле для запроса всех актеров, связанных с данным фильмом.

Используйте это поле для запроса всех актеров, связанных с данным фильмом.

  query GetActorsOnMovie($myKey: Movie_Key!) {
    actors_on_movies(where: { movie: { key: { eq: $myKey } } }) {
      actor { name }
    }
  }

Основные элементы запроса

Запросы Data Connect — это запросы GraphQL с расширениями Data Connect. Как и в случае с обычным запросом GraphQL, вы можете определить имя операции и список переменных GraphQL.

Data Connect расширяет запросы GraphQL с помощью настраиваемых директив , таких как @auth .

Итак, следующий запрос имеет:

  • Определение типа query
  • Имя операции (запроса) ListMoviesByGenre
  • Один аргумент запроса, здесь переменная $genre типа String
  • Одна директива, @auth .
  • Одно поле — movies .
query ListMoviesByGenre($genre: String!) @auth(level: PUBLIC) {
  movies(where: { genre: { eq: $genre } }) {
    id
    title
  }
}

Каждый аргумент запроса требует объявления типа: встроенного, например String , или пользовательского, определяемого схемой типа, например Movie .

В этом руководстве мы рассмотрим сигнатуру все более сложных запросов. Вы закончите введением мощных, кратких выражений отношений, которые вы сможете использовать для создания развертываемых запросов.

Ключевые скаляры в запросах

Но сначала — замечание о ключевых скалярах.

Data Connect определяет специальный скаляр ключа для представления первичных ключей каждой таблицы, идентифицируемой {TableType}_Key. Это объект JSON значений первичного ключа.

Скалярные значения ключей извлекаются в качестве ответа, возвращаемого большинством автоматически сгенерированных полей чтения, или, конечно, из запросов, в которых вы извлекаете все поля, необходимые для построения скалярного ключа.

Отдельные автоматические запросы, такие как movie в нашем примере, поддерживают ключевой аргумент, который принимает ключевой скаляр.

Вы можете передать скаляр ключа как литерал. Но вы можете определить переменные для передачи скаляров ключа как входных данных.

Запрос

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}
    

Ответ

{
  "data": {
    "movie": {
      "title": "Example Movie Title"
    }
  }
}
    

Их можно предоставить в запросе JSON следующим образом (или в других форматах сериализации):

{
  # 
  "variables": {
    "myKey": {"id": "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"}
  }
}

Благодаря пользовательскому скалярному разбору Movie_Key также может быть сконструирован с использованием синтаксиса объекта, который может содержать переменные. Это в основном полезно, когда вы хотите по какой-то причине разбить отдельные компоненты на разные переменные.

Напишите основные запросы

Вы можете начать писать запросы для получения отдельных записей из вашей базы данных или для составления списка записей из таблицы с возможностью ограничения и упорядочивания результатов.

Получить отдельные записи

Самый простой запрос получает одну запись по ID. Ваш запрос будет использовать автоматически сгенерированное поле movie .

Запрос

query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    id
    title
    imageUrl
    genre
  }
}
    

Ответ

{
  "data": {
    "movie": {
      "id": "some-uuid",
      "title": "Example Movie Title",
      "imageUrl": "https://example.com/movie.jpg",
      "genre": "Action"
    }
  }
}
    

Получить все записи в таблице

Чтобы получить подмножество полей для полного списка фильмов из таблицы Movies , ваш запрос будет использовать автоматически сгенерированное поле movies , а ваша реализация может выглядеть следующим образом.

Запрос

query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    genre
  }
}
    

Ответ

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "imageUrl": "https://example.com/movie.jpg",
        "genre": "Action"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title",
        "imageUrl": "https://example.com/another-movie.jpg",
        "genre": "Comedy"
      }
    ]
  }
}
    

Используйте операторы orderBy , limit и offset

Естественно, перечисление всех записей из таблицы имеет ограниченную полезность.

Вы можете заказать и выполнить пагинацию результатов. Эти аргументы принимаются, но не возвращаются в результатах.

Здесь запрос получает названия 10 лучших фильмов по рейтингу.

Запрос

query MoviesTop10 {
  movies(orderBy: [{ rating: DESC }], limit: 10) {
    # graphql: list the fields from the results to return
    title
  }
}
    

Ответ

{
  "data": {
    "movies": [
      { "title": "Top Movie 1" },
      { "title": "Top Movie 2" },
      { "title": "Top Movie 3" }
      // ... other 7 movies
    ]
  }
}
    

У вас может быть вариант использования для извлечения строк из смещения, например, фильмов 11–20, упорядоченных по рейтингу.

Запрос

query Movies11to20 {
  movies(orderBy: [{ rating: DESC }], limit: 10, offset: 10) {
    # graphql: list the fields from the results to return
    title
  }
}
    

Ответ

{
  "data": {
    "movies": [
      { "title": "Movie 11" },
      { "title": "Movie 12" },
      { "title": "Movie 13" }
      // ... other 7 movies
    ]
  }
}
    

Используйте псевдонимы в запросах

Data Connect поддерживает псевдонимы GraphQL в запросах. С помощью псевдонимов вы переименовываете данные, возвращаемые в результатах запроса. Один запрос Data Connect может применять несколько фильтров или других операций запроса в одном эффективном запросе к серверу, эффективно выдавая несколько «подзапросов» одновременно. Чтобы избежать конфликтов имен в возвращаемом наборе данных, вы используете псевдонимы для различения подзапросов.

Вот запрос, в котором выражение использует псевдонимы mostPopular и leastPopular .

Запрос

query ReviewPopularitySpread($genre: String) {
  mostPopular: review(
    first: {
      where: {genre: {eq: $genre}},
      orderBy: {popularity: DESC}
    }
  ),
  leastPopular: review(
    last: {
      where: {genre: {eq: $genre}},
      orderBy: {popularity: DESC}
    }
  )
}
    

Ответ

{
  "data": {
    "mostPopular": [
      { "popularity": 9 }
    ],
    "leastPopular": [
      { "popularity": 1 }
    ]
  }
}
    

Использовать фильтры запросов

Запросы Data Connect сопоставляются со всеми распространенными фильтрами SQL и операциями заказов.

Фильтр с where с операторами orderBy

Возвращает все соответствующие строки из таблицы (и вложенные ассоциации). Возвращает пустой массив, если ни одна запись не соответствует фильтру.

Запрос

query MovieByTopRating($genre: String) {
  mostPopular: movies(
    where: { genre: { eq: $genre } },
    orderBy: { rating: DESC }
  ) {
    # graphql: list the fields from the results to return
    id
    title
    genre
    description
  }
}
    

Ответ

{
  "data": {
    "mostPopular": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "genre": "Action",
        "description": "A great movie"
      }
    ]
  }
}
    

Фильтрация путем проверки на наличие нулевых значений

Вы можете проверить наличие null значений с помощью оператора isNull .

Запрос

query ListMoviesWithoutDescription {
  movies(where: { description: { isNull: true }}) {
    id
    title
  }
}
    

Ответ

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

Дополнительные операторы см. в справочнике по типам входных объектов .

Фильтр со сравнением значений

Для сравнения значений в запросах можно использовать такие операторы, как lt (меньше) и ge (больше или равно).

Запрос

query ListMoviesByRating($minRating: Int!, $maxRating: Int!) {
  movies(where: { rating: { ge: $minRating, lt: $maxRating }}) {
    id
    title
  }
}
    

Ответ

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

Фильтр с операторами includes и excludes для полей массива

Вы можете проверить, что поле массива включает указанный элемент.

Следующий пример иллюстрирует оператор includes .

Data Connect поддерживает includesAll , excludes , excludesAll и многое другое. Ознакомьтесь со всеми такими операторами для целых чисел, строк, дат и других типов данных в заголовках _ListFilter справочной документации .

Запрос

query ListMoviesByTag($tag: String!) {
  movies(where: { tags: { includes: $tag }}) {
    # graphql: list the fields from the results to return
    id
    title
  }
}
    

Ответ

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      }
    ]
  }
}
    

Фильтр со строковыми операциями и регулярными выражениями

Ваши запросы могут использовать типичные операции поиска и сравнения строк, включая регулярные выражения. Обратите внимание, что для эффективности вы объединяете здесь несколько операций и устраняете их неоднозначность с помощью псевдонимов.

query MoviesTitleSearch($prefix: String, $suffix: String, $contained: String, $regex: String) {
  prefixed: movies(where: {title: {startsWith: $prefix}}) {...}
  suffixed: movies(where: {title: {endsWith: $suffix}}) {...}
  contained: movies(where: {title: {contains: $contained}}) {...}
}

Фильтр с логикой операторов _or , _and , _not

Используйте _or для более сложной логики. Data Connect также поддерживает операторы _and и _not .

Запрос

query ListMoviesByGenreAndGenre($minRating: Int!, $genre: String) {
  movies(
    where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
  ) {
    # graphql: list the fields from the results to return
    title
  }
}
    

Ответ

{
  "data": {
    "movies": [
      { "title": "Movie Title 1" },
      { "title": "Movie Title 2" }
    ]
  }
}
    

Написание реляционных запросов

Запросы Data Connect могут получать доступ к данным на основе отношений между таблицами. Вы можете использовать отношения объектов (один к одному) или массивов (один ко многим), определенные в вашей схеме, для создания вложенных запросов, то есть извлечения данных для одного типа вместе с данными из вложенного или связанного типа.

Такие запросы используют магический синтаксис Data Connect _on_ и _via в сгенерированных полях чтения.

Не забудьте ознакомиться с образцом схемы .

Много к одному

Теперь рассмотрим запрос, иллюстрирующий синтаксис _on_ .

Запрос

query MyReviews @auth(level: USER) {
  user(key: {id_expr: "auth.uid"}) {
    reviews: reviews_on_user {
      movie { name }
      rating
    }
  }
}
    

Ответ

{
  "data": {
    "user": {
      "reviews": [
        {
          "movie": { "name": "Movie Title" },
          "rating": 5
        }
      ]
    }
  }
}
    

Один на один

Вы можете написать запрос «один к одному», используя синтаксис _on_ .

Запрос

query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    movieMetadatas_on_movie {
      director
    }
  }
}
    

Ответ

{
  "data": {
    "movie": {
      "movieMetadatas_on_movie": {
        "director": "Some Director"
      }
    }
  }
}
    

Многие ко многим

Запросы «многие ко многим» используют синтаксис _via_ . Запрос «многие ко многим» может извлекать актеров для указанного фильма.

Запрос

query MoviesActors($id: UUID!) @auth(level: USER) {
  movie(id: $id) {
     actors: actors_via_MovieActors {
        name
     }
  }
}
    

Ответ

{
  "data": {
    "movie": {
      "actors": [
        {
          "name": "Actor Name"
        }
      ]
    }
  }
}
    

Но мы можем написать более сложный запрос, используя псевдонимы, для фильтрации на основе role , чтобы получить актеров и связанные с ними фильмы в результатах mainActors и supportingActors . Поскольку это многие-ко-многим, используется синтаксис _via_ .

Запрос

query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
  movie(id: $movieId) {
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      name
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      name
    }
  }
  actor(id: $actorId) {
    mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      title
    }
    supportingRoles: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      title
    }
  }
}
    

Ответ

{
  "data": {
    "movie": {
      "mainActors": [
        {
          "name": "Main Actor Name"
        }
      ],
      "supportingActors": [
        {
          "name": "Supporting Actor Name"
        }
      ]
    },
    "actor": {
      "mainRoles": [
        {
          "title": "Main Role Movie Title"
        }
      ],
      "supportingRoles": [
        {
          "title": "Supporting Role Movie Title"
        }
      ]
    }
  }
}
    

Агрегационные запросы

Что такое агрегаты и зачем их использовать?

Агрегированные поля позволяют выполнять вычисления по списку результатов. С помощью агрегированных полей вы можете делать такие вещи, как:

  • Найдите среднюю оценку обзора
  • Узнать общую стоимость товаров в корзине
  • Найдите продукт с самым высоким или самым низким рейтингом
  • Подсчитайте количество товаров в вашем магазине

Агрегации выполняются на сервере, что дает ряд преимуществ по сравнению с их вычислением на стороне клиента:

  • Более высокая производительность приложения (так как вы избегаете вычислений на стороне клиента)
  • Сокращение затрат на отправку данных (поскольку вы отправляете только агрегированные результаты вместо всех входных данных)
  • Улучшенная безопасность (так как вы можете предоставить клиентам доступ к агрегированным данным, а не ко всему набору данных)

Пример схемы для агрегатов

В этом разделе мы перейдем к примеру схемы витрины, которая хорошо подходит для объяснения того, как использовать агрегаты:

  type Product @table {
    name: String!
    manufacturer: String!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

Простые агрегаты

_count для всех полей

Простейшее поле агрегации — _count : оно возвращает количество строк, соответствующих вашему запросу. Для каждого поля в вашем типе Data Connect генерирует соответствующие поля агрегации в зависимости от типа поля.

Запрос

query CountProducts {
  products {
    _count
  }
}

Ответ
one

Например, если в вашей базе данных 5 продуктов, результат будет следующим:

{
  "products": [
    {
    "_count": 5
    }
  ]
}

Во всех полях есть поле <field>_count , которое подсчитывает количество строк, имеющих ненулевое значение в этом поле.

Запрос

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

Ответ
field_count

Например, если у вас есть 3 продукта с истекающим сроком годности, результат будет следующим:

{
  "products": [
    {
    "expirationDate_count": 3
    }
  ]
}
_min, _max, _sum и _avg для числовых полей

Числовые поля (int, float, int64) также имеют <field>_min , <field>_max , <field>_sum и <field>_avg .

Запрос

query NumericAggregates {
  products {
  quantityInStock_max
  price_min
  price_avg
  quantityInStock_sum
  }
}

Ответ
_min _max _sum _avg

Например, если у вас есть следующие продукты:

  • Продукт A: quantityInStock: 10 , price: 2.99
  • Продукт B: quantityInStock: 5 , price: 5.99
  • Продукт C: quantityInStock: 20 , price: 1.99

Результат будет следующим:

{
  "products": [
    {
    "quantityInStock_max": 20,
    "price_min": 1.99,
    "price_avg": 3.6566666666666666,
    "quantityInStock_sum": 35
    }
  ]
}
_min и _max для дат и временных меток

Поля даты и времени имеют значения <field>_min и <field>_max .

Запрос

query DateAndTimeAggregates {
  products {
  expirationDate_max
  expirationDate_min
  }
}

Ответ
_min _maxdatetime

Например, если у вас следующие сроки годности:

  • Продукт А: 2024-01-01
  • Продукт B: 2024-03-01
  • Продукт C: 2024-02-01

Результат будет следующим:

{
  "products": [
    {
    "expirationDate_max": "2024-03-01",
    "expirationDate_min": "2024-01-01"
    }
  ]
}

Отчетливый

Аргумент distinct позволяет получить все уникальные значения для поля (или комбинации полей). Например:

Запрос

query ListDistinctManufacturers {
  products(distinct: true) {
    manufacturer
  }
}

Ответ
distinct

Например, если у вас есть следующие производители:

  • Продукт А: manufacturer: "Acme"
  • Продукт B: manufacturer: "Beta"
  • Продукт C: manufacturer: "Acme"

Результат будет следующим:

{
  "products": [
    { "manufacturer": "Acme" },
    { "manufacturer": "Beta" }
  ]
}

Вы также можете использовать аргумент distinct в полях агрегата, чтобы вместо этого агрегировать отдельные значения. Например:

Запрос

query CountDistinctManufacturers {
  products {
    manufacturer_count(distinct: true)
  }
}

Ответ
distinctonaggregate

Например, если у вас есть следующие производители:

  • Продукт А: manufacturer: "Acme"
  • Продукт B: manufacturer: "Beta"
  • Продукт C: manufacturer: "Acme"

Результат будет следующим:

{
  "products": [
    {
    "manufacturer_count": 2
    }
  ]
}

Сгруппированные агрегаты

Вы выполняете групповой агрегат, выбирая смесь агрегированных и неагрегированных полей в типе. Это группирует все соответствующие строки, которые имеют одинаковое значение для неагрегированных полей, и вычисляет агрегированные поля для этой группы. Например:

Запрос

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

Ответ
groupedaggregates

Например, если у вас есть следующие продукты:

  • Продукт А: manufacturer: "Acme" , price: 2.99
  • Продукт B: manufacturer: "Beta" , price: 5.99
  • Продукт C: manufacturer: "Acme" , price: 1.99

Результат будет следующим:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}
having и where с сгруппированными агрегатами

Вы также можете использовать аргументы having и where чтобы возвращать только те группы, которые соответствуют заданным критериям.

  • having позволяет вам фильтровать группы по их совокупным полям
  • where позволяет фильтровать строки на основе неагрегированных полей.

Запрос

query FilteredMostExpensiveProductByManufacturer {
  products(having: {price_max: {ge: 2.99}}) {
  manufacturer
  price_max
  }
}

Ответ
havingwhere

Например, если у вас есть следующие продукты:

  • Продукт А: manufacturer: "Acme" , price: 2.99
  • Продукт B: manufacturer: "Beta" , price: 5.99
  • Продукт C: manufacturer: "Acme" , price: 1.99

Результат будет следующим:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}

Агрегирует по таблицам

Агрегированные поля могут использоваться совместно с полями сгенерированных отношений один-ко-многим для ответа на сложные вопросы о ваших данных. Вот модифицированная схема с отдельной таблицей, Manufacturer , которую мы можем использовать в примерах:

  type Product @table {
    name: String!
    manufacturer: Manufacturer!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

  type Manufacturer @table {
    name: String!
    headquartersCountry: String!
  }

Теперь мы можем использовать агрегированные поля, чтобы, например, узнать, сколько продукции производит производитель:

Запрос

query GetProductCount($id: UUID) {
  manufacturers {
    name
    products_on_manufacturer {
      _count
    }
  }
}

Ответ
aggregatesacrosstables

Например, если у вас есть следующие производители:

  • Производитель A: name: "Acme" , products_on_manufacturer: 2
  • Производитель B: name: "Beta" , products_on_manufacturer: 1

Результат будет следующим:

{
  "manufacturers": [
    { "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
    { "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
  ]
}

Написание расширенных запросов: использование полей query для чтения данных в многошаговых операциях.

Существует множество ситуаций, в которых вам может понадобиться прочитать вашу базу данных во время выполнения мутации, чтобы просмотреть и проверить существующие данные перед выполнением, например, вставок или обновлений. Эти опции экономят операции кругового обхода и, следовательно, затраты.

Data Connect поддерживает эту функцию. См. многошаговые операции .

Следующие шаги

Вас может заинтересовать: