לדלג לתוכן

אינטגרציות

בחירת נתיב חיבור

סוג לקוח נתיב מומלץ מדוע
כלי BI (Tableau, Power BI, Looker) JDBC הזרמה טורית (columnar) של Arrow Flight על גבי הקו; לכלי BI יש אשף JDBC מובנה והם נהנים ממסירה טורית בעלת תפוקה גבוהה עבור סטי תוצאות גדולים
psql, DBeaver, כל כלי תואם PG pgwire (מנהל התקן PG native) ברירת המחדל חסרת החיכוך — אין צורך במנהל התקן מותאם; השתמשו במה שכבר יש לכם
מחסנית נתונים של Python (pandas, pyarrow) provisa-client או ADBC גולמי קבוצות (batches) Arrow בזרימה; ללא תקורת serialization של שורות
Spark, DuckDB, pipelines בעלי תפוקה גבוהה Arrow Flight (ADBC) הזרמה טורית בלתי מוגבלת ישירות לזיכרון Arrow
שירות-לשירות (חוזים בעלי טיפוסים) Protobuf gRPC proto מיוצר פר-תפקיד; שורות בזרימה; בטיחות טיפוסים
אפליקציות ווב, סקריפטים HTTP (/data/graphql, /data/sql) ללא מנהל התקן; HTTP סטנדרטי; בחירה מלאה של שפת שאילתה
לקוחות REST (תקן JSON:API) GET /data/jsonapi/{table} מעטפת JSON:API v1.0; שדות דלילים (sparse fieldsets), pagination, סינון דרך פרמטרי שאילתה; ללא מנהל התקן

pgwire — מנהל התקן PostgreSQL native

Provisa מיישמת את פרוטוקול ה-wire של PostgreSQL (גרסת פרוטוקול 3.0). כל לקוח שדובר PostgreSQL מתחבר ללא מנהל התקן מותאם.

הפעילו על ידי הגדרת PROVISA_PGWIRE_PORT (לדוגמה 5433) לפני הפעלת Provisa. מושבת כאשר לא מוגדר או 0.

מדוע pgwire ולא JDBC?

מנהל ההתקן JDBC משתמש ב-Arrow Flight כתעבורה שלו ודורש פריסה של provisa-jdbc.jar. pgwire לא דורש דבר — אם כבר יש לכם psql, DBeaver, SQLAlchemy, או מנהל התקן PG JDBC, סיימתם. זהו הנתיב בעל החיכוך הנמוך יותר עבור עומסי עבודה מבוססי SQL בלבד.

JDBC הוא הבחירה הנכונה עבור כלי BI בעלי אשף חיבור JDBC מובנה שנהנים מהזרמה טורית של Arrow Flight עבור סטי תוצאות גדולים. pgwire מקבל SQL חופשי כנגד הסכמה המפורסמת המלאה — אותן שאילתות, עלות הקמה נמוכה יותר.

psql

psql -h localhost -p 5433 -U alice

DBeaver

  1. New Connection → PostgreSQL
  2. Host: localhost, Port: 5433
  3. שם משתמש / סיסמה כפי שהוגדרו ב-Provisa
  4. אין צורך בהורדת מנהל התקן נוסף

SQLAlchemy (Python)

from sqlalchemy import create_engine

engine = create_engine("postgresql+psycopg2://alice:secret@localhost:5433/provisa")
df = pd.read_sql("SELECT * FROM sales.orders", engine)

או עם asyncpg:

engine = create_engine("postgresql+asyncpg://alice:secret@localhost:5433/provisa")

אימות (Authentication)

שדה ה-password של חבילת ה-startup נושא את האישור (credential), ומה שהאישור הוא קובע את השיטה: אסימון גישה אישי, אסימון bearer של OIDC, או סיסמה כנגד הספק המוגדר. תחת ספק ה-basic עם auth.scram: true הסיסמה מוכחת דרך SCRAM-SHA-256 במקום להישלח. תעודות לקוח (client certificates) נתמכות. במצב אמון (none) שם המשתמש ממופה ישירות לתפקיד והסיסמה מתעלמת.

טבלת המשטח × השיטה המלאה נמצאת ב-Security Model. MD5 אינו נתמך; הפעילו TLS (PROVISA_PGWIRE_CERT / PROVISA_PGWIRE_KEY) בעת הרצה על גבי רשת לא מהימנה.

מגבלות

  • SQL בלבד. GraphQL ו-Cypher אינם מתקבלים דרך pgwire.
  • לא לקריאה בלבד. שורות של טבלאות רשומות נכתבות עם INSERT, UPDATE, DELETE, MERGE, TRUNCATE ו-COPY ... FROM STDIN, כל אחת מתקבלת לפי זכות ה-write של התפקיד, הענקות העמודות שלו ומסנן השורות שלו; ראו כתיבת נתונים.
  • ללא הגדרות: CREATE, ALTER ו-DROP נדחים עם SQLSTATE 0A000, למעט טבלה זמנית של הסשן עצמו; ראו הגדרות. COPY ... TO STDOUT מייצא ב-text, csv ו-binary; COPY ... FROM STDIN קורא text ו-csv.
  • שאילתות information_schema ו-pg_catalog מיורטות ונענות מ-shim קטלוג DuckDB — כלי גילוי סכמה עובדים כראוי.

