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

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

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

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

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

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

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

नेटिव SQL के रूट फ़ील्ड

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

query फ़ील्ड

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

SQL क्वेरी को एक्ज़ीक्यूट करता है. इससे कोई भी लाइन या एक से ज़्यादा लाइनें दिखती हैं.

आर्गुमेंट:

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

नतीजा: JSON अरे ([Any]).

_selectFirst

SQL क्वेरी को एक्ज़ीक्यूट करता है. इससे कोई भी लाइन या एक लाइन दिखती है.

आर्गुमेंट:

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

नतीजा: JSON ऑब्जेक्ट (Any) या null.

mutation फ़ील्ड

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

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

आर्गुमेंट:

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

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

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

नतीजा: Int (असर डालने वाली लाइनों की संख्या).

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

_executeReturning

RETURNING क्लॉज़ के साथ डीएमएल स्टेटमेंट को एक्ज़ीक्यूट करता है. इससे कोई भी लाइन या एक से ज़्यादा लाइनें दिखती हैं.

आर्गुमेंट:

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

नतीजा: JSON अरे ([Any]).

_executeReturningFirst

RETURNING क्लॉज़ के साथ डीएमएल स्टेटमेंट को एक्ज़ीक्यूट करता है. इससे कोई भी लाइन या एक लाइन दिखती है.

आर्गुमेंट:

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

नतीजा: JSON ऑब्जेक्ट (Any) या null.

ध्यान दें:

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

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

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

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

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

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

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

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

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

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

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

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

queries.gql:

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

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

चौथा उदाहरण: 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: []
  )
}

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

पांचवा उदाहरण: RETURNING और ऑथ कॉन्टेक्स्ट के साथ 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]
  )
}

सातवां उदाहरण: Postgres एक्सटेंशन का इस्तेमाल करना

नेटिव SQL की मदद से, 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]
  )
}

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

सुरक्षा के सबसे सही तरीके: डाइनैमिक SQL और सेव किए गए प्रोसीज़र

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

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

-- 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 क्लॉज़ का इस्तेमाल करें: सेव किए गए प्रोसीज़र में डाइनैमिक SQL लिखते समय, डेटा पैरामीटर को सुरक्षित तरीके से बाइंड करने के लिए हमेशा 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;
$$;