Yerel SQL kullanarak Firebase Data Connect işlemlerini uygulama

Firebase Data Connect, Cloud SQL veritabanınızla etkileşim kurmanın birden fazla yolunu sunar:

  • Yerel GraphQL: schema.gql ve Data Connect içinde türleri tanımlayın. Bu türler, GraphQL işlemlerinizi SQL'e çevirir. Bu, güçlü türleme ve şema zorunlu yapılar sunan standart yaklaşımdır. Bu sayfa dışındaki Data Connect belgelerin çoğunda bu seçenek ele alınır. Mümkün olduğunda, tam tip güvenliği ve araç desteğinden yararlanmak için bu yöntemi kullanmanız gerekir.
  • @view yönergesi: schema.gql içinde, özel bir SELECT SQL ifadesiyle desteklenen bir GraphQL türü tanımlayın. Bu özellik, karmaşık SQL mantığına dayalı olarak salt okunur ve kesin olarak yazılmış görünümler oluşturmak için kullanışlıdır. Bu türler, normal türler gibi sorgulanabilir. @view sayfasına göz atın.
  • Yerel SQL: SQL ifadelerini doğrudan . içindeki adlandırılmış işlemlere yerleştirin.gql dosyaları özel kök alanları kullanılarak oluşturulur. Bu, özellikle standart GraphQL'de kolayca ifade edilemeyen, veritabanına özgü özelliklerden yararlanan veya PostgreSQL uzantılarını kullanan işlemler için maksimum esneklik ve doğrudan kontrol sağlar.

Bu kılavuzda Native SQL seçeneği ele alınmaktadır.

Doğal SQL'in yaygın kullanım alanları