מנהל התקן JDBC

מנהל ההתקן JDBC של Provisa משתמש ב-Arrow Flight כתעבורת הבסיס שלו. זהו הנתיב המומלץ עבור כלי BI בעלי אשף חיבור JDBC.

שאילתות עוברות דרך Arrow Flight כשפורט ה-Flight עונה בפתיחת החיבור, והתוצאות מגיעות אצווה אחר אצווה עם סוגי העמודות שלהן. פורט ה-Flight נחשב לפורט שליד פורט ה-HTTP (8815 ליד 8001); הגדירו את המאפיין flight_port כשזה לא המצב. כשפורט ה-Flight אינו נגיש בזמן ההתחברות, החיבור משתמש ב-API של HTTP לשאילתות שלו ורושם זאת ביומן ברמת INFO. כשחיבור כבר פועל דרך Flight, שגיאת שרת — אישורים שנדחו, תפקיד שאין למשתמש, הצהרה שנכשלה — מגיעה לקורא עם ההודעה של השרת; היא אינה מורצת שוב דרך HTTP. (REQ-293)

עיון בסכמה (getTables, getColumns, מפתחות ראשיים וזרים) נקרא דרך אותו פורט Flight עם אישורי הכניסה: הדרייבר מפרט את הקטלוג של התפקיד שבשמו הוא פועל, ולכן טבלה או עמודה שלא הוענקו לתפקיד זה אינן מופיעות. מפתח שמפנה לטבלה או עמודה שלא הוענקו לתפקיד גם הוא אינו מפורט. בחיבור שבו פורט ה-Flight לא היה נגיש בהתחברות, הדרייבר קורא את אותו קטלוג דרך HTTP (GET /data/catalog): השרת בונה את שתי הרשימות באותה פונקציה, ולכן הטבלאות, שמות העמודות, הסוגים והמפתחות אינם תלויים בפורט שענה. (REQ-128)

חיבור

הורידו את provisa-jdbc.jar (תמיד הגרסה האחרונה) והוסיפו אותו לנתיב מנהלי ההתקן של הכלי שלכם.

כתובת JDBC:

jdbc:provisa://<host>:8815

האימות משתמש בתכונות user / password הסטנדרטיות של JDBC. הדרייבר מחליף אותן בטוקן סשן ושולח אותו עם כל בקשה; כניסה שנדחתה מכשילה את החיבור עם הסטטוס והסיבה של השרת. Provisa גוזרת את התפקיד מהזהות שנכנסה. התכונה האופציונלית role (או ?role= בכתובת) מבקשת לפעול בתפקיד אחד שהמשתמש מחזיק, או בקבוצה של תפקידים כאלה מופרדים בפסיקים; תפקיד שאין למשתמש נדחה. בפריסה ללא ספק אימות אין כניסה, ושם המשתמש מתקבל כתפקיד המבוקש. (REQ-131, REQ-273)

הגדרת כלי BI

Tableau

  1. Manage → Drivers → Install Provisa JDBC
  2. Connect → Other Databases (JDBC)
  3. URL: jdbc:provisa://localhost:8815
  4. הזינו את שם המשתמש והסיסמה כאשר תתבקשו

DBeaver (נתיב JDBC — עבור נתיב pgwire ראו למעלה)

  1. Database → New Connection → JDBC
  2. Driver: הוסיפו את provisa-jdbc.jar
  3. URL: jdbc:provisa://localhost:8815
  4. הזינו את שם המשתמש והסיסמה בלשונית Authentication

Power BI — השתמשו בשער ODBC עם הגשר Provisa JDBC-ODBC (כלול במתקין).


לקוחות Arrow Flight

Arrow Flight (פורט 8815) הוא הנתיב המומלץ עבור כלי נתונים התומכים בו. תוצאות זורמות כ-Arrow RecordBatches מבלי להתממש בזיכרון של Provisa.

Python (provisa-client)

הנתיב המומלץ ב-Python — עוטף גם GraphQL וגם Arrow Flight:

pip install provisa-client
from provisa_client import ProvisaClient

client = ProvisaClient("http://localhost:8001", username="alice", password="secret")

# Arrow Flight → pyarrow Table (high-throughput, streaming)
table = client.flight("SELECT id, amount FROM sales.orders")

# Arrow Flight → pandas DataFrame
df = client.flight_df("SELECT id, amount FROM sales.orders")

# GraphQL → DataFrame
df = client.query_df("{ orders { id amount } }")

ראו docs/python-client.md להתייחסות מלאה כולל DB-API 2.0, dialect של SQLAlchemy, ו-ADBC.

Python (PyArrow גולמי)

import pyarrow.flight as flight

client = flight.connect("grpc://localhost:8815")
ticket = flight.Ticket(b'{"query": "SELECT id, amount FROM sales.orders"}')
df = client.do_get(ticket).read_all().to_pandas()

Flight נושא את האישור שלו במטען ה-JSON, כשדה token — אסימון bearer של ספק או אסימון גישה אישי. גם ה-handshake וגם כל ticket מקבלים אותו, ושניהם מאמתים אותו באותו אופן, כך שלקוח שהתאמת (authenticated) ב-handshake עדיין מציג את האסימון בכל do_get. שדה role לצידו מבקש תפקיד; השרת גוזר את התפקידים המורשים של הזהות ומחליף לערך המורשה, כך ששדה role ב-ticket לעולם אינו הזהות עצמה. (REQ-1263) ראו Security Model.

