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關鍵標量伺服器值

鍵值標量是簡潔的物件 ID,Data Connect 會自動從結構定義中的關鍵欄位組合這些 ID。關鍵標量可提升效率,讓您在單一呼叫中找到資料的識別資訊和結構。當您想對新記錄執行連續動作,並需要傳遞至後續作業的專屬 ID 時,這些鍵就特別實用。此外,如果您想存取關聯鍵來執行其他更複雜的作業,這些鍵也非常實用。

使用伺服器值,您就能讓伺服器根據 expr 引數中的特定伺服器端 CEL 運算式,使用儲存或可輕鬆計算的值,動態填入資料表中的欄位。舉例來說,您可以定義欄位,並在使用儲存在作業要求 updatedAt: Timestamp! @default(expr: "request.time") 中的時間存取該欄位時,套用時間戳記。

電影中繼資料表

接下來,我們來追蹤電影導演,並設定與 Movie 的一對一關係。

新增參照欄位來定義關係。

您可以使用 @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
}

演員和電影演員

接下來,您想讓演員在電影中擔任主角,由於電影和演員之間是多對多關係,因此請建立彙整表。

# 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)
整數值 GraphQL int Int2 (smallint、smallserial)、
int4 (整數、int、序列)
浮點值 GraphQL float8 float4 (real)
float8 (double precision)
numeric (十進位)
布林值 GraphQL 布林值 布林值
UUID 自訂 uuid uuid
Int64 自訂 bigint int8 (bigint、bigserial)
數值 (小數)
日期 自訂 date 日期
時間戳記 自訂 timestamptz

timestamptz

注意:系統不會儲存本機時區資訊。
PostgreSQL 會將這類時間戳記轉換並儲存為世界標準時間。

向量 自訂 向量

向量

請參閱「使用 Vertex AI 執行向量相似度搜尋」。

  • GraphQL List 會對應至一維陣列。
    • 例如 [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...

您也可以使用結構定義語言,明確控制如何使用 singularplural 指令引數為欄位產生名稱。

查詢和突變的指令

除了在定義類型和資料表時使用的指令之外,Data Connect 還提供 @auth@check@redact@transaction 指令,用於擴充查詢和變異的行為。

指令 適用於 說明
@auth 查詢和異動 定義查詢或 mutation 的驗證政策。請參閱授權和認證指南
@check 授權資料查詢查詢 驗證指定欄位是否出現在查詢結果中。一般運算語言 (CEL) 運算式用於測試欄位值。請參閱授權和認證指南
@redact 查詢 遮蓋用戶端回應中的部分內容。請參閱授權和認證指南
@transaction 異動 強制要求異動一律在資料庫交易中執行。請參閱電影應用程式變異示例

電影評論資料庫的查詢

您可以使用查詢作業類型宣告、作業名稱、零個或多個作業引數,以及零個或多個帶有引數的指令,定義 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 篩選器和排序作業。

whereorderBy 運算子 (單數、複數查詢)

傳回資料表 (和巢狀關聯) 中所有相符的資料列。如果沒有任何記錄符合篩選條件,則傳回空陣列。

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}]) {  }
}

limitoffset 運算子 (單數、複數查詢)

您可以對結果執行分頁。系統會接受這些引數,但不會在結果中傳回。

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 查詢可根據資料表之間的關係存取資料。您可以使用結構定義中定義的物件 (一對一) 或陣列 (一對多) 關係,建立巢狀查詢,也就是擷取某個類型的資料,以及巢狀或相關類型的資料。

這類查詢會在產生的讀取欄位中使用魔法 Data Connect _on__via 語法。

您將修改初始版本的結構定義。

多對一

讓我們在應用程式中新增評論,並使用 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")
}

查詢多對一

接下來,我們來看看使用別名的查詢,以便說明 _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
    }
  }
}

一對一

您可以看到模式。以下為示範目的修改結構定義。

# 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
    }
  }
}

匯總查詢

什麼是匯總資料,以及為何要使用匯總資料?

匯總欄位可讓您對結果清單執行計算。您可以使用匯總欄位執行下列操作:

  • 找出評論的平均分數
  • 找出購物車中的商品總價
  • 尋找評分最高或最低的產品
  • 計算商店中的產品數量

匯總作業會在伺服器上執行,相較於在用戶端計算,這項作業有許多優點:

  • 加快應用程式效能 (因為可避免用戶端計算)
  • 降低資料傳出成本 (因為您只傳送匯總結果,而非所有輸入內容)
  • 提升安全性 (因為您可以讓客戶存取匯總資料,而非整個資料集)

匯總資料的結構定義範例

在本節中,我們會切換至商店示例結構定義,這非常適合說明如何使用匯總資料:

  type Product @table {
    name: String!
    manufacturer: String!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

簡單的匯總

_count for all fields

最簡單的匯總欄位是 _count:它會傳回與查詢相符的資料列數量。針對類型中的每個欄位,Data Connect 會根據欄位類型產生對應的匯總欄位。

查詢 回覆
one
query CountProducts {
  products {
    _count
  }
}

舉例來說,假設資料庫中有 5 項產品,結果會是:

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

所有欄位都有 <field>_count 欄位,可計算該欄位中含有非空值的資料列數量。

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

舉例來說,假設你有下列產品:

  • 產品 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
  }
}

舉例來說,假設您有下列到期日:

  • 產品 A:2024-01-01
  • 產品 B:2024-03-01
  • 產品 C:2024-02-01

結果如下:

{
  "products": [
    {
    "expirationDate_max": "2024-03-01",
    "expirationDate_min": "2024-01-01"
    }
  ]
}

刪除重複記錄

distinct 引數可讓您取得欄位 (或欄位組合) 的所有不重複值。例如:

query ListDistinctManufacturers {
  products(distinct: true) {
    manufacturer
  }
}

舉例來說,假設你有下列製造商:

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

結果如下:

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

您也可以在匯總欄位上使用 distinct 引數,改為匯總不重複值。例如:

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

舉例來說,假設你有下列製造商:

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

結果如下:

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

已分組的匯總

您可以選取類型中的匯總欄位和非匯總欄位,執行分組匯總。這會將所有在非匯總欄位中具有相同值的資料列分組,並計算該群組的匯總欄位。例如:

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

舉例來說,假設你有下列產品:

  • 產品 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 含有分組匯總

您也可以使用 havingwhere 引數,只傳回符合指定條件的群組。

  • having 可讓您依群組的匯總欄位進行篩選
  • where 可讓您根據非匯總欄位篩選資料列。

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

舉例來說,假設你有下列產品:

  • 產品 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 }
  ]
}

匯總資料表

匯總欄位可與產生的一對多關係欄位搭配使用,回答資料的複雜問題。以下是經過修改的結構定義,其中包含可用於範例的 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
    }
  }
}

舉例來說,假設你有下列製造商:

  • 製造商 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
  })
}

或更新/插入。

# 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 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 })
}

或者,在常用的待辦事項清單應用程式中,建立新的待辦事項清單時,您可以傳遞 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);

後續步驟