নেটিভ SQL ব্যবহার করে Firebase Data Connect অপারেশন বাস্তবায়ন করুন

Firebase Data Connect আপনার ক্লাউড SQL ডেটাবেসের সাথে যোগাযোগ করার একাধিক উপায় প্রদান করে:

  • নেটিভ গ্রাফকিউএল (Native GraphQL) : আপনার schema.gql ফাইলে টাইপগুলো সংজ্ঞায়িত করুন এবং Data Connect আপনার গ্রাফকিউএল অপারেশনগুলোকে এসকিউএল (SQL)-এ অনুবাদ করবে। এটিই আদর্শ পদ্ধতি, যা শক্তিশালী টাইপিং এবং স্কিমা-দ্বারা-বাধ্যতামূলক কাঠামো প্রদান করে। এই পৃষ্ঠাটি ছাড়া Data Connect বেশিরভাগ ডকুমেন্টেশনে এই বিকল্পটি নিয়ে আলোচনা করা হয়েছে। যখন সম্ভব, সম্পূর্ণ টাইপ সুরক্ষা এবং টুলিং সাপোর্টের সুবিধা নিতে আপনার এই পদ্ধতিটি ব্যবহার করা উচিত।
  • @view ডিরেক্টিভ : schema.gql এ একটি কাস্টম SELECT SQL স্টেটমেন্ট দ্বারা সমর্থিত একটি GraphQL টাইপ সংজ্ঞায়িত করুন। জটিল SQL লজিকের উপর ভিত্তি করে রিড-অনলি, স্ট্রংলি-টাইপড ভিউ তৈরি করার জন্য এটি উপযোগী। এই টাইপগুলো সাধারণ টাইপের মতোই কোয়েরিযোগ্য। @view দেখুন।
  • নেটিভ SQL : বিশেষ রুট ফিল্ড ব্যবহার করে gql ফাইলের নামযুক্ত অপারেশনের মধ্যে সরাসরি SQL স্টেটমেন্ট এম্বেড করুন। এটি সর্বোচ্চ নমনীয়তা এবং সরাসরি নিয়ন্ত্রণ প্রদান করে, বিশেষ করে সেইসব অপারেশনের জন্য যা স্ট্যান্ডার্ড GraphQL-এ সহজে প্রকাশ করা যায় না, অথবা ডাটাবেস-নির্দিষ্ট বৈশিষ্ট্য বা PostgreSQL এক্সটেনশন ব্যবহার করে। GraphQL এবং @view ডিরেক্টিভের মতো নয়, নেটিভ SQL স্ট্রংলি-টাইপড আউটপুট প্রদান করে না।

এই নির্দেশিকাটি নেটিভ SQL বিকল্পটির উপর আলোকপাত করে।

নেটিভ SQL এর সাধারণ ব্যবহারের ক্ষেত্র