ticket = flight.Ticket(json.dumps({
    "query": "SELECT id, amount FROM sales.orders",
    "token": "provisa_pat_...",
    "role": "analyst",
}).encode())

גם עיון צריך את האישורים. list_flights, get_flight_info ו-get_schema אינם נושאים כרטיס, ולכן הם קוראים את האישורים מכותרת הקריאה authorization: Bearer <token> ואת התפקיד המבוקש מ-x-provisa-role (תפקיד אחד, או קבוצה מופרדת בפסיקים של תפקידים מוחזקים). כשהאימות פעיל, קריאת עיון ללא אישורים נדחית, והקטלוג שהיא מחזירה הוא של התפקיד המורשה: הטבלאות, העמודות והפקודות שהוענקו לתפקיד זה, ולא יותר. כרטיס קטלוג (כרטיס ללא query) נענה באותו אופן מה-token וה-role שלו. לכן לקוח חייב לשלוח את האישורים שלו לפני שהוא מעיין; provisa-client עושה זאת ב-list_tables(). בפריסה ללא ספק אימות הקריאות האלה מפרטות את הקטלוג כולו ואינן לוקחות אישורים. (REQ-1263, REQ-127)

options = flight.FlightCallOptions(headers=[
    (b"authorization", b"Bearer provisa_pat_..."),
    (b"x-provisa-role", b"analyst"),
])
for info in client.list_flights(b"", options):
    print(info.descriptor.path)

ADBC

import adbc_driver_flightsql.dbapi as adbc

conn = adbc.connect("grpc://localhost:8815", db_kwargs={"username": "alice", "password": "secret"})
cursor = conn.cursor()
cursor.execute("SELECT id, amount FROM sales.orders")
table = cursor.fetch_arrow_table()

DuckDB

import duckdb, pyarrow.flight as flight

client = flight.connect("grpc://localhost:8815")
ticket = flight.Ticket(b'{"query": "SELECT * FROM sales.orders"}')
arrow_table = client.do_get(ticket).read_all()

conn = duckdb.connect()
result = conn.execute("SELECT region, sum(amount) FROM arrow_table GROUP BY 1").df()

Spark (PySpark)

from pyspark.sql import SparkSession
spark = SparkSession.builder \
    .config("spark.jars.packages", "org.apache.arrow:flight-core:14.0.0") \
    .getOrCreate()

# Use ADBC Flight connector or load via pandas → Spark

Protobuf gRPC (פורט 50051)

נתיב משירות לשירות. Provisa מייצרת .proto לכל תפקיד ומייצרת אותו מחדש בכל שינוי במודל או בתפקיד — כל תפקיד רואה רק את הטבלאות והעמודות שיש לו גישה אליהן. בשירות יש RPC לכל טבלה בשורש הסכמה של התפקיד; טבלאות מקושרות נגישות דרך הקשרים שלהן, כמו ב-GraphQL.

הורידו את ה-proto עבור התפקיד שלכם:

curl http://localhost:8001/data/proto/analyst > provisa_analyst.proto

השתמשו ב-grpc_server_reflection כדי לגלות את הסכמה באופן תכנותי. הרפלקציה מתארת את השירות של התפקיד שבשמו הקריאה פועלת, אותו קובץ כמו ההורדה שלמעלה. כשהאימות כבוי, קריאת רפלקציה מציינת את התפקיד שלה כמו כל קריאה אחרת:

grpcurl -plaintext -H 'x-provisa-role: analyst' localhost:50051 list

כל RPC חייב לשאת אישור במפתח metadata authorization — אסימון ספק או אסימון גישה אישי. x-provisa-role מבקש תפקיד מתוך הקבוצה המורשית של הזהות; זה אינו אישור ולעולם לא היה. תעודות לקוח נתמכות. ראו Security Model.

שאילתות בזרימה (streaming) מנפיקות הודעה אחת לכל שורה; mutations הן unary.


הפעלת Commands על פני פרוטוקולים

command הוא פונקציה עקובה רשומה או webhook — יישות ניתנת לקריאה (callable) הרשומה במודל של Provisa עם kind (query או mutation) ו-impl_kind המתאר כיצד היא רצה. כל משטח מנתב הפעלות דרך מבצע מנוהל יחיד (invoke_tracked_function) שאוכף את שיוך ה-command (visible_to), את הזכות write למוטציה וממשל באופן אחיד (REQ-1156). [tool-verified: provisa/api/data/action_exec.py, provisa/bolt/session.py:786-791, provisa/grpc/server.py:107-135, provisa/pgwire/function_call.py:80-88, provisa/api/flight/server.py:542-554]

impl_kind מה רץ שדות קישור
source_procedure פרוצדורה מאוחסנת על מקור רשום (ברירת מחדל) sourceId, schemaName, functionName
script סקריפט בצד השרת script
http קריאת HTTP יוצאת url, method
grpc קריאת gRPC יוצאת לשרת חיצוני target, method
python יישות Python ניתנת לקריאה המתארחת על ידי Provisa (REQ-885) callable (למשל demo.py_functions:random_dataset)

