المخططات وطلبات البحث وعمليات التغيير في Data Connect

Firebase Data Connect يتيح لك إنشاء أدوات ربط لمثيل PostgreSQL المُدار باستخدام Google Cloud SQL. هذه الوصلات هي مجموعات من مخطّط وطلبات بحث وعمليات تحويل لاستخدام بياناتك.

قدّم دليل البدء مخطّط تطبيقات مراجعات الأفلام في PostgreSQL، ويتناول هذا الدليل بالتفصيل كيفية تصميم مخطّطات Data Connect لـ PostgreSQL.

يجمع هذا الدليل بين Data Connect طلبات البحث والطفرات مع نماذج المخطط. لماذا نناقش طلبات البحثعمليات التحويل) في دليل عن Data Connect المخططات؟ مثل المنصات الأخرى المستندة إلى GraphQL، Firebase Data Connect هي منصة تطوير تركز على طلبات البحث، لذا بصفتك أحد المطوّرين، عليك التفكير في البيانات التي يحتاجها العميل عند وضع نماذج للبيانات، ما سيؤثّر بشكل كبير في مخطّط البيانات الذي تُطوّره لمشروعك.

يبدأ هذا الدليل بمخطّط جديد لمراجعات الأفلام، ثم يتناول الاستعلامات وعمليات التحويل المشتقة من هذا المخطّط، ويقدّم أخيرًا قائمة SQL مكافئة للمخطّط الأساسي Data Connect.

مخطّط تطبيق لمراجعة الأفلام

لنفترض أنّك تريد إنشاء خدمة تتيح للمستخدمين إرسال مراجعات الأفلام والاطّلاع عليها.

تحتاج إلى مخطّط أوّلي لتطبيق كهذا. ويمكنك توسيع هذا المخطّط لاحقًا لإنشاء طلبات بحث علاقاتية معقّدة.

جدول الأفلام

يحتوي مخطّط الأفلام على توجيهات أساسية، مثل:

  • @table(name) و@col(name) لتخصيص أسماء جدول وعمود SQL تُنشئ أداة Data Connect أسماء بتنسيق snake_case في حال عدم تحديدها.
  • @col(dataType) لتخصيص أنواع أعمدة SQL
  • @default لضبط القيم التلقائية لعمود SQL أثناء الإدراج

لمزيد من التفاصيل، اطّلِع على المستندات المرجعية لكل من @table و@col و@default.

# Movies
type Movie @table(name: "movie", key: "id") {
  id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int
  genre: String @col(dataType: "varchar(20)")
  rating: Int
  description: String
}

القيم الثابتة الرئيسية وقيم الخادم

قبل الاطّلاع على مزيد من المعلومات عن تطبيق مراجعة الأفلام، لنلقِ نظرة على Data Connect المقاييس الرئيسية وقيم الخادم.

المقاييس الرئيسية هي معرّفات مختصرة للكائنات يجمعها "Data Connect" تلقائيًا من الحقول الرئيسية في مخطّطاتك. تعتمد المقاييس الأساسية على الكفاءة، مما يتيح لك العثور في مكالمة واحدة على معلومات عن هوية بياناتك وبنيتها. وتُعدّ مفيدة بشكل خاص عندما تريد تنفيذ إجراءات تسلسلية على السجلّات الجديدة وتحتاج إلى معرّف فريد لنقله إلى العمليات القادمة، وكذلك عندما تريد الوصول إلى المفاتيح المتعلّقة بهدف تنفيذ عمليات إضافية أكثر تعقيدًا.

باستخدام قيم الخادم، يمكنك السماح للخادم بتعبئة الحقول في الجداول بشكل ديناميكي باستخدام قيم محفوظة أو قابلة للحساب بسهولة وفقًا لتعبيرات CEL المحدّدة من جهة الخادم في الوسيطة expr. على سبيل المثال، يمكنك تحديد حقل تم تطبيق طابع زمني عليه عند الوصول إلى الحقل باستخدام الوقت المخزّن في طلب العملية، updatedAt: Timestamp! @default(expr: "request.time").

جدول البيانات الوصفية للفيلم

لنتتبّع الآن مخرجي الأفلام، وننشئ أيضًا علاقة شخصية مع Movie.

أضِف حقل المرجع لتحديد العلاقات.

يمكنك استخدام التوجيه @ref لتخصيص قيد المفتاح الخارجي.

  • @ref(fields) لتحديد حقول المفتاح الخارجي
  • @ref(references) لتحديد الحقول المُشار إليها في الجدول المستهدَف. يكون هذا المرجع تلقائيًا هو المفتاح الأساسي، ولكن الحقول التي تحتوي على @unique متوافقة أيضًا.

لمزيد من التفاصيل، اطّلِع على المستندات المرجعية @ref.

# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
  # @unique ensures that each Movie only has one MovieMetadata.
  movie: Movie! @unique
  # Since it references to another table type, it adds a foreign key constraint.
  #  movie: Movie! @unique @ref(fields: "movieId", references: "id")
  #  movieId: UUID! <- implicitly added foreign key field
  director: String
}

Actor وMovieActor

بعد ذلك، تريد أن يشارك ممثلون في أفلامك، وبما أنّ لديك علاقة بين الأفلام والممثلين تتعلّق بالعديد من القيم لكلّ منهما، عليك إنشاء جدول ربط.

# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
  id: UUID! @default(expr: "uuidV4()")
  name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
  movie: Movie!
  # movieId: UUID! <- implicitly added foreign key field
  actor: Actor!
  # actorId: UUID! <- implicitly added foreign key field
  role: String! # "main" or "supporting"
  # optional other fields
}

المستخدم

أخيرًا، المستخدمون الذين يستخدمون تطبيقك.

# Users
# Suppose a user can leave reviews for movies
type User @table {
  id: String! @default(expr: "auth.uid")
  username: String! @col(dataType: "varchar(50)")
}

أنواع البيانات المتوافقة

يتيح Data Connect أنواع البيانات السلاسل التالية، مع عمليات الربط بأنواع PostgreSQL باستخدام @col(dataType:).

نوع Data Connect نوع مضمّن في GraphQL أو
Data Connect نوع مخصّص
نوع PostgreSQL التلقائي أنواع PostgreSQL المتوافقة
(الاسم المعرِّف بين قوسين)
سلسلة GraphQL النص نص
bit(n)، varbit(n)
char(n)، varchar(n)
Int GraphQL int Int2 (smallint, smallserial)،
int4 (integer, int, serial)
عائم GraphQL float8 float4 (عدد حقيقي)
float8 (عدد فاصل عائم للدقة المزدوجة)
numeric (عدد عشري)
منطقي GraphQL قيمة منطقية قيمة منطقية
معرِّف فريد عالمي (UUID) مخصص uuid uuid
Int64 مخصص bigint int8 (bigint, bigserial)
numeric (decimal)
التاريخ مخصص date التاريخ
الطابع الزمني مخصص timestamptz

timestamptz

ملاحظة: لا يتم تخزين معلومات المنطقة الزمنية المحلية.
تحوِّل PostgreSQL هذه الطوابع الزمنية وتخزّنها بالتوقيت العالمي المنسَّق.

المتّجه مخصص vector

المتّجه

اطّلِع على إجراء بحث عن التشابه في المتّجهات باستخدام Vertex AI.

  • يتم ربط List في GraphQL بصفيف أحادي الأبعاد.
    • على سبيل المثال، يتم ربط [Int] بـ int5[]، و[Any] بـ jsonb[].
    • لا تتيح Data Connect استخدام الصفائف المُدمَجة.

استخدام الحقول التي تم إنشاؤها لإنشاء طلبات البحث والطفرات

ستوسّع طلبات البحث وعمليات التحويل Data Connect مجموعة من الحقول Data Connect التي يتم إنشاؤها تلقائيًا استنادًا إلى الأنواع ونوع العلاقات في مخطّطك. يتم إنشاء هذه الحقول بواسطة أدوات محلية عند تعديل المخطّط.

  • كما لاحظت في دليل "البدء"، تستخدم وحدة تحكّم Firebase وأدوات التطوير المحلي هذه الحقول التي يتم إنشاؤها تلقائيًا لتزويدك بطلبات بحث وعمليات تحويل إدارية مخصّصة يمكنك استخدامها لإنشاء قاعدة بيانات للبيانات و verifying the contents of your tables.

  • في عملية التطوير، ستنفِّذ طلبات بحث قابلة للنشر و عمليات تحويل قابلة للنشر مُجمَّعة في أدوات الربط، استنادًا إلى الحقول المُنشأة تلقائيًا هذه.

تسمية الحقول التي يتم إنشاؤها تلقائيًا

Data Connect تستنتج أسماء مناسبة للحقول التي يتم إنشاؤها تلقائيًا استنادًا إلى تعريفات أنواع المخططات. على سبيل المثال، عند استخدام مصدر PostgreSQL ، إذا حدّدت جدولاً باسم Movie، سينشئ الخادم ما يلي:

  • حقول لقراءة البيانات في حالات استخدام الجداول الفردية بالأسماء الودية movie (مفرد، لاسترداد نتائج فردية من خلال تمرير الوسيطات مثل eq) و movies (جمع، لاسترداد قوائم النتائج من خلال تمرير الوسيطات مثل gt و العمليات مثل orderby). ينشئ Data Connect أيضًا حقولًا ل العمليات ذات الصلة بعدة جداول بأسماء صريحة مثل actors_on_movies أو actors_via_actormovie.
  • حقول لكتابة البيانات بأسماء مألوفة مثل movie_insert، movie_upsert...

