Data Connect şemaları, sorguları ve mutasyonları

Firebase Data Connect, Google Cloud SQL ile yönetilen PostgreSQL örnekleriniz için bağlayıcılar oluşturmanıza olanak tanır. Bu bağlayıcılar, verilerinizi kullanmak için bir şema, sorgu ve mutasyonların bir kombinasyonudur.

Başlangıç kılavuzunda, PostgreSQL için bir film yorumu uygulaması şeması tanıtıldı. Bu kılavuzda, PostgreSQL için Data Connect şemalarının nasıl tasarlanacağına daha ayrıntılı bir şekilde değinilmektedir.

Bu kılavuzda, Data Connect sorguları ve mutasyonları şema örnekleriyle eşleştirilir. Data Connect şemaları hakkındaki bir kılavuzda neden sorgular (ve mutasyonlar) ele alınıyor? Diğer GraphQL tabanlı platformlar gibi Firebase Data Connect de sorgu öncelikli bir geliştirme platformudur. Bu nedenle, geliştirici olarak veri modellemenizde müşterilerinizin ihtiyaç duyduğu verileri düşünürsünüz. Bu veriler, projeniz için geliştirdiğiniz veri şemasını büyük ölçüde etkiler.

Bu kılavuzda, yeni bir film yorumları şemasıyla başlanır, ardından bu şemadan türetilen sorgular ve mutasyonlar ele alınır ve son olarak temel Data Connect şemasına eşdeğer bir SQL listesi sağlanır.

Film yorumu uygulamasının şeması

Kullanıcıların film yorumları gönderip görüntülemesine olanak tanıyan bir hizmet oluşturmak istediğinizi varsayalım.

Bu tür bir uygulama için ilk bir şemaya ihtiyacınız vardır. Daha sonra karmaşık ilişkisel sorgular oluşturmak için bu şemayı genişleteceksiniz.

Film tablosu

Filmler şemasında aşağıdaki gibi temel yönergeler bulunur:

  • @table(name) ve @col(name) kullanarak SQL tablo ve sütun adlarını özelleştirebilirsiniz. Belirtilmemişse Data Connect, snake_case adları oluşturur.
  • @col(dataType) tuşlarına basarak SQL sütun türlerini özelleştirebilirsiniz.
  • @default, ekleme sırasında SQL sütunu varsayılan değerlerini yapılandırmak için kullanılır.

Daha fazla bilgi için @table, @col ve @default ile ilgili referans dokümanlarına göz atın.

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

Anahtar skalerler ve sunucu değerleri

Film yorumu uygulamasına daha ayrıntılı bakmadan önce Data Connect anahtar skalerlerini ve sunucu değerlerini tanıtalım.

Anahtar skalerler, Data Connect'in şemalarınızdaki anahtar alanlardan otomatik olarak derlediği kısa nesne tanımlayıcılardır. Anahtar skalerler verimlilikle ilgilidir ve verilerinizin kimliği ve yapısıyla ilgili bilgileri tek bir çağrıda bulmanızı sağlar. Özellikle yeni kayıtlarda sıralı işlemler yapmak istediğinizde ve yaklaşan işlemlere iletilecek benzersiz bir tanımlayıcıya ihtiyaç duyduğunuzda ve ayrıca daha karmaşık işlemler yapmak için ilişkisel anahtarlara erişmek istediğinizde kullanışlıdırlar.

Sunucu değerlerini kullanarak, expr bağımsız değişkenindeki belirli sunucu tarafı CEL ifadelerine göre depolanan veya kolayca hesaplanabilen değerleri kullanarak sunucunun tablolarınızdaki alanları dinamik olarak doldurmasına izin verebilirsiniz. Örneğin, bir işlem isteğinde (updatedAt: Timestamp! @default(expr: "request.time")) depolanan saat kullanılarak alana erişildiğinde zaman damgası uygulanmış bir alan tanımlayabilirsiniz.

Film meta verileri tablosu

Şimdi film yönetmenlerini takip edip Movie ile bire bir ilişki oluşturalım.

İlişki tanımlamak için referans alanını ekleyin.

Yabancı anahtar kısıtlamasını özelleştirmek için @ref yönergesini kullanabilirsiniz.

  • @ref(fields) kullanarak yabancı anahtar alanlarını belirtin.
  • @ref(references) kullanarak hedef tabloda referans verilen alanları belirtin. Bu referans varsayılan olarak birincil anahtarı kullanır ancak @unique içeren alanlar da desteklenir.

Daha fazla bilgi için @ref ile ilgili referans dokümanlarına göz atın.

# 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 ve MovieActor