כאשר command מצהיר return_schema (JSON Schema עם type: array, items: object), הוא set-returning — כל משטח משליך אותו כסט שורות בעל טיפוס. ה-commands לדוגמה random_python_set (impl_kind python) ו-random_grpc_set (impl_kind grpc) ממחישים גם יישות מתארחת ניתנת לקריאה וגם גשר gRPC חיצוני המחזיר שורות בעלות ערך אקראי; שניהם רשומים ב-config/provisa-install.yaml. [tool-verified: config/provisa-install.yaml:809-856]

מטריצת פרוטוקולים

משטח תחביר דוגמה
GraphQL kind=query → שדה Query; kind=mutation → שדה Mutation; עם קידומת דומיין כאשר domain_prefix: true { ps__random_python_set(rows: 5, seed: 42) { id region amount } }
pgwire / Arrow Flight / MCP run_sql SELECT * FROM fn(args) או SELECT fn(args) SELECT * FROM random_python_set(5, 42)
Cypher HTTP (POST /data/cypher) CALL fn(args) YIELD cols CALL random_python_set(5, 42) YIELD id, region, amount
Bolt (Neo4j Browser / driver) CALL fn(args) — ארגומנטים לפי מיקום ממופים לשמות הארגומנטים המוצהרים CALL random_python_set(3, 7)
Provisa gRPC (פורט 50051) Unary CallCommand(CommandRequest{name, args_json}) → CommandResponse{rows_json} grpcurl -d '{"name":"random_python_set","args_json":"{\"rows\":5}"}' ... ProvisaService/CallCommand

שדה ה-kind שולט במיקום GraphQL בלבד — משטחי SQL, Cypher, Bolt, ו-gRPC מקבלים commands מסוג query ו-mutation באופן זהה.


Apollo Federation

Provisa יכולה לפעול כ-subgraph של Federation v2, וחושפת את הסכמה המפורסמת שלה ל-Apollo Router או Apollo Gateway.

הגדרה

הפעילו federation ב-config.yaml:

federation:
  enabled: true
  subgraph_name: provisa-data

Provisa מייצרת דירקטיבות @key על עמודות מפתח ראשי ו-@external/@provides על קשרים חוצי-subgraph באופן אוטומטי.

רישום עם Apollo Router

ב-supergraph.yaml שלכם:

subgraphs:
  provisa-data:
    routing_url: http://provisa:8001/data/graphql
    schema:
      subgraph_url: http://provisa:8001/data/graphql

הריצו rover supergraph compose --config supergraph.yaml לייצור סכמת ה-supergraph.

ישויות (Entities)

Provisa מגיבה לשאילתות _entities עבור joins חוצי-subgraph. כל טבלה עם מפתח ראשי ניתנת לפתירה אוטומטית כישות Federation.


ייבוא Hasura v2 / DDN

ראו docs/import.md עבור מעבר מ-Hasura ל-Provisa.


Kafka

ראו docs/sources.md עבור קונפיגורציית נושאי (topics) Kafka כטבלאות לקריאה בלבד ופתחי יעד (sinks) לתוצאות שאילתה.


בודקי איכות נתונים (Data Quality Checkers) (REQ-1443)

Soda Core ו-Great Expectations מתחברים ל-Provisa באותו אופן שבו כל לקוח postgres אחר מתחבר — דרך pgwire. זו כל האינטגרציה: הבודק מחזיק מנהל התקן postgres אחד וסורק את התצוגה הפדרטיבית, כך שטבלת Snowflake, טבלת Iceberg, ואוסף Mongo נבדקים כולם על ידי אותו dialect חוזה, ללא בודק פר-מערכת. [tool-verified: provisa/events/source_loader.py make_dq_loader]

הסריקה רצה בפרשן (interpreter) ילד — python -m provisa.dq.worker — שהוא המקום היחיד שבו soda_core או great_expectations מיובאים. שום דבר לא מקושר לתהליך השרת, וקריסת בודק מפילה תת-תהליך ולא את לולאת האירועים. [tool-verified: provisa/dq/runner.py build_command]

תוצאות הסריקה נוחתות כשורות מקור רגילות, כך שקצב (cadence), טריות (freshness), אירועים, lineage, ממשל, RLS, ה-grid והייצוא — כולם חלים ללא מנגנון שני. כתיבת חוזים, מעטפת התוצאה, והרישום הנגזר מכוסים ב-docs/sources.md.

התקנת בודק

אף אחת מהספריות לא מגיעה כברירת מחדל. המתקין שואל איזו אתם רוצים, והתשובה הופכת ל-dq_checker: none|soda|gx ב-~/.provisa/config.yaml. בשכבת Docker scripts/provisa הופך את זה לארגומנט build PROVISA_EXTRAS; בשכבה native first-launch.sh מתקין את ה-pyproject extra המתאים לתוך ה-venv. [tool-verified: scripts/provisa:69-79, packaging/linux/first-launch.sh _native_extras]

dq_checker ספרייה רישיון שכבת ענן מתארחת (hosted cloud plane)
soda soda-postgres Elastic License 2.0 מסורב (cloud_eligible: false)
gx great-expectations[postgresql] Apache 2.0 מותר

Elastic License 2.0 אוסר על אספקת התוכנה לצדדים שלישיים כשירות מתארח, וזה בדיוק מה שהרצת Soda בתוך שכבת ה-SaaS מטעם דייר תהיה. פריסה מתארחת שרוצה Soda מפנה לנקודת קצה Soda שהמפעיל מריץ בעצמו. ראו docs/configuration.md עבור מפתחות החיבור.


