Реализация запросов 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 также можно сконструировать с использованием синтаксиса объекта, который может содержать переменные. Это особенно полезно, когда по какой-то причине требуется разбить отдельные компоненты на отдельные переменные.

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

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

Извлечь отдельные записи

Простейший запрос возвращает одну запись по идентификатору. Ваш запрос будет использовать автоматически сгенерированное поле 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&quot;: 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" },
    { &quot;manufacturer": "Beta" }
  ]
}

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

Запрос

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

Ответ
distinctonaggregate

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

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

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

{
  "products": [
    {
    "manufacturer_count&quot;: 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 поддерживает эту функцию. См. раздел «Многошаговые операции» .

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

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

,

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 также можно сконструировать с использованием синтаксиса объекта, который может содержать переменные. Это особенно полезно, когда по какой-то причине требуется разбить отдельные компоненты на отдельные переменные.

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

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

Извлечь отдельные записи

Простейший запрос возвращает одну запись по идентификатору. Ваш запрос будет использовать автоматически сгенерированное поле 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&quot;: 5
    }
  ]
}

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

Запрос

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

Ответ
field_count

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

{
  "products": [
    {
    "expirationDate_count&quot;: 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" },
    { &quot;manufacturer": "Beta" }
  ]
}

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

Запрос

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

Ответ
distinctonaggregate

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

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

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

{
  "products": [
    {
    "manufacturer_count&quot;: 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 поддерживает эту функцию. См. раздел «Многошаговые операции» .

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

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