Ardından, filmlerinizde rol alacak aktörler istiyorsunuz. Filmler ile aktörler arasında çoklu ilişki olduğundan bir birleştirme tablosu oluşturun.

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

Kullanıcı

Son olarak, uygulamanızın kullanıcıları.

# Users
# Suppose a user can leave reviews for movies
type User @table {
  id: String! @default(expr: "auth.uid")
  username: String! @col(dataType: "varchar(50)")
}

Desteklenen veri türleri

Data Connect, @col(dataType:) kullanılarak PostgreSQL türlerine atamalarla aşağıdaki skaler veri türlerini destekler.

Data Connect türü GraphQL yerleşik türü veya
Data Connect özel türü
Varsayılan PostgreSQL türü Desteklenen PostgreSQL türleri
(parantez içinde takma ad)
Dize GraphQL metin text
bit(n), varbit(n)
char(n), varchar(n)
Int GraphQL int Int2 (smallint, smallserial),
int4 (integer, int, serial)
Kayan GraphQL float8 float4 (reel)
float8 (çift hassasiyet)
numeric (ondalık)
Boole GraphQL boolean boolean
UUID Özel uuid uuid
Int64 Özel bigint int8 (bigint, bigserial)
numeric (ondalık)
Tarih Özel date tarih
Zaman damgası Özel timestamptz

timestamptz

Not: Yerel saat dilimi bilgileri depolanmaz.
PostgreSQL bu tür zaman damgalarını UTC olarak dönüştürüp depolar.

Vector Özel vector

vektör

Vertex AI ile vektör benzerliği araması yapma başlıklı makaleyi inceleyin.

  • GraphQL List, tek boyutlu bir diziyle eşlenir.
    • Örneğin, [Int] int5[] ile, [Any] ise jsonb[] ile eşlenir.
    • Data Connect iç içe dizileri desteklemez.

Sorgu ve mutasyon oluşturmak için oluşturulan alanları kullanma

Data Connect sorgularınız ve mutasyonlarınız, şemanızdaki türlere ve tür ilişkilerine göre otomatik olarak oluşturulan bir Data Connect alan grubunu genişletir. Bu alanlar, şemanızı her düzenlediğinizde yerel araçlar tarafından oluşturulur.

  • Başlangıç kılavuzunda da belirtildiği gibi, Firebase konsolu ve yerel geliştirme araçlarımız, bu otomatik olarak oluşturulan alanları kullanarak size veri eklemek ve tablolarınızın içeriğini doğrulamak için kullanabileceğiniz ad hoc yönetimsel sorgular ve mutasyonlar sağlar.

  • Geliştirme sürecinizde, bu otomatik olarak oluşturulan alanlara göre bağlayıcılarınızda gruplandırılmış dağıtılabilir sorgular ve dağıtılabilir mutasyonlar uygularsınız.

Otomatik olarak oluşturulan alan adlandırma

Data Connect, şema türü beyanlarınıza göre otomatik olarak oluşturulan alanlar için uygun adları çıkarır. Örneğin, PostgreSQL kaynağıyla çalışırken Movie adlı bir tablo tanımlarsanız sunucu şunları oluşturur:

  • Tek tablolu kullanım alanlarında verileri okumak için movie (eq gibi bağımsız değişkenler göndererek tek tek sonuçları almak için tekil) ve movies (gt gibi bağımsız değişkenler ve orderby gibi işlemler göndererek sonuç listelerini almak için çoğul) gibi kullanıcı dostu adlara sahip alanlar. Data Connect, actors_on_movies veya actors_via_actormovie gibi açık adlara sahip çok tablolu, ilişkisel işlemler için de alanlar oluşturur.
  • movie_insert, movie_upsert gibi bilinen bir ada sahip verileri yazmak için alanlar

Şema tanımı dili, singular ve plural yönerge bağımsız değişkenlerini kullanarak alanlar için adların nasıl oluşturulacağını açıkça kontrol etmenize de olanak tanır.

Sorgular ve mutasyonlar için talimatlar

Türleri ve tabloları tanımlarken kullandığınız yönergelere ek olarak Data Connect, sorguların ve mutasyonların davranışını iyileştirmek için @auth, @check, @redact ve @transaction yönergelerini sağlar.