חילופי סמנטיקה עם Apache Ossie (REQ-1316)

Provisa מחליפה מודלים סמנטיים עם Apache Ossie (מפרט 0.2.0.dev0, באינקובציה; לשעבר Open Semantic Interchange) דרך מתאם גבול. אוצר המילים הפנימי של Provisa לעולם אינו משנה את שמו לאוצר המילים של Ossie — המפרט מצהיר ששינויים שוברים צפויים, ולכן הצימוד מוגבל למתאם. [tool-verified: provisa/ossie/convert.py module docstring; OSSIE_VERSION = "0.2.0.dev0"]

טיוטת המפרט שינתה את צורתה בלי לשנות את מחרוזת הגרסה. המתאם עוקב אחר המפרט כפי שהוא ב-commit 642cb894 של apache/ossie (2026-10-10): מודל אחד לכל מסמך, עם version, name, datasets, relationships ו-metrics בשורש המסמך ו- datatype מנומר. הייצוא נבדק בבדיקות יחידה מול core-spec/ossie-schema.json של אותו commit. [tool-verified: OSSIE_SPEC_COMMIT, provisa/ossie/convert.py; test_the_export_is_valid_against_ossies_own_schema, tests/unit/test_ossie_convert.py]

ייצוא

משטח הייצוא הקנוני הוא נקודת קצה HTTP חיה. היא גוזרת את מסמך ה-Ossie מהמצב החי בכל בקשה — ללא מטמון וללא שלב יצירה.

GET /admin/ossie
התגובה היא מסמך YAML עם Content-Disposition: attachment; filename=provisa.ossie.yaml. [tool-verified: ossie_router.py lines 20–33: "THE canonical live Ossie endpoint: the semantic model derived from live state on every read — no caching, no regeneration step"]

עמוד המדדים מציע גם כפתור הורדה וכתובת URL של נקודת הקצה הניתנת להעתקה בלוח Ossie Interchange, ושניהם מובילים לאותה נקודת קצה. [tool-verified: OssieInterchangePanel.tsx lines 64–79: endpointUrl = window.location.origin + OSSIE_ENDPOINT_PATH]

מה מיוצא

המתאם ממפה אובייקטי Provisa לאובייקטי Ossie כך:

אובייקט Provisa אובייקט Ossie הערות
Table dataset source = catalog.schema.table; מפתחות ראשיים/ייחודיים מקונפיגורציית העמודות ו-UniqueConstraint
Column field expression = הפניה לעמודה (דיאלקט ANSI_SQL); עמודות זמן מקבלות dimension.is_time: true
Relationship relationship הכינוי משמש כשם כשהוא מוגדר; קשרים מחושבים (עם יעד פונקציה) מדולגים
Metric metric name, expression (ANSI_SQL), datatype (ממופה כמו של עמודה), description, ai_context
modeling_role / modeling_history custom_extensions[].vendor_name="provisa" להלוך-ושוב בלבד; כלים אחרים רשאים להתעלם

[tool-verified: _table_to_dataset, build_ossie_model, provisa/ossie/convert.py: "Computed (function-target) relationships have no dataset target — not representable in Ossie; skipping is the defined export boundary"]

קשר על פני כמה עמודות מפרט כל עמודה ב-from_columns וב-to_columns. מודל ללא טבלאות נדחה עם 422: במסמך Ossie יש לפחות dataset אחד. [tool-verified: test_a_relationship_over_several_columns_lists_each_column, test_a_model_with_no_table_is_refused_by_name, tests/unit/test_ossie_convert.py]

ממשל, RLS, lineage וסמנטיקת הגרף אינם מיוצאים. הם יכולים לעבור בחריץ האופציונלי provisa ב-custom_extensions לשם נאמנות בהלוך-ושוב, אך החילופים לעולם אינם תלויים בכך שכלים אחרים קוראים אותו. [tool-verified: provisa/ossie/convert.py docstring lines 13–15]

סוג עמודה מחוץ לאוצר המילים של Ossie נכתב כ-Opaque, עם שם הסוג עצמו בהרחבת provisa של השדה — זה מה שהמפרט קובע לסוג ידוע שאין לו איבר עבורו. עמודה ללא סוג רשום אינה מציינת datatype. המתאם לעולם אינו ממפה לסוג שאין לעמודה. [tool-verified: _map_datatype, _typed, provisa/ossie/convert.py]

מיפוי סוגים

[tool-verified: _DATATYPE_MAP, provisa/ossie/convert.py]

סוג Provisa / מקור datatype של Ossie
varchar, text, char, uuid, string String
int, integer, bigint, smallint, int4, int8, tinyint Integer
numeric, decimal Decimal
float, double, real Float
bool, boolean Boolean
date Date
time Time
timestamp, datetime DateTime
timestamptz, timestamp with time zone DateTimeTz
כל סוג אחר Opaque, עם שם הסוג בהרחבת provisa

Decimal של Ossie אינו נושא דיוק וקנה מידה, ולכן numeric(10,2) מיוצא כ-Decimal וחוזר מהייבוא כ-decimal. [tool-verified: test_parse_round_trips_export, tests/unit/test_ossie_convert.py]

ייבוא