تتيح لك لغة تعريف المخطّط أيضًا التحكّم بشكل صريح في كيفية إنشاء الأسماء للحقول باستخدام وسيطات التوجيه singular وplural.

توجيهات لطلبات البحث والطفرات

بالإضافة إلى التوجيهات التي تستخدمها في تحديد الأنواع والجداول، يقدّم Data Connect توجيهات @auth و@check و@redact و @transaction لتحسين سلوك طلبات البحث والطفرات.

التوجيه تنطبق على الوصف
@auth طلبات البحث والطفرات تحدِّد سياسة المصادقة لطلب بحث أو عملية تعديل. راجِع دليل التفويض والإقرار.
@check طلبات البحث عن بيانات التفويض للتحقّق من توفّر حقول محدّدة في نتائج طلب البحث يتم استخدام تعبير لغة التعبير الشائعة (CEL) لاختبار قيم الحقول. راجِع دليل التفويض والإقرار.
@redact طلبات البحث تم إخفاء جزء من ردّ العميل. راجِع دليل التفويض والإقرار.
@transaction الطفرات يفرض هذا الإجراء تنفيذ عملية التعديل دائمًا في معاملة قاعدة بيانات. راجِع أمثلة على تغيير بيانات تطبيق الأفلام.

طلبات البحث في قاعدة بيانات مراجعات الأفلام

يمكنك تحديد طلب بحث Data Connect باستخدام بيان نوع عملية طلب البحث واسم العملية، ووسائط عملية صفرية أو أكثر، وتوجيهات صفرية أو أكثر مع وسيطات.

في الخطوات السريعة، لم يستخدم مثال طلب البحث listEmails أي مَعلمات. بطبيعة الحال، في كثير من الحالات، ستكون البيانات التي يتم تمريرها إلى حقول طلبات البحث ديناميكية. يمكنك استخدام بنية $variableName للعمل مع المتغيّرات كأحد مكوّنات تعريف query.

وبالتالي، يحتوي الطلب التالي على ما يلي:

  • تعريف نوع query
  • اسم عملية (طلب بحث) ListMoviesByGenre
  • وسيطة عملية $genre لمتغيّر واحد
  • توجيه واحد، @auth
query ListMoviesByGenre($genre: String!) @auth(level: USER)

تتطلّب كل وسيطة طلب بحث بيان نوع أو عنصر مضمّن مثل String أو نوع مخصّص محدّد من المخطّط مثل Movie.

لنلقِ نظرة على توقيع الاستعلامات الأكثر تعقيدًا. ستنتهي من خلال تقديم تعبيرات علاقات فعّالة وموجزة يمكنك استخدامها لإنشاء طلبات البحث القابلة للنشر.

القيم السلاسل الرئيسية في طلبات البحث

أولاً، ملاحظة حول السلاسل الرئيسية.

تحدّد Data Connect نوعًا خاصًا للمتغيّرات السلعية الرئيسية، والتي يتم تحديدها باستخدام _Key. على سبيل المثال، نوع متغيّر قياسي للمفتاح لجدول Movie هو Movie_Key.

يمكنك استرداد القيم السلاسل الرئيسية كاستجابة تعرضها معظم حقول القراءة المنشأة تلقائيًا، أو بالطبع من طلبات البحث التي استرددت فيها جميع الحقول اللازمة لإنشاء المفتاح السلاسل.

تتيح طلبات البحث التلقائية المفردة، مثل movie في المثال الجاري، استخدام مَعلمة مفتاح تقبل مقياسًا لمفتاح.

يمكنك تمرير مقياس مفتاح كقيمة ثابتة. ولكن يمكنك تحديد متغيّرات لتمرير المقاييس الرئيسية كإدخال.

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}

ويمكن تقديم هذه البيانات في طلب JSON على النحو التالي (أو بتنسيقات رمز ترميز برمجي أخرى):

{
  # 
  "variables": {
    "myKey": {"foo": "some-string-value", "bar": 42}
  }
}

بفضل تحليل المقاييس التكيُّفية، يمكن أيضًا إنشاء Movie_Key باستخدام بنية العنصر التي قد تحتوي على متغيّرات. يكون ذلك مفيدًا في أغلب الأحيان عندما تريد تقسيم مكوّنات فردية إلى متغيّرات مختلفة لسبب ما.

استخدام الأسماء البديلة في طلبات البحث