Direktif Geçerlilik kapsamı Açıklama
@auth Sorgular ve mutasyonlar Sorgu veya mutasyon için yetkilendirme politikasını tanımlar. Yetkilendirme ve doğrulama kılavuzuna bakın.
@check Çok adımlı işlemlerdeki query alanları Belirtilen alanların sorgu sonuçlarında bulunduğunu doğrular. Alan değerlerini test etmek için Common Expression Language (CEL) ifadesi kullanılır. Çok adımlı işlemler konusuna bakın.
@redact Sorgular İstemciden gelen yanıtın bir bölümünü çıkartır. Çok adımlı işlemler konusuna bakın.
@transaction Değişiklikler Bir mutasyonun her zaman bir veritabanı işleminde çalıştırılmasını zorunlu kılar. Çok adımlı işlemler konusuna bakın.

Film yorumu veritabanı sorguları

Sorgu işlemi türü beyanı, işlem adı, sıfır veya daha fazla işlem bağımsız değişkeni ve bağımsız değişken içeren sıfır veya daha fazla yönergeyle bir Data Connect sorgusu tanımlarsınız.

Hızlı başlangıç kılavuzundaki örnek listEmails sorgusunda parametre yoktu. Elbette, sorgu alanlarına iletilen veriler çoğu durumda dinamik olacaktır. Sorgu tanımının bileşenlerinden biri olarak değişkenlerle çalışmak için $variableName söz dizimini kullanabilirsiniz.

Dolayısıyla aşağıdaki sorguda:

  • query türü tanımı
  • ListMoviesByGenre işlemi (sorgu) adı
  • Tek değişkenli $genre işlem bağımsız değişkeni
  • Tek bir yönerge (@auth).
query ListMoviesByGenre($genre: String!) @auth(level: USER)

Her sorgu bağımsız değişkeni için bir tür beyanı, String gibi yerleşik bir tür veya Movie gibi şemada tanımlanmış özel bir tür gerekir.

Giderek daha karmaşık hale gelen sorguların imzasını inceleyelim. Dersi, dağıtılabilir sorgularınızı oluşturmak için kullanabileceğiniz güçlü ve kısa ilişki ifadelerini tanıtarak sonlandıracaksınız.

Sorgulardaki temel skalerler

Ancak öncelikle, temel skalerlerle ilgili bir not paylaşmak isterim.

Data Connect, _Key ile tanımlanan anahtar skalerler için özel bir tür tanımlar. Örneğin, Movie tablomuzdaki bir anahtar skalerinin türü Movie_Key'dur.

Anahtar skalerleri, otomatik olarak oluşturulan çoğu okuma alanı tarafından döndürülen bir yanıt olarak veya elbette skaler anahtarı oluşturmak için gereken tüm alanları aldığınız sorgulardan alırsınız.

Çalışan örneğimizdeki movie gibi tekil otomatik sorgular, bir anahtar skaler kabul eden bir anahtar bağımsız değişkenini destekler.

Bir anahtar skalerini değişmez değer olarak iletebilirsiniz. Ancak, giriş olarak temel skalerlerin iletileceği değişkenler tanımlayabilirsiniz.

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}

Bunlar, istek JSON'unda aşağıdaki gibi (veya diğer serileştirme biçimlerinde) sağlanabilir:

{
  # 
  "variables": {
    "myKey": {"foo": "some-string-value", "bar": 42}
  }
}

Özel skaler ayrıştırma sayesinde, Movie_Key değişken içerebilen nesne söz dizimi kullanılarak da oluşturulabilir. Bu yöntem, genellikle tek tek bileşenleri bir nedenle farklı değişkenlere bölmek istediğinizde yararlıdır.

Sorgularda takma ad kullanma

Data Connect, sorgularda GraphQL takma adlandırmasını destekler. Takma adlar, bir sorgunun sonuçlarında döndürülen verileri yeniden adlandırmanızı sağlar. Tek bir Data Connect sorgusu, sunucuya tek bir verimli istek göndererek birden fazla filtre veya başka sorgu işlemi uygulayabilir ve aynı anda birkaç "alt sorgu" yayınlayabilir. Döndürülen veri kümesinde ad çakışmalarını önlemek için alt sorguları ayırt etmek üzere takma adlar kullanırsınız.

Aşağıda, bir ifadenin mostPopular takma adını kullandığı bir sorgu verilmiştir.

query ReviewTopPopularity($genre: String) {
  mostPopular: review(first: {
    where: {genre: {eq: $genre}},
    orderBy: {popularity: DESC}
  }) {  }
}

Filtre içeren basit sorgular

Data Connect sorguları, tüm yaygın SQL filtreleriyle ve sıralama işlemleriyle eşlenir.

where ve orderBy operatörleri (tekil, çoğul sorgular)

Tablodaki eşleşen tüm satırları (ve iç içe yerleştirilmiş ilişkilendirmeleri) döndürür. Filtreyle eşleşen bir kayıt yoksa boş bir dizi döndürür.

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 ve offset operatörleri (tekil, çoğul sorgular)