הייבוא מקבל מסמך Ossie (YAML או JSON) ומחזיר הצעות רישום. דבר אינו נרשם אוטומטית — הגדרות מיובאות לעולם אינן עוקפות את שלב הסקירה.

POST /admin/ossie/import
Content-Type: text/yaml   (or application/json)

<ossie document>
השרת מנתח את המסמך עם parse_ossie_model, שבודק את המבנה ומחזיר dataclass של OssieImport עם הטבלאות, הקשרים והמדדים המוצעים כמילונים פשוטים. כל בעיה מבנית היא 400 עם שגיאה הנקובה לפי נתיב, למשל ossie import: missing $.datasets[1].source. [tool-verified: import_ossie, provisa/api/admin/ossie_router.py: "Nothing is registered here — imported definitions never bypass registration review"]

המייבא קורא רק את הצורה השטוחה של המסמך. שלושה דברים נדחים בשמם:

  • מסמך שעוטף את המודל שלו ב-semantic_model — הצורה שהייתה לטיוטת המפרט לפני ספטמבר 2026. השגיאה אומרת שהמסמך קודם לצורה השטוחה, ואיך להמיר אותו.
  • version שאינה 0.2.0.dev0.
  • datatype שאינו איבר באוצר המילים של Ossie; השגיאה נוקבת בנתיב ומפרטת את האיברים.

עמודה או מדד מוצעים מקבלים את הסוג שה-datatype של Ossie שלהם מייצג (Integer מציע bigint, DateTime מציע timestamp). Opaque מציע את הסוג שהרחבת provisa נוקבת בו כשהמסמך הגיע מ-Provisa, ואף סוג אחרת. ai_context שניתן כאובייקט נקרא לפי ה-instructions שלו.

שדה מוצע כעמודה רק כשהביטוי שלו הוא עמודת מקור אחת. שדה שהביטוי שלו הוא חישוב אינו מוצע; מסך הסקירה נוקב בו תחת הטבלה שלו כלא מיובא. שדה שמשנה את שם העמודה שלו מוצע כאותה עמודה, עם שם השדה ככינוי העמודה. ה-source של dataset חייב להיות בן שלושה חלקים, source.schema.table; שאילתה או שם קצר יותר נדחים, והשגיאה אומרת זאת. [tool-verified: parse_ossie_model, _parse_dataset, _proposed_type, _ai_context, provisa/ossie/convert.py; tests/unit/test_ossie_convert.py]

מ-dbt, Cube, Snowflake ושכבות סמנטיות אחרות

פרויקט Apache Ossie מתחזק ממירים בין Ossie לבין פורמטי המודל של כלים אחרים. הריצו את הממיר של הכלי שלכם, ואז שלחו את הפלט שלו לנקודת הקצה של הייבוא או בחרו את הקובץ במסך הסקירה שלהלן. Provisa קוראת מסמך Ossie; היא אינה קוראת את הקבצים של כלי אחר.

מאיפה פקודת הממיר מה הוא קורא
dbt (שכבה סמנטית) ossie-dbt msi-to-ossie -i target/semantic_manifest.json -o semantic_model.yaml ה-semantic_manifest.json ש-dbt כותב עבור MetricFlow
Cube ossie-cube import -i model/ -o model.yaml תיקיית מודל של Cube או קובצי cube ו-view בודדים

[tool-verified: converters/dbt/README.md and converters/cube/README.md in the apache/ossie repository, read 2026-10-10]

באותו מאגר יש ממירים ל-Snowflake, Databricks, Microsoft, Salesforce, ThoughtSpot, Sigma, Omni, Hex, GoodData ואחרים; לכל אחד README משלו. [tool-verified: directory listing of converters/ in apache/ossie, 2026-10-10]

שתי מגבלות שכדאי לדעת לפני ההמרה:

  • ממיר ה-dbt קורא רק את מניפסט השכבה הסמנטית. לפרויקט dbt ללא מודלים סמנטיים אין מה להמיר בו. הוא משמיט מדדי המרה, מדדים פרטיים ו ישויות עם מפתח טבעי, ומדפיס אזהרה לכל אחד. [tool-verified: converters/dbt/README.md, the "MSI → Ossie is lossy" table]
  • פלט הממיר עוקב אחר המפרט כפי שהוא ב-commit של הממיר עצמו. Provisa קוראת את הצורה השטוחה של commit 642cb894 של apache/ossie; ממיר שקודם לספטמבר 2026 כותב את עטיפת semantic_model, ש-Provisa דוחה בשמה.

שני הנתיבים הורצו ב-2026-10-10 עם ממירים שהותקנו מ-commit 642cb894 של apache/ossie (הם אינם מתפרסמים כחבילות; הם מותקנים מתיקיות converters/dbt ו- converters/cube של המאגר ודורשים Python 3.11 ומעלה). מודל של שתי טבלאות, קשר אחד ומדד אחד יוצא מ-Provisa, הומר למניפסט סמנטי של dbt ולמודל Cube, הומר בחזרה ל-Ossie עם ossie-dbt msi-to-ossie ו- ossie-cube import, ונקרא על ידי parse_ossie_model:

