การเขียนตัวแก้ไขที่กำหนดเองช่วยให้คุณขยาย Firebase SQL Connect เพื่อรองรับแหล่งข้อมูลอื่นๆ นอกเหนือจาก Cloud SQL ได้ จากนั้นคุณจะรวมแหล่งข้อมูลหลายแหล่ง (Cloud SQL และแหล่งข้อมูลที่ตัวแก้ไขที่กำหนดเองของคุณระบุ) ไว้ในคำค้นหาหรือการเปลี่ยนแปลงรายการเดียวได้
แนวคิดของ "แหล่งข้อมูล" นั้นยืดหยุ่น ซึ่งรวมถึง
- ฐานข้อมูลอื่นๆ นอกเหนือจาก Cloud SQL เช่น Cloud Firestore, MongoDB และ อื่นๆ
- บริการพื้นที่เก็บข้อมูล เช่น Cloud Storage, AWS S3 และอื่นๆ
- การผสานรวมที่อิงตาม API เช่น Stripe, SendGrid, Salesforce และอื่นๆ
- ตรรกะทางธุรกิจที่กำหนดเอง
เมื่อเขียนตัวแก้ไขที่กำหนดเองเพื่อรองรับแหล่งข้อมูลเพิ่มเติมแล้ว SQL Connect การค้นหาและการเปลี่ยนแปลงจะรวมแหล่งข้อมูลเหล่านั้นได้หลายวิธี ซึ่งจะให้ประโยชน์ต่างๆ เช่น
- เลเยอร์การให้สิทธิ์แบบรวมสำหรับแหล่งข้อมูล เช่น อนุญาต การเข้าถึงไฟล์ใน Cloud Storage โดยใช้ข้อมูลที่จัดเก็บไว้ใน Cloud SQL
- SDK ไคลเอ็นต์ที่ปลอดภัยสำหรับเว็บ, Android และ iOS
- การค้นหาที่แสดงข้อมูลจากหลายแหล่ง
- การเรียกใช้ฟังก์ชันที่จำกัดตามสถานะฐานข้อมูล
ข้อกำหนดเบื้องต้น
หากต้องการเขียนตัวแก้ไขที่กำหนดเอง คุณจะต้องมีสิ่งต่อไปนี้
- Firebase CLI v15.9.0 ขึ้นไป
- Firebase Functions SDK v7.1.0 ขึ้นไป
นอกจากนี้ คุณควรคุ้นเคยกับการเขียนฟังก์ชันโดยใช้ Cloud Functions for Firebase ซึ่งเป็นวิธีที่คุณจะใช้ตรรกะของตัวแก้ไขที่กำหนดเอง
ก่อนเริ่มต้น
คุณควรตั้งค่าโปรเจ็กต์ไว้แล้วเพื่อใช้ SQL Connect
คุณสามารถทำตามคู่มือเริ่มใช้งานฉบับย่อเพื่อตั้งค่าได้หากยังไม่ได้ตั้งค่า
เขียนตัวแก้ไขที่กำหนดเอง
การเขียนรีโซลเวอร์ที่กำหนดเองมี 3 ส่วน ได้แก่ ส่วนแรกคือการกำหนดสคีมาสำหรับรีโซลเวอร์ที่กำหนดเอง ส่วนที่สองคือการติดตั้งใช้งานรีโซลเวอร์โดยใช้ Cloud Functions และส่วนสุดท้ายคือการใช้ฟิลด์รีโซลเวอร์ที่กำหนดเองในคําค้นหาและการเปลี่ยนแปลง ซึ่งอาจใช้ร่วมกับ Cloud SQL หรือรีโซลเวอร์ที่กำหนดเองอื่นๆ
ทําตามขั้นตอนใน 2-3 ส่วนถัดไปเพื่อดูวิธีทํา สมมติว่าคุณมีข้อมูลโปรไฟล์สาธารณะของผู้ใช้ที่จัดเก็บไว้นอก Cloud SQL ตัวอย่างเหล่านี้ไม่ได้ระบุที่เก็บข้อมูลที่แน่นอน แต่ที่เก็บข้อมูลอาจเป็น Cloud Storage, อินสแตนซ์ MongoDB หรือที่เก็บข้อมูลอื่นๆ
ส่วนต่อไปนี้จะแสดงการติดตั้งใช้งานโครงร่างของรีโซลเวอร์ที่กำหนดเองซึ่งสามารถนำข้อมูลโปรไฟล์ภายนอกนั้นมายัง SQL Connect ได้
กำหนดสคีมาสำหรับตัวแก้ไขที่กำหนดเอง
ในไดเรกทอรีโปรเจ็กต์ Firebase ให้เรียกใช้คำสั่งต่อไปนี้
firebase init dataconnect:resolverFirebase CLI จะแจ้งให้คุณตั้งชื่อตัวแก้ไขที่กำหนดเอง และถามว่าจะสร้างการติดตั้งใช้งานตัวแก้ไขตัวอย่างใน TypeScript หรือ JavaScript หรือไม่ หากคุณทำตามคู่มือนี้ ให้ยอมรับชื่อเริ่มต้นและ สร้างตัวอย่าง TypeScript
จากนั้นเครื่องมือจะสร้างไฟล์
dataconnect/schema_resolver/schema.gqlที่ว่างเปล่าและเพิ่มการกำหนดค่ารีโซลเวอร์ใหม่ลงในไฟล์dataconnect.yamlอัปเดตไฟล์
schema.gqlนี้ด้วยสคีมา GraphQL ที่กำหนดการค้นหาและการเปลี่ยนแปลงที่ตัวแก้ไขที่กำหนดเองจะให้ เช่น นี่คือสคีมา สำหรับรีโซลเวอร์ที่กำหนดเองซึ่งสามารถดึงและอัปเดตโปรไฟล์สาธารณะของผู้ใช้ ที่จัดเก็บไว้ในพื้นที่เก็บข้อมูลอื่นที่ไม่ใช่ Cloud SQL# dataconnect/schema_resolver/schema.gql type PublicProfile { name: String! photoUrl: String! bioLine: String! } type Query { # This field will be backed by your Cloud Function. publicProfile(userId: String!): PublicProfile } type Mutation { # This field will be backed by your Cloud Function. updatePublicProfile( userId: String!, name: String, photoUrl: String, bioLine: String ): PublicProfile }
ใช้ตรรกะของตัวแก้ไขที่กำหนดเอง
จากนั้นใช้ตัวแก้ไขโดยใช้ Cloud Functions เบื้องหลังการทำงาน คุณจะต้อง สร้างเซิร์ฟเวอร์ GraphQL แต่ Cloud Functions มีเมธอดตัวช่วย onGraphRequest ที่จัดการรายละเอียดการดำเนินการดังกล่าว คุณจึงต้อง เขียนตรรกะของตัวแก้ไขที่เข้าถึงแหล่งข้อมูลเท่านั้น
เปิดไฟล์
functions/src/index.tsเมื่อคุณเรียกใช้
firebase init dataconnect:resolverข้างต้น คำสั่งจะสร้างไดเรกทอรีซอร์สโค้ดของ Cloud Functions นี้และเริ่มต้นด้วยโค้ดตัวอย่างในindex.tsเพิ่มคำจำกัดความต่อไปนี้
import { FirebaseContext, onGraphRequest, } from "firebase-functions/dataconnect/graphql"; const opts = { // Points to the schema you defined earlier, relative to the root of your // Firebase project. schemaFilePath: "dataconnect/schema_resolver/schema.gql", resolvers: { query: { // This resolver function populates the data for the "publicProfile" field // defined in your GraphQL schema located at schemaFilePath. publicProfile( _parent: unknown, args: Record<string, unknown>, _contextValue: FirebaseContext, _info: unknown ) { const userId = args.userId; // Here you would use the user ID to retrieve the user profile from your data // store. In this example, we just return a hard-coded value. return { name: "Ulysses von Userberg", photoUrl: "https://example.com/profiles/12345/photo.jpg", bioLine: "Just a guy on a mountain. Ski fanatic.", }; }, }, mutation: { // This resolver function updates data for the "updatePublicProfile" field // defined in your GraphQL schema located at schemaFilePath. updatePublicProfile( _parent: unknown, args: Record<string, unknown>, _contextValue: FirebaseContext, _info: unknown ) { const { userId, name, photoUrl, bioLine } = args; // Here you would update in your datastore the user's profile using the // arguments that were passed. In this example, we just return the profile // as though the operation had been successful. return { name, photoUrl, bioLine }; }, }, }, }; export const resolver = onGraphRequest(opts);
การติดตั้งใช้งานโครงร่างเหล่านี้แสดงรูปร่างทั่วไปที่ฟังก์ชันตัวแก้ไข ต้องใช้ หากต้องการสร้างตัวแก้ไขที่กำหนดเองซึ่งทำงานได้อย่างเต็มรูปแบบ คุณจะต้องกรอกข้อมูลในส่วนที่แสดงความคิดเห็นด้วยโค้ดที่อ่านและเขียนไปยังแหล่งข้อมูล
ใช้ตัวแก้ไขที่กำหนดเองในการค้นหาและการเปลี่ยนแปลง
เมื่อกำหนดสคีมาของตัวแก้ไขที่กำหนดเองและ ใช้ตรรกะที่สนับสนุนแล้ว คุณจะใช้ตัวแก้ไขที่กำหนดเองใน SQL Connect การค้นหาและการเปลี่ยนแปลงได้ ต่อมาคุณจะใช้การดำเนินการเหล่านี้ เพื่อสร้าง SDK ไคลเอ็นต์ที่กำหนดเองโดยอัตโนมัติ ซึ่งคุณสามารถใช้เพื่อเข้าถึงข้อมูลทั้งหมด ได้ ไม่ว่าจะได้รับการสนับสนุนจาก Cloud SQL, ตัวแก้ไขที่กำหนดเอง หรือทั้ง 2 อย่างรวมกัน
ใน
dataconnect/example/queries.gqlให้เพิ่มคำจำกัดความต่อไปนี้query GetPublicProfile($id: String!) @auth(level: PUBLIC, insecureReason: "Anyone can see a public profile.") { publicProfile(userId: $id) { name photoUrl bioLine } }การค้นหานี้จะดึงโปรไฟล์สาธารณะของผู้ใช้โดยใช้ตัวแก้ไขที่กำหนดเอง
ใน
dataconnect/example/mutations.gqlให้เพิ่มคำจำกัดความต่อไปนี้mutation SetPublicProfile( $id: String!, $name: String, $photoUrl: String, $bioLine: String ) @auth(expr: "vars.id == auth.uid") { updatePublicProfile(userId: $id, name: $name, photoUrl: $photoUrl, bioLine: $bioLine) { name photoUrl bioLine } }การเปลี่ยนแปลงนี้จะเขียนชุดข้อมูลโปรไฟล์ใหม่ลงในพื้นที่เก็บข้อมูล โดยใช้ตัวแก้ไขที่กำหนดเองอีกครั้ง โปรดทราบว่าสคีมาใช้คำสั่ง SQL Connect's
@authเพื่อให้มั่นใจว่าผู้ใช้จะอัปเดตได้เฉพาะโปรไฟล์ของตนเอง เนื่องจากคุณเข้าถึง Datastore ผ่าน SQL Connect คุณจึงใช้ประโยชน์จากฟีเจอร์ของ SQL Connect เช่น ฟีเจอร์นี้ได้โดยอัตโนมัติ
ในตัวอย่างข้างต้น คุณได้กำหนดSQL Connectการดำเนินการที่เข้าถึงข้อมูลจากพื้นที่เก็บข้อมูลโดยใช้รีโซลเวอร์ที่กำหนดเอง อย่างไรก็ตาม คุณไม่ได้ถูกจำกัด ในการดำเนินการให้เข้าถึงข้อมูลจาก Cloud SQL หรือจากแหล่งข้อมูลที่กำหนดเองเพียงแหล่งเดียว ดูตัวอย่างการใช้งานที่ซับซ้อนยิ่งขึ้นซึ่งรวมข้อมูลจากหลายแหล่งได้ในส่วนตัวอย่าง
ก่อนอื่น ให้ไปที่ส่วนถัดไปเพื่อดูตัวแก้ไขแบบกำหนดเองของคุณใน การทำงาน
ทำให้ใช้งานได้ตัวแก้ไขและตัวดำเนินการที่กำหนดเอง
เช่นเดียวกับการเปลี่ยนแปลงSQL Connectสคีมา คุณต้องทำให้ใช้งานได้เพื่อให้มีผล ก่อนดำเนินการดังกล่าว ให้ทำให้ใช้งานได้ตรรกะของรีโซลเวอร์ที่กำหนดเองที่คุณใช้ Cloud Functions สร้างขึ้นก่อน
firebase deploy --only functionsตอนนี้คุณสามารถติดตั้งใช้งานสคีมาและการดำเนินการที่อัปเดตแล้วได้โดยทำดังนี้
firebase deploy --only dataconnectหลังจากทำการเปลี่ยนแปลงสคีมา SQL Connect แล้ว คุณต้อง สร้าง SDK ไคลเอ็นต์ใหม่ด้วย
firebase dataconnect:sdk:generateตัวอย่าง
ตัวอย่างเหล่านี้แสดงวิธีใช้กรณีการใช้งานขั้นสูงบางอย่าง และวิธี หลีกเลี่ยงข้อผิดพลาดที่พบบ่อย
การให้สิทธิ์เข้าถึงรีโซลเวอร์ที่กำหนดเองโดยใช้ข้อมูลจาก Cloud SQL
ข้อดีอย่างหนึ่งของการผสานรวมแหล่งข้อมูลเข้ากับ SQL Connect โดยใช้ตัวแก้ไขที่กำหนดเองคือคุณสามารถเขียนการดำเนินการที่รวมแหล่งข้อมูล ได้
ในตัวอย่างนี้ สมมติว่าคุณกำลังสร้างแอปโซเชียลมีเดีย และมี การใช้การเปลี่ยนแปลงเป็นรีโซลเวอร์ที่กำหนดเอง ซึ่งจะส่งอีเมลการกระตุ้นเตือนไปยังเพื่อนของ ผู้ใช้หากเพื่อนไม่ได้มีส่วนร่วมกับผู้ใช้มาระยะหนึ่งแล้ว
หากต้องการใช้ฟีเจอร์การกระตุ้นเตือน ให้สร้างรีโซลเวอร์ที่กำหนดเองด้วยสคีมาดังต่อไปนี้
# A GraphQL server must define a root query type per the spec.
type Query {
unused: String
}
type Mutation {
sendEmail(id: String!, content: String): Boolean
}
คำจำกัดความนี้ได้รับการสนับสนุนโดย Cloud Function เช่น ฟังก์ชันต่อไปนี้
import {
FirebaseContext,
onGraphRequest,
} from "firebase-functions/dataconnect/graphql";
const opts = {
schemaFilePath: "dataconnect/schema_resolver/schema.gql",
resolvers: {
mutation: {
sendEmail(
_parent: unknown,
args: Record<string, unknown>,
_contextValue: FirebaseContext,
_info: unknown
) {
const { id, content } = args;
// Look up the friend's email address and call the cloud service of your
// choice to send the friend an email with the given content.
return true;
},
},
},
};
export const resolver = onGraphRequest(opts);
เนื่องจากการส่งอีเมลมีค่าใช้จ่ายทั้งสำหรับคุณและอาจเป็นช่องทางในการละเมิด คุณจึงควรตรวจสอบว่าผู้รับที่ต้องการอยู่ในรายชื่อเพื่อนของผู้ใช้แล้วก่อนที่จะใช้ sendEmail ตัวแก้ไขที่กำหนดเอง
สมมติว่าในแอปของคุณ ระบบจัดเก็บข้อมูลรายชื่อเพื่อนไว้ใน Cloud SQL
type User @table {
id: String! @default(expr: "auth.uid")
acceptNudges: Boolean! @default(value: false)
}
type UserFriend @table(key: ["user", "friend"]) {
user: User!
friend: User!
}
คุณสามารถเขียนการเปลี่ยนแปลงที่ค้นหา Cloud SQL ก่อนเพื่อให้แน่ใจว่าผู้ส่งอยู่ในรายชื่อเพื่อนของผู้รับก่อนที่จะใช้ตัวแก้ไขที่กำหนดเองเพื่อส่งอีเมลได้ดังนี้
# Send a "nudge" to a friend as a reminder. This will only let the user send a
# nudge if $friendId is in the user's friends list.
mutation SendNudge($friendId: String!) @auth(level: USER_EMAIL_VERIFIED) {
# Step 1: Query and check
query @redact {
userFriend(
key: {userId_expr: "auth.uid", friendId: $friendId}
# This checks that $friendId is in the user's friends list.
) @check(expr: "this != null", message: "You must be friends to nudge") {
friend {
# This checks that the friend is accepting nudges.
acceptNudges @check(expr: "this == true", message: "Not accepting nudges")
}
}
}
# Step 2: Act
sendEmail(id: $friendId, content: "You've been nudged!")
}
นอกจากนี้ ตัวอย่างนี้ยังแสดงให้เห็นว่าแหล่งข้อมูลในบริบทของ ตัวแก้ไขที่กำหนดเองอาจมีทรัพยากรอื่นๆ นอกเหนือจากฐานข้อมูลและระบบที่คล้ายกัน ในตัวอย่างนี้ แหล่งข้อมูลคือบริการส่งอีเมลในระบบคลาวด์
การตรวจสอบการดำเนินการตามลำดับโดยใช้การเปลี่ยนแปลง
เมื่อรวมแหล่งข้อมูล คุณมักจะต้องตรวจสอบว่าคำขอไปยังแหล่งข้อมูลหนึ่ง เสร็จสมบูรณ์ก่อนที่จะส่งคำขอไปยังแหล่งข้อมูลอื่น เช่น สมมติว่าคุณมีคำค้นหาที่ถอดเสียงวิดีโอออนดีมานด์แบบไดนามิก โดยใช้ AI API การเรียก API เช่นนี้อาจมีค่าใช้จ่ายสูง ดังนั้นคุณจึงควรจำกัดการเรียกไว้เบื้องหลังเกณฑ์บางอย่าง เช่น ผู้ใช้เป็นเจ้าของวิดีโอ หรือผู้ใช้ได้ซื้อเครดิตพรีเมียมบางประเภทในแอปของคุณ
ความพยายามครั้งแรกในการบรรลุเป้าหมายนี้อาจมีลักษณะดังนี้
# This won't work as expected.
query BrokenTranscribeVideo($videoId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
# Step 1: Check quota using SQL.
# Verify the user owns the video and has "pro" status or credits.
checkQuota: query @redact {
video(id: $videoId)
{
user @check(expr: "this.id == auth.uid && this.hasCredits == true", message: "Unauthorized access") {
id
hasCredits
}
}
}
# Step 2: Trigger expensive compute
# Only triggers if Step 1 succeeds? No! This won't work because query field
# execution order is not guaranteed.
triggerTranscription: query {
# For example, might call Vertex AI or Transcoder API.
startVideoTranscription(videoId: $videoId)
}
}
วิธีนี้ใช้ไม่ได้เนื่องจากระบบไม่รับประกันลำดับการดำเนินการของฟิลด์การค้นหา เซิร์ฟเวอร์ GraphQL คาดหวังว่าจะสามารถแก้ไขฟิลด์ได้ในลำดับใดก็ได้เพื่อเพิ่มการทำงานพร้อมกันให้ได้สูงสุด ในทางกลับกัน ฟิลด์ของการเปลี่ยนแปลง จะได้รับการแก้ไขตามลำดับเสมอ เนื่องจากเซิร์ฟเวอร์ GraphQL คาดหวังว่าฟิลด์บางรายการ ของการเปลี่ยนแปลงอาจมีผลข้างเคียงเมื่อได้รับการแก้ไข
แม้ว่าขั้นตอนแรกของการดำเนินการตัวอย่างจะไม่มีผลข้างเคียง คุณก็สามารถกำหนดการดำเนินการเป็นการเปลี่ยนแปลงเพื่อใช้ประโยชน์จาก ข้อเท็จจริงที่ว่าฟิลด์การเปลี่ยนแปลงจะได้รับการแก้ไขตามลำดับ
# By using a mutation, we guarantee the SQL check happens FIRST.
mutation TranscribeVideo($videoId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
# Step 1: Check quota using SQL.
# Verify the user owns the video and has "pro" status or credits.
checkQuota: query @redact {
video(id: $videoId)
{
user @check(expr: "this.id == auth.uid && this.hasCredits == true", message: "Unauthorized access") {
id
hasCredits
}
}
}
# Step 2: Trigger expensive compute
# This Cloud Function will ONLY trigger if Step 1 succeeds.
triggerTranscription: query {
# For example, might call Vertex AI or Transcoder API.
startVideoTranscription(videoId: $videoId)
}
}
ข้อจำกัด
ฟีเจอร์ตัวแก้ไขที่กำหนดเองเปิดตัวเป็นเวอร์ชันตัวอย่างแบบสาธารณะทดลอง โปรดทราบ ข้อจำกัดปัจจุบันต่อไปนี้
ไม่มีนิพจน์ CEL ในอาร์กิวเมนต์ของตัวแก้ไขที่กำหนดเอง
คุณไม่สามารถใช้นิพจน์ CEL แบบไดนามิกในอาร์กิวเมนต์ไปยังตัวแก้ไขที่กำหนดเองได้ ตัวอย่างเช่น คุณจะทำสิ่งต่อไปนี้ไม่ได้
mutation UpdateMyProfile($newName: String!) @auth(level: USER) {
updateMongoDocument(
collection: "profiles"
# This isn't supported:
id_expr: "auth.uid"
update: { name: $newName }
)
}
แต่ให้ส่งตัวแปรมาตรฐาน (เช่น $authUid) และตรวจสอบตัวแปรเหล่านั้นที่ระดับการดำเนินการโดยใช้คำสั่ง @auth(expr: ...) ที่ประเมินอย่างปลอดภัยแทน
mutation UpdateMyProfile(
$newName: String!, $authUid: String!
) @auth(expr: "vars.authUid == auth.uid") {
updateMongoDocument(
collection: "profiles"
id: $authUid
update: { name: $newName }
)
}
อีกวิธีแก้ปัญหาคือการย้ายตรรกะทั้งหมดไปยังตัวแก้ไขที่กำหนดเองและดำเนินการกับข้อมูลทั้งหมดจาก Cloud Functions
เช่น ลองดูตัวอย่างนี้ ซึ่งปัจจุบันยังใช้ไม่ได้
mutation BrokenForwardToEmail($chatMessageId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
query {
chatMessage(id: $chatMessageId) {
content
}
}
sendEmail(
title: "Forwarded Chat Message"
to_expr: "auth.token.email" # Not supported.
content_expr: "response.query.chatMessage.content" # Not supported.
)
}
แต่ให้ย้ายทั้งการค้นหา Cloud SQL และการเรียกใช้บริการอีเมลไปไว้ใน ฟิลด์การเปลี่ยนแปลงเดียวซึ่งมีฟังก์ชันเป็นข้อมูลสำรองแทน
mutation ForwardToEmail($chatMessageId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
forwardChatToEmail(
chatMessageId: $chatMessageId
)
}
สร้าง Admin SDK สำหรับฐานข้อมูลและใช้ในฟังก์ชันเพื่อทำการค้นหา Cloud SQL ดังนี้
const opts = {
schemaFilePath: "dataconnect/schema_resolver/schema.gql",
resolvers: {
query: {
async forwardToEmail(
_parent: unknown,
args: Record<string, unknown>,
_contextValue: FirebaseContext,
_info: unknown
) {
const chatMessageId = args.chatMessageId as string;
let decodedToken;
try {
decodedToken = await getAuth().verifyIdToken(_contextValue.auth.token ?? "");
} catch (error) {
return false;
}
const email = decodedToken.email;
if (!email) {
return false;
}
const response = await getChatMessage({chatMessageId});
const messageContent = response.data.chatMessage?.content;
// Here you call the cloud service of your choice to send the email with
// the message content.
return true;
}
},
},
};
export const resolver = onGraphRequest(opts);
ไม่มีประเภทออบเจ็กต์อินพุตในพารามิเตอร์ของตัวแก้ไขที่กำหนดเอง
ตัวแก้ไขที่กำหนดเองไม่ยอมรับประเภทอินพุต GraphQL ที่ซับซ้อน พารามิเตอร์ต้องเป็น
ประเภทสเกลาร์พื้นฐาน (String, Int, Date, Any ฯลฯ) และ Enum
input PublicProfileInput {
name: String!
photoUrl: String!
bioLine: String!
}
type Mutation {
# Not supported:
updatePublicProfile(userId: String!, profile: PublicProfileInput): PublicProfile
# OK:
updatePublicProfile(userId: String!, name: String, photoUrl: String, bioLine: String): PublicProfile
}
ตัวแก้ไขที่กำหนดเองต้องไม่มาก่อนการดำเนินการ SQL
ในการเปลี่ยนแปลง การวางตัวแก้ไขที่กำหนดเองก่อนการดำเนินการ SQL มาตรฐาน จะทำให้เกิดข้อผิดพลาด การดำเนินการที่อิงตาม SQL ทั้งหมดต้องปรากฏก่อนการเรียกใช้ตัวแก้ไขที่กำหนดเอง
ไม่มีธุรกรรม (@transaction)
ไม่สามารถรวมตัวแก้ไขที่กำหนดเองไว้ในบล็อก @transaction ที่มีการดำเนินการ SQL มาตรฐานได้ หาก Cloud Function ที่สนับสนุนตัวแก้ไขล้มเหลวหลังจากที่การแทรก SQL สำเร็จ ฐานข้อมูลจะไม่ย้อนกลับโดยอัตโนมัติ
หากต้องการให้การทำธุรกรรมระหว่าง SQL กับแหล่งข้อมูลอื่นปลอดภัย ให้ย้ายตรรกะการดำเนินการ SQL ไปไว้ใน Cloud Function และจัดการการตรวจสอบและการย้อนกลับโดยใช้ Admin SDK หรือการเชื่อมต่อ SQL โดยตรง