Sonuçlarda sayfalandırma yapabilirsiniz. Bu bağımsız değişkenler kabul edilir ancak sonuçlarda döndürülmez.

query MoviesTop10 {
  movies(orderBy: [{ rating: DESC }], limit: 10) {
    # graphql: list the fields from the results to return
    title
  }
}

Dizi alanları için içerir

Bir dizi alanının belirtilen bir öğeyi içerip içermediğini test edebilirsiniz.

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

Dize işlemleri ve normal ifadeler

Sorgularınızda normal ifadeler de dahil olmak üzere normal dize arama ve karşılaştırma işlemleri kullanılabilir. Verimliliği artırmak için burada birkaç işlemi bir araya getirdiğinizi ve bunları takma adlarla açıklığa kavuşturduğunuzu unutmayın.

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

Birleştirilmiş filtreler için or ve and

Daha karmaşık mantık için or ve and kullanın.

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

Karmaşık sorgular

Data Connect sorguları, tablolar arasındaki ilişkilere göre verilere erişebilir. İç içe yerleştirilmiş sorgular oluşturmak (ör. iç içe yerleştirilmiş veya ilgili bir türden veri ile birlikte bir tür için veri almak) amacıyla şemanızda tanımlanan nesne (bire bir) veya dizi (bir çok) ilişkilerini kullanabilirsiniz.

Bu tür sorgular, oluşturulan okuma alanlarında sihirli Data Connect _on_ ve _via söz dizimini kullanır.

İlk sürümümüzdeki şemada değişiklikler yapacaksınız.

Çoka-tek

Uygulamamıza yorumlar ekleyelim. Bunun için Review tablosu ve User'da değişiklikler yapalım.

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

Bire çok sorgu

Şimdi _via_ söz dizimini açıklamak için takma ad içeren bir sorguya bakalım.

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

Bire bir

Bu durumu görebilirsiniz. Aşağıda, şemada açıklama amaçlı değişiklikler yapılmıştır.

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

Bire bir sorgu

_on_ söz dizimini kullanarak sorgu oluşturabilirsiniz.

# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    movieMetadatas_on_movie {
      director
    }
  }
}

Çoka çok

Filmler oyunculara, oyuncular da filmlere ihtiyaç duyar. Bu iki tablo arasında MovieActors birleştirme tablosuyla modelleyebileceğiniz çoklu-çoklu bir ilişki vardır.

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

Çoka çok sorgu

_via_ söz dizimini açıklamak için takma ad içeren bir sorguya bakalım.

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

Toplama sorguları

Toplamalar nedir ve neden kullanılır?

Toplu alanlar, bir sonuç listesi üzerinde hesaplama yapmanıza olanak tanır. Toplama alanları ile aşağıdakiler gibi işlemler yapabilirsiniz:

  • Bir yorumun ortalama puanını bulma
  • Alışveriş sepetindeki öğelerin toplam maliyetini bulma
  • En yüksek veya en düşük puan alan ürünü bulma
  • Mağazanızdaki ürün sayısını sayma

Toplamalar sunucuda gerçekleştirilir. Bu, toplama işlemlerini istemci tarafında hesaplamaya kıyasla çeşitli avantajlar sunar:

  • Daha hızlı uygulama performansı (istemci tarafı hesaplamalardan kaçındığınız için)
  • Azaltılmış veri çıkış maliyetleri (tüm girişler yerine yalnızca birleştirilmiş sonuçları gönderdiğiniz için)
  • Daha iyi güvenlik (müşterilere veri kümesinin tamamı yerine toplu verilere erişim izni verebileceğiniz için)

Toplamalar için örnek şema

Bu bölümde, toplu verileri kullanmayı açıklamak için iyi bir örnek olan bir mağaza şema örneğine geçeceğiz:

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

Basit toplamlar

Tüm alanlar için _count

En basit toplama alanı _count'tür: Sorgunuzla eşleşen satır sayısını döndürür. Data Connect, türünüzün her bir alanı için alan türüne bağlı olarak ilgili toplu alanları oluşturur.

Sorgu

query CountProducts {
  products {
    _count
  }
}

Yanıt
one

Örneğin, veritabanınızda 5 ürün varsa sonuç şöyle olur:

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

Tüm alanların, bu alanda boş olmayan kaç satır olduğunu sayan bir <field>_count alanı vardır.

Sorgu

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

Yanıt
field_count

Örneğin, geçerlilik bitiş tarihi olan 3 ürününüz varsa sonuç şöyle olur:

