नेटिव SQL का इस्तेमाल करके, Firebase Data Connect की कार्रवाइयां लागू करना

Firebase Data Connect में, Cloud SQL डेटाबेस के साथ इंटरैक्ट करने के कई तरीके उपलब्ध हैं:

  • नेटिव GraphQL: अपने schema.gql में टाइप तय करें और Data Connect, GraphQL ऑपरेशन को एसक्यूएल में बदलता है. यह स्टैंडर्ड तरीका है. इसमें टाइपिंग और स्कीमा लागू करने की सुविधा मिलती है. इस पेज के अलावा, Data Connect के ज़्यादातर दस्तावेज़ों में इस विकल्प के बारे में बताया गया है. जब भी हो सके, आपको इस तरीके का इस्तेमाल करना चाहिए, ताकि टाइप की पूरी सुरक्षा और टूलिंग सपोर्ट का फ़ायदा मिल सके.
  • @view डायरेक्टिव: schema.gql में GraphQL टाइप तय करें. यह कस्टम SELECT एसक्यूएल स्टेटमेंट पर आधारित होता है. यह जटिल एसक्यूएल लॉजिक पर आधारित, सिर्फ़ पढ़ने के लिए और टाइप किए गए व्यू बनाने के लिए उपयोगी है. इन टाइप को सामान्य टाइप की तरह क्वेरी किया जा सकता है. @view देखें.
  • नेटिव एसक्यूएल: एसक्यूएल स्टेटमेंट को सीधे तौर पर . में नाम वाले ऑपरेशनों में एम्बेड करें.gql फ़ाइलों का इस्तेमाल किया जा सकता है. इससे ज़्यादा से ज़्यादा फ़्लेक्सिबिलिटी और डायरेक्ट कंट्रोल मिलता है. खास तौर पर, उन कार्रवाइयों के लिए जिन्हें स्टैंडर्ड GraphQL में आसानी से नहीं दिखाया जा सकता. साथ ही, डेटाबेस से जुड़ी सुविधाओं का फ़ायदा उठाने या PostgreSQL एक्सटेंशन का इस्तेमाल करने के लिए.

इस गाइड में, नेटिव एसक्यूएल विकल्प पर फ़ोकस किया गया है.

नेटिव एसक्यूएल के इस्तेमाल के सामान्य उदाहरण

नेटिव GraphQL, टाइप की पूरी सुरक्षा देता है. साथ ही, @view डायरेक्टिव, सिर्फ़ पढ़ने के लिए उपलब्ध एसक्यूएल रिपोर्ट के लिए, टाइप किए गए नतीजे देता है. वहीं, नेटिव एसक्यूएल इन कामों के लिए ज़रूरी फ़्लेक्सिबिलिटी देता है:

  • PostgreSQL एक्सटेंशन: इंस्टॉल किए गए किसी भी PostgreSQL एक्सटेंशन (जैसे, जियोस्पेशल डेटा के लिए PostGIS) को सीधे तौर पर क्वेरी करें और उसका इस्तेमाल करें. इसके लिए, आपको अपने GraphQL स्कीमा में जटिल टाइप मैप करने की ज़रूरत नहीं है.
  • जटिल क्वेरी: जॉइन, सबक्वेरी, एग्रीगेशन, विंडो फ़ंक्शन, और सेव की गई प्रक्रियाओं के साथ जटिल एसक्यूएल को लागू करें.
  • डेटा मैनिपुलेशन (डीएमएल): सीधे तौर पर INSERT, UPDATE, DELETE कार्रवाइयां करें. (हालांकि, डेटा डेफ़िनिशन लैंग्वेज (डीडीएल) कमांड के लिए नेटिव एसक्यूएल का इस्तेमाल न करें. आपको स्कीमा लेवल पर बदलाव करने के लिए, GraphQL का इस्तेमाल जारी रखना होगा, ताकि आपका बैकएंड और जनरेट किए गए एसडीके सिंक में रहें.)
  • डेटाबेस के हिसाब से उपलब्ध सुविधाएं: PostgreSQL के लिए खास तौर पर उपलब्ध फ़ंक्शन, ऑपरेटर या डेटा टाइप का इस्तेमाल करें.
  • परफ़ॉर्मेंस ऑप्टिमाइज़ेशन: अहम पाथ के लिए, एसक्यूएल स्टेटमेंट को मैन्युअल तरीके से ट्यून करें.

नेटिव एसक्यूएल रूट फ़ील्ड

SQL की मदद से डेटा में बदलाव करने के लिए, query या mutation टाइप के इन रूट फ़ील्ड में से किसी एक का इस्तेमाल करें:

query फ़ील्ड

फ़ील्ड ब्यौरा
_select

यह फ़ंक्शन, एसक्यूएल क्वेरी को लागू करता है. इससे शून्य या उससे ज़्यादा लाइनें मिलती हैं.