נתיב תוצאה
מודל Cube ← Ossie ← Provisa נקרא. הטבלאות, המפתחות, סוגי העמודות, הקשר והמדד חזרו; שם הקשר היה שם הממיר (orders_to_customers).
מניפסט סמנטי של dbt ← Ossie ← Provisa נקרא. הטבלאות, המפתחות הראשיים והמדד חזרו. סוגי העמודות לא (פלט הממיר אינו מציין אותם), והקשר חזר כפי שהממיר הסיק אותו מישויות dbt, שאינו הקשר הרשום. בדקו את הקשרים לפני ההחלה.

[tool-verified: both runs, 2026-10-10, apache/ossie commit 642cb894, ossie-dbt and ossie-cube 0.2.0.dev0, metricflow 0.213.0; inputs were files those converters had written from a Provisa export, not files authored in dbt or Cube]

ממיר ה-dbt דוחה מודל שבו לטבלה כלשהי יש אילוץ ייחודיות על פני כמה עמודות ("MetricFlow entities cannot represent composite keys losslessly"). [tool-verified: ossie-dbt ossie-to-msi on the same export with such a constraint, 2026-10-10]

מסך הסקירה

בממשק, כפתור ייבוא (עמוד המדדים ← לוח Ossie Interchange) פותח בורר קבצים. אחרי שהמסמך נשלח ונותח, נפתחת חלונית סקירה שבה כל טבלה, קשר ומדד מוצעים מפורטים כפריטים מסומנים. המעצב יכול לבטל את הסימון של כל דבר כדי להחריג אותו. החלה רושמת את הפריטים המסומנים דרך מוטציות הרישום הקיימות — קודם הטבלאות, אחר כך הקשרים (שמפנים לטבלאות), ואז המדדים. [tool-verified: OssieInterchangePanel.tsx lines 88–165: "Review screen opens with everything checked; trimming = unchecking"; "Tables first, then relationships... then metrics — each through the EXISTING registration mutations (REQ-1316)"]

תפקיד המידול וההיסטוריה שנשמרו במסמך Ossie שיוצא מ-Provisa עוברים הלוך-ושוב נכון בייבוא. [tool-verified: _parse_dataset custom_extensions handling, provisa/ossie/convert.py lines 287–300: "REQ-1320: round-trip the provisa modeling metadata slot"]

קובץ sources של dbt (REQ-1967)

לפרויקט dbt שאינו משתמש בשכבה הסמנטית, Provisa כותבת sources.yml של dbt ישירות. הוא נגזר מהמודל החי בכל בקשה.

GET /admin/dbt/sources?role=<role id>
התגובה היא YAML עם Content-Disposition: attachment; filename=provisa.sources.yml. הקורא צריך org_settings, ו-role חייב להיות תפקיד שהוא מחזיק; מחזיק access_config רשאי לנקוב בכל תפקיד. לוח החילופים בעמוד המדדים מציע את אותה הורדה עבור התפקיד הפעיל. [tool-verified: download_dbt_sources, provisa/api/admin/ossie_router.py; TestTheEndpoint, tests/unit/test_dbt_sources.py]

הקובץ מפרט כל טבלה שהתפקיד מקבל, תחת source של dbt לכל מקור וסכמה (שנקרא לפי מזהה המקור, או <source>_<schema> כשלמקור יש טבלאות ביותר מסכמה אחת), עם תיאור הטבלה, התיאורים של העמודות המוגשות והבדיקות האלה:

המודל מציין הקובץ כותב
מפתח ראשי מעמודה אחת unique ו-not_null על אותה עמודה
מפתח ראשי מכמה עמודות not_null על כל עמודה
אילוץ ייחודיות מעמודה אחת unique על אותה עמודה
קשר בין שתי טבלאות ששתיהן מוגשות לתפקיד relationships על עמודת המקור, אל source('<source>', '<table>')

עמודה שאינה מוגשת לתפקיד חסרה, וכך גם טבלה ללא עמודות מוגשות. למפתח על פני כמה עמודות אין בדיקת dbt סטנדרטית; הערת הפתיחה של הקובץ נוקבת בכל מפתח כזה, ו לא נכתבת עבורו בדיקה. המודל אינו רושם אפשרות NULL, ולכן not_null מופיע רק בעמודות מפתח. הקובץ אינו נושא מדדים, אבטחה ברמת השורה, מיסוך, תפקידים או lineage, והערת הפתיחה שלו אומרת זאת. [tool-verified: build_dbt_sources, provisa/dbt/sources.py; tests/unit/test_dbt_sources.py]

קובץ שנוצר ממודל של שתי טבלאות וקשר אחד נותח על ידי dbt-core 1.12.5 עם dbt-duckdb 1.11.0: שני sources וחמש בדיקות, ללא אזהרות. dbt-core 1.9.11 אינו קורא את בדיקות ה-relationships שלו, שמקננות את הארגומנטים שלהן תחת arguments; גרסאות ביניים לא נבדקו. [tool-verified: dbt parse and dbt ls on both versions, 2026-10-10; dbt test against data was not run]


מדדים (Metrics) על פני פרוטוקולים (REQ-1319)

ההגדרה של מדד מנוהל — הביטוי, התיאור, ו-ai_context שלו — נוסעת עם הערך לתוך כל משטח שאילתה דרך הרחבת מהדר (compiler) אחת. אין עותקים. המהדר שומר את הסכמה metrics עבור גישת SQL; כל פרוטוקול מוסיף אז ערוץ metadata משלו.