Data Connect يتيح استخدام الأسماء البديلة في GraphQL في طلبات البحث. باستخدام الأسماء البديلة، يمكنك إعادة تسمية البيانات التي يتم عرضها في نتائج طلب البحث. يمكن أن يطبّق طلب بحث واحد Data Connect فلاتر متعددة أو عمليات بحث أخرى في طلب واحد فعّال إلى الخادم، ما يؤدي إلى إصدار عدة "طلبات بحث فرعية" في آنٍ واحد. لتجنُّب تعارض الأسماء في مجموعة البيانات المعروضة، يمكنك استخدام الأسماء البديلة للتمييز بين طلبات البحث الفرعية.

في ما يلي طلب بحث يستخدم فيه تعبير الاسم المعرِّف mostPopular.

query ReviewTopPopularity($genre: String) {
  mostPopular: review(first: {
    where: {genre: {eq: $genre}},
    orderBy: {popularity: DESC}
  }) {  }
}

طلبات بحث بسيطة باستخدام الفلاتر

يتم ربط طلبات بحث Data Connect بجميع فلاتر SQL الشائعة وعمليات ترتيب.

عاملا التشغيل where وorderBy (طلبات البحث المفردة والمتعدّدة)

تعرض جميع الصفوف المطابقة من الجدول (والعمليات المرتبطة المتداخلة). تعرِض مصفوفة فارغة إذا لم تتطابق أي سجلّات مع الفلتر.

query MovieByTopRating($genre: String) {
  mostPopular: movies(
     where: { genre: { eq: $genre } }, orderBy: { rating: DESC }
  ) {
    # graphql: list the fields from the results to return
    id
    title
    genre
    description
  }
}

query MoviesByReleaseYear($min: Int, $max: Int) {
  movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) {  }
}

عاملا التشغيل limit وoffset (طلبات البحث المفردة والمتعدّدة)

يمكنك تقسيم النتائج إلى صفحات. يتم قبول هذه الوسيطات ولكن لا يتم عرضها في النتائج.

query MoviesTop10 {
  movies(orderBy: [{ rating: DESC }], limit: 10) {
    # graphql: list the fields from the results to return
    title
  }
}

تشمل حقول الصفيف

يمكنك اختبار ما إذا كان حقل مصفوفة يتضمّن عنصرًا محدّدًا.

# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
  movies(where: { tags: { includes: $tag }}) {
    # graphql: list the fields from the results to return
    id
    title
  }
}

عمليات السلاسل والتعابير العادية

يمكن أن تستخدم طلبات البحث عمليات البحث عن السلاسل والمقارنة الشائعة، بما في ذلك التعبيرات العادية. يُرجى العلم أنّه من أجل تحسين الأداء، يمكنك تجميع عدة عمليات هنا وإزالة الغموض عنها باستخدام الأسماء البديلة.

query MoviesTitleSearch($prefix: String, $suffix: String, $contained: String, $regex: String) {
  prefixed: movies(where: {title: {startsWith: $prefix}}) {...}
  suffixed: movies(where: {title: {endsWith: $suffix}}) {...}
  contained: movies(where: {title: {contains: $contained}}) {...}
  matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}

or وand للفلاتر المركبة

استخدِم or وand للعمليات المنطقية الأكثر تعقيدًا.

query ListMoviesByGenreAndGenre($minRating: Int!, $genre: String) {
  movies(
    where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
  ) {
    # graphql: list the fields from the results to return
    title
  }
}

طلبات البحث المعقّدة

يمكن أن تصل طلبات بحث Data Connect إلى البيانات استنادًا إلى العلاقات بين الجداول. يمكنك استخدام علاقات العناصر (واحد لواحد) أو الصفيف (واحد لمتعدّد) المحدّدة في مخطّطك لإجراء طلبات بحث مُدمجة، أي جلب بيانات لنوع واحد بالإضافة إلى بيانات من نوع مُدمج أو ذي صلة.

تستخدِم طلبات البحث هذه بنية الجملة السحرية Data Connect _on_ و_via في حقول القراءة التي تم إنشاؤها.

ستُجري تعديلات على المخطّط من النسخة الأولية.

من عدّة مصادر إلى مصدر واحد

لنضيف مراجعات إلى تطبيقنا، مع جدول Review وتعديلات على User.

# User table is keyed by Firebase Auth UID.
type User @table {
  # `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
  id: String! @default(expr: "auth.uid")
  username: String! @col(dataType: "varchar(50)")
  # The `user: User!` field in the Review table generates the following one-to-many query field.
  #  reviews_on_user: [Review!]!
  # The `Review` join table the following many-to-many query field.
  #  movies_via_Review: [Movie!]!
}

# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User  <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
  user: User!
  # The user field adds the following foreign key field. Feel free to uncomment and customize it.
  #  userUid: String!
  movie: Movie!
  # The movie field adds the following foreign key field. Feel free to uncomment and customize it.
  #  movieId: UUID!
  rating: Int
  reviewText: String
  reviewDate: Date! @default(expr: "request.time")
}