आर्ग्युमेंट:

  • sql: एसक्यूएल स्टेटमेंट स्ट्रिंग लिटरल. एसक्यूएल इंजेक्शन को रोकने के लिए, पैरामीटर वैल्यू के लिए पोज़ीशनल प्लेसहोल्डर ($1, $2 वगैरह) का इस्तेमाल करें.
  • params: यह प्लेसहोल्डर से बाइंड करने के लिए, वैल्यू की क्रम वाली सूची होती है. इसमें लिटरल, GraphQL वैरिएबल, और सर्वर से इंजेक्ट किए गए खास कॉन्टेक्स्ट मैप शामिल हो सकते हैं. जैसे, {_expr: "auth.uid"} (प्रमाणीकरण किए गए उपयोगकर्ता का आईडी).

जवाब: JSON कलेक्शन ([Any]).

_selectFirst

यह फ़ंक्शन, ऐसी एसक्यूएल क्वेरी को लागू करता है जिससे शून्य या एक लाइन मिलती है.

आर्ग्युमेंट:

  • sql: एसक्यूएल स्टेटमेंट स्ट्रिंग लिटरल. एसक्यूएल इंजेक्शन को रोकने के लिए, पैरामीटर वैल्यू के लिए पोज़ीशनल प्लेसहोल्डर ($1, $2 वगैरह) का इस्तेमाल करें.
  • params: यह प्लेसहोल्डर से बाइंड करने के लिए, वैल्यू की क्रम वाली सूची होती है. इसमें लिटरल, GraphQL वैरिएबल, और सर्वर से इंजेक्ट किए गए खास कॉन्टेक्स्ट मैप शामिल हो सकते हैं. जैसे, {_expr: "auth.uid"} (प्रमाणीकरण किए गए उपयोगकर्ता का आईडी).

वैल्यू दिखाता है: एक JSON ऑब्जेक्ट (Any) या null.

mutation फ़ील्ड

फ़ील्ड ब्यौरा
_execute

डीएमएल स्टेटमेंट (INSERT, UPDATE, DELETE) को लागू करता है.

आर्ग्युमेंट:

  • sql: एसक्यूएल स्टेटमेंट स्ट्रिंग लिटरल. एसक्यूएल इंजेक्शन को रोकने के लिए, पैरामीटर वैल्यू के लिए पोज़ीशनल प्लेसहोल्डर ($1, $2 वगैरह) का इस्तेमाल करें.

    यहां डेटा में बदलाव करने वाले कॉमन टेबल एक्सप्रेशन (उदाहरण के लिए, WITH new_row AS (INSERT...)) का इस्तेमाल किया जा सकता है, क्योंकि यह फ़ील्ड सिर्फ़ पंक्तियों की संख्या दिखाता है. सिर्फ़ _execute में सीटीई इस्तेमाल किए जा सकते हैं.

  • params: यह प्लेसहोल्डर से बाइंड करने के लिए, वैल्यू की क्रम वाली सूची होती है. इसमें लिटरल, GraphQL वैरिएबल, और सर्वर से इंजेक्ट किए गए खास कॉन्टेक्स्ट मैप शामिल हो सकते हैं. जैसे, {_expr: "auth.uid"} (प्रमाणीकरण किए गए उपयोगकर्ता का आईडी).

वैल्यू दिखाता है: Int (असर डालने वाली पंक्तियों की संख्या).

नतीजे में RETURNING क्लॉज़ को अनदेखा किया जाता है.

_executeReturning

यह फ़ंक्शन, RETURNING क्लॉज़ के साथ DML स्टेटमेंट को लागू करता है. इससे शून्य या उससे ज़्यादा लाइनें मिलती हैं.

आर्ग्युमेंट:

  • sql: एसक्यूएल स्टेटमेंट स्ट्रिंग लिटरल. एसक्यूएल इंजेक्शन को रोकने के लिए, पैरामीटर वैल्यू के लिए पोज़ीशनल प्लेसहोल्डर ($1, $2 वगैरह) का इस्तेमाल करें. डेटा में बदलाव करने वाली कॉमन टेबल एक्सप्रेशन का इस्तेमाल नहीं किया जा सकता.
  • params: यह प्लेसहोल्डर से बाइंड करने के लिए, वैल्यू की क्रम वाली सूची होती है. इसमें लिटरल, GraphQL वैरिएबल, और सर्वर से इंजेक्ट किए गए खास कॉन्टेक्स्ट मैप शामिल हो सकते हैं. जैसे, {_expr: "auth.uid"} (प्रमाणीकरण किए गए उपयोगकर्ता का आईडी).

जवाब: JSON कलेक्शन ([Any]).

_executeReturningFirst