[tool-verified: METRICS_SCHEMA = "metrics", provisa/compiler/metric_expand.py line 43; REQ-1319 requirement text: "the definition (description, ai_context) travels with the value everywhere, with no copies"]

SQL / pgwire

כתבו לכל מדד ככיחס וירטואלי (virtual relation) בסכמת ה-metrics. עמודות הממד שאתם בוחרים הופכות ל-GROUP BY:

-- Grand total
SELECT value FROM metrics.net_revenue;

-- By region
SELECT region, value FROM metrics.net_revenue GROUP BY region;

-- By region and month, filtered
SELECT region, month, value
FROM metrics.net_revenue
WHERE net_revenue.status = 'completed'
GROUP BY region, month;

המהדר מרחיב את הצורה metrics.<name> לצירוף (aggregate) המקובץ האמיתי לפני שהממשל רץ. תיאורי עמודות מוצגים כרשומות pg_description, כך ש-DBeaver ו-\d+ ב-psql מציגים אותם. [tool-verified: metric_semantic_sql, provisa/compiler/metric_expand.py lines 52–70; REQ-1319: "description surfaced via pg_description"]

SELECT * נדחה — ציינו את העמודות במפורש. [tool-verified: expand_metric_query, provisa/compiler/metric_expand.py lines 302–306]

GraphQL

מדדים מוקרנים בתוך שדה השורש _aggregate כבלוק metrics. [inferred: per REQ-1319; aggregate_gen.py not read in this session]

טקסט ההגדרה (description, ai_context) מופיע בתיעוד ה-introspection של GraphQL, כך שכלים מודעי-סכמה ו-codegen קולטים אותו אוטומטית. [inferred: per REQ-1319: "definition in introspection docs"]

MCP (סוכני AI)

שני כלים חושפים מדדים ללקוחות MCP:

  • list_metrics — מחזיר את כל המדדים המנוהלים הגלויים לסשן, עם name, description, ו-ai_context.
  • query_metric — מקבל שם מדד ורשימת ממדים וקורא לנתיב ה-SQL הסמנטי של המהדר, ומחזיר את תוצאת הצירוף.

[inferred: per REQ-1319: "MCP: list_metrics and query_metric tools carrying ai_context, so agents select governed meanings instead of composing aggregation SQL"; provisa/api/mcp/tools.py not read in this session]

סוכנים שקוראים ל-list_metrics לפני בניית שאילתה בוחרים מדד מנוהל לפי שם במקום לכתוב SQL צירוף (aggregation) ידנית. שדה ה-ai_context הוא המקום להציב בו את טקסט ההגדרה שמנחה בחירה נכונה.

Arrow Flight

מדדים ניתנים לכתובת כתיאורי טיסה (flight descriptors) של metric המחזירים טבלאות Arrow. [inferred: per REQ-1319: "Arrow Flight: metric flight descriptors returning Arrow tables"; provisa/api/flight/catalog.py not read in this session]

השתמשו באותה צורת SQL metrics.<name> דרך נתיב ticket הסטנדרטי של Flight SQL.

Bolt / Cypher (Neo4j Browser)

קריאה למדד באמצעות הפרוצדורה provisa.metric():

CALL provisa.metric('net_revenue', ['region']) YIELD region, value

[inferred: per REQ-1319: "Bolt/Cypher: a provisa.metric() procedure"; the procedure signature is inferred from the REQ text and not verified against provisa/bolt/session.py in this session]

טבלאות Fact ו-Dimension נושאות תוויות צומת :Fact ו-:Dimension בגרף הפדרטיבי, כך ש-Bloom מרנדר את צורת הכוכב אוטומטית. [inferred: per REQ-1319 and REQ-1320: "federated graph labels nodes :Fact/:Dimension so Bloom renders the star"; provisa/cypher/label_map.py not read in this session]

שאילתות בשפה טבעית

מתאם הסכמה של NL פותר אוצר מילים של מדדים בשאלות בשפה טבעית ישירות למדד ולממדים, ואז מייצר SQL סמנטי. [tool-verified: resolve_metric, provisa/nl/schema_matcher.py is exercised in test_nl_metrics.py lines 76–78: sql = matcher.resolve_metric("What is the total revenue by region?") → "SELECT region, value FROM metrics.total_revenue GROUP BY region"]

טבלאות Fact מתויגות [fact] בפרומפט ה-NL; טבלאות dimension מתויגות [dimension]. המתאם מטה נתיבי join מ-fact ל-dimension בעת פתירת שאלות. [tool-verified: test_format_entities_tags_star_roles, tests/unit/test_nl_metrics.py lines 129–132: assert "table: orders [fact] fields: amount" in block]

זרימה (Streaming)

שילוב view_metrics עם materialize שומר view של מדדים עדכני כשהקלטים שלו משתנים, באמצעות מכניקת ההמחשה הקיימת. אין צורך ב-pipeline חדש. [inferred: per REQ-1319. The table Kafka sink that requirement named is retired (REQ-176); a table publishes its new rows to Kafka through a live output (live.outputs, REQ-565)]

Observability (OTel)

הערכות מדד עוקבות (traced) וניתנות לייצוא כמדדי OpenTelemetry. [inferred: per REQ-1319: "Observability: metric evaluations traced and exportable as OTel metrics"; OTel integration code not read in this session]