Data Connect のスキーマ、クエリ、ミューテーション

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

注: ローカルのタイムゾーン情報は保存されません。
PostgreSQL は、このようなタイムスタンプを UTC に変換して保存します。

ベクトル カスタム 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_moviesactors_via_actormovie などの明示的な名前を持つマルチテーブル リレーショナル オペレーションのフィールドも生成します。
  • movie_insertmovie_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}}}) {...}
}

orand(合成フィルタ用)

より複雑なロジックには orand を使用します。

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

たとえば、データベースに 5 つの商品があるとします。結果は次のようになります。

{
  "products": [
    {
    "_count": 5
    }
  ]
}

すべてのフィールドには <field>_count フィールドがあり、そのフィールドに null 以外の値を持つ行の数をカウントします。

クエリ

query CountProductsWithExpirationDate {
  products {
    expirationDate_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

たとえば、次の商品があるとします。

  • プロダクト A: quantityInStock: 10price: 2.99
  • プロダクト B: quantityInStock: 5price: 5.99
  • プロダクト C: quantityInStock: 20price: 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

たとえば、次の有効期限があるとします。

  • 商品 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

たとえば、次のメーカーがあるとします。

  • 商品 A: manufacturer: "Acme"
  • 商品 B: manufacturer: "Beta"
  • 商品 C: manufacturer: "Acme"

結果は次のようになります。

{
  "products": [
    { "manufacturer": "Acme" },
    { "manufacturer": "Beta" }
  ]
}

集計フィールドで distinct 引数を使用して、一意の値を集計することもできます。例:

クエリ

query CountDistinctManufacturers {
  products {
    manufacturer_count(distinct: true)
  }
}

レスポンス
distinctonaggregate

たとえば、次のメーカーがあるとします。

  • 商品 A: manufacturer: "Acme"
  • 商品 B: manufacturer: "Beta"
  • 商品 C: manufacturer: "Acme"

結果は次のようになります。

{
  "products": [
    {
    "manufacturer_count": 2
    }
  ]
}

グループ化された集計

グループ化集計を実行するには、タイプで集計フィールドと非集計フィールドを組み合わせて選択します。これにより、集計以外のフィールドの値が同じ一致する行がすべてグループ化され、そのグループの集計フィールドが計算されます。例:

クエリ

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

レスポンス
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 }
  ]
}
havingwhere(グループ化された集計あり)

having 引数と where 引数を使用して、指定した条件を満たすグループのみを返すこともできます。

  • having を使用すると、グループを集計フィールドでフィルタできます。
  • where を使用すると、集計以外のフィールドに基づいて行をフィルタできます。

クエリ

query FilteredMostExpensiveProductByManufacturer {
  products(having: {price_max: {ge: 2.99}}) {
  manufacturer
  price_max
  }
}

レスポンス
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

たとえば、次のメーカーがあるとします。

  • メーカー 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: IntInt64Float データ型をインクリメント
  • dec: IntInt64Float データ型を減算
  • 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 Connectfield_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);

次のステップ