{
  "products": [
    {
    "expirationDate_count": 3
    }
  ]
}
Sayısal alanlar için _min, _max, _sum ve _avg

Sayısal alanlarda (int, float, int64) <field>_min, <field>_max, <field>_sum ve <field>_avg da bulunur.

Sorgu

query NumericAggregates {
  products {
  quantityInStock_max
  price_min
  price_avg
  quantityInStock_sum
  }
}

Yanıt
_min _max _sum _avg

Örneğin, aşağıdaki ürünlere sahipseniz:

  • A Ürünü: quantityInStock: 10, price: 2.99
  • B ürünü: quantityInStock: 5, price: 5.99
  • C ürünü: quantityInStock: 20, price: 1.99

Sonuç şöyle olur:

{
  "products": [
    {
    "quantityInStock_max": 20,
    "price_min": 1.99,
    "price_avg": 3.6566666666666666,
    "quantityInStock_sum": 35
    }
  ]
}
Tarihler ve zaman damgalarıyla ilgili _min ve _max

Tarih ve zaman damgası alanlarında <field>_min ve <field>_max bulunur.

Sorgu

query DateAndTimeAggregates {
  products {
  expirationDate_max
  expirationDate_min
  }
}

Yanıt
_min _maxdatetime

Örneğin, aşağıdaki geçerlilik bitiş tarihlerine sahipseniz:

  • A ürünü: 2024-01-01
  • Ürün B: 2024-03-01
  • C ürünü: 2024-02-01

Sonuç şöyle olur:

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

Farklı

distinct bağımsız değişkeni, bir alan (veya alan kombinasyonu) için tüm benzersiz değerleri almanıza olanak tanır. Örneğin:

Sorgu

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

Yanıt
distinct

Örneğin, aşağıdaki üreticilere sahipseniz:

  • A ürünü: manufacturer: "Acme"
  • Ürün B: manufacturer: "Beta"
  • C ürünü: manufacturer: "Acme"

Sonuç şöyle olur:

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

Ayrıca, farklı değerleri toplamak için toplu alanlarda distinct bağımsız değişkenini de kullanabilirsiniz. Örneğin:

Sorgu

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

Yanıt
distinctonaggregate

Örneğin, aşağıdaki üreticilere sahipseniz:

  • A ürünü: manufacturer: "Acme"
  • Ürün B: manufacturer: "Beta"
  • C ürünü: manufacturer: "Acme"

Sonuç şöyle olur:

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

Gruplandırılmış toplamalar

Bir türde toplu ve toplu olmayan alanların bir karışımını seçerek gruplandırılmış toplama işlemi gerçekleştirirsiniz. Bu işlem, toplu olmayan alanlar için aynı değere sahip tüm eşleşen satırları gruplandırır ve bu grup için toplu alanları hesaplar. Örneğin:

Sorgu

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

Yanıt
groupedaggregates

Örneğin, aşağıdaki ürünlere sahipseniz:

  • A Ürünü: manufacturer: "Acme", price: 2.99
  • B ürünü: manufacturer: "Beta", price: 5.99
  • C ürünü: manufacturer: "Acme", price: 1.99

Sonuç şöyle olur:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}
Gruplandırılmış toplama içeren having ve where

Yalnızca belirtilen ölçütleri karşılayan grupları döndürmek için having ve where bağımsız değişkenini de kullanabilirsiniz.

  • having, grupları toplu alanlarına göre filtrelemenize olanak tanır.
  • where, satırları toplu olmayan alanlara göre filtrelemenize olanak tanır.

Sorgu

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

Yanıt
havingwhere

Örneğin, aşağıdaki ürünlere sahipseniz:

  • A Ürünü: manufacturer: "Acme", price: 2.99
  • B ürünü: manufacturer: "Beta", price: 5.99
  • C ürünü: manufacturer: "Acme", price: 1.99

Sonuç şöyle olur:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}

Tablolar genelinde toplanır.

Toplama alanları, verilerinizle ilgili karmaşık soruları yanıtlamak için oluşturulan bire çok ilişki alanlarıyla birlikte kullanılabilir. Aşağıda, örneklerde kullanabileceğimiz ayrı bir Manufacturer tablosu içeren değiştirilmiş bir şema verilmiştir:

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

  type Manufacturer @table {
    name: String!
    headquartersCountry: String!
  }

Artık bir üreticinin kaç ürün ürettiğini bulmak gibi işlemleri yapmak için toplu alanları kullanabiliriz:

Sorgu

query GetProductCount($id: UUID) {
  manufacturers {
    name
    products_on_manufacturer {
      _count
    }
  }
}