यह फ़ंक्शन, RETURNING क्लॉज़ के साथ DML स्टेटमेंट को लागू करता है. इससे शून्य या एक लाइन मिलने की उम्मीद होती है.

आर्ग्युमेंट:

  • sql: एसक्यूएल स्टेटमेंट स्ट्रिंग लिटरल. एसक्यूएल इंजेक्शन को रोकने के लिए, पैरामीटर वैल्यू के लिए पोज़ीशनल प्लेसहोल्डर ($1, $2 वगैरह) का इस्तेमाल करें. डेटा में बदलाव करने वाली कॉमन टेबल एक्सप्रेशन का इस्तेमाल नहीं किया जा सकता.
  • params: यह प्लेसहोल्डर से बाइंड करने के लिए, वैल्यू की क्रम वाली सूची होती है. इसमें लिटरल, GraphQL वैरिएबल, और सर्वर से इंजेक्ट किए गए खास कॉन्टेक्स्ट मैप शामिल हो सकते हैं. जैसे, {_expr: "auth.uid"} (प्रमाणीकरण किए गए उपयोगकर्ता का आईडी).

वैल्यू दिखाता है: एक JSON ऑब्जेक्ट (Any) या null.

ध्यान दें:

  • कार्रवाइयां, Data Connect सेवा खाते को दी गई अनुमतियों का इस्तेमाल करके की जाती हैं.

  • अगर आपने @table डायरेक्टिव (@table(name: "ExampleTable")) का इस्तेमाल करके, टेबल का नाम साफ़ तौर पर सेट किया है, तो आपको अपने एसक्यूएल स्टेटमेंट (SELECT field FROM "ExampleTable" ...) में भी टेबल के नाम को कोटेशन मार्क में रखना होगा.

    कोटेशन मार्क के बिना, Data Connect टेबल के नाम को स्नेक केस (example_table) में बदल देगा.

सिंटैक्स के नियम और सीमाएं

नेटिव एसक्यूएल में पार्स करने के सख्त नियम लागू किए जाते हैं, ताकि सुरक्षा को पक्का किया जा सके और एसक्यूएल इंजेक्शन को रोका जा सके. इन बातों का ध्यान रखें:

  • टिप्पणियां: ब्लॉक टिप्पणियों (/* ... */) का इस्तेमाल करें. लाइन टिप्पणियों (--) का इस्तेमाल करने की अनुमति नहीं है, क्योंकि ये क्वेरी को एक साथ जोड़ते समय, बाद के क्लॉज़ (जैसे कि सुरक्षा फ़िल्टर) को छोटा कर सकती हैं.
  • पैरामीटर: पोज़िशनल पैरामीटर ($1, $2) का इस्तेमाल करें. ये params ऐरे के क्रम से मेल खाने चाहिए. नाम वाले पैरामीटर ($id, :name) काम नहीं करते.
  • स्ट्रिंग: एक्सटेंडेड स्ट्रिंग लिटरल (E'...') और डॉलर-कोट की गई स्ट्रिंग ($$...$$) इस्तेमाल की जा सकती हैं. PostgreSQL यूनिकोड एस्केप (U&'...') काम नहीं करते.

टिप्पणियों में पैरामीटर

पार्सर, ब्लॉक की गई टिप्पणी में मौजूद सभी चीज़ों को अनदेखा करता है. अगर आपको किसी पैरामीटर (उदाहरण के लिए, /* WHERE id = $1 */) वाली लाइन को कमेंट आउट करना है, तो आपको उस पैरामीटर को params सूची से भी हटाना होगा. ऐसा न करने पर, ऑपरेशन पूरा नहीं होगा और आपको गड़बड़ी unused parameter: $1 दिखेगी.

उदाहरण

उदाहरण 1: फ़ील्ड के लिए अन्य नाम का इस्तेमाल करके बुनियादी SELECT क्वेरी

क्लाइंट के रिस्पॉन्स को बेहतर बनाने के लिए, रूट फ़ील्ड (उदाहरण के लिए, movies: _select) को एलियास किया जा सकता है. इससे क्लाइंट को data._select के बजाय 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]
  )
}

क्लाइंट SDK का इस्तेमाल करके क्वेरी चलाने के बाद, नतीजा data.movies में दिखेगा.

उदाहरण 2: सामान्य 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]
  )
}

क्लाइंट SDK टूल का इस्तेमाल करके म्यूटेशन चलाने के बाद, असर वाली पंक्तियों की संख्या data._execute में होगी.

तीसरा उदाहरण: बुनियादी एग्रीगेशन

queries.gql:

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

क्लाइंट SDK का इस्तेमाल करके क्वेरी चलाने के बाद, नतीजा data.stats.total_reviews में दिखेगा.

उदाहरण 4: RANK फ़ंक्शन के साथ ऐडवांस एग्रीगेशन

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: []
  )
}