Yerel GraphQL, tam tür güvenliği sağlarken @view yönergesi salt okunur SQL raporları için kesin türü belirlenmiş sonuçlar sunar. Yerel SQL ise aşağıdakiler için gereken esnekliği sağlar:

  • PostgreSQL Uzantıları: GraphQL şemanızdaki karmaşık türleri eşlemenize gerek kalmadan, yüklenen tüm PostgreSQL uzantılarını (ör. coğrafi veriler için PostGIS) doğrudan sorgulayın ve kullanın.
  • Karmaşık Sorgular: Birleştirmeler, alt sorgular, toplama işlemleri, pencere işlevleri ve saklı prosedürler içeren karmaşık SQL'leri yürütün.
  • Veri Manipülasyonu (DML): INSERT, UPDATE, DELETE işlemlerini doğrudan gerçekleştirin. (Ancak Veri Tanımlama Dili (DDL) komutları için yerel SQL kullanmayın. Arka ucunuzu ve oluşturulan SDK'larınızı senkronize tutmak için şema düzeyindeki değişiklikleri GraphQL kullanarak yapmaya devam etmeniz gerekir.)
  • Veritabanına Özgü Özellikler: PostgreSQL'e özgü işlevleri, operatörleri veya veri türlerini kullanın.
  • Performans Optimizasyonu: Kritik yollar için SQL ifadelerini manuel olarak ayarlayın.

Yerel SQL kök alanları

SQL ile yazma işlemleri yapmak için query veya mutation türlerinin aşağıdaki kök alanlarından birini kullanın:

query alanı sayısı

Alan Açıklama
_select

Sıfır veya daha fazla satır döndüren bir SQL sorgusu yürütür.

Bağımsız değişkenler:

  • sql: SQL ifadesi dize değişmezi. SQL yerleştirme saldırılarını önlemek için parametre değerlerinde konumsal yer tutucular ($1, $2 vb.) kullanın.
  • params: Yer tutuculara bağlanacak değerlerin sıralı listesi. Buna değişmez değerler, GraphQL değişkenleri ve {_expr: "auth.uid"} (kimliği doğrulanmış kullanıcının kimliği) gibi sunucu tarafından yerleştirilmiş özel bağlam haritaları dahildir.

Döndürür: JSON dizisi ([Any]).

_selectFirst

Sıfır veya bir satır döndürmesi beklenen bir SQL sorgusunu yürütür.

Bağımsız değişkenler:

  • sql: SQL ifadesi dize değişmezi. SQL yerleştirme saldırılarını önlemek için parametre değerlerinde konumsal yer tutucular ($1, $2 vb.) kullanın.
  • params: Yer tutuculara bağlanacak değerlerin sıralı listesi. Buna değişmez değerler, GraphQL değişkenleri ve {_expr: "auth.uid"} (kimliği doğrulanmış kullanıcının kimliği) gibi sunucu tarafından yerleştirilmiş özel bağlam haritaları dahildir.

Döndürür: Bir JSON nesnesi (Any) veya null.

mutation alanı sayısı

Alan Açıklama
_execute

Bir DML ifadesini yürütür (INSERT, UPDATE, DELETE).

Bağımsız değişkenler:

  • sql: SQL ifadesi dize değişmezi. SQL yerleştirme saldırılarını önlemek için parametre değerlerinde konumsal yer tutucular ($1, $2 vb.) kullanın.

    Bu alan yalnızca satır sayısını döndürdüğünden burada veri değiştiren Ortak Tablo İfadeleri (ör. WITH new_row AS (INSERT...)) kullanabilirsiniz. Yalnızca _execute, CTE'leri destekler.

  • params: Yer tutuculara bağlanacak değerlerin sıralı listesi. Buna değişmez değerler, GraphQL değişkenleri ve {_expr: "auth.uid"} (kimliği doğrulanmış kullanıcının kimliği) gibi sunucu tarafından yerleştirilmiş özel bağlam haritaları dahildir.

Döndürür: Int (etkilenen satır sayısı).

Sonuçta RETURNING ifadeleri yoksayılır.

_executeReturning

RETURNING ibaresi içeren bir DML ifadesini yürütür ve sıfır veya daha fazla satır döndürür.

Bağımsız değişkenler:

  • sql: SQL ifadesi dize değişmezi. SQL yerleştirme saldırılarını önlemek için parametre değerlerinde konumsal yer tutucular ($1, $2 vb.) kullanın. Veri değiştiren Ortak Tablo İfadeleri desteklenmez.
  • params: Yer tutuculara bağlanacak değerlerin sıralı listesi. Buna değişmez değerler, GraphQL değişkenleri ve {_expr: "auth.uid"} (kimliği doğrulanmış kullanıcının kimliği) gibi sunucu tarafından yerleştirilmiş özel bağlam haritaları dahildir.

Döndürür: JSON dizisi ([Any]).

_executeReturningFirst

Sıfır veya bir satır döndürmesi beklenen bir RETURNING ifadesiyle DML ifadesi yürütür.

Bağımsız değişkenler:

  • sql: SQL ifadesi dize değişmezi. SQL yerleştirme saldırılarını önlemek için parametre değerlerinde konumsal yer tutucular ($1, $2 vb.) kullanın. Veri değiştiren Ortak Tablo İfadeleri desteklenmez.
  • params: Yer tutuculara bağlanacak değerlerin sıralı listesi. Buna değişmez değerler, GraphQL değişkenleri ve {_expr: "auth.uid"} (kimliği doğrulanmış kullanıcının kimliği) gibi sunucu tarafından yerleştirilmiş özel bağlam haritaları dahildir.

Döndürür: Bir JSON nesnesi (Any) veya null.

Notlar:

  • İşlemler, Data Connect hizmet hesabına verilen izinler kullanılarak yürütülür.

  • Tablo adını @table yönergesini kullanarak açıkça ayarlarsanız (@table(name: "ExampleTable")), SQL ifadelerinizde tablo adını da tırnak içine almanız gerekir (SELECT field FROM "ExampleTable" ...).

    Tırnak işaretleri olmadan Data Connect, tablo adını snake case'e (example_table) dönüştürür.

Söz dizimi kuralları ve sınırlamaları

Yerel SQL, güvenliği sağlamak ve SQL eklenmesini önlemek için katı ayrıştırma kurallarını zorunlu kılar. Aşağıdaki kısıtlamalara dikkat edin:

  • Yorumlar: Blok yorumları (/* ... */) kullanın. Satır yorumları (--), sorgu birleştirme sırasında sonraki ifadeleri (ör. güvenlik filtreleri) kesebileceğinden yasaktır.
  • Parametreler: params dizi sırasıyla eşleşen konumsal parametreler ($1, $2) kullanın. Adlandırılmış parametreler ($id, :name) desteklenmez.
  • Dizeler: Genişletilmiş dize değişmezleri (E'...') ve dolar işaretli dizeler ($$...$$) desteklenir. PostgreSQL Unicode kaçışları (U&'...') desteklenmez.

Yorumlardaki parametreler

Ayrıştırıcı, blok yorumun içindeki her şeyi yok sayar. Parametre içeren bir satırı (örneğin, /* WHERE id = $1 */) yorum satırı haline getirirseniz bu parametreyi params listesinden de kaldırmanız gerekir. Aksi takdirde işlem unused parameter: $1 hatasıyla başarısız olur.

Örnekler

Örnek 1: Alan takma adıyla temel SELECT

Kök alanı (ör. movies: _select) takma adlandırarak istemci yanıtını daha temiz hale getirebilirsiniz (data._select yerine data.movies).

queries.gql:

query GetMoviesByGenre($genre: String!, $limit:Int!) @auth(level: PUBLIC) {
  movies: _select(
    sql: """
      SELECT id, title, release_year, rating
      FROM movie
      WHERE genre = $1
      ORDER BY release_year DESC
      LIMIT $2
    """,
    params: [$genre, $limit]
  )
}

Sorgu, istemci SDK'sı kullanılarak çalıştırıldıktan sonra sonuç data.movies içinde yer alır.

Örnek 2: Temel UPDATE

mutations.gql:

mutation UpdateMovieRating($movieId: UUID!, $newRating: Float!) @auth(level: NO_ACCESS) {
  _execute(
    sql: """
      UPDATE movie
      SET rating = $2
      WHERE id = $1
    """,
    params: [$movieId, $newRating]
  )
}

Bir istemci SDK'sı kullanarak mutasyonu çalıştırdıktan sonra etkilenen satır sayısı data._execute içinde yer alır.

3. örnek: Temel toplama

queries.gql:

query GetTotalReviewCount @auth(level: PUBLIC) {
  stats: _selectFirst(
    sql: "SELECT COUNT(*) as total_reviews FROM \"Reviews\""
  )
}

Sorgu, istemci SDK'sı kullanılarak çalıştırıldıktan sonra sonuç data.stats.total_reviews içinde yer alır.

Örnek 4: RANK ile gelişmiş toplama

queries.gql:

query GetMoviesRankedByRating @auth(level: PUBLIC) {
  _select(
    sql: """
      SELECT
        id,
        title,
        rating,
        RANK() OVER (ORDER BY rating DESC) as rank
      FROM movie
      WHERE rating IS NOT NULL
      LIMIT 20
    """,
    params: []
  )
}

Sorgu, istemci SDK'sı kullanılarak çalıştırıldıktan sonra sonuç data._select içinde yer alır.

Örnek 5: RETURNING ve Auth Context ile UPDATE

mutations.gql:

mutation UpdateMyReviewText($movieId: UUID!, $newText: String!) @auth(level: USER) {
  updatedReview: _executeReturningFirst(
    sql: """
      UPDATE "Reviews"
      SET review_text = $2
      WHERE movie_id = $1 AND user_id = $3
      RETURNING movie_id, user_id, rating, review_text
    """,
    params: [$movieId,$newText,{_expr: "auth.uid" }]
  )
}

İstemci SDK'sı kullanılarak mutasyon çalıştırıldıktan sonra, güncellenen gönderi verileri data.updatedReview içinde yer alır.

6. örnek: Ekleme/güncelleme işlemleri içeren gelişmiş CTE (atomik get-or-create)

Bu desen, bağımlı kayıtların (ör. Kullanıcılar veya Filmler) alt kayıt (ör. Yorum) eklenmeden önce tek bir veritabanı işleminde mevcut olmasını sağlamak için kullanışlıdır.

mutations.gql:

mutation CreateMovieCTE($movieId: UUID!, $userId: UUID!, $reviewId: UUID!) {
  _execute(
    sql: """
      WITH
      new_user AS (
        INSERT INTO "user" (id, username)
        VALUES ($2, 'Auto-Generated User')
        ON CONFLICT (id) DO NOTHING
        RETURNING id
      ),
      movie AS (
        INSERT INTO movie (id, title, image_url, release_year, genre)
        VALUES ($1, 'Auto-Generated Movie', 'https://placeholder.com', 2025, 'Sci-Fi')
        ON CONFLICT (id) DO NOTHING
        RETURNING id
      )
      INSERT INTO "Reviews" (id, movie_id, user_id, rating, review_text, review_date)
      VALUES (
        $3,
        $1,
        $2,
        5,
        'Good!',
        NOW()
      )
    """,
    params: [$movieId, $userId, $reviewId]
  )
}

7. örnek: Postgres uzantılarını kullanma

Yerel SQL, karmaşık geometri türlerini GraphQL şemanıza eşlemeniz veya temel tablolarınızı değiştirmeniz gerekmeden PostGIS gibi Postgres uzantılarını kullanmanıza olanak tanır.

Bu örnekte, restoran uygulamanızın meta veri JSON sütununda (örneğin, {"latitude": 37.3688, "longitude": -122.0363}) konum verilerini depolayan bir tablosu olduğunu varsayalım. PostGIS uzantısını etkinleştirdiyseniz, bu değerleri anında ayıklamak ve PostGIS ST_MakePoint işlevine aktarmak için standart Postgres JSON operatörlerini (->>) kullanabilirsiniz.

query GetNearbyActiveRestaurants($userLong: Float!, $userLat: Float!, $maxDistanceMeters: Float!) @auth(level: USER) {
  nearby: _select(
    sql: """
      SELECT
        id,
        name,
        tags,
        ST_Distance(
          ST_MakePoint((metadata->>'longitude')::float, (metadata->>'latitude')::float)::geography,
          ST_MakePoint($1, $2)::geography
        ) as distance_meters
      FROM restaurant
      WHERE active = true
        AND metadata ? 'longitude' AND metadata ? 'latitude'
        AND ST_DWithin(
          ST_MakePoint((metadata->>'longitude')::float, (metadata->>'latitude')::float)::geography,
          ST_MakePoint($1, $2)::geography,
          $3
        )
      ORDER BY distance_meters ASC
      LIMIT 10
    """,
    params: [$userLong, $userLat, $maxDistanceMeters]
  )
}

Sorgu, istemci SDK'sı kullanılarak çalıştırıldıktan sonra sonuç data.nearby içinde yer alır.

Güvenlikle ilgili en iyi uygulamalar: dinamik SQL ve saklı yordamlar

Data Connect GraphQL'den veritabanına sınırında tüm girişleri güvenli bir şekilde parametrelendirerek standart SQL sorgularınızı birinci derece SQL eklemeye karşı tamamen korur. Ancak, dinamik SQL'i yürüten özel Postgres saklı yordamlarını veya işlevlerini çağırmak için SQL kullanıyorsanız dahili PL/pgSQL kodunuzun bu parametreleri güvenli bir şekilde işlediğinden emin olmanız gerekir.

Saklı yordamınız, kullanıcı girişlerini doğrudan bir EXECUTE dizeye birleştiriyorsa parametrelendirmeyi atlar ve ikinci dereceden bir SQL ekleme güvenlik açığı oluşturur:

-- INSECURE: Do not concatenate parameters into dynamic strings!
CREATE OR REPLACE PROCEDURE unsafe_update(user_input TEXT)
LANGUAGE plpgsql AS $$
BEGIN
    -- A malicious user_input (e.g., "val'; DROP TABLE users; --") will execute as code.
    EXECUTE 'UPDATE target_table SET status = ''' || user_input || '''';
END;
$$;

Bunu önlemek için şu en iyi uygulamaları izleyin:

  • USING ifadesini kullanın: Saklı yordamlarınızda dinamik SQL yazarken veri parametrelerini güvenli bir şekilde bağlamak için her zaman USING ifadesini kullanın.
  • Tanımlayıcılar için format() kullanın: Güvenli veritabanı tanımlayıcı ekleme (ör. tablo adları) için format() ile %I işaretini kullanın.
  • Tanımlayıcılara kesinlikle izin verin: İstemci uygulamalarının veritabanı tanımlayıcılarını rastgele seçmesine izin vermeyin. Prosedürünüz dinamik tanımlayıcılar gerektiriyorsa yürütmeden önce girişi PL/pgSQL mantığınızdaki sabit kodlanmış bir izin verilenler listesine göre doğrulayın.
-- SECURE: Use format() for identifiers and USING for data values
CREATE OR REPLACE PROCEDURE secure_update(target_table TEXT, new_value TEXT, row_id INT)
LANGUAGE plpgsql AS $$
BEGIN
    -- Validate the dynamic table name against an allowlist
    IF target_table NOT IN ('orders', 'users', 'inventory') THEN
        RAISE EXCEPTION 'Invalid table name';
    END IF;

    -- Execute securely
    EXECUTE format('UPDATE %I SET status = $1 WHERE id = $2', target_table)
    USING new_value, row_id;
END;
$$;