Yanıt
aggregatesacrosstables

Örneğin, aşağıdaki üreticilere sahipseniz:

  • A üreticisi: name: "Acme", products_on_manufacturer: 2
  • Üretici B: name: "Beta", products_on_manufacturer: 1

Sonuç şöyle olur:

{
  "manufacturers": [
    { "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
    { "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
  ]
}

Film eleştirisi veritabanı için mutasyonlar

Daha önce de belirtildiği gibi, şemanızda bir tablo tanımladığınızda Data Connect, her tablo için temel _insert, _update vb. alanlar oluşturur.

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

Bu sayede, giderek daha karmaşık temel CRUD işlemlerini uygulayabilirsiniz. Bunu hızlıca beş kez söyle.

Oluştur

Temel oluşturma işlemlerini yapalım.

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

Veya bir 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"
  })
}

Güncelleme yapma

Güncellemeleri aşağıda bulabilirsiniz. Yapımcılar ve yönetmenler, bu ortalama puanların trende uygun olmasını ister.

movie_update alanı, bir kaydı tanımlamak için beklenen bir id bağımsız değişkeni ve bu güncellemede değerleri ayarlamak için kullanabileceğiniz bir data alanı içerir.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id,
    data: {
      genre: $genre
      rating: $rating
      description: $description
    })
}

Birden fazla güncelleme yapmak için movie_updateMany alanını kullanın.

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    data:
      {
        rating: $rating
      })
}

_update ile artma, azalma, ekleme ve önce ekleme işlemlerini kullanma

_update ve _updateMany mutasyonlarında data:'de değerleri açıkça ayarlayabilirsiniz ancak değerleri güncellemek için genellikle artma gibi bir operatör uygulamak daha mantıklı olur.

Önceki güncelleme örneğini değiştirmek için belirli bir filmin puanını artırmak istediğinizi varsayalım. rating_update söz dizimini inc operatörüyle kullanabilirsiniz.

mutation UpdateMovie(
  $id: UUID!,
  $ratingIncrement: Int!
) {
  movie_update(id: $id, data: {
    rating_update: {
      inc: $ratingIncrement
    }
  })
}

Data Connect, alan güncellemeleri için aşağıdaki operatörleri destekler:

  • Int, Int64, Float, Date ve Timestamp veri türlerini artırmak için inc
  • Int, Int64, Float, Date ve Timestamp veri türlerini azaltmak için dec

Listeleri aşağıdakileri kullanarak tek tek değerlerle veya değer listeleriyle de güncelleyebilirsiniz:

  • add, vektör listeleri hariç liste türlerine henüz eklenmemiş öğeleri eklemek için
  • remove, Vektör listeleri hariç liste türlerinden mevcut tüm öğeleri kaldırmak için
  • Vektör listeleri hariç liste türlerine öğe eklemek için append
  • Vektör listeleri hariç liste türlerine öğe eklemek için prepend

Silme işlemleri gerçekleştirme

Film verilerini silebilirsiniz. Film koruma uzmanları, fiziksel filmlerin mümkün olduğunca uzun süre korunmasını ister.

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

Burada _deleteMany kullanabilirsiniz.

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

İlişkilerde mutasyon yazma

Bir ilişkide örtülü _upsert mutasyonunun nasıl kullanıldığını gözlemleyin.

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

Data Connect'ün field_expr söz dizimini kullanarak değer sağlamasına izin verin

Anahtar skaler ve sunucu değerleri bölümünde belirtildiği gibi, şemanızı, sunucunun istemci isteklerine yanıt olarak id ve tarihler gibi ortak alanlar için değerleri dolduracağı şekilde tasarlayabilirsiniz.

Ayrıca, istemci uygulamalarından Data Connect request nesnelerinde gönderilen kullanıcı kimlikleri gibi verilerden yararlanabilirsiniz.

Mutasyonlar uygularken sunucu tarafından oluşturulan güncellemeleri tetiklemek veya isteklerdeki verilere erişmek için field_expr söz dizimini kullanın. Örneğin, bir istekte saklanan uid yetkilendirmesini bir _upsert işlemine iletmek için userId_expr alanına "auth.uid" değerini iletin.

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

Alternatif olarak, tanıdık bir yapılacaklar listesi uygulamasında yeni bir yapılacaklar listesi oluştururken id_expr değerini ileterek sunucunun liste için otomatik olarak bir UUID oluşturmasını sağlayabilirsiniz.

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

Daha fazla bilgi için skalar referansında _Expr skalarlarına bakın.

Çok adımlı işlemler

