Схемы подключения к данным, запросы и мутации

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 и ключевые скаляры .

Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя сохраненные или легко вычислимые значения в соответствии с определенными выражениями на стороне сервера. Например, вы можете определить поле с меткой времени, применяемой при доступе к полю, используя выражение updatedAt: Timestamp! @default(expr: "request.time") .

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

Таблица метаданных фильма

Теперь давайте проследим за режиссерами фильмов, а также настроим отношения один-к-одному с 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 MoiveMetadata.
  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 (бигинт, большойсериал)
числовой (десятичный)
Дата Обычай дата дата
Временная метка Обычай временная метка

временная метка

Примечание. Информация о местном часовом поясе не сохраняется.
PostgreSQL преобразует и сохраняет такие временные метки, как UTC.

Вектор Обычай вектор

вектор

См. раздел Выполнение поиска по сходству векторов с помощью Vertex AI .

  • List GraphQL отображается в одномерный массив.
    • Например, [Int] сопоставляется с int5[] , [Any] сопоставляется с jsonb[] .
    • Data Connect не поддерживает вложенные массивы.

Неявные и предопределенные запросы и мутации

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

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

Неявный запрос и именование мутаций

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 . Например, тип ключевого скаляра для нашей таблицы MovieMovie_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
    }
  }
}

Мутации в базе данных обзоров фильмов

Как уже упоминалось, когда вы определяете таблицу в своей схеме, 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"
  })
}

Выполнение обновлений

Вот обновления. Продюсеры и режиссеры, конечно, надеются, что эти средние рейтинги находятся в тренде.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id, data: {
    genre: $genre
    rating: $rating
    description: $description
  })
}

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $ratingIncrement: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    update: { rating: { inc: $ratingIncrement } }
  )
}

Выполнить удаление

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

# 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 можно авторизовать, сначала запросив базу данных и проверив результаты запроса с помощью выражений 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);

Что дальше?

,

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 и ключевые скаляры .

Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя сохраненные или легко вычислимые значения в соответствии с определенными выражениями на стороне сервера. Например, вы можете определить поле с меткой времени, применяемой при доступе к полю, используя выражение updatedAt: Timestamp! @default(expr: "request.time") .

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

Таблица метаданных фильма

Теперь давайте проследим за режиссерами фильмов, а также настроим отношения один-к-одному с 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 MoiveMetadata.
  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 (бигинт, большойсериал)
числовой (десятичный)
Дата Обычай дата дата
Временная метка Обычай временная метка

временная метка

Примечание. Информация о местном часовом поясе не сохраняется.
PostgreSQL преобразует и сохраняет такие временные метки, как UTC.

Вектор Обычай вектор

вектор

См. раздел Выполнение поиска по сходству векторов с помощью Vertex AI .

  • List GraphQL отображается в одномерный массив.
    • Например, [Int] сопоставляется с int5[] , [Any] сопоставляется с jsonb[] .
    • Data Connect не поддерживает вложенные массивы.

Неявные и предопределенные запросы и мутации

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

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

Неявный запрос и именование мутаций

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 . Например, тип ключевого скаляра для нашей таблицы MovieMovie_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
    }
  }
}

Мутации в базе данных обзоров фильмов

Как уже упоминалось, когда вы определяете таблицу в своей схеме, 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"
  })
}

Выполнение обновлений

Вот обновления. Продюсеры и режиссеры, конечно, надеются, что эти средние рейтинги находятся в тренде.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id, data: {
    genre: $genre
    rating: $rating
    description: $description
  })
}

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $ratingIncrement: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    update: { rating: { inc: $ratingIncrement } }
  )
}

Выполнить удаление

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

# 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 можно авторизовать, сначала запросив базу данных и проверив результаты запроса с помощью выражений 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);

Что дальше?

,

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 и скаляры ключей .

Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя хранимые или легкокомпюссимые значения в соответствии с конкретными выражениями на стороне сервера. Например, вы можете определить поле с помощью метки времени, применяемой, когда поле будет доступно с использованием выражения 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 MoiveMetadata.
  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 Float8 float4 (реально)
float8 (двойная точность)
числовое (десятичное)
логическое значение ГрафQL логический логический
Uuid Обычай uuid uuid
Int64 Обычай bigint int8 (bigint, bigserial)
числовое (десятичное)
Дата Обычай дата дата
Временная метка Обычай временный

временный

Примечание: локальная информация о часовом поясе не хранится.
PostgreSQL преобразует и хранит такие временные метки, как UTC.

Вектор Обычай вектор

вектор

См. Выполните поиск сходства вектора с AI Vertex .

  • List GraphQL карты в одномерный массив.
    • Например, [Int] карты на int5[] , [Any] карты в jsonb[] .
    • Data Connect не поддерживает вложенные массивы.

Неявные и предопределенные запросы и мутации

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

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

Неявное именование запросов и мутаций

Data Connect подходящие названия для неявных запросов и мутаций из объявлений типа схемы. Например, работа с источником PostgreSQL, если вы определите таблицу с именем Movie , сервер будет генерировать неявное:

  • Запросы для вариантов использования одиночных таблиц с movie «Дружественные имена» (единственное число, для получения отдельных результатов, передавающих аргументы, такие как eq ) и movies (множественное число, для получения списков результатов, проходящих ARG, таких как 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
    }
  }
}

Мутации для базы данных обзоров фильмов

Как уже упоминалось, когда вы определяете таблицу в своей схеме, 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"
  })
}

Выполнить обновления

Вот обновления. Производители и директора, безусловно, надеются, что эти средние рейтинги находятся на тренде.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id, data: {
    genre: $genre
    rating: $rating
    description: $description
  })
}

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $ratingIncrement: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    update: { rating: { inc: $ratingIncrement } }
  )
}

Выполнить удаления

Вы, конечно, можете удалить данные фильма. Компания 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 могут быть авторизованы путем первого запроса базы данных и проверки результатов запроса с помощью выражений 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);

Что дальше?