Firebase Data Connect を使用すると、Google Cloud SQL で管理されている PostgreSQL インスタンスのコネクタを作成できます。これらのコネクタは、データを使用するためのスキーマ、クエリ、ミューテーションの組み合わせです。
スタートガイドでは、PostgreSQL の映画レビュー アプリのスキーマについて説明しました。このガイドでは、PostgreSQL の Data Connect スキーマを設計する方法について詳しく説明します。
このガイドでは、Data Connect クエリとミューテーションをスキーマの例と組み合わせて説明します。Data Connect スキーマに関するガイドでクエリ(およびミューテーション)について説明する理由他の GraphQL ベースのプラットフォームと同様に、Firebase Data Connect はクエリファーストの開発プラットフォームです。そのため、デベロッパーはデータモデリングでクライアントが必要とするデータについて検討する必要があります。これは、プロジェクト用に開発するデータスキーマに大きな影響を与えます。
このガイドでは、まず新しい映画レビューのスキーマについて説明します。次に、そのスキーマから派生したクエリとミューテーションについて説明します。最後に、コア Data Connect スキーマと同等の SQL リストを示します。
映画レビュー アプリのスキーマ
ユーザーが映画のレビューを送信して表示できるサービスを構築するとします。
このようなアプリには初期スキーマが必要です。このスキーマは後で拡張して、複雑なリレーショナル クエリを作成します。
映画の表
映画のスキーマには、次のようなコア ディレクティブが含まれています。
@table(name)
と@col(name)
: SQL テーブル名と列名をカスタマイズします。指定しない場合、Data Connect は snake_case 名を生成します。@col(dataType)
: SQL 列の型をカスタマイズします。@default
: 挿入時に SQL 列のデフォルト値を構成します。
詳しくは、@table
、@col
、@default
のリファレンス ドキュメントをご覧ください。
# Movies
type Movie @table(name: "movie", key: "id") {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String @col(dataType: "varchar(20)")
rating: Int
description: String
}
キースカラーとサーバー値
映画レビュー アプリについて詳しく説明する前に、Data Connect のキー スカラーとサーバー値について説明します。
キー スカラーは、スキーマ内のキーフィールドから Data Connect によって自動的にアセンブルされる簡潔なオブジェクト識別子です。キースカラーは効率性を重視しており、1 回の呼び出しでデータの ID と構造に関する情報を取得できます。これは、新しいレコードに対して連続的なアクションを実行し、今後のオペレーションに渡す一意の識別子が必要である場合に特に便利です。また、リレーショナル キーにアクセスして、より複雑な追加オペレーションを実行する場合にも便利です。
サーバー値を使用すると、expr
引数の特定のサーバーサイド CEL 式に従って、保存されている値またはすぐに計算できる値を使用して、テーブルのフィールドに値を動的に入力できます。たとえば、オペレーション リクエストに保存されている時間(updatedAt: Timestamp! @default(expr: "request.time")
)を使用して、フィールドにアクセスされたときにタイムスタンプが適用されるフィールドを定義できます。
映画のメタデータ テーブル
次に、映画監督を追跡し、Movie
と 1 対 1 の関係を設定します。
参照フィールドを追加して、関係を定義します。
@ref
ディレクティブを使用して、外部キー制約をカスタマイズできます。
@ref(fields)
: 外部キー フィールドを指定します。@ref(references)
: ターゲット テーブルで参照されるフィールドを指定します。この参照はデフォルトでプライマリ キーになりますが、@unique
を含むフィールドもサポートされています。
詳しくは、@ref
のリファレンス ドキュメントをご覧ください。
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
# @unique ensures that each Movie only has one MovieMetadata.
movie: Movie! @unique
# Since it references to another table type, it adds a foreign key constraint.
# movie: Movie! @unique @ref(fields: "movieId", references: "id")
# movieId: UUID! <- implicitly added foreign key field
director: String
}
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 {
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 は、@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 | float8 | float4(実数) float8(倍精度) 数値(小数) |
ブール値 | GraphQL | ブール値 | ブール値 |
UUID | カスタム | uuid | uuid |
Int64 | カスタム | bigint | int8(bigint、bigserial) 数値(小数) |
日付 | カスタム | date | date |
タイムスタンプ | カスタム | timestamptz | timestamptz 注: ローカルのタイムゾーン情報は保存されません。 |
ベクトル | カスタム | vector | ベクトル Vertex AI でベクトル類似性検索を実行するをご覧ください。 |
- GraphQL の
List
は 1 次元配列にマッピングされます。- たとえば、
[Int]
はint5[]
にマッピングされ、[Any]
はjsonb[]
にマッピングされます。 - Data Connect はネストされた配列をサポートしていません。
- たとえば、
生成されたフィールドを使用してクエリとミューテーションを作成する
Data Connect クエリとミューテーションは、スキーマの型と型の関係に基づいて自動生成された Data Connect のフィールドセットを拡張します。これらのフィールドは、スキーマを編集するたびにローカル ツールによって生成されます。
スタートガイドで説明したように、Firebase コンソールとローカル開発ツールは、これらの自動生成フィールドを使用して、データのシーディングやテーブルの内容の検証に使用できるアドホック管理クエリとミューテーションを提供します。
開発プロセスでは、これらの自動生成フィールドに基づいて、コネクタにバンドルされたデプロイ可能なクエリとデプロイ可能なミューテーションを実装します。
自動生成されたフィールド名
Data Connect は、スキーマ型宣言に基づいて自動生成されるフィールドに適した名前を推論します。たとえば、PostgreSQL ソースで Movie
という名前のテーブルを定義すると、サーバーは次を生成します。
- 単一テーブルのユースケースでデータを読み取るためのフィールド。わかりやすい名前は
movie
(単数形、eq
などの引数を渡して個々の結果を取得する場合)とmovies
(複数形、gt
などの引数とorderby
などのオペレーションを渡して結果リストを取得する場合)です。Data Connect は、actors_on_movies
やactors_via_actormovie
などの明示的な名前を持つマルチテーブル リレーショナル オペレーションのフィールドも生成します。 movie_insert
、movie_upsert
などのわかりやすい名前でデータを書き込むフィールド。
スキーマ定義言語では、singular
ディレクティブ引数と plural
ディレクティブ引数を使用して、フィールドの名前の生成方法を明示的に制御することもできます。
クエリとミューテーションのディレクティブ
Data Connect には、型とテーブルの定義に使用するディレクティブに加えて、クエリとミューテーションの動作を拡張するための @auth
、@check
、@redact
、@transaction
ディレクティブが用意されています。
ディレクティブ | 対象 | 説明 |
---|---|---|
@auth |
クエリとミューテーション | クエリまたはミューテーションの認証ポリシーを定義します。承認と構成証明のガイドをご覧ください。 |
@check |
認可データのルックアップ クエリ | 指定されたフィールドがクエリ結果に存在することを確認します。フィールド値をテストするには、Common Expression Language(CEL)式を使用します。承認と構成証明のガイドをご覧ください。 |
@redact |
クエリ | クライアントからのレスポンスの一部を削除します。承認と構成証明のガイドをご覧ください。 |
@transaction |
ミューテーション | ミューテーションが常にデータベース トランザクション内で実行されるようにします。映画アプリのミューテーションの例をご覧ください。 |
映画レビュー データベースに対するクエリ
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
の変更により、アプリにレビューを追加しましょう。
# 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")
}
多対 1 のクエリ
次に、エイリアスを使用したクエリを見て、_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
}
}
}
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
}
}
}
集約クエリ
集計とは何か、集計を使用する理由
集計フィールドを使用すると、結果のリストを計算できます。集計フィールドを使用すると、次のようなことができます。
- レビューの平均スコアを確認する
- ショッピング カート内の商品の合計金額を確認する
- 評価が最も高い商品または評価が最も低い商品を見つける
- ストアの商品数をカウントする
集計はサーバー上で実行されるため、クライアントサイドで計算する場合と比べて次のようなメリットがあります。
- アプリのパフォーマンスの向上(クライアントサイドの計算が不要になるため)
- データの下り(外向き)コストの削減(すべての入力ではなく、集計結果のみを送信するため)
- セキュリティの強化(データセット全体ではなく集計データにクライアントがアクセスできるため)
集計のスキーマの例
このセクションでは、集計を使用する方法を説明するために、ストアフロントのサンプル スキーマに切り替えます。
type Product @table {
name: String!
manufacturer: String!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
単純な集計
すべてのフィールドの _count
最も単純な集計フィールドは _count
です。これは、クエリに一致する行数を返します。型内の各フィールドについて、Data Connect はフィールドの型に応じて対応する集計フィールドを生成します。
クエリ
query CountProducts {
products {
_count
}
}
レスポンスone
one
たとえば、データベースに 5 つの商品があるとします。結果は次のようになります。
{
"products": [
{
"_count": 5
}
]
}
すべてのフィールドには <field>_count
フィールドがあり、そのフィールドに null 以外の値を持つ行の数をカウントします。
クエリ
query CountProductsWithExpirationDate {
products {
expirationDate_count
}
}
レスポンスfield_count
field_count
たとえば、有効期限のある商品が 3 つある場合、結果は次のようになります。
{
"products": [
{
"expirationDate_count": 3
}
]
}
数値フィールドの _min、_max、_sum、_avg
数値フィールド(int、float、int64)にも <field>_min
、<field>_max
、<field>_sum
、<field>_avg
があります。
クエリ
query NumericAggregates {
products {
quantityInStock_max
price_min
price_avg
quantityInStock_sum
}
}
レスポンス_min _max _sum _avg
_min _max _sum _avg
たとえば、次の商品があるとします。
- プロダクト A:
quantityInStock: 10
、price: 2.99
- プロダクト B:
quantityInStock: 5
、price: 5.99
- プロダクト C:
quantityInStock: 20
、price: 1.99
結果は次のようになります。
{
"products": [
{
"quantityInStock_max": 20,
"price_min": 1.99,
"price_avg": 3.6566666666666666,
"quantityInStock_sum": 35
}
]
}
日付とタイムスタンプの _min と _max
日付フィールドとタイムスタンプ フィールドには <field>_min
と <field>_max
があります。
クエリ
query DateAndTimeAggregates {
products {
expirationDate_max
expirationDate_min
}
}
レスポンス_min _maxdatetime
_min _maxdatetime
たとえば、次の有効期限があるとします。
- 商品 A:
2024-01-01
- 商品 B:
2024-03-01
- 商品 C:
2024-02-01
結果は次のようになります。
{
"products": [
{
"expirationDate_max": "2024-03-01",
"expirationDate_min": "2024-01-01"
}
]
}
Distinct
distinct
引数を使用すると、フィールド(またはフィールドの組み合わせ)の一意の値をすべて取得できます。例:
クエリ
query ListDistinctManufacturers {
products(distinct: true) {
manufacturer
}
}
レスポンスdistinct
distinct
たとえば、次のメーカーがあるとします。
- 商品 A:
manufacturer: "Acme"
- 商品 B:
manufacturer: "Beta"
- 商品 C:
manufacturer: "Acme"
結果は次のようになります。
{
"products": [
{ "manufacturer": "Acme" },
{ "manufacturer": "Beta" }
]
}
集計フィールドで distinct
引数を使用して、一意の値を集計することもできます。例:
クエリ
query CountDistinctManufacturers {
products {
manufacturer_count(distinct: true)
}
}
レスポンスdistinctonaggregate
distinctonaggregate
たとえば、次のメーカーがあるとします。
- 商品 A:
manufacturer: "Acme"
- 商品 B:
manufacturer: "Beta"
- 商品 C:
manufacturer: "Acme"
結果は次のようになります。
{
"products": [
{
"manufacturer_count": 2
}
]
}
グループ化された集計
グループ化集計を実行するには、タイプで集計フィールドと非集計フィールドを組み合わせて選択します。これにより、集計以外のフィールドの値が同じ一致する行がすべてグループ化され、そのグループの集計フィールドが計算されます。例:
クエリ
query MostExpensiveProductByManufacturer {
products {
manufacturer
price_max
}
}
レスポンスgroupedaggregates
groupedaggregates
たとえば、次の商品があるとします。
- プロダクト A:
manufacturer: "Acme"
、price: 2.99
- プロダクト B:
manufacturer: "Beta"
、price: 5.99
- プロダクト C:
manufacturer: "Acme"
、price: 1.99
結果は次のようになります。
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
having
と where
(グループ化された集計あり)
having
引数と where
引数を使用して、指定した条件を満たすグループのみを返すこともできます。
having
を使用すると、グループを集計フィールドでフィルタできます。where
を使用すると、集計以外のフィールドに基づいて行をフィルタできます。
クエリ
query FilteredMostExpensiveProductByManufacturer {
products(having: {price_max: {ge: 2.99}}) {
manufacturer
price_max
}
}
レスポンスhavingwhere
havingwhere
たとえば、次の商品があるとします。
- プロダクト A:
manufacturer: "Acme"
、price: 2.99
- プロダクト B:
manufacturer: "Beta"
、price: 5.99
- プロダクト C:
manufacturer: "Acme"
、price: 1.99
結果は次のようになります。
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
テーブル間の集計
集計フィールドは、生成された 1 対多の関係フィールドと組み合わせて使用することで、データに関する複雑な質問に回答できます。以下は、例で使用できる個別のテーブル Manufacturer
を含む変更済みスキーマです。
type Product @table {
name: String!
manufacturer: Manufacturer!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
type Manufacturer @table {
name: String!
headquartersCountry: String!
}
これで、集計フィールドを使用して、メーカーが製造する商品の数などを調べることができます。
クエリ
query GetProductCount($id: UUID) {
manufacturers {
name
products_on_manufacturer {
_count
}
}
}
レスポンスaggregatesacrosstables
aggregatesacrosstables
たとえば、次のメーカーがあるとします。
- メーカー A:
name: "Acme"
、products_on_manufacturer: 2
- メーカー B:
name: "Beta"
、products_on_manufacturer: 1
結果は次のようになります。
{
"manufacturers": [
{ "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
{ "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
]
}
映画レビュー データベースのミューテーション
前述のように、スキーマでテーブルを定義すると、Data Connect は各テーブルの基本的な暗黙的なミューテーションを生成します。
type Movie @table { ... }
extend type Mutation {
# Insert a row into the movie table.
movie_insert(...): Movie_Key!
# Upsert a row into movie."
movie_upsert(...): Movie_Key!
# Update a row in Movie. Returns null if a row with the specified id/key does not exist
movie_update(...): Movie_Key
# Update rows based on a filter in Movie.
movie_updateMany(...): Int!
# Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
movie_delete(...): Movie_Key
# Delete rows based on a filter in Movie.
movie_deleteMany(...): Int!
}
これにより、コア CRUD ケースの複雑さを増やすことができます。これを 5 回続けて言ってみましょう。
@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
})
}
または 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"
})
}
更新を実行する
最新情報は次のとおりです。プロデューサーとディレクターは、平均評価がトレンドに沿っていることを願っています。
movie_update
フィールドには、レコードを識別する id
引数と、この更新で値の設定に使用できる data
フィールドが含まれています。
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
複数の更新を行うには、movie_updateMany
フィールドを使用します。
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
data:
{
rating: $rating
})
}
_update
でインクリメント、デクリメント、追加、先頭追加のオペレーションを使用する
_update
ミューテーションと _updateMany
ミューテーションでは data:
で値を明示的に設定できますが、多くの場合、インクリメントなどの演算子を適用して値を更新することをおすすめします。
前の更新例を変更して、特定の映画の評価を増やすとします。rating_update
構文は inc
演算子で使用できます。
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect は、フィールドの更新に次の演算子をサポートしています。
inc
:Int
、Int64
、Float
データ型をインクリメントdec
:Int
、Int64
、Float
データ型を減算
append
: ベクトルリストを除くリスト型に追加します。prepend
: ベクトルリストを除くリスト型の前に追加
削除を実行する
もちろん、映画データは削除できます。映画の保存者は、物理的なフィルムをできるだけ長く維持したいと考えています。
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
ここでは _deleteMany
を使用できます。
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
リレーションにミューテーションを書き込む
関係で暗黙的な _upsert
ミューテーションを使用する方法を確認します。
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Data Connect が field_expr
構文を使用して値を提供する
キー スカラーとサーバー値で説明したように、クライアント リクエストに応じて、id
や日付などの一般的なフィールドにサーバーが値を入力するようにスキーマを設計できます。
また、クライアント アプリから Data Connect request
オブジェクトで送信されたユーザー ID などのデータを利用することもできます。
ミューテーションを実装する場合は、field_expr
構文を使用して、サーバー生成の更新をトリガーするか、リクエストからデータにアクセスします。たとえば、リクエストに保存されている承認 uid
を _upsert
オペレーションに渡すには、userId_expr
フィールドに "auth.uid"
を渡します。
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}
# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
または、よく使う ToDo リスト アプリで新しい ToDo リストを作成するときに id_expr
を渡して、リストの UUID を自動生成するようサーバーに指示することもできます。
mutation CreateTodoListWithFirstItem(
$listName: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
name: $listName,
})
}
詳細については、スカラー リファレンスの _Expr
スカラーをご覧ください。
認可データのルックアップ クエリ
Data Connect ミューテーションを承認するには、まずデータベースにクエリを実行し、CEL 式でクエリの結果を確認します。これは、テーブルへの書き込み中に、別のテーブルの行の内容を確認する必要がある場合に便利です。
この機能は次のものをサポートしています。
@check
ディレクティブ: フィールドの内容を評価し、その評価結果に基づいて次の処理を行います。- ミューテーションで定義された作成、更新、削除を続行する
- クエリの結果を返す
- 返された値を使用して、クライアント コードでさまざまなロジックを実行する
@redact
ディレクティブ。ワイヤー プロトコルの結果からクエリ結果を省略できます。
これらの機能は、認可フローに役立ちます。
同等の SQL スキーマ
これは説明のみを目的としています。-- Movies Table
CREATE TABLE Movies (
movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
title VARCHAR(255) NOT NULL,
release_year INT,
genre VARCHAR(30),
rating INT,
description TEXT,
tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
director VARCHAR(255) NOT NULL,
PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
movie_id UUID REFERENCES Movies(movie_id),
actor_id UUID REFERENCES Actors(actor_id),
role VARCHAR(50) NOT NULL, # "main" or "supporting"
PRIMARY KEY (movie_id, actor_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_auth VARCHAR(255) NOT NULL
username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID REFERENCES Users(user_id),
movie_id UUID REFERENCES Movies(movie_id),
rating INT,
review_text TEXT,
review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (movie_id, user_id)
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);
次のステップ
- 認可と構成証明を使用してクエリとミューテーションを保護する方法について学習する。
- 自動生成された ウェブ SDK、Android SDK、iOS SDK、Flutter SDK からクエリとミューテーションを呼び出す方法を学びます。