যদিও নেটিভ GraphQL সম্পূর্ণ টাইপ সেফটি প্রদান করে এবং @view ডিরেক্টিভটি রিড-অনলি SQL রিপোর্টের জন্য স্ট্রংলি-টাইপড ফলাফল দেয়, নেটিভ SQL নিম্নলিখিত ক্ষেত্রগুলির জন্য প্রয়োজনীয় নমনীয়তা প্রদান করে:

  • PostgreSQL এক্সটেনশন : আপনার GraphQL স্কিমাতে জটিল টাইপ ম্যাপ করার প্রয়োজন ছাড়াই, ইনস্টল করা যেকোনো PostgreSQL এক্সটেনশন (যেমন ভূ-স্থানিক ডেটার জন্য PostGIS ) সরাসরি কোয়েরি করুন এবং ব্যবহার করুন।
  • জটিল কোয়েরি : জয়েন, সাবকোয়েরি, অ্যাগ্রিগেশন, উইন্ডো ফাংশন এবং স্টোরড প্রসিডিউর ব্যবহার করে জটিল SQL সম্পাদন করুন।
  • ডেটা ম্যানিপুলেশন (DML) : সরাসরি INSERT, UPDATE, DELETE অপারেশন সম্পাদন করুন। (তবে, ডেটা ডেফিনিশন ল্যাঙ্গুয়েজ (DDL) কমান্ডের জন্য নেটিভ SQL ব্যবহার করবেন না। আপনার ব্যাকএন্ড এবং জেনারেটেড SDK-গুলোকে সিঙ্কে রাখতে আপনাকে অবশ্যই 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

একটি DML স্টেটমেন্ট ( INSERT, UPDATE, DELETE ) কার্যকর করে।

যুক্তি :

  • sql : SQL স্টেটমেন্টের স্ট্রিং লিটারেল। SQL ইনজেকশন প্রতিরোধ করতে, প্যারামিটারের মানের জন্য পজিশনাল প্লেসহোল্ডার ( $1 , $2 ইত্যাদি) ব্যবহার করুন।

    আপনি এখানে ডেটা-পরিবর্তনকারী কমন টেবিল এক্সপ্রেশন (যেমন, WITH new_row AS (INSERT...) ) ব্যবহার করতে পারেন, কারণ এই ফিল্ডটি শুধুমাত্র সারির সংখ্যা ফেরত দেয়। শুধুমাত্র _execute ফাংশনটিই CTE সমর্থন করে।

  • params : প্লেসহোল্ডারগুলির সাথে বাইন্ড করার জন্য মানগুলির একটি ক্রমিক তালিকা। এর মধ্যে লিটারেল, GraphQL ভেরিয়েবল এবং বিশেষ সার্ভার-ইনজেক্টেড কনটেক্সট ম্যাপ যেমন {_expr: "auth.uid"} (প্রমাণিত ব্যবহারকারীর আইডি) অন্তর্ভুক্ত থাকতে পারে।

রিটার্ন করে : একটি Int (প্রভাবিত সারির সংখ্যা)।

ফলাফলে RETURNING ক্লজগুলো উপেক্ষা করা হয়।

_executeReturning

RETURNING ক্লজ সহ একটি DML স্টেটমেন্ট কার্যকর করে, যা শূন্য বা তার অধিক সংখ্যক সারি ফেরত দেয়।

যুক্তি :

  • sql : SQL স্টেটমেন্টের স্ট্রিং লিটারেল। SQL ইনজেকশন প্রতিরোধ করতে, প্যারামিটারের মানের জন্য পজিশনাল প্লেসহোল্ডার ( $1 , $2 ইত্যাদি) ব্যবহার করুন। ডেটা পরিবর্তনকারী কমন টেবিল এক্সপ্রেশন সমর্থিত নয়।
  • params : প্লেসহোল্ডারগুলির সাথে বাইন্ড করার জন্য মানগুলির একটি ক্রমিক তালিকা। এর মধ্যে লিটারেল, GraphQL ভেরিয়েবল এবং বিশেষ সার্ভার-ইনজেক্টেড কনটেক্সট ম্যাপ যেমন {_expr: "auth.uid"} (প্রমাণিত ব্যবহারকারীর আইডি) অন্তর্ভুক্ত থাকতে পারে।

ফেরত দেয় : একটি JSON অ্যারে ( [Any] )।

_executeReturningFirst

RETURNING ক্লজ সহ একটি DML স্টেটমেন্ট কার্যকর করে, যা থেকে শূন্য বা একটি সারি ফেরত আসার কথা।

যুক্তি :

  • 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 কঠোর পার্সিং নিয়ম প্রয়োগ করে। নিম্নলিখিত সীমাবদ্ধতাগুলো সম্পর্কে সচেতন থাকুন:

  • মন্তব্য : ব্লক কমেন্ট ( /* ... */ ) ব্যবহার করুন। লাইন কমেন্ট ( -- ) ব্যবহার করা নিষিদ্ধ, কারণ কোয়েরি সংযুক্ত করার সময় এগুলি পরবর্তী ক্লজগুলিকে (যেমন সিকিউরিটি ফিল্টার) কেটে ফেলতে পারে।
  • প্যারামিটার : params অ্যারের ক্রম অনুসারে পজিশনাল প্যারামিটার ( $1 , $2 ) ব্যবহার করুন। নেমড প্যারামিটার ( $id , :name ) সমর্থিত নয়।
  • স্ট্রিং : বর্ধিত স্ট্রিং লিটারেল ( E'...' ) এবং ডলার-কোটেড স্ট্রিং ($$...$$ ) সমর্থিত। PostgreSQL ইউনিকোড এস্কেপ ( U&'...' ) সমর্থিত নয়।

মন্তব্যে প্যারামিটার

পার্সার ব্লক কমেন্টের ভেতরের সবকিছু উপেক্ষা করে। যদি আপনি কোনো প্যারামিটারযুক্ত লাইন কমেন্ট করে দেন (উদাহরণস্বরূপ, /* WHERE id = $1 */ ), তাহলে আপনাকে অবশ্যই params তালিকা থেকেও সেই প্যারামিটারটি সরিয়ে ফেলতে হবে, নতুবা অপারেশনটি unused parameter: $1 ত্রুটিসহ ব্যর্থ হবে।

উদাহরণ

উদাহরণ ১: ফিল্ড অ্যালিয়াসিং সহ সাধারণ SELECT

ক্লায়েন্টের প্রতিক্রিয়া আরও পরিচ্ছন্ন করার জন্য ( data.movies এর পরিবর্তে data._select ) আপনি রুট ফিল্ডের একটি অ্যালিয়াস (যেমন, movies: _select ) ব্যবহার করতে পারেন।

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 ফাইলে পাওয়া যাবে।

উদাহরণ ২: মৌলিক আপডেট

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 ফাইলে পাওয়া যাবে।

উদাহরণ ৪: 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" }]
  )
}