طلب بحث من عدّة عناصر إلى عنصر واحد

لنلقِ الآن نظرة على طلب بحث يتضمّن أسماء بديلة لتوضيح بنية _via_.

query UserMoviePreferences($username: String!) @auth(level: USER) {
  users(where: { username: { eq: $username } }) {
    likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
      title
      genre
    }
    dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
      title
      genre
    }
  }
}

اجتماعات فردية

يمكنك الاطّلاع على النمط. في ما يلي، تم تعديل المخطّط للتوضيح.

# Movies
type Movie
  @table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
  id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int @col(name: "release_year")
  genre: String
  rating: Int @col(name: "rating")
  description: String @col(name: "description")
  tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
  @table(
    name: "MovieMetadata"
  ) {
  # @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
  director: String @col(name: "director")
}


extend type MovieMetadata {
  movieId: UUID! # matches primary key of referenced type
...
}

extend type Movie {
  movieMetadata: MovieMetadata # can only be non-nullable on ref side
  # conflict-free name, always generated
  movieMetadatas_on_movie: MovieMetadata
}

طلب محادثة بين شخصين

يمكنك إجراء طلب بحث باستخدام بنية _on_.

# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    movieMetadatas_on_movie {
      director
    }
  }
}

بين عدّة عناصر

تحتاج الأفلام إلى ممثلين، والممثلون يحتاجون إلى أفلام. بينهما علاقة "واحد إلى واحد" يمكنك وضع نموذج لها باستخدام جدول ربط MovieActors.

# MovieActors Join Table Definition
type MovieActors @table(
  key: ["movie", "actor"] # join key triggers many-to-many generation
) {
  movie: Movie!
  actor: Actor!
}

# generated extensions for the MovieActors join table
extend type MovieActors {
  movieId: UUID!
  actorId: UUID!
}

# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
  movieActors: [MovieActors!]! # standard many-to-one relation to join table
  actors: [Actor!]! # many-to-many via join table

  movieActors_on_actor: [MovieActors!]!
  # since MovieActors joins distinct types, type name alone is sufficiently precise
  actors_via_MovieActors: [Actor!]!
}

extend type Actor {
  movieActors: [MovieActors!]! # standard many-to-one relation to join table
  movies: [Movie!]! # many-to-many via join table

  movieActors_on_movie: [MovieActors!]!
  movies_via_MovieActors: [Movie!]!
}

طلب بحث بين مجموعات متعددة

لنلقِ نظرة على طلب بحث يتضمّن أسماء بديلة لتوضيح بنية _via_.

query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
  movie(id: $movieId) {
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      name
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      name
    }
  }
  actor(id: $actorId) {
    mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      title
    }
    supportingRoles: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      title
    }
  }
}

طلبات البحث التجميعية

ما هي القيم المجمّعة، ولماذا نستخدمها؟

تسمح لك الحقول المجمّعة بإجراء عمليات حسابية على قائمة بالنتائج. باستخدام الحقول المجمّعة، يمكنك تنفيذ إجراءات مثل:

  • العثور على متوسط نتيجة مراجعة
  • العثور على التكلفة الإجمالية للسلع في سلة تسوّق
  • العثور على المنتج الأعلى أو الأقل تقييمًا
  • احتساب عدد المنتجات في متجرك

يتمّ تنفيذ عمليات التجميع على الخادم، ما يقدّم عددًا من المزايا مقارنةً بحسابها من جهة العميل:

  • أداء أسرع للتطبيق (لأنّك تتجنب العمليات الحسابية من جهة العميل)
  • تقليل تكاليف نقل البيانات (لأنّك ترسل النتائج المجمّعة فقط بدلاً من جميع المدخلات)
  • أمان محسّن (لأنّه يمكنك منح العملاء إذن الوصول إلى البيانات المجمّعة بدلاً من مجموعة البيانات بالكامل)

مثال على مخطّط للقيم المجمّعة

