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 によって自動的にアセンブルされる簡潔なオブジェクト識別子です。キースカラーは効率性に関するものであり、1 回の呼び出しでデータの ID と構造に関する情報を見つけることができます。新しいレコードに対して連続的なアクションを実行し、今後のオペレーションに渡す一意の識別子が必要である場合に特に便利です。また、リレーショナル キーにアクセスして、より複雑な追加オペレーションを実行する場合にも便利です。
映画のメタデータ テーブル
次に、映画監督を追跡し、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 は、@col(dataType:)
を使用して PostgreSQL 型に割り当てられる次のスカラー データ型をサポートしています。
Data Connect 型 | GraphQL 組み込み型または Data Connect カスタム型 |
デフォルトの PostgreSQL タイプ | サポートされている PostgreSQL 型 (かっこ内のエイリアス) |
---|---|---|---|
文字列 | GraphQL | テキスト | text bit(n)、varbit(n) char(n)、varchar(n) |
Int | GraphQL | 整数 | Int2(smallint、smallserial)、 int4(integer、int、serial) |
浮動小数点数 | GraphQL | 浮動小数点数 8 | float4(実数) float8(倍精度) 数値(小数) |
ブール値 | GraphQL | ブール値 | ブール値 |
UUID | カスタム | uuid | uuid |
Int64 | カスタム | bigint | int8(bigint、bigserial) 数値(10 進数) |
日付 | カスタム | date | 日付 |
タイムスタンプ | カスタム | timestamptz | timestamptz 注: ローカルのタイムゾーン情報は保存されません。 |
ベクトル | カスタム | ベクトル | ベクトル 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
ディレクティブ引数を使用して、オペレーションの名前を明示的に設定することもできます。
映画レビュー データベースに対するクエリ
Data Connect クエリは、クエリ オペレーション タイプの宣言、オペレーション名、0 個以上のオペレーション引数、引数を持つ 0 個以上のディレクティブで定義します。
クイックスタートの例の 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 対 1)または配列(1 対多)のリレーションを使用して、ネストされたクエリを作成できます。つまり、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 からクエリとミューテーションを呼び出す方法を確認する。