Firebase Data Connect позволяет создавать коннекторы для ваших экземпляров PostgreSQL, управляемых с помощью Google Cloud SQL. Эти соединители представляют собой комбинацию схемы, запросов и мутаций для использования ваших данных.
В руководстве по началу работы представлена схема приложения для просмотра фильмов для PostgreSQL, а в этом руководстве более подробно рассматривается разработка схем Data Connect для PostgreSQL.
В этом руководстве запросы и мутации Data Connect сочетаются с примерами схем. Зачем обсуждать запросы (и мутации ) в руководстве по схемам Data Connect ? Как и другие платформы на основе GraphQL, Firebase Data Connect — это платформа разработки , ориентированная на запросы , поэтому, будучи разработчиком, при моделировании данных вы будете думать о данных, которые нужны вашим клиентам, что сильно повлияет на схему данных, которую вы разрабатываете для своего проекта.
Это руководство начинается с новой схемы для обзоров фильмов , затем рассматриваются запросы и изменения, полученные на основе этой схемы, и, наконец, приводится список SQL, эквивалентный основной схеме Data Connect .
Схема приложения для обзора фильмов
Представьте, что вы хотите создать сервис, который позволит пользователям отправлять и просматривать обзоры фильмов.
Вам нужна исходная схема для такого приложения. Позже вы расширите эту схему для создания сложных реляционных запросов.
Стол для фильмов
Схема фильмов содержит такие основные директивы, как:
-
@table(name)
и@col(name)
для настройки имен таблиц и столбцов SQL. Data Connect генерирует имена Snake_case, если они не указаны. -
@col(dataType)
для настройки типов столбцов SQL. -
@default
для настройки значений по умолчанию для столбца SQL во время вставки.
Для получения более подробной информации ознакомьтесь со справочной документацией по @table
, @col
, @default
.
# Movies
type Movie @table(name: "movie", key: "id") {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String @col(dataType: "varchar(20)")
rating: Int
description: String
}
Ключевые скаляры и значения сервера
Прежде чем подробнее рассмотреть приложение для просмотра фильмов, давайте познакомимся со скалярами ключей Data Connect и значениями сервера .
Ключевые скаляры — это краткие идентификаторы объектов, которые Data Connect автоматически собирает из ключевых полей в ваших схемах. Ключевые скаляры связаны с эффективностью, позволяя вам за один вызов найти информацию о личности и структуре ваших данных. Они особенно полезны, когда вы хотите выполнить последовательные действия над новыми записями и вам нужен уникальный идентификатор для перехода к предстоящим операциям, а также когда вы хотите получить доступ к реляционным ключам для выполнения дополнительных, более сложных операций.
Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя сохраненные или легко вычислимые значения в соответствии с конкретными выражениями CEL на стороне сервера в аргументе expr
. Например, вы можете определить поле с меткой времени, применяемой при доступе к полю, используя время, сохраненное в запросе операции, updatedAt: Timestamp! @default(expr: "request.time")
.
Таблица метаданных фильма
Теперь давайте проследим за режиссерами фильмов, а также настроим отношения один-к-одному с Movie
.
Добавьте поле ссылки, чтобы определить отношения.
Вы можете использовать директиву @ref
для настройки ограничения внешнего ключа.
-
@ref(fields)
для указания полей внешнего ключа. -
@ref(references)
для указания полей, на которые имеются ссылки в целевой таблице. По умолчанию для этой ссылки используется первичный ключ, но поля с@unique
также поддерживаются.
Для получения более подробной информации ознакомьтесь со справочной документацией по @ref
.
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
# @unique ensures that each Movie only has one MovieMetadata.
movie: Movie! @unique
# Since it references to another table type, it adds a foreign key constraint.
# movie: Movie! @unique @ref(fields: "movieId", references: "id")
# movieId: UUID! <- implicitly added foreign key field
director: String
}
Актер и киноактер
Далее вы хотите, чтобы актеры снимались в ваших фильмах, и, поскольку между фильмами и актерами существует связь «многие ко многим», создайте соединительную таблицу.
# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
id: UUID! @default(expr: "uuidV4()")
name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
movie: Movie!
# movieId: UUID! <- implicitly added foreign key field
actor: Actor!
# actorId: UUID! <- implicitly added foreign key field
role: String! # "main" or "supporting"
# optional other fields
}
Пользователь
Наконец, пользователи вашего приложения.
# Users
# Suppose a user can leave reviews for movies
type User @table {
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
}
Поддерживаемые типы данных
Data Connect поддерживает следующие скалярные типы данных с присвоением типов PostgreSQL с помощью @col(dataType:)
.
Тип Data Connect | Встроенный тип GraphQL или Пользовательский тип Data Connect | Тип PostgreSQL по умолчанию | Поддерживаемые типы PostgreSQL (псевдоним в скобках) |
---|---|---|---|
Нить | ГрафQL | текст | текст бит (n), варбит (n) символ (п), варчар (п) |
Int | ГрафQL | интервал | Int2 (маллинт, малый сериал), int4 (целое число, целое число, серийный номер) |
Плавать | ГрафQL | поплавок8 | float4 (реальный) float8 (двойная точность) числовой (десятичный) |
логическое значение | ГрафQL | логическое значение | логическое значение |
UUID | Обычай | uuid | uuid |
Int64 | Обычай | bigint | int8 (бигинт, большойсериал) числовой (десятичный) |
Дата | Обычай | дата | дата |
Временная метка | Обычай | временная метка | временная метка Примечание. Информация о местном часовом поясе не сохраняется. |
Вектор | Обычай | вектор | вектор См. раздел Выполнение поиска по сходству векторов с помощью Vertex AI . |
-
List
GraphQL отображается в одномерный массив.- Например,
[Int]
сопоставляется сint5[]
,[Any]
сопоставляется сjsonb[]
. - Data Connect не поддерживает вложенные массивы.
- Например,
Используйте сгенерированные поля для построения запросов и мутаций.
Ваши запросы и мутации Data Connect расширят набор полей, автоматически создаваемых Data Connect на основе типов и отношений типов в вашей схеме. Эти поля генерируются локальными инструментами всякий раз, когда вы редактируете свою схему.
Как вы узнали из руководства по началу работы, консоль Firebase и наши локальные инструменты разработки используют эти автоматически сгенерированные поля, чтобы предоставлять вам специальные административные запросы и изменения, которые вы можете использовать для заполнения данных и проверки содержимого ваших таблиц.
В процессе разработки вы будете реализовывать развертываемые запросы и развертываемые мутации, включенные в ваши соединители, на основе этих автоматически созданных полей.
Автоматически создаваемое именование полей
Data Connect определяет подходящие имена для полей, автоматически создаваемых на основе объявлений типа схемы. Например, при работе с источником PostgreSQL, если вы определите таблицу с именем Movie
, сервер сгенерирует:
- Поля для чтения данных в сценариях использования одной таблицы с понятными именами
movie
(единственное число, для получения отдельных результатов с передачей аргументов, напримерeq
) иmovies
(множественное число, для получения списков результатов с передачей аргументов, таких какgt
, и операций, таких какorderby
). Data Connect также генерирует поля для многотабличных реляционных операций с явными именами, такими какactors_on_movies
илиactors_via_actormovie
. - Поля для записи данных со знакомым названием типа
movie_insert
,movie_upsert
...
Язык определения схемы также позволяет явно контролировать создание имен для полей с использованием аргументов директивы singular
и plural
.
Директивы для запросов и мутаций
В дополнение к директивам, которые вы используете при определении типов и таблиц, Data Connect предоставляет директивы @auth
, @check
, @redact
и @transaction
для улучшения поведения запросов и мутаций.
Директива | Применимо к | Описание |
---|---|---|
@auth | Запросы и мутации | Определяет политику аутентификации для запроса или мутации. См. руководство по авторизации и аттестации . |
@check | Запросы на поиск данных авторизации | Проверяет наличие указанных полей в результатах запроса. Выражение Common Expression Language (CEL) используется для проверки значений полей. См. руководство по авторизации и аттестации . |
@redact | Запросы | Редактирует часть ответа клиента. См. руководство по авторизации и аттестации . |
@transaction | Мутации | Обеспечивает, чтобы мутация всегда выполнялась в транзакции базы данных. См. примеры мутаций приложения Movie . |
Запросы к базе данных обзоров фильмов
Запрос Data Connect определяется с помощью объявления типа операции запроса, имени операции, нуля или более аргументов операции и нуля или более директив с аргументами.
В кратком руководстве пример запроса listEmails
не имел параметров. Конечно, во многих случаях данные, передаваемые в поля запроса, будут динамическими. Вы можете использовать синтаксис $variableName
для работы с переменными как с одним из компонентов определения запроса.
Итак, следующий запрос имеет:
- Определение типа
query
- Имя операции (запроса)
ListMoviesByGenre
- Аргумент операции
$genre
с одной переменной - Одна директива
@auth
.
query ListMoviesByGenre($genre: String!) @auth(level: USER)
Для каждого аргумента запроса требуется объявление типа, встроенного, например String
, или пользовательского типа, определяемого схемой, например Movie
.
Давайте посмотрим на сигнатуру все более сложных запросов. В конце вы представите мощные и лаконичные выражения отношений, которые можно использовать для построения развертываемых запросов.
Ключевые скаляры в запросах
Но сначала замечание о ключевых скалярах.
Data Connect определяет специальный тип для ключевых скаляров, идентифицируемый _Key
. Например, тип ключевого скаляра для нашей таблицы Movie
— Movie_Key
.
Вы получаете скаляры ключей в качестве ответа, возвращаемого большинством автоматически сгенерированных полей чтения, или, конечно, из запросов, в которых вы получили все поля, необходимые для построения скалярного ключа.
Сингулярные автоматические запросы, такие как movie
в нашем примере, поддерживают ключевой аргумент, который принимает ключевой скаляр.
Вы можете передать ключевой скаляр как литерал. Но вы можете определить переменные для передачи ключевых скаляров в качестве входных данных.
query GetMovie($myKey: Movie_Key!) {
movie(key: $myKey) { title }
}
Их можно предоставить в запросе JSON следующим образом (или в других форматах сериализации):
{
# …
"variables": {
"myKey": {"foo": "some-string-value", "bar": 42}
}
}
Благодаря пользовательскому скалярному анализу Movie_Key
также может быть создан с использованием синтаксиса объекта, который может содержать переменные. Это особенно полезно, когда по какой-то причине вы хотите разбить отдельные компоненты на разные переменные.
Псевдонимы в запросах
Data Connect поддерживает псевдонимы GraphQL в запросах. С помощью псевдонимов вы переименовываете данные, возвращаемые в результатах запроса. Один запрос Data Connect может применять несколько фильтров или других операций запроса в одном эффективном запросе к серверу, эффективно выдавая одновременно несколько «подзапросов». Чтобы избежать конфликтов имен в возвращаемом наборе данных, вы используете псевдонимы для различения подзапросов.
Вот запрос, в котором выражение использует псевдоним mostPopular
.
query ReviewTopPopularity($genre: String) {
mostPopular: review(first: {
where: {genre: {eq: $genre}},
orderBy: {popularity: DESC}
}) { … }
}
Простые запросы с фильтрами
Запросы 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
}
}
query MoviesByReleaseYear($min: Int, $max: Int) {
movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) { … }
}
Операторы limit
и offset
(запросы в единственном и множественном числе)
Вы можете выполнить нумерацию страниц для результатов. Эти аргументы принимаются, но не возвращаются в результатах.
query MoviesTop10 {
movies(orderBy: [{ rating: DESC }], limit: 10) {
# graphql: list the fields from the results to return
title
}
}
включает в себя поля массива
Вы можете проверить, содержит ли поле массива указанный элемент.
# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
movies(where: { tags: { includes: $tag }}) {
# graphql: list the fields from the results to return
id
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}}) {...}
matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}
or
и and
для составных фильтров
Используйте or
и and
для более сложной логики.
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 Connect могут получать доступ к данным на основе связей между таблицами. Вы можете использовать отношения объекта (один-к-одному) или массива (один-ко-многим), определенные в вашей схеме, для создания вложенных запросов, т. е. выборки данных для одного типа вместе с данными из вложенного или связанного типа.
Такие запросы используют магический синтаксис Data Connect _on_
и _via
в генерируемых полях чтения.
Вы будете вносить изменения в схему из нашей первоначальной версии .
Многие к одному
Давайте добавим отзывы в наше приложение, добавив таблицу Review
и изменив User
.
# User table is keyed by Firebase Auth UID.
type User @table {
# `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
# The `user: User!` field in the Review table generates the following one-to-many query field.
# reviews_on_user: [Review!]!
# The `Review` join table the following many-to-many query field.
# movies_via_Review: [Movie!]!
}
# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
user: User!
# The user field adds the following foreign key field. Feel free to uncomment and customize it.
# userUid: String!
movie: Movie!
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
# movieId: UUID!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
Запрос «многие к одному»
Теперь давайте посмотрим на запрос с псевдонимами, чтобы проиллюстрировать синтаксис _via_
.
query UserMoviePreferences($username: String!) @auth(level: USER) {
users(where: { username: { eq: $username } }) {
likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
title
genre
}
dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
title
genre
}
}
}
Один к одному
Вы можете увидеть закономерность. Ниже схема изменена для иллюстрации.
# Movies
type Movie
@table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int @col(name: "release_year")
genre: String
rating: Int @col(name: "rating")
description: String @col(name: "description")
tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
@table(
name: "MovieMetadata"
) {
# @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
# In this case, @ref(fields: "id") is implied
movie: Movie! @ref
# movieId: UUID <- this is created by the above @ref
director: String @col(name: "director")
}
extend type MovieMetadata {
movieId: UUID! # matches primary key of referenced type
...
}
extend type Movie {
movieMetadata: MovieMetadata # can only be non-nullable on ref side
# conflict-free name, always generated
movieMetadatas_on_movie: MovieMetadata
}
Запрос один к одному
Вы можете выполнить запрос, используя синтаксис _on_
.
# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
movie(id: $id) {
movieMetadatas_on_movie {
director
}
}
}
Многие ко многим
Кино нуждаются в актерах, а актерам нужны фильмы. У них есть отношения многие-ко-многим, которые вы можете смоделировать с помощью таблицы соединений MovieActors
.
# MovieActors Join Table Definition
type MovieActors @table(
key: ["movie", "actor"] # join key triggers many-to-many generation
) {
movie: Movie!
actor: Actor!
}
# generated extensions for the MovieActors join table
extend type MovieActors {
movieId: UUID!
actorId: UUID!
}
# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
actors: [Actor!]! # many-to-many via join table
movieActors_on_actor: [MovieActors!]!
# since MovieActors joins distinct types, type name alone is sufficiently precise
actors_via_MovieActors: [Actor!]!
}
extend type Actor {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
movies: [Movie!]! # many-to-many via join table
movieActors_on_movie: [MovieActors!]!
movies_via_MovieActors: [Movie!]!
}
Запрос от многих ко многим
Давайте рассмотрим запрос с псевдонимами, чтобы проиллюстрировать синтаксис _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
}
}
}
Агрегационные запросы
Что такое агрегаты и зачем их использовать?
Поля агрегирования позволяют выполнять вычисления на основе списка результатов. С помощью агрегированных полей вы можете делать такие вещи, как:
- Найдите средний балл отзыва
- Узнать общую стоимость товаров в корзине
- Найдите продукт с самым высоким или самым низким рейтингом
- Подсчитайте количество товаров в вашем магазине
Агрегаты выполняются на сервере, что дает ряд преимуществ по сравнению с их расчетом на стороне клиента:
- Более высокая производительность приложения (поскольку вы избегаете вычислений на стороне клиента)
- Снижение затрат на исходящие данные (поскольку вы отправляете только агрегированные результаты вместо всех входных данных).
- Повышенная безопасность (поскольку вы можете предоставить клиентам доступ к агрегированным данным, а не ко всему набору данных).
Пример схемы агрегатов
В этом разделе мы перейдем к примерной схеме витрины, которая хорошо объясняет, как использовать агрегаты:
type Product @table {
name: String!
manufacturer: String!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
Простые агрегаты
_count для всех полей
Простейшим агрегатным полем является _count
: оно возвращает количество строк, соответствующих вашему запросу. Для каждого поля вашего типа Data Connect создает соответствующие агрегированные поля в зависимости от типа поля.
query CountProducts {
products {
_count
}
}
Например, если в вашей базе данных есть 5 продуктов, результат будет таким:
{
"products": [
{
"_count": 5
}
]
}
Все поля имеют поле <field>_count
, которое подсчитывает, сколько строк имеют ненулевое значение в этом поле.
query CountProductsWithExpirationDate {
products {
expirationDate_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
}
}
Например, если у вас есть следующие продукты:
- Товар А:
quantityInStock: 10
,price: 2.99
- Товар Б:
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
}
}
Например, если у вас есть следующие сроки годности:
- Продукт А:
2024-01-01
- Продукт Б:
2024-03-01
- Продукт С:
2024-02-01
Результатом будет:
{
"products": [
{
"expirationDate_max": "2024-03-01",
"expirationDate_min": "2024-01-01"
}
]
}
Отчетливый
distinct
аргумент позволяет получить все уникальные значения поля (или комбинации полей). Например:
query ListDistinctManufacturers {
products(distinct: true) {
manufacturer
}
}
Например, если у вас есть следующие производители:
- Товар А:
manufacturer: "Acme"
- Продукт Б:
manufacturer: "Beta"
- Товар С:
manufacturer: "Acme"
Результатом будет:
{
"products": [
{ "manufacturer": "Acme" },
{ "manufacturer": "Beta" }
]
}
Вы также можете использовать distinct
аргумент в агрегированных полях, чтобы вместо этого агрегировать отдельные значения. Например:
query CountDistinctManufacturers {
products {
manufacturer_count(distinct: true)
}
}
Например, если у вас есть следующие производители:
- Товар А:
manufacturer: "Acme"
- Продукт Б:
manufacturer: "Beta"
- Товар С:
manufacturer: "Acme"
Результатом будет:
{
"products": [
{
"manufacturer_count": 2
}
]
}
Сгруппированные агрегаты
Вы выполняете сгруппированное агрегирование, выбирая сочетание агрегированных и неагрегированных полей для типа. При этом группируются все совпадающие строки, имеющие одинаковое значение для неагрегированных полей, и вычисляются агрегатные поля для этой группы. Например:
query MostExpensiveProductByManufacturer {
products {
manufacturer
price_max
}
}
Например, если у вас есть следующие продукты:
- Товар А:
manufacturer: "Acme"
,price: 2.99
- Товар Б:
manufacturer: "Beta"
,price: 5.99
- Товар С:
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
}
}
Например, если у вас есть следующие продукты:
- Товар А:
manufacturer: "Acme"
,price: 2.99
- Товар Б:
manufacturer: "Beta"
,price: 5.99
- Товар С:
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
}
}
}
Например, если у вас есть следующие производители:
- Производитель А:
name: "Acme"
,products_on_manufacturer: 2
- Производитель Б:
name: "Beta"
,products_on_manufacturer: 1
Результатом будет:
{
"manufacturers": [
{ "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
{ "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
]
}
Мутации в базе данных обзоров фильмов
Как уже упоминалось, когда вы определяете таблицу в своей схеме, Data Connect генерирует базовые неявные мутации для каждой таблицы.
type Movie @table { ... }
extend type Mutation {
# Insert a row into the movie table.
movie_insert(...): Movie_Key!
# Upsert a row into movie."
movie_upsert(...): Movie_Key!
# Update a row in Movie. Returns null if a row with the specified id/key does not exist
movie_update(...): Movie_Key
# Update rows based on a filter in Movie.
movie_updateMany(...): Int!
# Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
movie_delete(...): Movie_Key
# Delete rows based on a filter in Movie.
movie_deleteMany(...): Int!
}
С их помощью вы можете реализовывать все более сложные основные случаи CRUD. Скажи это пять раз быстрее!
директива @transaction
Эта директива обеспечивает, чтобы мутация всегда выполнялась в транзакции базы данных.
Мутации с @transaction
гарантированно либо полностью успешны, либо полностью провалены. Если какое-либо из полей транзакции не заполнено, вся транзакция откатывается. С точки зрения клиента, любой сбой ведет себя так, как если бы весь запрос завершился неудачей с ошибкой запроса и выполнение не началось.
Мутации без @transaction
последовательно выполняют каждое корневое поле одно за другим. Он отображает любые ошибки как частичные ошибки поля, но не как последствия последующих выполнений.
Создавать
Давайте сделаем базовые создания.
# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
movie_insert(data: {
title: $title
releaseYear: $releaseYear
genre: $genre
rating: $rating
})
}
# Create a movie with default values
mutation CreateMovie2 {
movie_insert(data: {
title: "Sherlock Holmes"
releaseYear: 2009
genre: "Mystery"
rating: 5
})
}
Или расстройство.
# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
movie_upsert(data: {
title: $title
releaseYear: 2009
genre: "Mystery"
rating: 5
genre: "Mystery/Thriller"
})
}
Выполнение обновлений
Вот обновления. Продюсеры и режиссеры, конечно, надеются, что эти средние рейтинги находятся в тренде.
Поле movie_update
содержит ожидаемый аргумент id
для идентификации записи и поле data
, которое можно использовать для установки значений в этом обновлении.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
Чтобы выполнить несколько обновлений, используйте поле movie_updateMany
.
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
data:
{
rating: $rating
})
}
Используйте операции увеличения, уменьшения, добавления и добавления с помощью _update
Хотя в мутациях _update
и _updateMany
вы можете явно устанавливать значения в data:
, часто имеет смысл применять такой оператор, как приращение, для обновления значений.
Чтобы изменить предыдущий пример обновления, предположим, что вы хотите увеличить рейтинг определенного фильма. Вы можете использовать синтаксис rating_update
с оператором inc
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect поддерживает следующие операторы для обновлений на местах:
-
inc
для увеличения типов данныхInt
,Int64
,Float
,Date
иTimestamp
-
dec
для уменьшения типов данныхInt
,Int64
,Float
,Date
иTimestamp
Для списков вы также можете обновить отдельные значения или списки значений, используя:
-
add
для добавления элементов, если они еще не присутствуют в типах списков, за исключением векторных списков -
remove
, чтобы удалить все элементы, если они есть, из типов списков, кроме векторных списков. -
append
— добавление элементов к типам списков, кроме векторных списков. -
prepend
для добавления элементов к типам списков, кроме векторных списков.
Выполнить удаление
Конечно, вы можете удалить данные фильма. Специалисты по сохранению фильмов, безусловно, захотят, чтобы физические фильмы хранились как можно дольше.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Здесь вы можете использовать _deleteMany
.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Напишите мутации в отношениях
Обратите внимание, как использовать неявную мутацию _upsert
в отношении.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Позвольте Data Connect предоставлять значения, используя синтаксис field_expr
Как обсуждалось в разделе «Ключевые скаляры и значения сервера» , вы можете спроектировать свою схему так, чтобы сервер заполнил значения для общих полей, таких как id
и даты, в ответ на запросы клиентов.
Кроме того, вы можете использовать данные, такие как идентификаторы пользователей, отправленные в объектах request
Data Connect из клиентских приложений.
При реализации мутаций используйте синтаксис field_expr
для запуска обновлений, генерируемых сервером, или доступа к данным из запросов. Например, чтобы передать uid
авторизации, сохраненный в запросе, операции _upsert
, передайте "auth.uid"
в поле userId_expr
.
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}
# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
Или, в знакомом приложении списка дел, при создании нового списка дел вы можете передать id_expr
, чтобы дать серверу указание автоматически сгенерировать UUID для списка.
mutation CreateTodoListWithFirstItem(
$listName: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
name: $listName,
})
}
Дополнительные сведения см. в разделе скаляры _Expr
в справочнике по скалярам .
Запросы на поиск данных авторизации
Мутации Data Connect можно авторизовать, сначала запросив базу данных и проверив результаты запроса с помощью выражений CEL. Это полезно, например, когда вы выполняете запись в таблицу и вам необходимо проверить содержимое строки в другой таблице.
Эта функция поддерживает:
- Директива
@check
, позволяющая оценить содержимое полей и по результатам такой оценки:- Продолжайте создавать, обновлять и удалять, определенные мутацией.
- Перейдите к возврату результатов запроса
- Используйте возвращаемые значения для выполнения различной логики в клиентском коде.
- Директива
@redact
, которая позволяет исключить результаты запроса из результатов протокола проводной связи.
Эти функции полезны для потоков авторизации .
Эквивалентная схема SQL
-- Movies Table
CREATE TABLE Movies (
movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
title VARCHAR(255) NOT NULL,
release_year INT,
genre VARCHAR(30),
rating INT,
description TEXT,
tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
director VARCHAR(255) NOT NULL,
PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
movie_id UUID REFERENCES Movies(movie_id),
actor_id UUID REFERENCES Actors(actor_id),
role VARCHAR(50) NOT NULL, # "main" or "supporting"
PRIMARY KEY (movie_id, actor_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_auth VARCHAR(255) NOT NULL
username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID REFERENCES Users(user_id),
movie_id UUID REFERENCES Movies(movie_id),
rating INT,
review_text TEXT,
review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (movie_id, user_id)
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);
Что дальше?
- Узнайте, как обеспечить безопасность ваших запросов и изменений с помощью авторизации и аттестации .
- Узнайте, как вызывать запросы и мутации из автоматически создаваемого веб-SDK , Android SDK , iOS SDK и Flutter SDK .
Firebase Data Connect позволяет создавать коннекторы для ваших экземпляров PostgreSQL, управляемых с помощью Google Cloud SQL. Эти соединители представляют собой комбинацию схемы, запросов и мутаций для использования ваших данных.
В руководстве по началу работы представлена схема приложения для просмотра фильмов для PostgreSQL, а в этом руководстве более подробно рассматривается разработка схем Data Connect для PostgreSQL.
В этом руководстве запросы и мутации Data Connect сочетаются с примерами схем. Зачем обсуждать запросы (и мутации ) в руководстве по схемам Data Connect ? Как и другие платформы на основе GraphQL, Firebase Data Connect — это платформа разработки , ориентированная на запросы , поэтому, как разработчик, при моделировании данных вы будете думать о данных, которые нужны вашим клиентам, что сильно повлияет на схему данных, которую вы разрабатываете для своего проекта.
Это руководство начинается с новой схемы для обзоров фильмов , затем рассматриваются запросы и изменения, полученные на основе этой схемы, и, наконец, приводится список SQL, эквивалентный базовой схеме Data Connect .
Схема приложения для обзора фильмов
Представьте, что вы хотите создать сервис, который позволит пользователям отправлять и просматривать обзоры фильмов.
Вам нужна исходная схема для такого приложения. Позже вы расширите эту схему для создания сложных реляционных запросов.
Стол для фильмов
Схема фильмов содержит такие основные директивы, как:
-
@table(name)
и@col(name)
для настройки имен таблиц и столбцов SQL. Data Connect генерирует имена Snake_case, если они не указаны. -
@col(dataType)
для настройки типов столбцов SQL. -
@default
для настройки значений по умолчанию для столбца SQL во время вставки.
Для получения более подробной информации ознакомьтесь со справочной документацией по @table
, @col
, @default
.
# Movies
type Movie @table(name: "movie", key: "id") {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String @col(dataType: "varchar(20)")
rating: Int
description: String
}
Ключевые скаляры и значения сервера
Прежде чем подробнее рассмотреть приложение для просмотра фильмов, давайте познакомимся со скалярами ключей Data Connect и значениями сервера .
Ключевые скаляры — это краткие идентификаторы объектов, которые Data Connect автоматически собирает из ключевых полей в ваших схемах. Ключевые скаляры связаны с эффективностью, позволяя вам за один вызов найти информацию о личности и структуре ваших данных. Они особенно полезны, когда вы хотите выполнить последовательные действия над новыми записями и вам нужен уникальный идентификатор для перехода к предстоящим операциям, а также когда вы хотите получить доступ к реляционным ключам для выполнения дополнительных, более сложных операций.
Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя сохраненные или легко вычислимые значения в соответствии с конкретными выражениями CEL на стороне сервера в аргументе expr
. Например, вы можете определить поле с меткой времени, применяемой при доступе к полю, используя время, сохраненное в запросе операции, updatedAt: Timestamp! @default(expr: "request.time")
.
Таблица метаданных фильма
Теперь давайте проследим за режиссерами фильмов, а также настроим отношения один-к-одному с Movie
.
Добавьте поле ссылки, чтобы определить отношения.
Вы можете использовать директиву @ref
для настройки ограничения внешнего ключа.
-
@ref(fields)
для указания полей внешнего ключа. -
@ref(references)
для указания полей, на которые имеются ссылки в целевой таблице. По умолчанию для этой ссылки используется первичный ключ, но поля с@unique
также поддерживаются.
Для получения более подробной информации ознакомьтесь со справочной документацией по @ref
.
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
# @unique ensures that each Movie only has one MovieMetadata.
movie: Movie! @unique
# Since it references to another table type, it adds a foreign key constraint.
# movie: Movie! @unique @ref(fields: "movieId", references: "id")
# movieId: UUID! <- implicitly added foreign key field
director: String
}
Актер и киноактер
Далее вы хотите, чтобы актеры снимались в ваших фильмах, и, поскольку между фильмами и актерами существует связь «многие ко многим», создайте соединительную таблицу.
# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
id: UUID! @default(expr: "uuidV4()")
name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
movie: Movie!
# movieId: UUID! <- implicitly added foreign key field
actor: Actor!
# actorId: UUID! <- implicitly added foreign key field
role: String! # "main" or "supporting"
# optional other fields
}
Пользователь
Наконец, пользователи вашего приложения.
# Users
# Suppose a user can leave reviews for movies
type User @table {
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
}
Поддерживаемые типы данных
Data Connect поддерживает следующие скалярные типы данных с присвоением типов PostgreSQL с помощью @col(dataType:)
.
Тип Data Connect | Встроенный тип GraphQL или Пользовательский тип Data Connect | Тип PostgreSQL по умолчанию | Поддерживаемые типы PostgreSQL (псевдоним в скобках) |
---|---|---|---|
Нить | ГрафQL | текст | текст бит (n), варбит (n) символ (п), варчар (п) |
Int | ГрафQL | интервал | Int2 (маллинт, малый сериал), int4 (целое число, целое число, серийный номер) |
Плавать | ГрафQL | поплавок8 | float4 (реальный) float8 (двойная точность) числовой (десятичный) |
логическое значение | ГрафQL | логическое значение | логическое значение |
UUID | Обычай | uuid | uuid |
Int64 | Обычай | bigint | int8 (бигинт, большойсериал) числовой (десятичный) |
Дата | Обычай | дата | дата |
Временная метка | Обычай | временная метка | временная метка Примечание. Информация о местном часовом поясе не сохраняется. |
Вектор | Обычай | вектор | вектор См. раздел Выполнение поиска по сходству векторов с помощью Vertex AI . |
-
List
GraphQL отображается в одномерный массив.- Например,
[Int]
сопоставляется сint5[]
,[Any]
сопоставляется сjsonb[]
. - Data Connect не поддерживает вложенные массивы.
- Например,
Используйте сгенерированные поля для построения запросов и мутаций.
Ваши запросы и мутации Data Connect расширят набор полей, автоматически создаваемых Data Connect на основе типов и отношений типов в вашей схеме. Эти поля генерируются локальными инструментами всякий раз, когда вы редактируете свою схему.
Как вы узнали из руководства по началу работы, консоль Firebase и наши локальные инструменты разработки используют эти автоматически сгенерированные поля, чтобы предоставлять вам специальные административные запросы и изменения, которые вы можете использовать для заполнения данных и проверки содержимого ваших таблиц.
В процессе разработки вы будете реализовывать развертываемые запросы и развертываемые мутации, включенные в ваши соединители, на основе этих автоматически созданных полей.
Автоматически создаваемое именование полей
Data Connect определяет подходящие имена для полей, автоматически создаваемых на основе объявлений типа схемы. Например, при работе с источником PostgreSQL, если вы определите таблицу с именем Movie
, сервер сгенерирует:
- Поля для чтения данных в сценариях использования одной таблицы с понятными именами
movie
(единственное число, для получения отдельных результатов с передачей аргументов, напримерeq
) иmovies
(множественное число, для получения списков результатов с передачей аргументов, таких какgt
, и операций, таких какorderby
). Data Connect также генерирует поля для многотабличных реляционных операций с явными именами, такими какactors_on_movies
илиactors_via_actormovie
. - Поля для записи данных со знакомым названием типа
movie_insert
,movie_upsert
...
Язык определения схемы также позволяет явно контролировать создание имен для полей с использованием аргументов директивы singular
и plural
.
Директивы для запросов и мутаций
В дополнение к директивам, которые вы используете при определении типов и таблиц, Data Connect предоставляет директивы @auth
, @check
, @redact
и @transaction
для улучшения поведения запросов и мутаций.
Директива | Применимо к | Описание |
---|---|---|
@auth | Запросы и мутации | Определяет политику аутентификации для запроса или мутации. См. руководство по авторизации и аттестации . |
@check | Запросы на поиск данных авторизации | Проверяет наличие указанных полей в результатах запроса. Выражение Common Expression Language (CEL) используется для проверки значений полей. См. руководство по авторизации и аттестации . |
@redact | Запросы | Редактирует часть ответа клиента. См. руководство по авторизации и аттестации . |
@transaction | Мутации | Обеспечивает, чтобы мутация всегда выполнялась в транзакции базы данных. См. примеры мутаций приложения Movie . |
Запросы к базе данных обзоров фильмов
Запрос Data Connect определяется с помощью объявления типа операции запроса, имени операции, нуля или более аргументов операции и нуля или более директив с аргументами.
В кратком руководстве пример запроса listEmails
не имел параметров. Конечно, во многих случаях данные, передаваемые в поля запроса, будут динамическими. Вы можете использовать синтаксис $variableName
для работы с переменными как с одним из компонентов определения запроса.
Итак, следующий запрос имеет:
- Определение типа
query
- Имя операции (запроса)
ListMoviesByGenre
- Аргумент операции
$genre
с одной переменной - Одна директива
@auth
.
query ListMoviesByGenre($genre: String!) @auth(level: USER)
Для каждого аргумента запроса требуется объявление типа, встроенного, например String
, или пользовательского типа, определяемого схемой, например Movie
.
Давайте посмотрим на сигнатуру все более сложных запросов. В конце вы представите мощные и лаконичные выражения отношений, которые можно использовать для построения развертываемых запросов.
Ключевые скаляры в запросах
Но сначала замечание о ключевых скалярах.
Data Connect определяет специальный тип для ключевых скаляров, идентифицируемый _Key
. Например, тип ключевого скаляра для нашей таблицы Movie
— Movie_Key
.
Вы получаете скаляры ключей в качестве ответа, возвращаемого большинством автоматически сгенерированных полей чтения, или, конечно, из запросов, в которых вы получили все поля, необходимые для построения скалярного ключа.
Сингулярные автоматические запросы, такие как movie
в нашем примере, поддерживают ключевой аргумент, который принимает ключевой скаляр.
Вы можете передать ключевой скаляр как литерал. Но вы можете определить переменные для передачи ключевых скаляров в качестве входных данных.
query GetMovie($myKey: Movie_Key!) {
movie(key: $myKey) { title }
}
Их можно предоставить в запросе JSON следующим образом (или в других форматах сериализации):
{
# …
"variables": {
"myKey": {"foo": "some-string-value", "bar": 42}
}
}
Благодаря пользовательскому скалярному анализу Movie_Key
также может быть создан с использованием синтаксиса объекта, который может содержать переменные. Это в основном полезно, когда вы хотите разбить отдельные компоненты на разные переменные по какой -то причине.
Псевдоним в запросах
Data Connect поддерживает псевдоним graphQL в запросах. С псевдонимами вы переименуете данные, которые возвращаются в результатах запроса. Один запрос Data Connect может применить несколько фильтров или других операций запроса в одном эффективном запросе на сервер, эффективно выпустив несколько «подраздел» одновременно. Чтобы избежать столкновений имен в возвращенном наборе данных, вы используете псевдонимы для различения подразделений.
Вот запрос, в котором выражение использует псевдоним mostPopular
.
query ReviewTopPopularity($genre: String) {
mostPopular: review(first: {
where: {genre: {eq: $genre}},
orderBy: {popularity: DESC}
}) { … }
}
Простые запросы с фильтрами
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
}
}
query MoviesByReleaseYear($min: Int, $max: Int) {
movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) { … }
}
Операторы limit
и offset
(единственные, множественные запросы)
Вы можете выполнить страницу на результатах. Эти аргументы принимаются, но не возвращаются в результатах.
query MoviesTop10 {
movies(orderBy: [{ rating: DESC }], limit: 10) {
# graphql: list the fields from the results to return
title
}
}
Включает в массивные поля
Вы можете проверить, что поле массива включает указанный элемент.
# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
movies(where: { tags: { includes: $tag }}) {
# graphql: list the fields from the results to return
id
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}}) {...}
matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}
or
and
для составленных фильтров
Используйте or
and
для более сложной логики.
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 Connect могут получить доступ к данным на основе отношений между таблицами. Вы можете использовать отношения объекта (один на один) или массив (один-один-многие), определенные в вашей схеме, для создания вложенных запросов, то есть получает данные для одного типа вместе с данными из вложенного или родственного типа.
Такие запросы используют Magic Data Connect _on_
и _via
Syntax в сгенерированных полях чтения.
Вы будете вносить модификации схемы из нашей первоначальной версии .
Многие к одному
Давайте добавим обзоры в наше приложение, с таблицей Review
и модификациями для User
.
# User table is keyed by Firebase Auth UID.
type User @table {
# `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
# The `user: User!` field in the Review table generates the following one-to-many query field.
# reviews_on_user: [Review!]!
# The `Review` join table the following many-to-many query field.
# movies_via_Review: [Movie!]!
}
# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
user: User!
# The user field adds the following foreign key field. Feel free to uncomment and customize it.
# userUid: String!
movie: Movie!
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
# movieId: UUID!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
Запрос для многих к одному
Теперь давайте посмотрим на запрос с псевдонимом, чтобы проиллюстрировать _via_
syntax.
query UserMoviePreferences($username: String!) @auth(level: USER) {
users(where: { username: { eq: $username } }) {
likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
title
genre
}
dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
title
genre
}
}
}
Один к одному
Вы можете увидеть шаблон. Ниже схема модифицирована для иллюстрации.
# Movies
type Movie
@table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int @col(name: "release_year")
genre: String
rating: Int @col(name: "rating")
description: String @col(name: "description")
tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
@table(
name: "MovieMetadata"
) {
# @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
# In this case, @ref(fields: "id") is implied
movie: Movie! @ref
# movieId: UUID <- this is created by the above @ref
director: String @col(name: "director")
}
extend type MovieMetadata {
movieId: UUID! # matches primary key of referenced type
...
}
extend type Movie {
movieMetadata: MovieMetadata # can only be non-nullable on ref side
# conflict-free name, always generated
movieMetadatas_on_movie: MovieMetadata
}
Запрос на один на один
Вы можете запросить, используя _on_
синтаксис.
# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
movie(id: $id) {
movieMetadatas_on_movie {
director
}
}
}
Многие ко многим
Фильмы нуждаются в актерах, а актерам нужны фильмы. У них есть много отношений, которые вы можете моделировать с помощью таблицы MovieActors
.
# MovieActors Join Table Definition
type MovieActors @table(
key: ["movie", "actor"] # join key triggers many-to-many generation
) {
movie: Movie!
actor: Actor!
}
# generated extensions for the MovieActors join table
extend type MovieActors {
movieId: UUID!
actorId: UUID!
}
# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
actors: [Actor!]! # many-to-many via join table
movieActors_on_actor: [MovieActors!]!
# since MovieActors joins distinct types, type name alone is sufficiently precise
actors_via_MovieActors: [Actor!]!
}
extend type Actor {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
movies: [Movie!]! # many-to-many via join table
movieActors_on_movie: [MovieActors!]!
movies_via_MovieActors: [Movie!]!
}
Запрос многих ко многим
Давайте посмотрим на запрос с псевдонимом, чтобы проиллюстрировать _via_
syntax.
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
}
}
}
Запросы агрегации
Что такое агрегаты и зачем их использовать?
Совокупные поля позволяют выполнять расчеты в списке результатов. С совокупными полями вы можете делать что -то вроде:
- Найдите средний балл обзора
- Найдите общую стоимость товаров в корзине
- Найдите продукт с самым высоким или с самым низким рейтингом
- Считайте количество продуктов в вашем магазине
Агрегаты выполняются на сервере, который предлагает ряд преимуществ по сравнению с их клиентской стороной:
- Более высокая производительность приложения (поскольку вы избегаете расчетов на стороне клиента)
- Снижение выходящих затрат на данные (поскольку вы отправляете только агрегированные результаты вместо всех входов)
- Улучшенная безопасность (поскольку вы можете предоставить клиентам доступ к агрегированным данным вместо всего набора данных)
Пример схемы для агрегатов
В этом разделе мы переключимся на пример схемы магазина, которая хорошо объясняет, как использовать агрегаты:
type Product @table {
name: String!
manufacturer: String!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
Простые агрегаты
_count для всех полей
Самое простое поле агрегирования - _count
: оно возвращает, сколько рядов соответствует вашему запросу. Для каждого поля в вашем типе Data Connect генерирует соответствующие поля агрегации в зависимости от типа поля.
query CountProducts {
products {
_count
}
}
Например, если у вас есть 5 продуктов в вашей базе данных, результат будет:
{
"products": [
{
"_count": 5
}
]
}
Все поля имеют поле <field>_count
, которое подсчитывает, сколько строк имеет не нулевое значение в этом поле.
query CountProductsWithExpirationDate {
products {
expirationDate_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
}
}
Например, если у вас есть следующие продукты:
- Продукт 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
}
}
Например, если у вас есть следующие даты истечения срока действия:
- Продукт A:
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
}
}
Например, если у вас есть следующие производители:
- Продукт A:
manufacturer: "Acme"
- Продукт B:
manufacturer: "Beta"
- Продукт C:
manufacturer: "Acme"
Результатом будет:
{
"products": [
{ "manufacturer": "Acme" },
{ "manufacturer": "Beta" }
]
}
Вы также можете использовать distinct
аргумент на совокупных полях, чтобы вместо этого собирать различные значения. Например:
query CountDistinctManufacturers {
products {
manufacturer_count(distinct: true)
}
}
Например, если у вас есть следующие производители:
- Продукт A:
manufacturer: "Acme"
- Продукт B:
manufacturer: "Beta"
- Продукт C:
manufacturer: "Acme"
Результатом будет:
{
"products": [
{
"manufacturer_count": 2
}
]
}
Сгруппированные агрегаты
Вы выполняете сгруппированный заполнитель, выбирая смесь агрегатных и неагрегированных полей на типе. Это объединяет все соответствующие строки, которые имеют одинаковое значение для неагрегических полей, и рассчитывают совокупные поля для этой группы. Например:
query MostExpensiveProductByManufacturer {
products {
manufacturer
price_max
}
}
Например, если у вас есть следующие продукты:
- Продукт A:
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
}
}
Например, если у вас есть следующие продукты:
- Продукт A:
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
}
}
}
Например, если у вас есть следующие производители:
- Производитель 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 } }
]
}
Мутации для базы данных обзоров фильмов
Как уже упоминалось, когда вы определяете таблицу в своей схеме, Data Connect будет генерировать основные неявные мутации для каждой таблицы.
type Movie @table { ... }
extend type Mutation {
# Insert a row into the movie table.
movie_insert(...): Movie_Key!
# Upsert a row into movie."
movie_upsert(...): Movie_Key!
# Update a row in Movie. Returns null if a row with the specified id/key does not exist
movie_update(...): Movie_Key
# Update rows based on a filter in Movie.
movie_updateMany(...): Int!
# Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
movie_delete(...): Movie_Key
# Delete rows based on a filter in Movie.
movie_deleteMany(...): Int!
}
С этим вы можете реализовать все более сложные ядра CRUD. Скажи это пять раз быстро!
@transaction
Directive
Эта директива обеспечивает соблюдение того, что мутация всегда работает в транзакции базы данных.
Мутации с @transaction
гарантированно либо полностью преуспевают, либо полностью пройдут. Если какое -либо из полей в рамках транзакции не удается, вся транзакция откатится назад. С точки зрения клиента, любой сбой ведет себя так, как если бы весь запрос не удался с ошибкой запроса, и выполнение не началось.
Мутации без @transaction
выполняют каждое корневое поле один за другим в последовательности. Он поверхностных ошибок в качестве частичных поля ошибок, но не воздействие последующих выполнений.
Создавать
Давайте сделаем базовые создания.
# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
movie_insert(data: {
title: $title
releaseYear: $releaseYear
genre: $genre
rating: $rating
})
}
# Create a movie with default values
mutation CreateMovie2 {
movie_insert(data: {
title: "Sherlock Holmes"
releaseYear: 2009
genre: "Mystery"
rating: 5
})
}
Или поднятие.
# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
movie_upsert(data: {
title: $title
releaseYear: 2009
genre: "Mystery"
rating: 5
genre: "Mystery/Thriller"
})
}
Выполнить обновления
Вот обновления. Производители и директора, безусловно, надеются, что эти средние рейтинги находятся на тренде.
Поле movie_update
содержит ожидаемый аргумент id
для определения записи и поле data
, которое вы можете использовать для установки значений в этом обновлении.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
Чтобы выполнить несколько обновлений, используйте поле movie_updateMany
.
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
data:
{
rating: $rating
})
}
Используйте приращение, уменьшение, добавление и подготовку операций с помощью _update
В то время как в мутациях _update
и _updateMany
вы можете явно установить значения в data:
,, часто имеет смысл применять оператор, как приращение к обновлению значений.
Чтобы изменить более ранний пример обновления, предположим, что вы хотите повысить рейтинг конкретного фильма. Вы можете использовать синтаксис rating_update
с оператором inc
.
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect поддерживает следующие операторы для обновлений поля:
-
inc
для увеличенияInt
,Int64
,Float
,Date
иTimestamp
Data -
dec
to DermentInt
,Int64
,Float
,Date
иTimestamp
Data
Для списков вы также можете обновить с отдельными значениями или списками значений, используя:
-
add
в добавление элементов, если они еще не присутствуют для списков, кроме списков векторов -
remove
, чтобы удалить все элементы, если они присутствуют, из типов списков, кроме векторных списков -
append
к добавлению элементов (ы) к типам списков, кроме векторных списков -
prepend
для подготовки элементов (ы) к типам списков, кроме списков векторов
Выполнить удаления
Вы, конечно, можете удалить данные фильма. Компания Conservationisters, безусловно, хотела бы, чтобы физические фильмы поддерживались как можно дольше.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Здесь вы можете использовать _deleteMany
.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Напишите мутации об отношениях
Соблюдайте, как использовать неявную мутацию _upsert
для отношения.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Пусть Data Connect значения питания с помощью синтаксиса field_expr
Как обсуждалось в ключевых скаляциях и значениях сервера , вы можете разработать свою схему, чтобы сервер заполнял значения для общих полей, таких как id
, и даты в ответ на запросы клиентов.
Кроме того, вы можете использовать данные, такие как идентификаторы пользователей, отправленные в объектах request
Data Connect из клиентских приложений.
Когда вы реализуете мутации, используйте синтаксис field_expr
для запуска обновлений, сгенерированных сервером или доступа к данным из запросов. Например, чтобы передать uid
авторизации, хранящийся в запросе на операцию _upsert
, пройти "auth.uid"
в поле userId_expr
.
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}
# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
Или, в знакомых приложениях для списков дел при создании нового списка дел, вы можете передать id_expr
для обучения сервера автоматически генерировать UUID для списка.
mutation CreateTodoListWithFirstItem(
$listName: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
name: $listName,
})
}
Для получения дополнительной информации см. Скалары _Expr
в ссылке на скаляры .
Запросы поиска данных авторизации
Мутации Data Connect могут быть авторизованы путем первого запроса базы данных и проверки результатов запроса с помощью выражений CEL. Это полезно, когда, например, вы пишете в таблицу и вам нужно проверить содержимое строки в другой таблице.
Эта функция поддерживает:
- Директива
@check
, которая позволяет оценивать содержимое полей и на основе результатов такой оценки:- Перейдите с созданием, обновлениями и удалениями, определяемыми мутацией
- Перейти, чтобы вернуть результаты запроса
- Используйте возвращаемые значения, чтобы выполнить различную логику в вашем клиентском коде
- Директива
@redact
, которая позволяет вам опустить результаты запроса из результатов протокола провода.
Эти функции полезны для потоков авторизации .
Эквивалентная схема SQL
-- Movies Table
CREATE TABLE Movies (
movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
title VARCHAR(255) NOT NULL,
release_year INT,
genre VARCHAR(30),
rating INT,
description TEXT,
tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
director VARCHAR(255) NOT NULL,
PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
movie_id UUID REFERENCES Movies(movie_id),
actor_id UUID REFERENCES Actors(actor_id),
role VARCHAR(50) NOT NULL, # "main" or "supporting"
PRIMARY KEY (movie_id, actor_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_auth VARCHAR(255) NOT NULL
username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID REFERENCES Users(user_id),
movie_id UUID REFERENCES Movies(movie_id),
rating INT,
review_text TEXT,
review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (movie_id, user_id)
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);
Что дальше?
- Узнайте, как сделать ваши запросы и мутации безопасными с разрешением и аттестацией .
- Узнайте, как называть ваши запросы и мутации из автоматически сгенерированного веб-SDK , Android SDK , iOS SDK и Flutter SDK .
Firebase Data Connect позволяет создавать разъемы для ваших экземпляров PostgreSQL, управляемых с Google Cloud SQL. Эти разъемы представляют собой комбинации схемы, запросов и мутаций для использования ваших данных.
Руководство «Начало работы» представило схему приложения для обзора фильма для PostgreSQL, и это руководство более подробно рассматривает, как разработать схемы Data Connect для PostgreSQL.
Это руководство сочетает Data Connect запросы и мутации с примерами схемы. Зачем обсуждать запросы (и мутации ) в руководстве о схемах Data Connect ? Как и другие платформы на основе GraphQL, Firebase Data Connect является платформой разработки , первого запроса , поэтому в качестве разработчика в вашем моделировании данных вы будете думать о данных, которые нужны ваши клиенты, что сильно повлияет на схему данных, которую вы разрабатываете для своего проекта.
Это руководство начинается с новой схемы для обзоров фильмов , а затем охватывает запросы и мутации, полученные из этой схемы, и, наконец, предоставляет список SQL, эквивалентный основной схеме Data Connect .
Схема для приложения для обзора фильма
Представьте, что вы хотите создать услугу, которая позволяет пользователям отправлять и просматривать обзоры фильмов.
Вам нужна начальная схема для такого приложения. Вы продлите эту схему позже, чтобы создать сложные реляционные запросы.
Стол фильма
Схема для фильмов содержит основные директивы, такие как:
-
@table(name)
и@col(name)
чтобы настроить таблицу SQL и имена столбцов. Data Connect генерирует имена smake_case, если не указано. -
@col(dataType)
для настройки типов столбцов SQL. -
@default
для настройки значений по умолчанию в столбце SQL во время вставки.
Для получения более подробной информации ознакомьтесь с эталонными документами для @table
, @col
, @default
.
# Movies
type Movie @table(name: "movie", key: "id") {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String @col(dataType: "varchar(20)")
rating: Int
description: String
}
Ключевые скаляры и значения сервера
Прежде чем больше посмотреть на приложение для обзора фильма, давайте введем данные о клавишах Data Connect и значениями сервера .
Ключевые скаляры - это краткие идентификаторы объектов, которые данные подключаются автоматически собираются из ключевых полей в ваших схемах. Ключевые скаляры - это эффективность, что позволяет вам найти информацию об одном вызове об идентификации и структуре ваших данных. Они особенно полезны, если вы хотите выполнить последовательные действия на новых записях и нуждаются в уникальном идентификаторе для передачи на предстоящие операции, а также когда вы хотите получить доступ к реляционным ключам, чтобы выполнить дополнительные более сложные операции.
Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя хранимые или легкокомпюссимые значения в соответствии с конкретными выражениями CEL на стороне сервера в аргументе expr
. Например, вы можете определить поле с помощью временной метки, применяемой, когда поле будет доступно с использованием времени, хранящегося в запросе операции, updatedAt: Timestamp! @default(expr: "request.time")
.
Стол метаданных фильмов
Теперь давайте следим за режиссерами кино, а также установим отношения с одним к одному с Movie
.
Добавьте справочное поле, чтобы определить отношения.
Вы можете использовать директиву @ref
для настройки ограничения иностранного ключа.
-
@ref(fields)
, чтобы указать поля иностранного ключа. -
@ref(references)
, чтобы указать поля, упомянутые в целевой таблице. Эта ссылка по умолчанию по умолчанию на первичный ключ, но поля с@unique
также поддерживаются.
Для получения более подробной информации, ознакомьтесь с эталонными документами для @ref
.
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
# @unique ensures that each Movie only has one MovieMetadata.
movie: Movie! @unique
# Since it references to another table type, it adds a foreign key constraint.
# movie: Movie! @unique @ref(fields: "movieId", references: "id")
# movieId: UUID! <- implicitly added foreign key field
director: String
}
Актер и движущийся
Далее вы хотите, чтобы актеры снимались в ваших фильмах, и, поскольку у вас есть отношения между фильмами и актерами, создали таблицу соединения.
# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
id: UUID! @default(expr: "uuidV4()")
name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
movie: Movie!
# movieId: UUID! <- implicitly added foreign key field
actor: Actor!
# actorId: UUID! <- implicitly added foreign key field
role: String! # "main" or "supporting"
# optional other fields
}
Пользователь
Наконец, пользователи для вашего приложения.
# Users
# Suppose a user can leave reviews for movies
type User @table {
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
}
Поддерживаемые типы данных
Data Connect поддерживает следующие скалярные типы данных с назначениями для типов PostgreSQL с использованием @col(dataType:)
.
Data Connect тип | Встроенный тип graphQL или Data Connect пользовательский тип | По умолчанию PostgreSQL Тип | Поддерживается типы PostgreSQL (псевдоним в скобках) |
---|---|---|---|
Нить | ГрафQL | текст | текст бит (n), varbt (n) char (n), varchar (n) |
Int | ГрафQL | интервал | Int2 (smallint, smotherial), int4 (Integer, int, сериал) |
Плавать | ГрафQL | поплавок8 | float4 (реально) float8 (двойная точность) числовое (десятичное) |
логическое значение | ГрафQL | логическое значение | логическое значение |
UUID | Обычай | uuid | uuid |
Int64 | Обычай | bigint | int8 (bigint, bigserial) числовое (десятичное) |
Дата | Обычай | дата | дата |
Временная метка | Обычай | временная метка | временная метка Примечание: локальная информация о часовом поясе не хранится. |
Вектор | Обычай | вектор | вектор |
-
List
GraphQL карты в одномерный массив.- Например,
[Int]
карты наint5[]
,[Any]
карты вjsonb[]
. - Data Connect не поддерживает вложенные массивы.
- Например,
Используйте сгенерированные поля для построения запросов и мутаций
Ваши Data Connect и мутации будут расширять набор полей, сгенерированных Data Connect на основе типов и типов отношений в вашей схеме. Эти поля генерируются локальными инструментами всякий раз, когда вы редактируете свою схему.
Как вы обнаружили в Руководстве по началу работы, консоль Firebase и наше локальное инструменты для разработки используют эти автоматические поля, чтобы предоставить вам специальные административные запросы и мутации, которые вы можете использовать для семян данных и проверить содержимое ваших таблиц.
В процессе разработки вы будете внедрять развертываемые запросы и развертываемые мутации , связанные с вашими разъемами, на основе этих автоматических полей.
Автопогенерированное поле поля
Data Connect Prefers Подходящие имена для поля автоматически генерируются на основе объявлений типа схемы. Например, работа с источником PostgreSQL, если вы определите таблицу с именем Movie
, сервер будет генерировать:
- Поля для чтения данных в вариантах использования в одиночной таблице с
movie
«Дружественные имена» (единственное число, для получения индивидуальных результатов, передавающих аргументы, такие какeq
) иmovies
(множественное число, для получения списков результатов, таких какgt
, и такие операции, какorderby
). Data Connect также генерирует поля для многоточных реляционных операций с явными именами, такими какactors_on_movies
илиactors_via_actormovie
. - Поля для написания данных со знакомым именем, таким как
movie_insert
,movie_upsert
...
Язык определения схемы также позволяет четко контролировать, как имена генерируются для областей, используя singular
и plural
директивные аргументы.
Директивы на запросы и мутации
В дополнение к директивам, которые вы используете при определении типов и таблиц, Data Connect предоставляет директивы @auth
, @check
, @redact
и @transaction
для увеличения поведения запросов и мутаций.
Директива | Применимо к | Описание |
---|---|---|
@auth | Запросы и мутации | Определяет политику аутентификации для запроса или мутации. См. Руководство по авторизации и аттестации . |
@check | Запросы поиска данных авторизации | Проверяет, что указанные поля присутствуют в результатах запроса. Экспрессия общего языка экспрессии (CEL) используется для проверки значений поля. См. Руководство по авторизации и аттестации . |
@redact | Запросы | Отредактирует часть ответа от клиента. См. Руководство по авторизации и аттестации . |
@transaction | Мутации | Обеспечивает соблюдение того, что мутация всегда работает в транзакции базы данных. См. Примеры мутации фильма . |
Запросы на базу данных обзоров фильмов
Вы определяете запрос Data Connect с помощью объявления типа операции запроса, имени операции, ноль или более аргументов операции и нулевых или более директив с аргументами.
В QuickStart пример запроса listEmails
не принял параметров. Конечно, во многих случаях данные, передаваемые полям запроса, будут динамическими. Вы можете использовать синтаксис $variableName
для работы с переменными в качестве одного из компонентов определения запроса.
Итак, в следующем запросе есть:
- Определение типа
query
-
ListMoviesByGenre
операции (запрос) - Один переменная аргумент операции
$genre
- Единственная директива,
@auth
.
query ListMoviesByGenre($genre: String!) @auth(level: USER)
Каждый аргумент запроса требует объявления типа, встроенной подобной String
или пользовательского типа, определенного схемой, подобным Movie
.
Давайте посмотрим на подпись все более сложных запросов. Вы закончите, введя мощные, краткие выражения отношений, которые вы можете использовать для построения развертываемых запросов.
Ключевые скаляры в запросах
Но сначала примечание о ключевых скалярах.
Data Connect Определяет специальный тип для скаляров с ключами, идентифицированный _Key
. Например, тип ключевого скаляра для нашей таблицы Movie
- Movie_Key
.
Вы получаете клавишные скаляры в качестве ответа, возвращаемого большинством автоматических полей чтения, или, конечно, из запросов, где вы извлеките все поля, необходимые для создания скалярного ключа.
Университетские автоматические запросы, такие как movie
в нашем бегущем примере, поддерживают ключевой аргумент, который принимает ключевой скаляр.
Вы можете передать ключевой скаляр как буквальный. Но вы можете определить переменные для передачи скаляров с ключами в качестве входных.
query GetMovie($myKey: Movie_Key!) {
movie(key: $myKey) { title }
}
Они могут быть предоставлены в запросе json, подобный этому (или другим форматам сериализации):
{
# …
"variables": {
"myKey": {"foo": "some-string-value", "bar": 42}
}
}
Благодаря пользовательскому скалярному анализу, Movie_Key
также может быть построен с использованием синтаксиса объекта, который может содержать переменные. Это в основном полезно, когда вы хотите разбить отдельные компоненты на разные переменные по какой -то причине.
Псевдоним в запросах
Data Connect поддерживает псевдоним graphQL в запросах. С псевдонимами вы переименуете данные, которые возвращаются в результатах запроса. Один запрос Data Connect может применить несколько фильтров или других операций запроса в одном эффективном запросе на сервер, эффективно выпустив несколько «подраздел» одновременно. Чтобы избежать столкновений имен в возвращенном наборе данных, вы используете псевдонимы для различения подразделений.
Вот запрос, в котором выражение использует псевдоним mostPopular
.
query ReviewTopPopularity($genre: String) {
mostPopular: review(first: {
where: {genre: {eq: $genre}},
orderBy: {popularity: DESC}
}) { … }
}
Простые запросы с фильтрами
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
}
}
query MoviesByReleaseYear($min: Int, $max: Int) {
movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) { … }
}
Операторы limit
и offset
(единственные, множественные запросы)
Вы можете выполнить страницу на результатах. Эти аргументы принимаются, но не возвращаются в результатах.
query MoviesTop10 {
movies(orderBy: [{ rating: DESC }], limit: 10) {
# graphql: list the fields from the results to return
title
}
}
Включает в массивные поля
Вы можете проверить, что поле массива включает указанный элемент.
# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
movies(where: { tags: { includes: $tag }}) {
# graphql: list the fields from the results to return
id
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}}) {...}
matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}
or
and
для составленных фильтров
Используйте or
and
для более сложной логики.
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 Connect могут получить доступ к данным на основе отношений между таблицами. Вы можете использовать отношения объекта (один на один) или массив (один-один-многие), определенные в вашей схеме, для создания вложенных запросов, то есть получает данные для одного типа вместе с данными из вложенного или родственного типа.
Такие запросы используют Magic Data Connect _on_
и _via
Syntax в сгенерированных полях чтения.
Вы будете вносить модификации схемы из нашей первоначальной версии .
Многие к одному
Давайте добавим обзоры в наше приложение, с таблицей Review
и модификациями для User
.
# User table is keyed by Firebase Auth UID.
type User @table {
# `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
# The `user: User!` field in the Review table generates the following one-to-many query field.
# reviews_on_user: [Review!]!
# The `Review` join table the following many-to-many query field.
# movies_via_Review: [Movie!]!
}
# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
user: User!
# The user field adds the following foreign key field. Feel free to uncomment and customize it.
# userUid: String!
movie: Movie!
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
# movieId: UUID!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
Запрос для многих к одному
Теперь давайте посмотрим на запрос с псевдонимом, чтобы проиллюстрировать _via_
syntax.
query UserMoviePreferences($username: String!) @auth(level: USER) {
users(where: { username: { eq: $username } }) {
likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
title
genre
}
dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
title
genre
}
}
}
Один к одному
Вы можете увидеть шаблон. Ниже схема модифицирована для иллюстрации.
# Movies
type Movie
@table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int @col(name: "release_year")
genre: String
rating: Int @col(name: "rating")
description: String @col(name: "description")
tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
@table(
name: "MovieMetadata"
) {
# @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
# In this case, @ref(fields: "id") is implied
movie: Movie! @ref
# movieId: UUID <- this is created by the above @ref
director: String @col(name: "director")
}
extend type MovieMetadata {
movieId: UUID! # matches primary key of referenced type
...
}
extend type Movie {
movieMetadata: MovieMetadata # can only be non-nullable on ref side
# conflict-free name, always generated
movieMetadatas_on_movie: MovieMetadata
}
Запрос на один на один
Вы можете запросить, используя _on_
синтаксис.
# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
movie(id: $id) {
movieMetadatas_on_movie {
director
}
}
}
Многие ко многим
Фильмы нуждаются в актерах, а актерам нужны фильмы. У них есть много отношений, которые вы можете моделировать с помощью таблицы MovieActors
.
# MovieActors Join Table Definition
type MovieActors @table(
key: ["movie", "actor"] # join key triggers many-to-many generation
) {
movie: Movie!
actor: Actor!
}
# generated extensions for the MovieActors join table
extend type MovieActors {
movieId: UUID!
actorId: UUID!
}
# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
actors: [Actor!]! # many-to-many via join table
movieActors_on_actor: [MovieActors!]!
# since MovieActors joins distinct types, type name alone is sufficiently precise
actors_via_MovieActors: [Actor!]!
}
extend type Actor {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
movies: [Movie!]! # many-to-many via join table
movieActors_on_movie: [MovieActors!]!
movies_via_MovieActors: [Movie!]!
}
Запрос многих ко многим
Давайте посмотрим на запрос с псевдонимом, чтобы проиллюстрировать _via_
syntax.
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
}
}
}
Запросы агрегации
Что такое агрегаты и зачем их использовать?
Совокупные поля позволяют выполнять расчеты в списке результатов. С совокупными полями вы можете делать что -то вроде:
- Найдите средний балл обзора
- Найдите общую стоимость товаров в корзине
- Найдите продукт с самым высоким или с самым низким рейтингом
- Считайте количество продуктов в вашем магазине
Агрегаты выполняются на сервере, который предлагает ряд преимуществ по сравнению с их клиентской стороной:
- Более высокая производительность приложения (поскольку вы избегаете расчетов на стороне клиента)
- Снижение выходящих затрат на данные (поскольку вы отправляете только агрегированные результаты вместо всех входов)
- Улучшенная безопасность (поскольку вы можете предоставить клиентам доступ к агрегированным данным вместо всего набора данных)
Пример схемы для агрегатов
В этом разделе мы переключимся на пример схемы магазина, которая хорошо объясняет, как использовать агрегаты:
type Product @table {
name: String!
manufacturer: String!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
Простые агрегаты
_count для всех полей
Самое простое поле агрегирования - _count
: оно возвращает, сколько рядов соответствует вашему запросу. Для каждого поля в вашем типе Data Connect генерирует соответствующие поля агрегации в зависимости от типа поля.
query CountProducts {
products {
_count
}
}
Например, если у вас есть 5 продуктов в вашей базе данных, результат будет:
{
"products": [
{
"_count": 5
}
]
}
Все поля имеют поле <field>_count
, которое подсчитывает, сколько строк имеет не нулевое значение в этом поле.
query CountProductsWithExpirationDate {
products {
expirationDate_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
}
}
Например, если у вас есть следующие продукты:
- Продукт 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
}
}
Например, если у вас есть следующие даты истечения срока действия:
- Продукт A:
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
}
}
Например, если у вас есть следующие производители:
- Продукт A:
manufacturer: "Acme"
- Продукт B:
manufacturer: "Beta"
- Продукт C:
manufacturer: "Acme"
Результатом будет:
{
"products": [
{ "manufacturer": "Acme" },
{ "manufacturer": "Beta" }
]
}
Вы также можете использовать distinct
аргумент на совокупных полях, чтобы вместо этого собирать различные значения. Например:
query CountDistinctManufacturers {
products {
manufacturer_count(distinct: true)
}
}
Например, если у вас есть следующие производители:
- Продукт A:
manufacturer: "Acme"
- Продукт B:
manufacturer: "Beta"
- Продукт C:
manufacturer: "Acme"
Результатом будет:
{
"products": [
{
"manufacturer_count": 2
}
]
}
Сгруппированные агрегаты
Вы выполняете сгруппированный заполнитель, выбирая смесь агрегатных и неагрегированных полей на типе. Это объединяет все соответствующие строки, которые имеют одинаковое значение для неагрегических полей, и рассчитывают совокупные поля для этой группы. Например:
query MostExpensiveProductByManufacturer {
products {
manufacturer
price_max
}
}
Например, если у вас есть следующие продукты:
- Продукт A:
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
}
}
Например, если у вас есть следующие продукты:
- Продукт A:
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
}
}
}
Например, если у вас есть следующие производители:
- Производитель 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 } }
]
}
Мутации для базы данных обзоров фильмов
Как уже упоминалось, когда вы определяете таблицу в своей схеме, Data Connect будет генерировать основные неявные мутации для каждой таблицы.
type Movie @table { ... }
extend type Mutation {
# Insert a row into the movie table.
movie_insert(...): Movie_Key!
# Upsert a row into movie."
movie_upsert(...): Movie_Key!
# Update a row in Movie. Returns null if a row with the specified id/key does not exist
movie_update(...): Movie_Key
# Update rows based on a filter in Movie.
movie_updateMany(...): Int!
# Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
movie_delete(...): Movie_Key
# Delete rows based on a filter in Movie.
movie_deleteMany(...): Int!
}
С этим вы можете реализовать все более сложные ядра CRUD. Скажи это пять раз быстро!
@transaction
Directive
Эта директива обеспечивает соблюдение того, что мутация всегда работает в транзакции базы данных.
Мутации с @transaction
гарантированно либо полностью преуспевают, либо полностью пройдут. Если какое -либо из полей в рамках транзакции не удается, вся транзакция откатится назад. С точки зрения клиента, любой сбой ведет себя так, как если бы весь запрос не удался с ошибкой запроса, и выполнение не началось.
Мутации без @transaction
выполняют каждое корневое поле один за другим в последовательности. Он поверхностных ошибок в качестве частичных поля ошибок, но не воздействие последующих выполнений.
Создавать
Давайте сделаем базовые создания.
# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
movie_insert(data: {
title: $title
releaseYear: $releaseYear
genre: $genre
rating: $rating
})
}
# Create a movie with default values
mutation CreateMovie2 {
movie_insert(data: {
title: "Sherlock Holmes"
releaseYear: 2009
genre: "Mystery"
rating: 5
})
}
Или поднятие.
# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
movie_upsert(data: {
title: $title
releaseYear: 2009
genre: "Mystery"
rating: 5
genre: "Mystery/Thriller"
})
}
Выполнить обновления
Вот обновления. Производители и директора, безусловно, надеются, что эти средние рейтинги находятся на тренде.
Поле movie_update
содержит ожидаемый аргумент id
для определения записи и поле data
, которое вы можете использовать для установки значений в этом обновлении.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
Чтобы выполнить несколько обновлений, используйте поле movie_updateMany
.
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
data:
{
rating: $rating
})
}
Используйте приращение, уменьшение, добавление и подготовку операций с помощью _update
В то время как в мутациях _update
и _updateMany
вы можете явно установить значения в data:
,, часто имеет смысл применять оператор, как приращение к обновлению значений.
Чтобы изменить более ранний пример обновления, предположим, что вы хотите повысить рейтинг конкретного фильма. Вы можете использовать синтаксис rating_update
с оператором inc
.
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect поддерживает следующие операторы для обновлений поля:
-
inc
для увеличенияInt
,Int64
,Float
,Date
иTimestamp
Data -
dec
to DermentInt
,Int64
,Float
,Date
иTimestamp
Data
Для списков вы также можете обновить с отдельными значениями или списками значений, используя:
-
add
в добавление элементов, если они еще не присутствуют для списков, кроме списков векторов -
remove
, чтобы удалить все элементы, если они присутствуют, из типов списков, кроме векторных списков -
append
к добавлению элементов (ы) к типам списков, кроме векторных списков -
prepend
для подготовки элементов (ы) к типам списков, кроме списков векторов
Выполнить удаления
Вы, конечно, можете удалить данные фильма. Film preservationists will certainly want the physical films to be maintained for as long as possible.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Here you can use _deleteMany
.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Write mutations on relations
Observe how to use the implicit _upsert
mutation on a relation.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Let Data Connect supply values using field_expr
syntax
As discussed in key scalars and server values , you can design your schema so that the server populates values for common fields like id
s and dates in response to client requests.
In addition, you can make use of data, such as user IDs, sent in Data Connect request
objects from client apps.
When you implement mutations, use field_expr
syntax to trigger server-generated updates or access data from requests. For example, to pass the authorization uid
stored in a request to an _upsert
operation, pass "auth.uid"
in the userId_expr
field.
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}
# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
Or, in a familiar to-do list app, when creating a new to-do list, you could pass id_expr
to instruct the server to auto-generate a UUID for the list.
mutation CreateTodoListWithFirstItem(
$listName: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
name: $listName,
})
}
For more information, see the _Expr
scalars in the scalars reference .
Authorization data lookup queries
Data Connect mutations can be authorized by first querying the database and verifying the results of the query with CEL expressions. This is useful when, for example, you are writing to a table, and need to check the contents of a row in another table.
This feature supports:
- The
@check
directive , which lets you evaluate the contents of fields, and based on the results of such evaluation:- Proceed with create, update and deletes defined by a mutation
- Proceed to return the results of a query
- Use returned values to perform different logic in your client code
- The
@redact
directive , which lets you omit query results from wire protocol results.
These features are useful for authorization flows .
Equivalent SQL schema
-- Movies Table
CREATE TABLE Movies (
movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
title VARCHAR(255) NOT NULL,
release_year INT,
genre VARCHAR(30),
rating INT,
description TEXT,
tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
director VARCHAR(255) NOT NULL,
PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
movie_id UUID REFERENCES Movies(movie_id),
actor_id UUID REFERENCES Actors(actor_id),
role VARCHAR(50) NOT NULL, # "main" or "supporting"
PRIMARY KEY (movie_id, actor_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_auth VARCHAR(255) NOT NULL
username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID REFERENCES Users(user_id),
movie_id UUID REFERENCES Movies(movie_id),
rating INT,
review_text TEXT,
review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (movie_id, user_id)
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);
Что дальше?
- Learn how to make your queries and mutations secure with authorization and attestation .
- Learn how to call your queries and mutations from an automatically-generated web SDK , Android SDK , iOS SDK , and Flutter SDK .
Firebase Data Connect lets you create connectors for your PostgreSQL instances managed with Google Cloud SQL. These connectors are combinations of a schema, queries, and mutations for using your data.
The Get started guide introduced a movie review app schema for PostgreSQL, and this guide takes a deeper look at how to design Data Connect schemas for PostgreSQL.
This guide pairs Data Connect queries and mutations with schema examples. Why discuss queries (and mutations ) in a guide about Data Connect schemas? Like other GraphQL-based platforms, Firebase Data Connect is a query-first development platform, so as a developer, in your data modeling you'll be thinking about the data your clients need, which will greatly influence the data schema you develop for your project.
This guide starts with a new schema for movie reviews , then covers the queries and mutations derived from that schema, and lastly provides a SQL listing equivalent to the core Data Connect schema.
The schema for a movie review app
Imagine you want to build a service that lets users submit and view movies reviews.
You need an initial schema for such an app. You will extend this schema later to create complex relational queries.
Movie table
The schema for Movies contains core directives like:
-
@table(name)
and@col(name)
to customize the SQL table and column names. Data Connect generates snake_case names if not specified. -
@col(dataType)
to customize SQL column types. -
@default
to configure SQL column default values during insert.
For more details, check out the reference docs for @table
, @col
, @default
.
# Movies
type Movie @table(name: "movie", key: "id") {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String @col(dataType: "varchar(20)")
rating: Int
description: String
}
Key scalars and server values
Before looking more at the movie review app, let's introduce Data Connect key scalars and server values .
Key scalars are concise object identifiers that Data Connect automatically assembles from key fields in your schemas. Key scalars are about efficiency, allowing you to find in a single call information about the identity and structure of your data. They are especially useful when you want to perform sequential actions on new records and need a unique identifier to pass to upcoming operations, and also when you want to access relational keys to perform additional more complex operations.
Using server values , you can effectively let the server dynamically populate fields in your tables using stored or readily-computable values according to particular server-side CEL expressions in the expr
argument. For example, you can define a field with a timestamp applied when the field is accessed using the time stored in an operation request, updatedAt: Timestamp! @default(expr: "request.time")
.
Movie metadata table
Now let's keep track of movie directors, as well as set up a one-to-one relationship with Movie
.
Add the reference field to define a relationships.
You can use @ref
directive to customize foreign key constraint.
-
@ref(fields)
to specify the foreign key fields. -
@ref(references)
to specify the fields referenced in the target table. This reference defaults to the primary key, but fields with@unique
are also supported.
For more details, check out the reference docs for @ref
.
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
# @unique ensures that each Movie only has one MovieMetadata.
movie: Movie! @unique
# Since it references to another table type, it adds a foreign key constraint.
# movie: Movie! @unique @ref(fields: "movieId", references: "id")
# movieId: UUID! <- implicitly added foreign key field
director: String
}
Actor and MovieActor
Next, you want actors to star in your movies, and since you have a many-to-many relationship between movies and actors, create a join table.
# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
id: UUID! @default(expr: "uuidV4()")
name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
movie: Movie!
# movieId: UUID! <- implicitly added foreign key field
actor: Actor!
# actorId: UUID! <- implicitly added foreign key field
role: String! # "main" or "supporting"
# optional other fields
}
Пользователь
Lastly, users for your app.
# Users
# Suppose a user can leave reviews for movies
type User @table {
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
}
Supported data types
Data Connect supports the following scalar data types, with assignments to PostgreSQL types using @col(dataType:)
.
Data Connect type | GraphQL built-in type or Data Connect custom type | Default PostgreSQL type | Supported PostgreSQL types (alias in parentheses) |
---|---|---|---|
Нить | ГрафQL | текст | текст bit(n), varbit(n) char(n), varchar(n) |
Int | ГрафQL | интервал | Int2 (smallint, smallserial), int4 (integer, int, serial) |
Плавать | ГрафQL | поплавок8 | float4 (real) float8 (double precision) numeric (decimal) |
логическое значение | ГрафQL | логическое значение | логическое значение |
UUID | Обычай | uuid | uuid |
Int64 | Обычай | bigint | int8 (bigint, bigserial) numeric (decimal) |
Дата | Обычай | дата | дата |
Временная метка | Обычай | временная метка | временная метка Note: Local timezone information is not stored. |
Вектор | Обычай | вектор | вектор |
- GraphQL
List
maps to a one-dimensional array.- For example,
[Int]
maps toint5[]
,[Any]
maps tojsonb[]
. - Data Connect does not support nested arrays.
- For example,
Use generated fields to build queries and mutations
Your Data Connect queries and mutations will extend a set of fields automatically generated Data Connect based on the types and type relationships in your schema. These fields are are generated by local tooling whenever you edit your schema.
As you discovered in the Get started guide, the Firebase console and our local development tooling use these auto-generated fields to provide you with ad hoc administrative queries and mutations you can use to seed data and verify the contents of your tables.
In your development process, you will implement deployable queries and deployable mutations bundled in your connectors, based on these auto-generated fields.
Auto-generated field naming
Data Connect infers suitable names for fields auto-generated based on your schema type declarations. For example, working with a PostgreSQL source, if you define a table named Movie
, the server will generate:
- Fields for reading data in single table use cases with the friendly names
movie
(singular, for retrieving individual results passing args likeeq
) andmovies
(plural, for retrieving result lists passing args likegt
and operations likeorderby
). Data Connect also generates fields for multi-table, relational operations with explicit names likeactors_on_movies
oractors_via_actormovie
. - Fields for writing data with familiar name like
movie_insert
,movie_upsert
...
The schema definition language also lets you explicitly control how names are generated for fields using singular
and plural
directive arguments.
Directives for queries and mutations
In addition to the directives you use in defining types and tables, Data Connect provides the @auth
, @check
, @redact
and @transaction
directives for augmenting the behavior of queries and mutations.
Директива | Применимо к | Описание |
---|---|---|
@auth | Queries and mutations | Defines the authentication policy for a query or mutation. See the authorization and attestation guide . |
@check | Authorization data lookup queries | Verifies that specified fields are present in query results. A Common Expression Language (CEL) expression is used to test field values. See the authorization and attestation guide . |
@redact | Запросы | Redacts a part of the response from the client. See the authorization and attestation guide . |
@transaction | Мутации | Enforces that a mutation always run in a database transaction. See the movie app mutation examples . |
Queries for the movie review database
You define a Data Connect query with a query operation type declaration, operation name, zero or more operation arguments, and zero or more directives with arguments.
In the quickstart, the example listEmails
query took no parameters. Of course, in many cases, data passed to query fields will be dynamic. You can use $variableName
syntax to work with variables as one of the components of a query definition.
So the following query has:
- A
query
type definition - A
ListMoviesByGenre
operation (query) name - A single variable
$genre
operation argument - A single directive,
@auth
.
query ListMoviesByGenre($genre: String!) @auth(level: USER)
Every query argument requires a type declaration, a built-in like String
, or a custom, schema-defined type like Movie
.
Let's look at the signature of increasingly complex queries. You'll end by introducing powerful, concise relationship expressions you can use to build your deployable queries.
Key scalars in queries
But first, a note about key scalars.
Data Connect defines a special type for key scalars, identified by _Key
. For example, the type of a key scalar for our Movie
table is Movie_Key
.
You retrieve key scalars as a response returned by most auto-generated read fields, or of course from queries where you have retrieved all the fields needed to build the scalar key.
Singular automatic queries, like movie
in our running example, support a key argument that accepts a key scalar.
You might pass a key scalar as a literal. But, you can define variables to pass key scalars as input.
query GetMovie($myKey: Movie_Key!) {
movie(key: $myKey) { title }
}
These can be provided in request JSON like this (or other serialization formats):
{
# …
"variables": {
"myKey": {"foo": "some-string-value", "bar": 42}
}
}
Thanks to custom scalar parsing, a Movie_Key
can also be constructed using the object syntax, which may contain variables. This is mostly useful when you want to break individual components into different variables for some reason.
Aliasing in queries
Data Connect supports GraphQL aliasing in queries. With aliases, you rename the data that is returned in a query's results. A single Data Connect query can apply multiple filters or other query operations in one efficient request to the server, effectively issuing several "sub-queries" at once. To avoid name collisions in the returned data set, you use aliases to distinguish the sub-queries.
Here is a query where an expression uses the alias mostPopular
.
query ReviewTopPopularity($genre: String) {
mostPopular: review(first: {
where: {genre: {eq: $genre}},
orderBy: {popularity: DESC}
}) { … }
}
Simple queries with filters
Data Connect queries map to all common SQL filters and order operations.
where
and orderBy
operators (singular, plural queries)
Returns all matched rows from the table (and nested associations). Returns an empty array if no records match the filter.
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
}
}
query MoviesByReleaseYear($min: Int, $max: Int) {
movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) { … }
}
limit
and offset
operators (singular, plural queries)
You can perform pagination on results. These arguments are accepted but not returned in results.
query MoviesTop10 {
movies(orderBy: [{ rating: DESC }], limit: 10) {
# graphql: list the fields from the results to return
title
}
}
includes for array fields
You can test that an array field includes a specified item.
# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
movies(where: { tags: { includes: $tag }}) {
# graphql: list the fields from the results to return
id
title
}
}
String operations and regular expressions
Your queries can use typical string search and comparison operations, including regular expressions. Note for efficiency you are bundling several operations here and disambiguating them with aliases.
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}}) {...}
matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}
or
and and
for composed filters
Use or
and and
for more complex logic.
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
}
}
Complex queries
Data Connect queries can access data based on the relationships among tables. You can use the object (one-to-one) or array (one-to-many) relationships defined in your schema to make nested queries, ie fetch data for one type along with data from a nested or related type.
Such queries use magic Data Connect _on_
and _via
syntax in generated read fields.
You'll be making modifications to the schema from our initial version .
Many to one
Let's add reviews to our app, with a Review
table and modifications to User
.
# User table is keyed by Firebase Auth UID.
type User @table {
# `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
# The `user: User!` field in the Review table generates the following one-to-many query field.
# reviews_on_user: [Review!]!
# The `Review` join table the following many-to-many query field.
# movies_via_Review: [Movie!]!
}
# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
user: User!
# The user field adds the following foreign key field. Feel free to uncomment and customize it.
# userUid: String!
movie: Movie!
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
# movieId: UUID!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
Query for many to one
Now let's look at a query, with aliasing, to illustrate _via_
syntax.
query UserMoviePreferences($username: String!) @auth(level: USER) {
users(where: { username: { eq: $username } }) {
likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
title
genre
}
dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
title
genre
}
}
}
Один к одному
You can see the pattern. Below, the schema is modified for illustration.
# Movies
type Movie
@table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int @col(name: "release_year")
genre: String
rating: Int @col(name: "rating")
description: String @col(name: "description")
tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
@table(
name: "MovieMetadata"
) {
# @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
# In this case, @ref(fields: "id") is implied
movie: Movie! @ref
# movieId: UUID <- this is created by the above @ref
director: String @col(name: "director")
}
extend type MovieMetadata {
movieId: UUID! # matches primary key of referenced type
...
}
extend type Movie {
movieMetadata: MovieMetadata # can only be non-nullable on ref side
# conflict-free name, always generated
movieMetadatas_on_movie: MovieMetadata
}
Query for one to one
You can query using _on_
syntax.
# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
movie(id: $id) {
movieMetadatas_on_movie {
director
}
}
}
Many to many
Movies need actors, and actors need movies. They have a many to many relationship you can model with a MovieActors
join table.
# MovieActors Join Table Definition
type MovieActors @table(
key: ["movie", "actor"] # join key triggers many-to-many generation
) {
movie: Movie!
actor: Actor!
}
# generated extensions for the MovieActors join table
extend type MovieActors {
movieId: UUID!
actorId: UUID!
}
# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
actors: [Actor!]! # many-to-many via join table
movieActors_on_actor: [MovieActors!]!
# since MovieActors joins distinct types, type name alone is sufficiently precise
actors_via_MovieActors: [Actor!]!
}
extend type Actor {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
movies: [Movie!]! # many-to-many via join table
movieActors_on_movie: [MovieActors!]!
movies_via_MovieActors: [Movie!]!
}
Query for many to many
Let's look at a query, with aliasing, to illustrate _via_
syntax.
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
}
}
}
Aggregation queries
What are aggregates, and why use them?
Aggregate fields let you perform calculations on a list of results. With aggregate fields, you can do things like:
- Find the average score of a review
- Find the total cost of items in a shopping cart
- Find the highest- or lowest-rated product
- Count the number of products in your store
Aggregates are performed on the server, which offers a number of benefits over calculating them client side:
- Faster app performance (since you avoid client side calculations)
- Reduced data egress costs (since you send just the aggregated results instead of all of the inputs)
- Improved security (since you can give clients access to aggregated data instead of the entire data set)
Example schema for aggregates
In this section, we'll switch to a storefront example schema, which is a good for explaining how to use aggregates:
type Product @table {
name: String!
manufacturer: String!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
Simple aggregates
_count for all fields
The simplest aggregate field is _count
: it returns how many rows match your query. For each field in your type, Data Connect generates corresponding aggregate fields depending on the field type.
query CountProducts {
products {
_count
}
}
For example, if you have 5 products in your database, the result would be:
{
"products": [
{
"_count": 5
}
]
}
All fields have a <field>_count
field, which counts how many rows have a non-null value in that field.
query CountProductsWithExpirationDate {
products {
expirationDate_count
}
}
For example, if you have 3 products with an expiration date, the result would be:
{
"products": [
{
"expirationDate_count": 3
}
]
}
_min, _max, _sum, and _avg for numeric fields
Numeric fields (int, float, int64) also have <field>_min
, <field>_max
, <field>_sum
, and <field>_avg
.
query NumericAggregates {
products {
quantityInStock_max
price_min
price_avg
quantityInStock_sum
}
}
For example, if you have the following products:
- Product A:
quantityInStock: 10
,price: 2.99
- Product B:
quantityInStock: 5
,price: 5.99
- Product C:
quantityInStock: 20
,price: 1.99
The result would be:
{
"products": [
{
"quantityInStock_max": 20,
"price_min": 1.99,
"price_avg": 3.6566666666666666,
"quantityInStock_sum": 35
}
]
}
_min and _max for dates and timestamps
Date and timestamp fields have <field>_min
and <field>_max
.
query DateAndTimeAggregates {
products {
expirationDate_max
expirationDate_min
}
}
For example, if you have the following expiration dates:
- Product A:
2024-01-01
- Product B:
2024-03-01
- Product C:
2024-02-01
The result would be:
{
"products": [
{
"expirationDate_max": "2024-03-01",
"expirationDate_min": "2024-01-01"
}
]
}
Отчетливый
The distinct
argument lets you get all unique values for a field (or combination of fields). Например:
query ListDistinctManufacturers {
products(distinct: true) {
manufacturer
}
}
For example, if you have the following manufacturers:
- Product A:
manufacturer: "Acme"
- Product B:
manufacturer: "Beta"
- Product C:
manufacturer: "Acme"
The result would be:
{
"products": [
{ "manufacturer": "Acme" },
{ "manufacturer": "Beta" }
]
}
You can also use the distinct
argument on aggregate fields to instead aggregate the distinct values. Например:
query CountDistinctManufacturers {
products {
manufacturer_count(distinct: true)
}
}
For example, if you have the following manufacturers:
- Product A:
manufacturer: "Acme"
- Product B:
manufacturer: "Beta"
- Product C:
manufacturer: "Acme"
The result would be:
{
"products": [
{
"manufacturer_count": 2
}
]
}
Grouped aggregates
You perform a grouped aggregate by selecting a mix of aggregate and non-aggregate fields on a type. This groups together all matching rows that have the same value for the non-aggregate fields, and calculate the aggregate fields for that group. Например:
query MostExpensiveProductByManufacturer {
products {
manufacturer
price_max
}
}
For example, if you have the following products:
- Product A:
manufacturer: "Acme"
,price: 2.99
- Product B:
manufacturer: "Beta"
,price: 5.99
- Product C:
manufacturer: "Acme"
,price: 1.99
The result would be:
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
having
and where
with grouped aggregates
You can also use the having
and where
argument to only return groups that meet a provided criteria.
-
having
lets you filter groups by their aggregate fields where
lets you filter the rows based on non-aggregate fields.
query FilteredMostExpensiveProductByManufacturer {
products(having: {price_max: {ge: 2.99}}) {
manufacturer
price_max
}
}
For example, if you have the following products:
- Product A:
manufacturer: "Acme"
,price: 2.99
- Product B:
manufacturer: "Beta"
,price: 5.99
- Product C:
manufacturer: "Acme"
,price: 1.99
The result would be:
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
Aggregates across tables
Aggregate fields can be used in concert with generated one-to-many relationship fields to answer complex questions about your data. Here is a modified schema, with separate table, Manufacturer
, we can use in examples:
type Product @table {
name: String!
manufacturer: Manufacturer!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
type Manufacturer @table {
name: String!
headquartersCountry: String!
}
Now we can use aggregate fields to do things like find how many products a manufacturer makes:
query GetProductCount($id: UUID) {
manufacturers {
name
products_on_manufacturer {
_count
}
}
}
For example, if you have the following manufacturers:
- Manufacturer A:
name: "Acme"
,products_on_manufacturer: 2
- Manufacturer B:
name: "Beta"
,products_on_manufacturer: 1
The result would be:
{
"manufacturers": [
{ "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
{ "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
]
}
Mutations for the movie review database
As mentioned, when you define a table in your schema, Data Connect will generate basic implicit mutations for each table.
type Movie @table { ... }
extend type Mutation {
# Insert a row into the movie table.
movie_insert(...): Movie_Key!
# Upsert a row into movie."
movie_upsert(...): Movie_Key!
# Update a row in Movie. Returns null if a row with the specified id/key does not exist
movie_update(...): Movie_Key
# Update rows based on a filter in Movie.
movie_updateMany(...): Int!
# Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
movie_delete(...): Movie_Key
# Delete rows based on a filter in Movie.
movie_deleteMany(...): Int!
}
With these, you can implement increasingly complex core CRUD cases. Say that five times fast!
@transaction
directive
This directive enforces that a mutation always run in a database transaction.
Mutations with @transaction
are guaranteed to either fully succeed or fully fail. If any of the fields within the transaction fails, the entire transaction is rolled back. From a client standpoint, any failure behaves as if the entire request had failed with a request error and execution had not begun.
Mutations without @transaction
execute each root field one after another in sequence. It surfaces any errors as partial field errors, but not the impacts of the subsequent executions.
Создавать
Let's do basic creates.
# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
movie_insert(data: {
title: $title
releaseYear: $releaseYear
genre: $genre
rating: $rating
})
}
# Create a movie with default values
mutation CreateMovie2 {
movie_insert(data: {
title: "Sherlock Holmes"
releaseYear: 2009
genre: "Mystery"
rating: 5
})
}
Or an upsert.
# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
movie_upsert(data: {
title: $title
releaseYear: 2009
genre: "Mystery"
rating: 5
genre: "Mystery/Thriller"
})
}
Perform updates
Here are updates. Producers and directors certainly hope that those average ratings are on trend.
The movie_update
field contains an expected id
argument to identify a record and a data
field you can use to set values in this update.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
To perform multiple updates, use the movie_updateMany
field.
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
data:
{
rating: $rating
})
}
Use increment, decrement, append, and prepend operations with _update
While in _update
and _updateMany
mutations you can explicitly set values in data:
, it often makes more sense to apply an operator like increment to update values.
To modify the earlier update example, assume you want to increment the rating of a particular movie. You can use rating_update
syntax with the inc
operator.
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect supports the following operators for field updates:
-
inc
to incrementInt
,Int64
,Float
,Date
andTimestamp
data types -
dec
to decrementInt
,Int64
,Float
,Date
andTimestamp
data types
For lists, you can also update with individual values or lists of values using:
-
add
to append item(s) if they are not already present to list types, except Vector lists -
remove
to remove all items, if present, from list types, except Vector lists -
append
to append item(s) to list types, except Vector lists -
prepend
to prepend item(s) to list types, except Vector lists
Perform deletes
You can of course delete movie data. Film preservationists will certainly want the physical films to be maintained for as long as possible.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
Here you can use _deleteMany
.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
Write mutations on relations
Observe how to use the implicit _upsert
mutation on a relation.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Let Data Connect supply values using field_expr
syntax
As discussed in key scalars and server values , you can design your schema so that the server populates values for common fields like id
s and dates in response to client requests.
In addition, you can make use of data, such as user IDs, sent in Data Connect request
objects from client apps.
When you implement mutations, use field_expr
syntax to trigger server-generated updates or access data from requests. For example, to pass the authorization uid
stored in a request to an _upsert
operation, pass "auth.uid"
in the userId_expr
field.
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}
# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
Or, in a familiar to-do list app, when creating a new to-do list, you could pass id_expr
to instruct the server to auto-generate a UUID for the list.
mutation CreateTodoListWithFirstItem(
$listName: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
name: $listName,
})
}
For more information, see the _Expr
scalars in the scalars reference .
Authorization data lookup queries
Data Connect mutations can be authorized by first querying the database and verifying the results of the query with CEL expressions. This is useful when, for example, you are writing to a table, and need to check the contents of a row in another table.
This feature supports:
- The
@check
directive , which lets you evaluate the contents of fields, and based on the results of such evaluation:- Proceed with create, update and deletes defined by a mutation
- Proceed to return the results of a query
- Use returned values to perform different logic in your client code
- The
@redact
directive , which lets you omit query results from wire protocol results.
These features are useful for authorization flows .
Equivalent SQL schema
-- Movies Table
CREATE TABLE Movies (
movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
title VARCHAR(255) NOT NULL,
release_year INT,
genre VARCHAR(30),
rating INT,
description TEXT,
tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
director VARCHAR(255) NOT NULL,
PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
movie_id UUID REFERENCES Movies(movie_id),
actor_id UUID REFERENCES Actors(actor_id),
role VARCHAR(50) NOT NULL, # "main" or "supporting"
PRIMARY KEY (movie_id, actor_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_auth VARCHAR(255) NOT NULL
username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID REFERENCES Users(user_id),
movie_id UUID REFERENCES Movies(movie_id),
rating INT,
review_text TEXT,
review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (movie_id, user_id)
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);
Что дальше?
- Learn how to make your queries and mutations secure with authorization and attestation .
- Learn how to call your queries and mutations from an automatically-generated web SDK , Android SDK , iOS SDK , and Flutter SDK .