في هذا القسم، سننتقل إلى مثال على مخطّط متجر، وهو مثال جيد لشرح كيفية استخدام القيم المجمّعة:

  type Product @table {
    name: String!
    manufacturer: String!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

التجميعات البسيطة

دالة _count لجميع الحقول

أبسط حقل مجمّع هو _count: فهو يعرض عدد الصفوف التي تتطابق مع طلب البحث. لكل حقل في نوعك، تنشئ دالة Data Connect حقولًا مجمّعة مقابلة استنادًا إلى نوع الحقل.

طلب البحث

query CountProducts {
  products {
    _count
  }
}

الردّ
one

على سبيل المثال، إذا كان لديك 5 منتجات في قاعدة بياناتك، ستكون النتيجة على النحو التالي:

{
  "products": [
    {
    "_count": 5
    }
  ]
}

تحتوي جميع الحقول على حقل <field>_count، الذي يحصّل عدد الصفوف التي تحتوي على قيمة غير صفرية في ذلك الحقل.

طلب البحث

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

الردّ
field_count

على سبيل المثال، إذا كان لديك 3 منتجات لها تاريخ انتهاء صلاحية، ستكون النتيجة على النحو التالي:

{
  "products": [
    {
    "expirationDate_count": 3
    }
  ]
}
دالة _min ودالة _max ودالة _sum ودالة _avg للحقول الرقمية

تحتوي الحقول الرقمية (int وfloat وint64) أيضًا على <field>_min و<field>_max <field>_sum و<field>_avg.

طلب البحث

query NumericAggregates {
  products {
  quantityInStock_max
  price_min
  price_avg
  quantityInStock_sum
  }
}

الردّ
_min _max _sum _avg

على سبيل المثال، إذا كانت لديك المنتجات التالية:

  • المنتج "أ": quantityInStock: 10، price: 2.99
  • المنتج (ب): quantityInStock: 5، price: 5.99
  • المنتج "ج": quantityInStock: 20، price: 1.99

ستكون النتيجة على النحو التالي:

{
  "products": [
    {
    "quantityInStock_max": 20,
    "price_min": 1.99,
    "price_avg": 3.6566666666666666,
    "quantityInStock_sum": 35
    }
  ]
}
_min و_max للتواريخ والطوابع الزمنية

يحتوي حقلَا التاريخ وطابع الوقت على <field>_min و<field>_max.

طلب البحث

query DateAndTimeAggregates {
  products {
  expirationDate_max
  expirationDate_min
  }
}

الردّ
_min _maxdatetime

على سبيل المثال، إذا كانت لديك تواريخ انتهاء الصلاحية التالية:

  • المنتج "أ": 2024-01-01
  • المنتج "ب": 2024-03-01
  • المنتج "ج": 2024-02-01

ستكون النتيجة على النحو التالي:

{
  "products": [
    {
    "expirationDate_max": "2024-03-01",
    "expirationDate_min": "2024-01-01"
    }
  ]
}

فريد

تتيح لك الوسيطة distinct الحصول على جميع القيم الفريدة لحقل (أو مجموعة حقول). على سبيل المثال:

طلب البحث

query ListDistinctManufacturers {
  products(distinct: true) {
    manufacturer
  }
}

الردّ
distinct

على سبيل المثال، إذا كان لديك المصنّعون التاليون:

  • المنتج "أ": manufacturer: "Acme"
  • المنتج "ب": manufacturer: "Beta"
  • المنتج "ج": manufacturer: "Acme"

ستكون النتيجة على النحو التالي:

{
  "products": [
    { "manufacturer": "Acme" },
    { "manufacturer": "Beta" }
  ]
}

يمكنك أيضًا استخدام الوسيطة distinct في الحقول المجمّعة بدلاً من تجميع القيم الفريدة. على سبيل المثال:

طلب البحث

query CountDistinctManufacturers {
  products {
    manufacturer_count(distinct: true)
  }
}

الردّ
distinctonaggregate

على سبيل المثال، إذا كان لديك المصنّعون التاليون:

  • المنتج "أ": manufacturer: "Acme"
  • المنتج "ب": manufacturer: "Beta"
  • المنتج "ج": manufacturer: "Acme"

ستكون النتيجة على النحو التالي:

{
  "products": [
    {
    "manufacturer_count": 2
    }
  ]
}

القيم المجمّعة

يمكنك إجراء تجميع مجمّع من خلال اختيار مزيج من الحقول المجمّعة والغير مجمّعة في نوع معيّن. يؤدي ذلك إلى تجميع جميع الصفوف المطابقة التي تحتوي على القيمة نفسها للحقول غير المجمّعة، وحساب الحقول المجمّعة لتلك المجموعة. على سبيل المثال:

طلب البحث

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

الردّ
groupedaggregates

على سبيل المثال، إذا كانت لديك المنتجات التالية:

  • المنتج "أ": manufacturer: "Acme"، price: 2.99
  • المنتج "ب": manufacturer: "Beta"، price: 5.99
  • المنتج "ج": manufacturer: "Acme"، price: 1.99

ستكون النتيجة على النحو التالي:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}
having وwhere مع الإجماليات المجمّعة

يمكنك أيضًا استخدام الوسيطات having وwhere لعرض المجموعات التي تفي فقط بمعايير مقدَّمة.

  • تتيح لك having فلترة المجموعات حسب حقول التجميع.
  • where يتيح لك فلترة الصفوف استنادًا إلى الحقول غير المجمّعة.

