Yerel SQL kullanarak Firebase SQL Connect işlemlerini uygulama

Firebase SQL Connect işlemlerini GraphQL yerine SQL ile yazma kılavuzu. page_type: guide announcement: > Yerel SQL, özellik önizlemesi olarak kullanılabilir. Bu nedenle, herhangi bir HDS'ye veya kullanımdan kaldırma politikasına tabi değildir ve geriye dönük olarak uyumsuz olacak şekilde değişebilir. Bu özelliği saklanmış yordamlar veya dinamik SQL yürüten işlevlerle kullanıyorsanız bu sayfanın alt kısmında açıklanan güvenlikle ilgili en iyi uygulamaları izleyin.

Firebase SQL Connect, Cloud SQL veritabanınızla etkileşim kurmanın çeşitli yollarını sunar:

  • Yerel GraphQL: schema.gql içinde türleri tanımlayın ve SQL Connect, GraphQL işlemlerinizi SQL'e çevirir. Bu, güçlü türleme ve şemayla zorunlu kılınan yapılar sunan standart yaklaşımdır. Bu sayfa dışındaki SQL Connect belgelerin çoğunda bu seçenek ele alınır. Mümkün olduğunda tam tip güvenliğinden 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 türde 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: Özel kök alanları kullanarak SQL ifadelerini doğrudan .gql dosyalarındaki adlandırılmış işlemlere yerleştirin. Bu, özellikle standart GraphQL tarafından desteklenmeyen işlemler, veritabanına özgü özelliklerden yararlanma veya PostgreSQL uzantılarını kullanma gibi durumlarda maksimum esneklik ve doğrudan kontrol sağlar. GraphQL ve @view yönergesinin aksine, yerel SQL kesin tür çıkışı sağlamaz.

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

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

Yerel GraphQL, tam tip 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 uçunuzu ve oluşturulan SDK'larınızı senkronize tutmak için şema düzeyinde değişiklikler yapmaya GraphQL kullanarak 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 özel sunucu tarafından yerleştirilmiş 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 özel sunucu tarafından yerleştirilmiş 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 (örneğin, 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 özel sunucu tarafından yerleştirilmiş 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 özel sunucu tarafından yerleştirilmiş bağlam haritaları dahildir.

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

_executeReturningFirst

RETURNING ifadesi içeren bir DML ifadesini yürütür, sıfır veya bir satır döndürmesi beklenir.

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 özel sunucu tarafından yerleştirilmiş bağlam haritaları dahildir.

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

Notlar:

  • İşlemler, SQL Connect hizmet hesabına verilen izinler kullanılarak yürütülü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 dizisinin 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 yoksayar. 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.

Adlandırma kuralları

Yerel SQL yazarken doğrudan PostgreSQL veritabanınızla etkileşimde bulunursunuz. Bu nedenle, tablolar ve sütunlar için gerçek veritabanı adlarını kullanmanız gerekir. Varsayılan olarak SQL Connect, GraphQL şemanızdaki adları veritabanında snake case ile otomatik olarak eşler. Ancak @table(name) ve @col(name) yönergelerini kullanarak Postgres tanımlayıcılarını açıkça özelleştirmeniz gerekir.

Bir türü yönergeler olmadan tanımlarsanız GraphQL tablosu ve alan adları, varsayılan snake_case Postgres tanımlayıcılarıyla eşlenir:

schema.gql queries.gql
type UserProfile {
  userId: ID!
  displayName: String
}
query GetUserProfileDefault($id: ID!) {
  profile: _selectFirst(
    sql: """
      SELECT user_id, display_name
      FROM user_profile
      WHERE user_id = $1
    """,
    params: [$id]
  )
}

PostgreSQL tanımlayıcıları varsayılan olarak büyük/küçük harfe duyarlı değildir. Büyük harf veya karışık harf içeren bir ad belirtmek için @table ya da @col gibi yönergeler kullanıyorsanız bu tanımlayıcıyı SQL ifadelerinizde çift tırnak içine almanız gerekir.

Aşağıdaki örnekte, tablo adı için "UserProfiles", userId sütunu için ise "profileId" kullanmanız gerekir. displayName alanı, display_name için varsayılan dönüşümü izler:

schema.gql queries.gql
type UserProfileCustom @table(name: "UserProfiles") {
  userId: ID! @col(name: "profileId")
  displayName: String
}
query GetUserProfileCustom($id: ID!) {
  profile: _selectFirst(
    sql: """
      SELECT "profileId", display_name
      FROM "UserProfiles"
      WHERE "profileId" = $1
    """,
    params: [$id]
  )
}

Kullanım örnekleri

Ö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.

2. örnek: 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 olmasını sağlamak için kullanışlıdır.

mutations.gql:

mutation CreateMovieCTE($movieId: UUID!, $userId: UUID!, $reviewId: UUID!) @auth(level: USER) {
  _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]
  )
}

_executeReturning ve _executeReturningFirst, çıkışı JSON olarak biçimlendirmek için sorgunuzu üst CTE'ye sarar. PostgreSQL, veri değiştiren bir CTE'nin başka bir veri değiştiren ifadenin içine yerleştirilmesine izin vermediğinden sorgu başarısız olur.

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 konum verilerini meta veri JSON sütununda (ör. {"latitude": 37.3688, "longitude": -122.0363}) 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

SQL 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;
$$;