क्लाइंट SDK का इस्तेमाल करके क्वेरी चलाने के बाद, नतीजा data._select में दिखेगा.

पांचवां उदाहरण: RETURNING और Auth Context के साथ 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" }]
  )
}

क्लाइंट एसडीके का इस्तेमाल करके म्यूटेशन चलाने के बाद, अपडेट किया गया पोस्ट डेटा data.updatedReview में होगा.

छठा उदाहरण: अपसर्ट (एटॉमिक गेट-ऑर-क्रिएट) के साथ ऐडवांस सीटीई

यह पैटर्न, यह पक्का करने के लिए काम आता है कि किसी चाइल्ड रिकॉर्ड (जैसे, समीक्षा) को डालने से पहले, निर्भर रिकॉर्ड (जैसे, उपयोगकर्ता या फ़िल्में) मौजूद हों. यह सब एक ही डेटाबेस ट्रांज़ैक्शन में होता है.

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: Postgres एक्सटेंशन का इस्तेमाल करना

नेटिव एसक्यूएल की मदद से, Postgres एक्सटेंशन का इस्तेमाल किया जा सकता है. जैसे, PostGIS. इसके लिए, आपको अपने GraphQL स्कीमा में जटिल ज्यामिति टाइप को मैप करने या अपनी बुनियादी टेबल में बदलाव करने की ज़रूरत नहीं होती.

इस उदाहरण में मान लें कि आपके रेस्टोरेंट ऐप्लिकेशन में एक टेबल है. यह टेबल, मेटाडेटा JSON कॉलम (उदाहरण के लिए, {"latitude": 37.3688, "longitude": -122.0363}) में जगह की जानकारी का डेटा सेव करती है. अगर आपने PostGIS एक्सटेंशन चालू किया है, तो Postgres के स्टैंडर्ड JSON ऑपरेटर (->>) का इस्तेमाल करके, इन वैल्यू को तुरंत निकाला जा सकता है. साथ ही, इन्हें PostGIS ST_MakePoint फ़ंक्शन में पास किया जा सकता है.

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

क्लाइंट SDK का इस्तेमाल करके क्वेरी चलाने के बाद, नतीजा data.nearby में दिखेगा.

सुरक्षा के सबसे सही तरीके: डाइनैमिक एसक्यूएल और स्टोर की गई प्रक्रियाएं

Data Connect GraphQL-to-database बाउंड्री पर सभी इनपुट को सुरक्षित तरीके से पैरामीटर में बदलता है. इससे, आपकी स्टैंडर्ड एसक्यूएल क्वेरी को पहले ऑर्डर के एसक्यूएल इंजेक्शन से पूरी तरह से सुरक्षित रखा जाता है. हालांकि, अगर आपको डाइनैमिक एसक्यूएल को लागू करने वाले कस्टम Postgres सेव किए गए प्रोसीज़र या फ़ंक्शन को कॉल करने के लिए एसक्यूएल का इस्तेमाल करना है, तो आपको यह पक्का करना होगा कि आपका इंटरनल PL/pgSQL कोड इन पैरामीटर को सुरक्षित तरीके से हैंडल करता हो.

अगर आपकी सेव की गई प्रक्रिया, उपयोगकर्ता के इनपुट को सीधे तौर पर EXECUTE स्ट्रिंग में जोड़ती है, तो यह पैरामीटर के इस्तेमाल को बायपास कर देती है. इससे दूसरे क्रम की एसक्यूएल इंजेक्शन की कमज़ोरी पैदा होती है:

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

इससे बचने के लिए, यहां दिए गए सबसे सही तरीके अपनाएं:

  • USING क्लॉज़ का इस्तेमाल करें: सेव किए गए अपने प्रोसीज़र में डाइनैमिक एसक्यूएल लिखते समय, डेटा पैरामीटर को सुरक्षित तरीके से बाइंड करने के लिए हमेशा USING क्लॉज़ का इस्तेमाल करें.
  • पहचानकर्ताओं के लिए format() का इस्तेमाल करें: सुरक्षित डेटाबेस आइडेंटिफ़ायर इंजेक्शन (जैसे कि टेबल के नाम) के लिए, %I फ़्लैग के साथ format() का इस्तेमाल करें.
  • सिर्फ़ आइडेंटिफ़ायर की अनुमति दें: क्लाइंट ऐप्लिकेशन को डेटाबेस आइडेंटिफ़ायर चुनने की अनुमति न दें. अगर आपकी प्रोसेस के लिए डाइनैमिक आइडेंटिफ़ायर की ज़रूरत है, तो एक्ज़ीक्यूशन से पहले, अपने PL/pgSQL लॉजिक में हार्डकोड की गई अनुमति वाली सूची के हिसाब से इनपुट की पुष्टि करें.
-- 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;
$$;