طلب البحث

query FilteredMostExpensiveProductByManufacturer {
  products(having: {price_max: {ge: 2.99}}) {
  manufacturer
  price_max
  }
}

الردّ
havingwhere

على سبيل المثال، إذا كانت لديك المنتجات التالية:

  • المنتج "أ": manufacturer: "Acme"، price: 2.99
  • المنتج "ب": manufacturer: "Beta"، price: 5.99
  • المنتج "ج": manufacturer: "Acme"، price: 1.99

ستكون النتيجة على النحو التالي:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}

القيم المجمّعة في جميع الجداول

يمكن استخدام الحقول المجمّعة مع حقول ملفّات تعريف الارتباط التي تم إنشاؤها لعلاقات من واحد إلى عدّة للإجابة عن أسئلة معقّدة حول بياناتك. في ما يلي مخطّط معدَّل يتضمّن جدولاً منفصلاً، Manufacturer، يمكننا استخدامه في الأمثلة:

  type Product @table {
    name: String!
    manufacturer: Manufacturer!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

  type Manufacturer @table {
    name: String!
    headquartersCountry: String!
  }

يمكننا الآن استخدام الحقول المجمّعة لإجراء إجراءات مثل معرفة عدد المنتجات التي يصنّعها أحد المصنّعين:

طلب البحث

query GetProductCount($id: UUID) {
  manufacturers {
    name
    products_on_manufacturer {
      _count
    }
  }
}

الردّ
aggregatesacrosstables

على سبيل المثال، إذا كان لديك المصنّعون التاليون:

  • الشركة المصنّعة "أ": name: "Acme"، products_on_manufacturer: 2
  • الشركة المصنّعة (ب): name: "Beta"، products_on_manufacturer: 1

ستكون النتيجة على النحو التالي:

{
  "manufacturers": [
    { "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
    { "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
  ]
}

تعديلات على قاعدة بيانات مراجعات الأفلام

كما ذكرنا، عند تحديد جدول في مخطّطك، سيُنشئ Data Connect طفرات ضمنية أساسية لكل جدول.

type Movie @table { ... }

extend type Mutation {
  # Insert a row into the movie table.
  movie_insert(...): Movie_Key!
  # Upsert a row into movie."
  movie_upsert(...): Movie_Key!
  # Update a row in Movie. Returns null if a row with the specified id/key does not exist
  movie_update(...): Movie_Key
  # Update rows based on a filter in Movie.
  movie_updateMany(...): Int!
  # Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
  movie_delete(...): Movie_Key
  # Delete rows based on a filter in Movie.
  movie_deleteMany(...): Int!
}

باستخدام هذه الوظائف، يمكنك تنفيذ حالات CRUD الأساسية الأكثر تعقيدًا. قل ذلك خمس مرات بسرعة.

توجيه @transaction

تفرض هذه التوجيهات تنفيذ عملية تعديل دائمًا في معاملة قاعدة بيانات.

يُضمن أنّ الطفرات التي تحتوي على @transaction ستنجح بالكامل أو لن تنجح بالكامل. إذا تعذّر إكمال أيّ من الحقول في المعاملة، يتمّ التراجع عن المعاملة بالكامل. من وجهة نظر العميل، يتصرف أيّ تعذّر كما لو أنّه تعذّر تنفيذ الطلب بالكامل بسبب خطأ في الطلب ولم يبدأ التنفيذ.

لا تنفِّذ عمليات التحويل التي لا تتضمّن @transaction كل حقل جذر تلو الآخر بالتسلسل. ويعرِض أي أخطاء كأخطاء جزئية في الحقول، ولكن ليس تأثيرات عمليات التنفيذ اللاحقة.

إنشاء

لنبدأ بإنشاء المحتوى الأساسي.

# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

# Create a movie with default values
mutation CreateMovie2 {
  movie_insert(data: {
    title: "Sherlock Holmes"
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
  })
}

أو عملية إضافة وتعديل.

# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
  movie_upsert(data: {
    title: $title
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
    genre: "Mystery/Thriller"
  })
}

إجراء التحديثات

في ما يلي آخر المعلومات. يأمل المنتجون والمخرجون بالتأكيد أن يكون متوسط التقييمات مرتفعًا.

يحتوي الحقل movie_update على مَعلمة id متوقّعة لتحديد سجلّ، وحقل data يمكنك استخدامه لضبط القيم في هذا التعديل.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id,
    data: {
      genre: $genre
      rating: $rating
      description: $description
    })
}

لإجراء تعديلات متعددة، استخدِم الحقل movie_updateMany.

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    data:
      {
        rating: $rating
      })
}

استخدام عمليات الزيادة والنقصان والتضمين والإضافة باستخدام _update

