Firebase Data Connect を使用すると、Google Cloud SQL で管理される PostgreSQL インスタンスのコネクタを作成できます。これらのコネクタは、データを使用するためのスキーマ、クエリ、ミューテーションの組み合わせです。
スタートガイドでは、PostgreSQL のメールアプリ スキーマを紹介しましたが、このガイドでは、映画レビュー データベースを例として、PostgreSQL の Data Connect スキーマを設計する方法について詳しく説明します。
このガイドでは、Data Connect クエリとミューテーションをスキーマの例と組み合わせて説明します。Data Connect スキーマに関するガイドでクエリ(およびミューテーション)について説明する理由他の GraphQL ベースのプラットフォームと同様に、Firebase Data Connect はクエリ優先の開発プラットフォームです。そのため、デベロッパーはデータ モデリングでクライアントが必要とするデータを考慮することになり、プロジェクト用に開発するデータ スキーマに大きな影響を与えます。
このガイドでは、まず映画レビューの新しいスキーマについて説明し、次にそのスキーマから派生したクエリとミューテーションについて説明します。最後に、コア Data Connect スキーマに相当する SQL リストを提供します。
映画レビュー アプリのスキーマ
ユーザーが映画のレビューを送信して閲覧できるサービスを構築するとします。
このようなアプリには初期スキーマが必要です。このスキーマは後で拡張して、複雑なリレーショナル クエリを作成します。
映画の表
映画のスキーマには、次のようなコア ディレクティブが含まれています。
@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 がスキーマのキーフィールドから自動的に組み立てる簡潔なオブジェクト ID です。キー スカラーは効率性に関するもので、1 回の呼び出しでデータの ID と構造に関する情報を見つけることができます。これらは、新しいレコードに対して順次アクションを実行し、今後のオペレーションに渡す一意の識別子が必要な場合や、リレーショナル キーにアクセスしてより複雑なオペレーションを実行する場合に特に便利です。
ID タイプ
GraphQL では、ID 型は文字列としてシリアル化される不透明型として定義されます。GraphQL は ID 形式に依存しませんが、入力から文字列と整数を強制的に変換します。
PostgreSQL キーは通常、文字列ではなく整数または UUID です。Data Connect は、スキーマからこのようなキーを自動的に生成します。アクター テーブルの id フィールド定義 id: ID! … @default(generate: "UUID") に示すように、@default ディレクティブを使用してキー生成をカスタマイズできます。
映画のメタデータ テーブル
次に、映画監督を追跡し、Movie との 1 対 1 の関係を設定します。
@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")
}
Actor と MovieActor
次に、映画に俳優を出演させたいとします。映画と俳優の間には多対多の関係があるため、結合テーブルを作成します。
# 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 の型 (エイリアスは括弧内) |
|---|---|---|---|
| 文字列 | GraphQL | テキスト | text bit(n)、varbit(n) char(n)、varchar(n) |
| Int | GraphQL | int | Int2(smallint、smallserial)、 int4(integer、int、serial) |
| 浮動小数点数 | GraphQL | float8 | float4(実数) float8(倍精度) numeric(10 進数) |
| ブール値 | GraphQL | ブール値 | ブール値 |
| UUID | カスタム | uuid | uuid |
| Int64 | カスタム | bigint | int8(bigint、bigserial) numeric(decimal) |
| 日付 | カスタム | date | 日付 |
| タイムスタンプ | カスタム | timestamptz | timestamptz 注: ローカル タイムゾーン情報は保存されません。 |
| 列挙 | カスタム | enum | enum |
| ベクトル | カスタム | vector | ベクトル Vertex AI でベクトル類似度検索を行うをご覧ください。 |
- GraphQL
Listは 1 次元配列にマッピングされます。- たとえば、
[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 ディレクティブ引数を使用して、オペレーションの名前を明示的に設定することもできます。
映画レビュー データベースのクエリ
クエリ オペレーションの型宣言、オペレーション名、0 個以上のオペレーション引数、引数付きの 0 個以上のディレクティブを使用して Data Connect クエリを定義します。
クイックスタートでは、listEmails クエリの例でパラメータは使用されていません。もちろん、多くの場合、クエリ フィールドに渡されるデータは動的です。$variableName 構文を使用して、クエリ定義のコンポーネントの 1 つとして変数を操作できます。
したがって、次のクエリには次のものが含まれます。
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 エイリアシングをサポートしています。エイリアスを使用すると、クエリの結果で返されるデータの名前を変更できます。1 つの Data Connect クエリで、複数のフィルタや他のクエリ オペレーションを 1 つの効率的なリクエストでサーバーに適用し、複数の「サブクエリ」を一度に発行できます。返されるデータセットで名前の競合を回避するには、エイリアスを使用してサブクエリを区別します。
次のクエリでは、式でエイリアス 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
}
}
配列フィールドの includes
配列フィールドに指定された項目が含まれていることをテストできます。
# 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 クエリは、テーブル間の関係に基づいてデータにアクセスできます。スキーマで定義されたオブジェクト(一対一)または配列(一対多)の関係を使用して、ネストされたクエリを作成できます。つまり、1 つのタイプのデータを、ネストされたタイプまたは関連するタイプのデータとともに取得できます。
このようなクエリでは、生成された暗黙的なクエリでマジック Data Connect _on_ と _via の構文が使用されます。
初期バージョンのスキーマを変更します。
多対 1
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")
}
多対 1 のクエリ
次に、エイリアスを使用して _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
}
}
}
1 対 1
パターンを確認できます。以下では、スキーマをわかりやすくするために変更しています。
# 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
}
1 対 1 のクエリ
_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 ケースを実装できます。早口で 5 回言ってみましょう。
作成
基本的な作成を行いましょう。
# 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
})
}
または upsert。
# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
movie_upsert(data: {
title: $title
releaseYear: 2009
genre: "Mystery"
rating: 5
genre: "Mystery/Thriller"
})
}
更新を行う
最新情報をお知らせします。プロデューサーや監督は、平均評価がトレンドに乗っていることを願っています。
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 }
)
}
同等の 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 からクエリとミューテーションを呼び出す方法について説明します。