ক্লায়েন্ট SDK ব্যবহার করে মিউটেশনটি চালানোর পর, আপডেট করা পোস্ট ডেটা data.updatedReview -এ থাকবে।

উদাহরণ ৬: আপসার্ট সহ উন্নত CTE (অ্যাটোমিক গেট-অর-ক্রিয়েট)

এই প্যাটার্নটি একটিমাত্র ডাটাবেস ট্রানজ্যাকশনের মধ্যে কোনো চাইল্ড রেকর্ড (যেমন রিভিউ) ইনসার্ট করার আগে ডিপেন্ডেন্ট রেকর্ড (যেমন ইউজার বা মুভি) বিদ্যমান আছে কিনা তা নিশ্চিত করতে সহায়ক।

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

উদাহরণ ৭: পোস্টগ্রেস এক্সটেনশন ব্যবহার

নেটিভ SQL আপনাকে আপনার GraphQL স্কিমাতে জটিল জ্যামিতি টাইপ ম্যাপ করা বা আপনার অন্তর্নিহিত টেবিল পরিবর্তন করার প্রয়োজন ছাড়াই PostGIS-এর মতো Postgres এক্সটেনশন ব্যবহার করার সুযোগ দেয়।

এই উদাহরণে, ধরুন আপনার রেস্তোরাঁ অ্যাপে একটি টেবিল আছে যা একটি মেটাডেটা 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 তে পাওয়া যাবে।

নিরাপত্তার সর্বোত্তম অনুশীলন: ডায়নামিক SQL এবং স্টোরড প্রসিডিউর

Data Connect গ্রাফকিউএল-টু-ডাটাবেস সীমানায় সমস্ত ইনপুটকে নিরাপদে প্যারামিটারাইজ করে, যা আপনার স্ট্যান্ডার্ড SQL কোয়েরিগুলোকে ফার্স্ট-অর্ডার SQL ইনজেকশন থেকে সম্পূর্ণরূপে সুরক্ষিত রাখে। তবে, আপনি যদি কাস্টম পোস্টগ্রেস স্টোরড প্রসিডিউর বা ডাইনামিক SQL এক্সিকিউট করে এমন ফাংশন কল করার জন্য 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 লজিকের ভিতরে একটি হার্ডকোডেড অনুমোদিত তালিকার (allowlist) সাথে ইনপুটটি যাচাই করে নিন।
-- 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;
$$;