في حين أنّه يمكنك في طفرات _update و_updateMany ضبط القيم صراحةً في data:، من المنطقي في أغلب الأحيان تطبيق عامل تشغيل مثل الزيادة لتعديل القيم.

لتعديل مثال التعديل السابق، لنفترض أنّك تريد زيادة التقييم لفيلم معيّن. يمكنك استخدام بنية rating_update مع عامل التشغيل inc.

mutation UpdateMovie(
  $id: UUID!,
  $ratingIncrement: Int!
) {
  movie_update(id: $id, data: {
    rating_update: {
      inc: $ratingIncrement
    }
  })
}

تتيح Data Connect عوامل التشغيل التالية لتعديلات الحقول:

  • inc لزيادة أنواع البيانات Int وInt64 وFloat
  • dec لتقليل أنواع البيانات Int وInt64 وFloat
  • append لإلحاقها بأنواع القوائم، باستثناء قوائم "الأشكال الهندسية"
  • prepend لإضافة العنصر إلى أنواع القوائم، باستثناء قوائم المتجهات

تنفيذ عمليات الحذف

يمكنك بالطبع حذف بيانات الأفلام. سيريد القائمون على الحفاظ على الأفلام بالتأكيد الاحتفاظ بالأفلام المادية لأطول فترة ممكنة.

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

يمكنك هنا استخدام _deleteMany.

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

كتابة طفرات في العلاقات

راقِب كيفية استخدام عملية التحويل الضمنية _upsert في علاقة.

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

السماح لـ Data Connect بتقديم القيم باستخدام بنية field_expr

كما هو موضّح في مقالة المقاييس الأساسية لقيم الخادم، يمكنك تصميم مخطّطك بحيث يعبّئ الخادم قيمًا للحقول الشائعة، مثل id والتواريخ، استجابةً لطلبات العميل.

بالإضافة إلى ذلك، يمكنك الاستفادة من البيانات، مثل أرقام تعريف المستخدمين، المُرسَلة في عناصر Data Connect request من تطبيقات العملاء.

عند تنفيذ عمليات التحويل، استخدِم بنية field_expr لبدء التحديثات التي ينشئها الخادم أو الوصول إلى البيانات من الطلبات. على سبيل المثال، لتمرير تفويض uid المخزَّن في طلب إلى عملية _upsert، مرِّر "auth.uid" في الحقل userId_expr.

# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

أو في تطبيق قائمة مهام مألوف، عند إنشاء قائمة مهام جديدة، يمكنك إدخال id_expr لتوجيه الخادم إلى إنشاء معرّف UUID تلقائيًا للقائمة.

mutation CreateTodoListWithFirstItem(
  $listName: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
}

لمزيد من المعلومات، اطّلِع على مقاييس _Expr في مرجع المقاييس.

طلبات البحث عن بيانات التفويض

Data Connect يمكن تفويض عمليات التحويل من خلال الاستعلام أولاً عن قاعدة البيانات والتحقّق من نتائج الاستعلام باستخدام تعبيرات CEL. يكون ذلك مفعّلاً عند كتابة بيانات في جدول، مثلاً، وتحتاج إلى التحقّق من محتويات صف في جدول آخر.

تتيح هذه الميزة ما يلي:

  • توجيه @check الذي يتيح لك تقييم محتويات الحقول، واستنادًا إلى نتائج هذا التقييم:
    • المتابعة مع عمليات الإنشاء والتعديل والحذف المحدّدة من خلال عملية طفرية
    • المتابعة لعرض نتائج طلب بحث
    • استخدام القيم المعروضة لتنفيذ منطق مختلف في رمز العميل
  • توجيه @redact، الذي يتيح لك حذف نتائج طلبات البحث من نتائج بروتوكول الشبكات

هذه الميزات مفيدة في عمليات التفويض.

مخطّط SQL المكافئ

-- Movies Table
CREATE TABLE Movies (
    movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    release_year INT,
    genre VARCHAR(30),
    rating INT,
    description TEXT,
    tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
    movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
    director VARCHAR(255) NOT NULL,
    PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
    actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
    movie_id UUID REFERENCES Movies(movie_id),
    actor_id UUID REFERENCES Actors(actor_id),
    role VARCHAR(50) NOT NULL, # "main" or "supporting"
    PRIMARY KEY (movie_id, actor_id),
    FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
    FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
    user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_auth VARCHAR(255) NOT NULL
    username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
    review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
    user_id UUID REFERENCES Users(user_id),
    movie_id UUID REFERENCES Movies(movie_id),
    rating INT,
    review_text TEXT,
    review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (movie_id, user_id)
    FOREIGN KEY (user_id) REFERENCES Users(user_id),
    FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);

ما هي الخطوات التالية؟