סכמות מרוחקות (Remote Schemas)¶
מקור של סכמה מרוחקת מחבר API חיצוני, בין אם GraphQL (כולל GitHub), gRPC או REST (OpenAPI), למודל של Provisa. הוספת המקור לא רושמת שום טבלה. המקור מציע טבלאות, ו-Steward רושם כל טבלה רצויה בבורר Register Table; הרישום הזה הוא שלב האוצרות. טבלה רשומה היא טבלת Provisa מלאה, ולכן כל כלל ממשל, כל ממשק שאילתה וכל שכבת אבטחה חלים עליה אוטומטית. השירות המרוחק לעולם לא רואה את כללי הממשל של Provisa.
שלושה סוגי מקור¶
סכמה מרוחקת של GraphQL¶
איך להוסיף את המקור. שלחו POST ל-/admin/sources/graphql-remote עם כתובת ה-endpoint, namespace, ואימות אופציונלי. Provisa מפעילה שאילתת אינטרוספקציה סטנדרטית __schema מול ה-endpoint המרוחק כדי לאשר את ה-endpoint ואת האישורים.
הוספת המקור אינה רושמת טבלה ואינה רושמת command. כל סוג מקור מרוחק עונה להוספה ולרענון באותם מונים: tables (טבלאות שנרשמו או שהובאו להתאמה; 0 בהוספה), available_tables (טבלאות מוצעות), mutations (תמיד 0) ו-available_mutations (commands מוצעים).
רישום טבלאות. פתחו Tables, אחר כך Register Table, בחרו את המקור ואת הסכמה graphql, ובחרו את הטבלאות והעמודות הרצויות. דרך ה-API של GraphQL לניהול: availableTables(sourceId, schemaName) מפרט את הטבלאות המוצעות, availableColumns מפרט את עמודות הטבלה, ו-registerTable(input: TableInput) רושם טבלה עם העמודות שנבחרו. טבלה רשומה כפופה לממשל.
האופן שבו נקראת טבלה רשומה (שדה שורש, נתיב שורות, ארגומנטים נדרשים, ארגומנטי עימוד) נשמר ב-sources.mapping["tables"], כך שתהליך שהופעל מחדש קורא אותה בלי לשאול את השירות המרוחק על הסכמה שלו.
{
"source_id": "petstore-gql",
"url": "https://api.example.com/graphql",
"namespace": "petstore",
"domain_id": "veterinary",
"auth": { "type": "bearer", "token": "..." },
"cache_ttl": 300,
"field_overrides": { "createPet": "query" },
"relationships": [
{ "source_table": "petstore__pets", "source_column": "owner_id",
"target_table": "owners__users", "target_column": "id" }
]
}
אפשרויות אימות: none, bearer (כותרת Authorization), basic (Base64 username:password).
דריסות שדה (Field overrides). field_overrides היא מפת {fieldName: "query" | "mutation"} המוחלת לאחר האינטרוספקציה. יש לה עדיפות על פני סיווג מבני. רק שדות מסוג-query ניתנים לסיווג-מחדש כ-mutations; לשדות מסוג-mutation אין נתיב דריסה ב-GraphQL.
קשרים בזמן רישום. relationships מצהיר נתיבי join של FK/PK בין טבלאות בזמן הרישום. אלה נשמרים כקשרים מוצהרים-ידנית (ללא דגל remote_managed). ברענון, קשרים שאותרו-אוטומטית (אלה עם remote_managed: True) רצים מחדש ועשויים להשתנות; קשרים מוצהרים-ידנית אינם נגעים.
מה המקור מציע. כל שדה מסוג Query המרוחק שמחזיר אובייקט או רשימת אובייקטים מוצע כטבלה, וכך גם כל חיבור Relay תחת שדה של אובייקט יחיד (ראו להלן). רישום טבלה מוצעת הופך אותה לטבלה. כל שדה מסוג Mutation המרוחק הוא פקודה מוצעת, הנספרת ב-available_mutations; הוספת המקור אינה רושמת אף אחת. רשמו את הרצויות כפקודות; ראו פעולת כתיבה של מקור מרוחק.
שיוך שמות טבלה. טבלאות נקראות {namespace}__{field_name}. עם namespace petstore ושדה שאילתה pets: שם הטבלה הוא petstore__pets.
חיבורי Relay. ממשקי API רבים מחזירים רשימות כחיבורי Relay: אובייקט עם nodes (או edges { node }) לצד pageInfo. Provisa ממפה חיבור לטבלה של הצמתים שלו וקוראת אותו עמוד אחר עמוד.
- שדה שורש המחזיר חיבור (
securityAdvisories) הופך לטבלה אחת של הצמתים שלו. - חיבור על האובייקט היחיד שמחזיר שדה שורש הופך לטבלה משלו. הטבלה לוקחת את הארגומנטים הנדרשים של שדה השורש. עם
repository(owner, name)וחיבורissuesעלRepository, הטבלה היאrepositoryIssues, שם ה-SQL הואgh__repository_issuesתחת ה-namespacegh. סננו אותה דרך העמודות_nf_ownerו-_nf_name:WHERE _nf_owner = 'acme' AND _nf_name = 'widgets'. - חיבור אינו אף פעם עמודה. אחרת שורה הייתה נושאת קריאה שהשירות המרוחק מחשב לכל שורה, עבור כל חיבור שיש לטיפוס שלה.
- חיבור הוא טבלה רק אם השדה שלו מקבל
firstו-after, כך שאפשר לקרוא אותו עמוד אחר עמוד. חיבור שדורש ארגומנט משלו אינו טבלה. וגם לא חיבור של union, או כל חיבור תחת שדה שורש המחזיר רשימה.
מיפוי טיפוסים. שדות סקלריים ממופים ישירות לטיפוסי Provisa. שדות OBJECT מתפצלים לשני מקרים תלוי אם הטיפוס היעד ממושל (ראו "טבלאות ממושלות" למטה).
| טיפוס GraphQL | טיפוס Provisa |
|---|---|
String |
text |
ID |
text |
Int |
integer |
Float |
numeric |
Boolean |
boolean |
OBJECT (טיפוס inline לא-ממושל, למשל ContactInfo) |
עמודת blob מסוג jsonb |
| OBJECT (טיפוס יעד-ממושל) | מוחרג לחלוטין מ-SDL ומאיסוף |
| כל ENUM | jsonb |
| סקלר מותאם-אישית | text (ברירת מחדל) |
טבלאות ממושלות. טיפוס GQL הוא ממושל כאשר הוא מופיע כשדה שורש Query בסכמה המרוחקת. _collect_queryable_types אוסף אלה במהלך הרישום, מעדיף שדות ללא-ארגומנט-נדרש כך שניתן לאסוף אותם בכמות כיעדי join.
כאשר עמודה מסוג-OBJECT בטבלה ממושלת מצביעה לטיפוס ממושל אחר, עמודה זו כפופה לשלושה כללים בו-זמנית:
- מוחרגת מהאיסוף של GQL — השדה אינו מבוקש בעת איסוף שורות הטבלה ההורה.
- מוחרגת מה-SDL — השדה אינו מופיע על הטיפוס ההורה בסכמה הנוצרת.
- נגישה רק דרך קשר מוצהר — סטיוארד חייב לרשום JOIN בין שתי הטבלאות הממושלות הממומשות. בלעדיו, השדה פשוט נעדר; אין fallback ל-blob.
טיפוסי OBJECT שאינם ניתנים-להשגה כשדות שורש Query (טיפוסים inline כמו ContactInfo או Address) עוקבים אחר כללים שונים: הם נאספים כעמודות blob מסוג jsonb ומופיעים ב-SDL כשדות אובייקט-מקונן. שדות-משנה נגישים דרך חילוץ ->> ב-SQL.
שדות הדורשים ארגומנט אינם עמודות. שדה עם ארגומנט נדרש לא ניתן לבחירה חשופה, ולכן הוא נשאר מחוץ לעמודות הטבלה ומחוץ לבחירות מקוננות.
ארגומנטים נדרשים. כאשר לשדה שאילתה-שורש יש ארגומנטים לא-null ללא ערך ברירת-מחדל, אלה הופכים לעמודות native_filter_type: query_param על הטבלה (מוקדמות ב-_nf_ בזמן הזרקה). ה-executor מעביר אותן כמשתני GraphQL.
קשרים שמזוהים אוטומטית. Provisa סורק את העמודות מסוג OBJECT בכל טבלה רשומה. כשסוג ה-GQL שעמודה מפנה אליו הוא גם טבלה רשומה באותו מקור, והעמודה שעליה נשען הקשר היא אחת מהעמודות הרשומות, הקשר נשמר. טבלה שעדיין לא נרשמה לא מקבלת קשר. קשרי many-to-one מסיקים את עמודות המקור והיעד שלהם ממוסכמות שמות (breedName בסוג המקור → name בסוג היעד Breed). שדות one-to-many (LIST) יוצרים קשרים עם הפניות עמודה ריקות, כי ה-FK נמצא בצד היעד.
Mutations. שדה mutation נרשם כפקודה מסוג source_operation, אחד אחד. הארגומנטים שלה מוגדרים כל אחד כ-json ומועברים לשירות המרוחק כמשתנים מוגדרי סוג; התשובה היא ה-JSON שהשירות המרוחק מחזיר, ללא return_schema. ראו פעולת כתיבה של מקור מרוחק.
רענון (Refresh). שלחו POST ל-/admin/sources/graphql-remote/{id}/refresh. מבצע אינטרוספקציה מחדש של הסכמה המרוחקת ומביא את הטבלאות שכבר נרשמו להתאמה אליה. הוא אינו מוסיף טבלה או עמודה: טבלה או עמודה שהסכמה הוסיפה נשארת מוצעת, ועמודה שהסכמה איבדה מוסרת. כללי ממשל קיימים (RLS, מיסוך) נשמרים.
מגבלות.
- שדות שאילתה-שורש סקלריים ו-ENUM (טיפוס ההחזרה אינו OBJECT) הופכים ל-commands עוקבים, לא לטבלאות וירטואליות. ה-
return_schemaשלהם הוא עמודתvalueיחידה מהטיפוס הסקלרי הממופה. - קינון אובייקטים נפתר בזמן הרישום עד
graphql_remote.max_object_depth(ברירת מחדל: 5). הן בחירת השליפה המרוחקת והן מטא-נתוני שדות-המשנה נבנים עד לעומק זה; שדות מעבר לגבול אינם נשלפים ואינם זמינים לחילוץ ב-SQL. טיפוס נכנס פעם אחת לאורך כל נתיב: שדה שהטיפוס שלו כבר נמצא בדרך למטה מושמט, כך שסכמה שהטיפוסים שלה מפנים זה לזה נסרקת פעם אחת לכל טיפוס, ולא פעם אחת לכל רמת עומק. - שדות OBJECT מקוננים מסוג-LIST (למשל
breed.awards: [Award]) נכללים בבחירת השליפה עדgraphql_remote.max_list_depthרמות קינון (ברירת מחדל: 2). בגבול זה הרשימה נשלפת כמערךjsonbבעמודת האב. כאשר שדה הרשימה מצהיר על ארגומנטfirst(Relay, PostGraphile, pg_graphql) או על ארגומנטlimit(Hasura), הבחירה מעבירה אותו כ-first: Nאוlimit: N, כאשר N הואgraphql_remote.max_list_items(ברירת מחדל: 100). שדה רשימה שאינו מצהיר על אף אחד מהם אינו מקבל ארגומנט, מפני ששירות מרוחק דוחה ארגומנט שהשדה אינו מצהיר עליו. מעבר ל-max_list_depth, שדה ה-LIST מוחרג לחלוטין כדי למנוע התפשטות נתונים בלתי מוגבלת. ב-SQL, ניגשים למערך דרךjson_array_elements(column_name)או חילוץ אינדקס עם->>. אם לטיפוס הפריט של הרשימה יש שאילתת שורש משלו, רשמו אותו במקום זאת כטבלה נפרדת וצרו קשר. נתיב ה-join יעיל יותר ועוקף את ה-blob. - עבור שאילתות SQL, עמודות מסוג-OBJECT לא-ממושלות נאספות במלואן מהמרוחק (כל שדות-המשנה עד העומק המוגדר) ונשמרות במטמון כ-
jsonb. גישה לשדה-משנה ב-SQL מטופלת דרך חילוץ->>מול ה-blob; הבקשה המרוחקת אינה מצומצמת רק לשדות ששאילתת ה-SQL בוחרת. כאשר לטיפוס פריט-הרשימה אין query שורש וייצוג ה-blob אינו מספיק, כתבו את השאילתה ב-SDL של GraphQL ישירות. Provisa משחזרת בנאמנות את בחירת שדה ה-GQL, כך שהמרוחק רואה בדיוק את השדות המבוקשים. - אם השרת המרוחק דוחה שדה מסוג-OBJECT מכיוון שהוא דורש בחירת שדה-משנה (מה שלא אמור לקרות כאשר
gql_selectionזמין), ה-executor מנסה שוב פעם אחת עם שדות אלה מוסרים כך שעמודות סקלריות עדיין מוחזרות. הדבר חל על טבלאות הנקראות משדה שורש. טבלת חיבור אינה עוברת בנתיב זה.
קריאות מעומדות. טבלת חיבור נקראת באמצעות סמן (cursor). כל עמוד מבקש first: N, after: $pageCursor עם pageInfo { hasNextPage endCursor }, והקריאה עוקבת אחר endCursor עד שהשירות המרוחק מדווח שאין עמוד הבא.
| הגדרה | ברירת מחדל | השפעה |
|---|---|---|
graphql_remote.max_list_items |
100 |
שורות לכל עמוד. |
graphql_remote.max_rows |
10000 |
המספר הגדול ביותר של שורות שקריאה אחת של טבלת חיבור לוקחת. קריאה שמגיעה אליו נעצרת ורושמת אזהרה ביומן. |
שתי תגובות גורמות ל-executor לנסות שוב:
- עמוד כבד מדי. כאשר השירות המרוחק עונה 502 או 504, אותו עמוד מתבקש שוב בחצי מהגודל, עד שורה אחת.
- הגבלת קצב עם זמן המתנה. כאשר השירות המרוחק עונה 403 או 429 עם
Retry-Afterשל 120 שניות או פחות, ה-executor ממתין פרק זמן זה ושולח את הבקשה שוב, עד שלושה ניסיונות. סירוב ללאRetry-After, או כזה שמבקש המתנה ארוכה יותר, מועלה כשגיאה. הדבר חל על כל קריאה, חיבור או לא.
כל שגיאה אחרת בתגובה מכשילה את הקריאה, אלא אם סוג המקור קובע אחרת (ראו GitHub להלן). לחיבור שהאב שלו חזר null אין שורות.
GitHub¶
GitHub הוא סוג מקור רגיל. ה-API שלו הוא GraphQL, ולכן הטבלאות שלו מתנהגות כמתואר לעיל, כולל טבלאות חיבור כגון gh__repository_issues.
הוספת המקור.
- פתחו Sources והוסיפו מקור מסוג GitHub.
- הזינו אסימון גישה (token) של GitHub. אפשר להזין namespace, הקידומת של שמות הטבלאות; ברירת המחדל היא
gh. - שמרו. Provisa בודקת את האסימון מול GitHub. אסימון ש-GitHub דוחה מכשיל את ההוספה עם ההודעה של GitHub.
הוספת המקור אינה רושמת טבלאות.
רישום טבלאות. פתחו Tables, אחר כך Register Table. בחרו את מקור GitHub, בחרו את הסכמה graphql, ואז בחרו את הטבלאות הרצויות. כל טבלה ש-GitHub מציע מופיעה ברשימה; הרישום הוא בחירתכם מה לחשוף.
היקפי הרשאה (scopes) של האסימון. כאשר אתם רושמים טבלה, Provisa בודקת אותה פעם אחת מול GitHub עם האסימון שלכם.
- שדה שהיקפי האסימון אינם מכסים מושמט מהטבלה. התוצאה מנקבת כל שדה שהושמט:
Left out, because the source's credential may not read them: projectsV2. - טבלה שהאסימון אינו יכול לקרוא כלל נדחית, עם הסיבה של GitHub:
GitHub does not let this source's credential read gh__repository_issues: ....
שורות שהאסימון אינו רשאי לראות. GitHub עונה FORBIDDEN עבור שדה שהאסימון אינו רשאי לראות בשורה מסוימת, כגון משתפי הפעולה של מאגר ללא הרשאת push, ו-NOT_ORG_OWNED_REPO עבור שדה שקיים רק במאגרים בבעלות ארגון. שדה זה הוא null באותה שורה, שאר הקריאה נמשכת, ו-Provisa רושמת אזהרה ביומן. שגיאה כלפי הטבלה עצמה מכשילה את הקריאה.
עמודים כבדים. כאשר GitHub עונה RESOURCE_LIMITS_EXCEEDED מפני שחישוב עמוד יקר מדי, העמוד מתבקש שוב בחצי מהגודל.
אובייקטים מקוננים. טבלאות GitHub משתמשות בעומק קינון משלהן, 0 (max_object_depth=0 לסוג המקור הזה), במקום graphql_remote.max_object_depth. עמודת אובייקט מקונן נבחרת רק עם השדות הסקלריים שלה, ואובייקטים בתוכה מוצגים כ-__typename.
אחסון האסימון. האסימון נשמר בכספת הסודות, ושורת המקור שומרת הפניה, כך שהפעלה מחדש קוראת את המקור שוב בלי להזין את האסימון מחדש.
איך זה עובד (למפעילים). הסכמה של GitHub נשלחת עם Provisa, ולכן הוספת המקור אינה מבצעת קריאת אינטרוספקציה, וסכמה גדולה אינה עולה דבר ברישום. טבלאות ממופות ממנה אחת אחת עם רישומן. נקודת הקצה refresh מסרבת לסוג מקור זה; סכמת GitHub חדשה מגיעה עם גרסה של Provisa.
GitLab¶
GitLab הוא סוג מקור רגיל ונוסף ונרשם באותו אופן כמו GitHub: הוסיפו מקור מסוג GitLab עם אסימון גישה, ואז רשמו את הטבלאות הרצויות מהסכמה graphql. קידומת שמות הטבלאות המוגדרת כברירת מחדל היא gl. המקור מגיע אל gitlab.com.
בחירת עמודות. GitLab מתמחר כל שאילתה ודוחה שאילתה יקרה מדי: 200 נקודות למבקש אנונימי, 250 עם אסימון. טבלה רחבה שכל עמודותיה נבחרו חורגת ממחיר זה, ולכן רשמו טבלת GitLab עם העמודות שאתם רוצים.
- כאשר אתם רושמים טבלה, Provisa שואלת את GitLab פעם אחת אם יספק את הבחירה בגודל העמוד שבו משתמשות קריאות. אם GitLab עונה שהשאילתה מורכבת מדי או גדולה מדי, הטבלה אינה נרשמת והתוצאה נושאת את ההודעה של GitLab:
Table 'gl__project_issues' was not registered with the columns selected: Query has complexity of 1733, which exceeds max complexity of 200. Choose fewer columns. - מחיר עמודה תלוי בסוגה. ערך פשוט עולה כנקודה אחת; עמודת אובייקט מקונן עולה פי כמה וכמה. הסרת עמודות אובייקט מקונן חוסכת הכי הרבה.
- גודל העמוד הוא חלק מהמחיר. זהו
graphql_remote.max_list_items.
בדיקת טוקן. GitLab עונה על טוקן שהוא לא מזהה בתוצאה ריקה במקום בשגיאה. Provisa מתייחס לזה כטוקן שנדחה ולא מוסיף את המקור.
סכמה מרוחקת של gRPC¶
איך להוסיף את המקור. שלחו POST ל-/admin/grpc-remote/register עם כתובת השרת, נתיב או URL לקובץ .proto, והגדרת TLS אופציונלית. הוספת המקור אינה רושמת אף טבלה.
{
"source_id": "orders-grpc",
"proto_path": "https://api.example.com/orders.proto",
"server_address": "grpc.example.com:443",
"namespace": "orders",
"domain_id": "commerce",
"tls": true,
"cache_ttl": 300,
"method_overrides": { "CreateOrder": "query" },
"relationships": [
{ "source_table": "orders__OrderService__ListOrders", "source_column": "customer_id",
"target_table": "customers__CustomerService__GetCustomer", "target_column": "id" }
]
}
Provisa מביאה את ה-proto, מפענחת אותו עם parser טקסט-טהור (ללא תלויות proto חיצוניות בזמן פענוח), מקמפלת stubs של Python דרך grpc_tools.protoc, ופותחת grpc.aio.Channel מתמיד.
קבצי proto יכולים להיות גם נתיבים מקומיים. נתיבי import עבור טיפוסים מוכרים-היטב (google/protobuf/timestamp.proto) נשמרים בזמן הרישום ונעשה בהם שימוש חוזר ברענון.
מה המקור מציע. כל שיטת rpc ב-proto מסווגת כ-query או כ-mutation באמצעות שלושה אותות לפי סדר עדיפות:
method_overridesבמטען הרישום —{"MethodName": "query"}או{"MethodName": "mutation"}דורס הכל.server_streaming: true— השרת שולח stream של הודעות; תמיד טבלה וירטואלית (אלא אם הפלט הוא סקלר).- הודעת פלט בעלת שדה מסוג-הודעה חוזר — למשל
ListOrdersResponse { repeated Order items; }נחשבת ל-list-wrapper והופכת לטבלה וירטואלית. שדות סקלריים חוזרים (למשלrepeated string tags) אינם מפעילים זאת, כי הם תכונות מערך על ישות בודדת, לא מקורות שורה.
שיטות שאינן תואמות אף אחד מהאותות הללו (RPC יוני-קאסטי המחזיר הודעת ישות בודדת, או כל פלט סקלרי) הופכות ל-commands עוקבים.
רישום טבלאות. כל שיטת query מוצעת כטבלה אחת, בשם {namespace}__{Service}__{Method}, תחת סכמת הבורר grpc_remote. רשמו את הרצויות דרך בורר Register Table (availableTables, availableColumns, registerTable, כמו במקורות GraphQL), ובחרו עמודות תגובה. שדות הבקשה הופכים לעמודות מסנן-מקורי _nf_*, ואלה נכללות תמיד.
מתודות mutation הן פקודות מוצעות, הנספרות ב-available_mutations; הוספת המקור אינה רושמת אף אחת. mutation של gRPC נרשם בעמוד הפקודות על ידי בחירת המקור ואחר כך המתודה, בשם Service.Method; סוג הפקודה הוא source_operation. ראו פעולת כתיבה של מקור מרוחק.
שיוך שמות טבלה. השם ברירת המחדל הוא {namespace}__{ServiceName}__{MethodName}. ללא namespace, שמות השירות והשיטה מחוברים ישירות. לכל טבלה רשומה ניתן לתת alias; כשמוגדר, ה-alias הוא השם המשמש בכל מקום (שאילתות, SDL, קשרים). השם שנוצר-אוטומטית הוא מפתח הרישום ולעולם אינו משתנה.
מיפוי טיפוסים. טיפוסים סקלריים של proto ממופים לטיפוסי SQL כך.
| טיפוס Proto | טיפוס SQL |
|---|---|
string, bytes |
text |
int32 / uint32 / sint32 / fixed32 / sfixed32 |
integer |
int64 / uint64 / sint64 / fixed64 / sfixed64 |
bigint |
float |
real |
double |
numeric |
bool |
boolean |
repeated <T> |
jsonb |
| הודעה מקוננת | jsonb |
| Enum | text |
קשרים בזמן הרישום. relationships עובד בדיוק כמו במתאם ה-GQL: הוא מצהיר על נתיבי join של FK/PK, שנשמרים כקשרים שהוצהרו ידנית (בלי הדגל remote_managed). רענון משאיר אותם כמו שהם.
שיטות Query. שדות הודעת פלט הופכים לעמודות טבלה. שדות הודעת קלט הופכים הן לארגומנטי GraphQL המועברים לקריאה המרוחקת והן נרשמים כעמודות מוקדמות-_nf_ עם native_filter_type: "grpc_input", אותו מנגנון ש-GQL ו-OpenAPI משתמשים בו עבור הזרקת פילטר-ילידי.
שדות-משנה של הודעה מקוננת. עבור שיטות query, שדות מסוג-הודעה שאינם-חוזרים בעומק 0 (עמודות פלט ישירות) פותרים את שדות-המשנה שלהם רמה אחת עמוקה ונשמרים כ-object_fields על ה-ColumnDef. מטא-דאטה זו משמשת לחילוץ שדה-משנה מסוג-jsonb ב-SQL ולתיעוד סכמה. שדות מקוננים מעבר לעומק 1 אינם מורחבים באופן רקורסיבי.
שיטות server-streaming אוספות את כל ההודעות ה-streamed לרשימה לפני החזרת שורות.
מתודות mutation. מתודת mutation רשומה היא פקודה שהארגומנטים שלה הם שדות הודעת הקלט, כל אחד מוגדר כ-json ומועבר ללא שינוי. התשובה של השירות המרוחק חוזרת כשורות; קריאה שנדחתה היא 422, functions.remote_refused. ראו פעולת כתיבה של מקור מרוחק.
ניהול ערוץ (Channel). grpc.aio.Channel אחד לכל מקור רשום נשמר במצב האפליקציה ונעשה בו שימוש חוזר על פני בקשות. הערוץ הישן נסגר לפני שהחדש נפתח ברענון.
רענון. שלחו POST ל-/admin/grpc-remote/refresh/{source_id}. טוען מחדש את ה-proto מהנתיב השמור, מקמפל מחדש את ה-stubs ומביא את הטבלאות שכבר נרשמו להתאמה ל-proto, עם העמודות שבהן נרשמה כל אחת. הוא אינו רושם טבלה חדשה; שיטת query שנוספה ל-proto נשארת מוצעת. לחלופין, שלחו PUT ל-/admin/grpc-remote/{source_id}/proto עם proto_text חדש כדי לעדכן את ה-proto בתוך הבקשה.
מגבלות.
- חילוץ אובייקט שדה-משנה הוא רמה אחת עמוקה. שדות הודעה מקוננים מעבר לעומק 1 אינם מורחבים באופן רקורסיבי.
OpenAPI / REST¶
איך להוסיף את המקור. שלחו POST ל-/admin/openapi/register עם מזהה מקור ו-spec, הנטען מקובץ מקומי או מ-URL. ה-spec מפוענח ונשמר עם המקור; לא נרשמת אף טבלה ואף command. התגובה מדווחת tables: 0 ו-mutations: 0, כשהמונים המוצעים מופיעים ב-available_tables וב-available_mutations.
רישום טבלאות. רשמו כל פעולת GET רצויה דרך בורר Register Table (availableTables, availableColumns, registerTable), ובחרו עמודות. רשמו כל פעולה שאינה GET בנפרד כפקודה בעמוד הפקודות, המפורטות על ידי availableFunctions; ראו פעולת כתיבה של מקור מרוחק. PUT /admin/openapi/spec/{source_id} שומר spec שנערך ידנית, אינו רושם דבר, ומחזיר available_tables ו-available_mutations.
מטען רישום. נקודת הקצה /admin/openapi/register מקבלת שני שדות נוספים לצד source_id, spec_path וכו':
{
"operation_overrides": { "createPet": "query", "listOrders": "mutation" },
"relationships": [
{ "source_table": "pets__listPets", "source_column": "owner_id",
"target_table": "owners__listOwners", "target_column": "id" }
]
}
מה המקור מציע. כל פעולת GET במפרט מוצעת כטבלה, אלא אם סכמת התשובה שלה היא טיפוס סקלרי (string, number, boolean, integer); GET שמחזיר סקלר הוא פונקציה עם עמודה יחידה value. כל פעולה אחרת (POST, PUT, PATCH, DELETE) מוצעת כ-command, בשם ה-operationId שלה. אחרי הרישום, היא מקבלת את פרמטרי הנתיב של הפעולה וארגומנט body לגוף הבקשה, כל אחד מטיפוס json, וכל ארגומנט אחר נכנס למחרוזת השאילתה. ראו פעולת כתיבה של מקור מרוחק.
עדיפות סיווג: operation_overrides (מטען) דורס x-provisa-kind (הרחבת spec) דורס את היוריסטיקת ה-GET. operation_overrides הוא נתיב הדריסה המומלץ; x-provisa-kind מיועד למקרים בהם ה-spec עצמו אמור לשאת את הסיווג.
קשרים בזמן הרישום. relationships עובד כמו במתאמים האחרים: נשמרים כקשרים שהוצהרו ידנית ונשמרים גם ברענון.
שיוך שמות טבלה. טבלאות משתמשות ב-operationId של הפעולה. אם operationId אינו מוגדר, Provisa בונה slug מ-{method}_{path}. alias נגזר על ידי הסרת קטע הפועל המוביל ויחוד השם-העצם (findPetsByStatus → pet_by_status).
מיפוי טיפוסים. טיפוסי JSON Schema ממופים לטיפוסי Provisa כך.
| טיפוס JSON Schema | טיפוס Provisa |
|---|---|
string |
string |
integer |
integer |
number |
number |
boolean |
boolean |
array |
jsonb |
object |
jsonb |
פרמטרים כעמודות סינון מקוריות. פרמטרי נתיב ושאילתה שאינם כבר שדות בתשובה הופכים לעמודות עם native_filter_type שערכו path_param או query_param, עם הקידומת _nf_. כשלפרמטר יש אותו שם כמו לשדה בתשובה, המטא-נתונים שלו מתמזגים לרשומה של העמודה הזו, כך שלא נוצר כפיל.
פתירת סכמת תגובה. ה-mapper בודק responses.200, לאחר-מכן responses.2xx, לאחר-מכן responses.default. תגובות מסוג-array נפתחות לסכמת הפריט שלהן. הפניות $ref נפתרות רמה אחת עמוקה.
שדות-משנה של אובייקט. תכונות תגובה מסוג type: object בעלות properties משלהן נשמרות כ-object_fields על העמודה. שדות-משנה אלה גלויים ב-SDL ומשמשים לחילוץ jsonb בשאילתות.
מטמון תגובה. תוצאות פעולת GET נשמרות במטמון ב-PostgreSQL על ידי pg_cache.py. כל צירוף של פרמטרי בקשה מקבל קבוצת _params_hash משלו. שורות עבור hash נתון מוחלפות כאשר ה-TTL פג. נקודות קצה של פרמטר-נתיב (/pets/{id}) מדלגות על האיסוף הראשוני בכמות. טבלת המטמון נוצרת ריקה עבור אינטרוספקציית סכמה, ואז מאוכלסת לפי-PK כאשר בקשות מגיעות.
רענון. שלחו POST ל-/admin/openapi/refresh/{source_id}. מפענח מחדש את ה-spec דרך _load_and_register, שאינו רושם דבר: הוא אינו מוסיף טבלה או עמודה. כללי ממשל קיימים נשמרים. טבלה רשומה שומרת על העמודות שלה; היא נקראת דרך הפעולה ב-spec המרוענן.
מגבלות.
- חילוץ אובייקט שדה-משנה הוא רמה אחת עמוקה. תכונות מקוננות בתוך
object_fieldsאינן מורחבות באופן רקורסיבי. - פרמטרי header ו-cookie מתעלמים; רק פרמטרי
pathו-queryנרשמים. - פתירת
$refברמת ה-spec היא רמה אחת עמוקה עבור סכמות תכונה; הפניות רכיב מקוננות-עמוק עשויות לא להיפתר.
ההשפעה של רישום טבלה מרוחקת¶
טבלה הרשומה מכל מקור סכמה מרוחקת היא טבלת Provisa רגילה. שום דבר בה אינו מטופל אחרת מטבלה יחסית מחוברת-מקומית בזמן ריצה.
ממשקי שאילתה. הטבלה ניתנת לשאילתה מיידית דרך GraphQL, SQL (pgwire או ישיר), Cypher (GQL), JSON:API, ו-Arrow Flight. יצירת סכמה מסנתזת ColumnMetadata עבור טבלאות מרוחקות מכיוון שאין להן קטלוג; מיפוי טיפוסים מוחל בזמן בניית הסכמה.
מודל אבטחה. כל חמש שכבות הממשל חלות:
- בקרת גישת דומיין — ה-
domain_idשל הטבלה מסייג אילו תפקידים יכולים לראות אותה. - אבטחה ברמת-שורה (RLS) — פילטרי שורה המוגדרים על הטבלה מוזרקים לכל שאילתה, ללא קשר לממשק.
- נראות עמודה — רשימת
visible_toעל כל עמודה שולטת בחשיפת שדה לפי-תפקיד. - מיסוך עמודה — כללי מיסוך חלים בשלב 2 של צינור הממשל.
- שומר פרדיקט — עמודות ממוסכות נדחות מסעיפי WHERE ו-HAVING.
שאילתות אד-הוק מול טבלאות מרוחקות מותרות על סמך ההרשאות של המשתמש בלבד. הגישה עוקבת בכל מקום אחר אותו מודל מבוסס הרשאות (הרשאות טבלה ועמודה ועוד קשרים מאושרים), בלי מצב ממשל לכל טבלה.
ממשל קשרים (V002). תנאי JOIN מול טבלאות מרוחקות, בשאילתת SQL או Cypher, חייב להתאים לקשר רשום ומאושר. שאילתות GraphQL מדלגות על בדיקת V002, כי קשרים שמוגדרים ב-SDL מאושרים מעצם התכנון. ראו docs/security.md.
עמודות מסוג-OBJECT. כאשר עמודה ממופה לטיפוס OBJECT inline לא-ממושל של GQL או OpenAPI, טיפוס ה-Provisa שלה הוא jsonb. העמודה שומרת את ה-blob המלא של ה-JSON המקונן. כאשר שדות-משנה מוצהרים (gql_object_fields או object_fields), מפת gql_object_columns מאוכלסת בזמן בניית הסכמה. מחולל ה-SQL משתמש במפה זו כדי לפלוט ביטויי חילוץ ->> עבור שדות-משנה כאשר שאילתה בוחרת אותם.
ארגומנטים נדרשים כפרמטרי פילטר-ילידי. שדות שאילתה-שורש עם ארגומנטים לא-null וללא-ברירת-מחדל מזריקים עמודות נוספות לטבלה הרשומה. עמודות אלה נושאות native_filter_type: query_param. מתרגם ה-Cypher כותב מחדש WHERE n.id = $val ל-WHERE n._nf_id = $val, ו-executor ה-GraphQL אוסף אותן כמשתנים להעברה לנקודה הקצה המרוחקת.
ההשפעה של יצירת קשר מכסה (covering relationship)¶
כאשר סטיוארד רושם קשר בין שתי טבלאות מרוחקות (או בין טבלה מרוחקת לטבלה מקומית), הקשר הופך לנתיב ה-join המשמש בזמן שאילתה.
איך ה-join מנצח. בקימפול שאילתה, Provisa פותרת את נתיב ה-join דרך הקשר הרשום. source_column ו-target_column על הקשר הופכים לתנאי ה-join ב-SQL הנוצר. ה-join מחליף כל קריאה מרוחקת לכל-טבלה שהייתה אחרת נדרשת עבור הטיפוס המחובר.
ה-blob הגולמי לעולם לא נחשף ב-SQL. אי אפשר לבחור את העמודה breed של petstore__pets כערך jsonb גולמי בשאילתת SQL. ברגע שנרשם קשר בין petstore__pets ל-petstore__breeds, שאילתות SQL עוברות דרך ה-join: SELECT breed.name FROM petstore__pets נפתר דרך ה-join לפי FK. כשאין קשר רשום אבל לעמודה יש תת-שדות מוצהרים (gql_object_fields), הפניות SQL לתת-שדות נכתבות מחדש כחילוץ ->> מתוך ה-blob השמור. הנתיב הזה קיים רק לטיפוסים מוטמעים שאינם מנוהלים; שדות שהיעד שלהם מנוהל לא מופיעים כלל ב-SDL ואין להם blob לחלץ ממנו. ה-blob הגולמי עצמו לעולם לא מוחזר כערך עמודה חשוף.
ב-SDL של GraphQL, שדה OBJECT מוטמע שאינו מנוהל מקבל את טיפוס האובייקט המקונן. אם בזמן הביצוע הוא מוגש על ידי join או על ידי חילוץ מה-blob, צורת ה-SDL לא משתנה. ברגע שטיפוס הילד נרשם כטבלה משלו, ולכן מנוהל, כל חמש שכבות הממשל חלות עליו בנפרד: כללי ה-RLS שלו, נראות העמודות, כללי המיסוך, שומרי הפרדיקטים ובקרת הגישה לתחום. חילוץ מה-blob עוקף את כל זה, כי נתוני הילד מגיעים מוטמעים בשורת ההורה ורק הכללים של טבלת ההורה חלים עליהם. כדי לנהל את טיפוס הילד ברמת פירוט, רשמו אותו כטבלה וצרו קשר.
graphql_alias על הקשר. שדה graphql_alias נותן שם לשדה ה-SDL שהקשר חושף על הטיפוס ההורה. כשנעדר, השם נגזר מ-field_name של טבלת היעד ומעוצמת (cardinality) הקשר דרך rel_field_name(target.field_name, cardinality).
V002 על נתיב ה-join. שאילתות SQL ו-Cypher החוצות את הקשר כפופות לממשל קשרים V002. הקשר חייב להיות רשום ומאושר כדי שה-join יורשה. חצייה דרך שדה קשר SDL של GraphQL תמיד מאושרת-מראש.
דגל remote-managed. קשרים שאותרו-אוטומטית במהלך רישום מרוחק של GraphQL נשמרים עם remote_managed: True. זהו סמן מטא-דאטה; הוא אינו משנה התנהגות ממשל.
התנהגות type-def-only¶
לא כל טיפוס בסכמה מרוחקת צריך להיות טבלה הניתנת-לשאילתה.
כאשר root_table_ids מוגדר על SchemaInput, טבלאות שמזהיהן נעדרים מאותה קבוצה מוחרגות משדות שאילתת השורש ב-SDL הנוצר. הן נשארות נוכחות כטיפוסי GraphQL וניתנות להשגה דרך שדות קשר על טבלאות שכן יש להן רשומות שורש.
אותו מנגנון חל כשהסכמה נבנית בסינון לפי תחום. טבלאות בתחומים שלתפקיד אין גישה אליהם הן הגדרות טיפוס בלבד: הגדרת הטיפוס שלהן קיימת ב-SDL לצורך מעבר על קשרים, אבל לא נוצר עבורן שום שדה שאילתה בשורש.
טבלת type-def-only:
- אין לה שדה שאילתת שורש, ולכן לקוחות אינם יכולים לשאול אותה ישירות לפי שם.
- ניתנת להשגה דרך שדות קשר על טבלאות שכן יש להן רשומות שורש.
- עדיין מופיעה באינטרוספקציית סכמה כטיפוס בעל-שם.
- עדיין חלים עליה כל כללי הממשל כאשר הנתונים ניגשים דרך קשר.
טבלה, כולל הגדרת הטיפוס שלה, יוצאת לגמרי מהסכמה רק כשהרישום שלה נמחק. הפיכת טבלה להגדרת טיפוס בלבד (על ידי הסרת המזהה שלה מ-root_table_ids, או דרך סינון לפי גישת תחום) לא מסירה את הטיפוס.
כך Stewards יכולים לחשוף גרפים של אובייקטים שאפשר לנווט בהם, שבהם לחלק מהטיפוסים מגיעים רק במעבר ואי אפשר לשאול עליהם בנפרד.