Bir mutasyona birden fazla yazma alanı (ör. eklemeler) eklemek isteyebileceğiniz birçok durum vardır. Ayrıca, örneğin ekleme veya güncelleme yapmadan önce mevcut verileri aramak ve doğrulamak için bir mutasyonun yürütülmesi sırasında veritabanınızı okumak isteyebilirsiniz. Bu seçenekler, gidiş dönüş işlemlerinden ve dolayısıyla maliyetlerden tasarruf sağlar.

Data Connect, aşağıdakileri destekleyerek mutasyonlarınızda çok adımlı mantık gerçekleştirmenize olanak tanır:

  • Birden fazla yazma alanı

  • Mutasyonlarınızda birden fazla okuma alanı (query alan anahtar kelimesi kullanılarak).

  • İlişkisel veritabanlarında aşina olduğunuz işlem desteği sağlayan @transaction direktifi.

  • CEL ifadelerini kullanarak okumaların içeriğini değerlendirmenize ve bu değerlendirmenin sonuçlarına göre işlem yapmanıza olanak tanıyan @check yönergesi:

    • Bir mutasyonla tanımlanan oluşturma, güncelleme ve silme işlemlerine devam edin
    • Sorgu alanının sonuçlarını döndürmeye devam edin
    • İstemci kodunuzda uygun mantığı gerçekleştirmek için döndürülen mesajları kullanma
  • Sorgu alanı sonuçlarını kablo protokolü sonuçlarından çıkarmanıza olanak tanıyan @redact yönergesi.

  • Karmaşık, çok adımlı bir işlemde gerçekleştirilen tüm mutasyonların ve sorguların birikmiş sonuçlarını depolayan CEL response bağlaması. response bağlamasına şu şekilde erişebilirsiniz:

    • @check yönergelerinde expr: bağımsız değişkeni aracılığıyla
    • Sunucu değerleriyle, field_expr söz dizimini kullanarak

@transaction yönergesi

Çok adımlı mutasyonlar için destek, işlemleri kullanarak hata işleme içerir.

@transaction yönergesi, tek bir yazma alanıyla (ör. _insert veya _update) veya birden fazla yazma alanıyla bir mutasyonun her zaman bir veritabanı işleminde çalıştırılmasını zorunlu kılar.

  • @transaction içermeyen mutasyonlar, her bir kök alanı sırayla birbiri ardına yürütür. İşlem, hataları kısmi alan hataları olarak gösterir ancak sonraki yürütmelerin etkilerini göstermez.

  • @transaction içeren mutasyonların tamamen başarılı veya tamamen başarısız olacağı garanti edilir. İşlemdeki alanlardan herhangi biri başarısız olursa tüm işlem geri alınır.

@check ve @redact yönergeleri

@check yönergesi, belirtilen alanların sorgu sonuçlarında bulunup bulunmadığını doğrular. Alan değerlerini test etmek için Common Expression Language (CEL) ifadesi kullanılır. Yönergenin varsayılan davranışı, değeri null veya [] (boş listeler) olan düğümleri kontrol edip reddetmektir.

@redact yönergesi, istemciden gelen yanıtın bir bölümünü çıkartır. Kırpılan alanlar, yan etkiler (veri değişiklikleri ve @check dahil) açısından değerlendirilmeye devam eder ve sonuçlar CEL ifadelerindeki sonraki adımlarda kullanılabilir.

@check, @check(message:) ve @redact özelliğini kullanın

@check reklam @redact'un önemli bir kullanımı, belirli işlemlerin yetkilendirilip yetkilendirilmeyeceğine karar vermek için ilgili verileri aramaktır. Bu işlem, mantıkta aramayı kullanarak ancak istemcilerden gizleyerek yapılır. Sorgunuz, istemci kodunda doğru işleme için yararlı mesajlar döndürebilir.

Aşağıdaki sorgu alanında, istek sahibinin bir filmi düzenleyebilecek kullanıcıları görüntülemek için uygun "yönetici" rolüne sahip olup olmadığı kontrol edilir.

query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

Yetkilendirme kontrollerindeki @check ve @redact yönergeleri hakkında daha fazla bilgi edinmek için yetkilendirme verisi aramayla ilgili tartışmaya bakın.

Anahtarları doğrulamak için @check'ü kullanma

Belirtilen bir anahtara sahip bir kayıt yoksa _update gibi bazı mutasyon alanları işlem yapmayabilir. Benzer şekilde, aramalar null veya boş bir liste döndürebilir. Bunlar hata olarak kabul edilmez ve bu nedenle geri alma işlemi tetiklenmez.

Bu sonuca karşı önlem almak için @check yönergesini kullanarak anahtarların bulunup bulunamayacağını test edin.

