Firebase Data Connect позволяет создавать коннекторы для ваших экземпляров PostgreSQL, управляемых с помощью Google Cloud SQL. Эти соединители представляют собой комбинацию схемы, запросов и мутаций для использования ваших данных.
В руководстве по началу работы представлена схема приложения для просмотра фильмов для PostgreSQL, а в этом руководстве более подробно рассматривается разработка схем Data Connect для PostgreSQL.
В этом руководстве запросы и мутации Data Connect сочетаются с примерами схем. Зачем обсуждать запросы (и мутации ) в руководстве по схемам Data Connect ? Как и другие платформы на основе GraphQL, Firebase Data Connect — это платформа разработки, ориентированная на запросы , поэтому, как разработчик, при моделировании данных вы будете думать о данных, которые нужны вашим клиентам, что сильно повлияет на схему данных, которую вы разрабатываете для своих клиентов. проект.
Это руководство начинается с новой схемы для обзоров фильмов , затем рассматриваются запросы и изменения, полученные на основе этой схемы, и, наконец, приводится список SQL, эквивалентный базовой схеме Data Connect .
Схема приложения для обзора фильмов
Представьте, что вы хотите создать сервис, который позволит пользователям отправлять и просматривать обзоры фильмов.
Вам нужна исходная схема для такого приложения. Позже вы расширите эту схему для создания сложных реляционных запросов.
Стол для фильмов
Схема фильмов содержит такие основные директивы, как:
-
@table
, которая позволяет нам устанавливать имена операций, используя аргументыsingular
иplural
-
@col
для явного задания имен столбцов -
@default
, чтобы разрешить установку значений по умолчанию.
# 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")
}
Значения сервера и ключевые скаляры
Прежде чем перейти к приложению для просмотра фильмов, давайте представим значения сервера Data Connect и ключевые скаляры .
Используя значения сервера , вы можете эффективно позволить серверу динамически заполнять поля в ваших таблицах, используя сохраненные или легко вычислимые значения в соответствии с определенными выражениями на стороне сервера. Например, вы можете определить поле с меткой времени, применяемой при доступе к полю, используя выражение updatedAt: Timestamp! @default(expr: "request.time")
.
Ключевые скаляры — это краткие идентификаторы объектов, которые Data Connect автоматически собирает из ключевых полей в ваших схемах. Ключевые скаляры связаны с эффективностью, позволяя вам за один вызов найти информацию о личности и структуре ваших данных. Они особенно полезны, когда вы хотите выполнить последовательные действия над новыми записями и вам нужен уникальный идентификатор для перехода к предстоящим операциям, а также когда вы хотите получить доступ к реляционным ключам для выполнения дополнительных, более сложных операций.
Таблица метаданных фильма
Теперь давайте проследим за режиссерами фильмов, а также настроим отношения один-к-одному с Movie
.
Добавьте директиву @ref
для определения отношений.
# 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")
}
Актер и киноактер
Далее вы хотите, чтобы актеры снимались в ваших фильмах, и, поскольку между фильмами и актерами существует связь «многие ко многим», создайте соединительную таблицу.
# 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(name: "Actors", singular: "actor", plural: "actors") {
id: UUID! @col(name: "actor_id") @default(expr: "uuidV4()")
name: String! @col(name: "name", dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary key(s) of this table
# In this case, the keys are [movieId, actorId], the generated fields of the reference types [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
# @ref creates a field in the current table (MovieActor) 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, see: implicit.gql
actor: Actor! @ref
# actorId: UUID! <- this is created by the above @ref, see: implicit.gql
role: String! @col(name: "role") # "main" or "supporting"
# optional other fields
}
Пользователь
Наконец, пользователи вашего приложения.
# Users
# Suppose a user can leave reviews for movies
# user:reviews is a one to many relationship, movie:reviews is a one to many relationship, movie:user is a many to many relationship
type User
@table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
auth: String @col(name: "user_auth") @default(expr: "auth.uid")
username: String! @col(name: "username", dataType: "varchar(30)")
# The following are generated from the @ref in the Review table
# reviews_on_user
# movies_via_Review
}
Поддерживаемые типы данных
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 на основе типов и отношений типов в вашей схеме. Неявные запросы и мутации генерируются локальными инструментами всякий раз, когда вы редактируете схему.
В процессе разработки вы будете реализовывать предопределенные запросы и предопределенные мутации на основе этих неявных операций.
Неявный запрос и именование мутаций
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
.
# Users
# Suppose a user can leave reviews for movies
# user:reviews is a one to many relationship,
# movie:reviews is a one to many relationship,
# movie:user is a many to many relationship
type User
@table(name: "Users", singular: "user", plural: "users", key: ["id"]) {
id: UUID! @col(name: "user_id") @default(expr: "uuidV4()")
auth: String @col(name: "user_auth") @default(expr: "auth.uid")
username: String! @col(name: "username", dataType: "varchar(30)")
# The following are generated from the @ref in the Review table
# reviews_on_user
# movies_via_Review
}
# Reviews
type Review @table(name: "Reviews", key: ["movie", "user"]) {
id: UUID! @col(name: "review_id") @default(expr: "uuidV4()")
user: User! @ref
movie: Movie! @ref
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
description
}
dislikedMovies: movies_via_review(where: { rating: { le: 2 } }) {
title
genre
description
}
}
}
Один к одному
Вы можете увидеть закономерность. Ниже схема изменена для иллюстрации.
# 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);
Что дальше?
- Узнайте, как обеспечить безопасность ваших запросов и изменений с помощью авторизации и аттестации .
- Узнайте, как вызывать запросы и мутации из автоматически создаваемого веб-SDK , Android SDK , iOS SDK и Flutter SDK .