# Delete by key, error if not found
mutation MustDeleteMovie($id: UUID!) @transaction {
  movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
}

Çok aşamalı mutasyonları zincirlemek için response bağlamasını kullanma

İlgili kayıtlar (ör. yeni bir Movie ve ilişkili bir MovieMetadata girişi) oluşturmanın temel yaklaşımı şudur:

  1. Movie için _insert mutasyonu çağırma
  2. Oluşturulan filmin döndürülen anahtarını depolama
  3. Ardından, MovieMetadata kaydını oluşturmak için ikinci bir _insert mutasyonu çağırın.

Ancak Data Connect ile bu yaygın durumu, ikinci _insert'da ilk _insert'ın sonuçlarına erişerek tek bir çok adımlı işlemde halledebilirsiniz.

Başarılı bir film yorumu uygulaması oluşturmak çok fazla iş gerektirir. To do listemizi yeni bir örnekle takip edelim.

Alanları sunucu değerleriyle ayarlamak için response kullanın

Aşağıdaki yapılacaklar listesi mutasyonunda:

  • response bağlaması, şimdiye kadarki kısmi yanıt nesnesini temsil eder. Bu nesne, mevcut olandan önceki tüm üst düzey mutasyon alanlarını içerir.
  • id (anahtar) alanını döndüren ilk todoList_insert işleminin sonuçlarına response.todoList_insert.id içinde daha sonra erişilir. Böylece hemen yeni bir yapılacaklar listesi öğesi ekleyebiliriz.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1:
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

Alanları doğrulamak için @check kullanarak response

response, @check(expr: "...")'te de kullanılabilir. Böylece daha da karmaşık sunucu tarafı mantığı oluşturmak için kullanabilirsiniz. Mutasyonlarda query { … } adımı ile birlikte, ek istemci-sunucu gidiş gelişi olmadan çok daha fazlasını yapabilirsiniz.

Aşağıdaki örnekte, @check'in her zaman eklendiği adımdan sonra çalıştığı için @check'in response.query'a zaten erişimi olduğunu unutmayın.

mutation CreateTodoInNamedList(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1: Look up List.id by its name
  query
  @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
  @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
    todoLists(where: { name: $listName }) {
      id
    }
  }
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
    content: $itemContent,
  })
}

response bağlama hakkında daha fazla bilgi için CEL referansına bakın.

@transaction ve query @check ile kesintiye uğrayan işlemleri anlama

Çok adımlı mutasyonlarda hatalarla karşılaşılabilir:

  • Veritabanı işlemleri başarısız olabilir.
  • sorgusu @check mantığı işlemleri sonlandırabilir.

Data Connect, çok adımlı mutasyonlarınızda @transaction yönergesini kullanmanızı önerir. Bu sayede, daha tutarlı bir veritabanı ve istemci kodunda daha kolay yönetilebilen mutasyon sonuçları elde edilir:

  • İlk hata veya başarısız @check'te işlem sonlandırılır. Bu nedenle, sonraki alanların yürütülmesini veya CEL'in değerlendirilmesini yönetmeniz gerekmez.
  • Geri alma işlemleri, veritabanı hatalarına veya @check mantığına yanıt olarak gerçekleştirilir ve tutarlı bir veritabanı durumu sağlar.
  • Geri alma hatası her zaman istemci koduna döndürülür.

@transaction kullanmamayı tercih edebileceğiniz bazı kullanım alanları olabilir: Örneğin, daha yüksek işleme hızı, ölçeklenebilirlik veya kullanılabilirliğe ihtiyacınız varsa nihai tutarlılığı tercih edebilirsiniz. Ancak sonuçların elde edilebilmesi için veritabanınızı ve istemci kodunuzu yönetmeniz gerekir:

  • Bir alan veritabanı işlemleri nedeniyle başarısız olursa sonraki alanlar yürütülmeye devam eder. Ancak başarısız @check'ler, işlemin tamamını sonlandırır.
  • Geri alma işlemi yapılmaz. Bu durumda, bazı güncellemeler başarılı, bazı güncellemeler ise başarısız olur ve karma bir veritabanı durumu oluşur.
  • @check mantığınız önceki bir adımdaki okuma ve/veya yazma işlemlerinin sonuçlarını kullanıyorsa @check ile yaptığınız işlemler daha tutarsız sonuçlar verebilir.
  • İstemci koduna döndürülen sonuç, ele alınması gereken daha karmaşık bir başarı ve başarısızlık yanıtı karışımı içerir.

Eşdeğer SQL şeması

-- 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);

Sırada ne var?