Tipi di origine¶
Modello di esecuzione¶
Ogni query finisce per passare dal motore di federazione, ed è questo che permette di unire qualsiasi origine a qualsiasi altra. Come un'origine ci arriva dipende da come Provisa la raggiunge, e questo mette ogni origine in uno di cinque gruppi.
| Categoria | Ha driver diretto | Ha connettore federato | Esempi |
|---|---|---|---|
| Direct-capable | Sì | Sì | PostgreSQL, MySQL, MariaDB, SingleStore, SQL Server, Oracle, DuckDB |
| Solo federazione | No | Sì | Redshift, Druid, Exasol, Hive, Iceberg, Delta Lake, Hive (basato su S3) |
| Direct-read (replica) | Sì | Sì | Snowflake, Databricks, ClickHouse — il driver legge i dati e atterra una replica; le query girano contro la replica nel motore attivo |
| Direct (nessun connettore cross-source) | Sì | No | Athena — le istruzioni su singola origine vengono eseguite interamente in Athena; i join tra origini diverse atterrano una replica completa |
| Materializza → Federazione | No | No | REST/OpenAPI, GraphQL remoto, gRPC, Neo4j Cypher, SPARQL, WebSocket, RSS, CSV, SQLite, Parquet, Ingest (ricevitore push), GovData, SharePoint, Salesforce, ServiceNow, Cloud inventory, Splunk, Qdrant, Azure Cosmos DB, Logs, Google Workspace, Microsoft 365 |
Le origini Direct-capable rispondono a una query che tocca solo loro tramite il proprio driver, in meno di 100 ms, senza passare dal motore di federazione. Mantengono il connettore completo, quindi si uniscono ancora ad altre origini tramite il motore.
Le origini Solo federazione non hanno un driver diretto. Ogni query le raggiunge attraverso il layer di federazione.
Le origini Direct-read (replica) hanno un DirectDriver che legge dal warehouse nativamente (Arrow-native dove possibile) e atterra una replica nello store di materializzazione del motore attivo. Le query girano poi contro quella replica. Vedi Warehouse come origini nominate.
Le origini Direct (nessun connettore cross-source) hanno un DirectDriver che esegue un'istruzione su singola origine interamente nell'origine, con ogni filtro, colonna e limite spinto fino a essa. Non esiste un connettore Trino per loro, quindi quando un'istruzione ne unisce una a un'altra origine, il driver legge i dati e atterra una replica, e il join gira contro quella replica nel motore attivo.
Nemmeno le origini Materialize hanno un connettore federato. Provisa recupera i loro dati, all'avvio o al momento della query, e li mette in cache come Parquet su S3 o in PostgreSQL. Da lì il motore di federazione li raggiunge per le query cross-source.
Tutte le origini¶
Provisa registra 73 tipi di origine, quelli che il modulo Sources offre. Le tabelle qui sotto le coprono tutte e 73; l'indice è il conteggio.
| # | Gruppo | Tipi di origine |
|---|---|---|
| 1–15 | RDBMS | postgresql, mysql, mariadb, singlestore, sqlserver, oracle, duckdb, cockroachdb, yugabytedb, greenplum, tidb, firebird, airport, saphana, hiveserver2 |
| 16–24 | Data warehouse cloud | snowflake, bigquery, databricks, redshift, athena, fabric, synapse, trino, motherduck |
| 25–29 | Analytics / OLAP | clickhouse, druid, exasol, elasticsearch, pinot |
| 30–33 | Data Lake / formati tabella aperti | iceberg, delta_lake, hive, hive_s3 |
| 34–38 | NoSQL | mongodb, cosmosdb, qdrant, cassandra, redis |
| 39–40 | Sorgenti di codice | node, python |
| 41–43 | Streaming | kafka, websocket, rss |
| 44 | Ricevitore push | ingest |
| 45–46 | Grafo e semantico | neo4j, sparql |
| 47–51 | Basate su file | sqlite, csv, parquet, files, logs |
| 52–53 | Osservabilità e altro | google_sheets, prometheus |
| 54–61 | Connettori SaaS enterprise | sharepoint, salesforce, servicenow, jira, cloudops, splunk, google_workspace, microsoft_365 |
| 62–67 | Origini API | openapi, graphql_remote, grpc_remote, GitHub, GitLab, Stripe |
| 68 | GovData | govdata |
| 69–70 | Controlli di qualità dei dati | soda, great_expectations |
| 71 | Data profiler | data_profiler |
| 72 | Dataset Kaggle | Kaggle (messo in stage tramite il modulo Sources; si registra come origine files — vedere Dataset Kaggle) |
| 73 | Wikipedia | Wikipedia (si registra come origine files che scansiona le pagine indicate — vedere Wikipedia) |
Riferimento per ogni tipo di origine supportato da Provisa. "Driver diretto" significa che le query a singola origine vengono eseguite nativamente contro l'origine (sub-100ms). "Nome connettore" è il connettore federato usato quando l'origine partecipa a JOIN multi-source.
RDBMS¶
| Tipo di origine | Driver diretto | Nome connettore | Dialetto | Mutation |
|---|---|---|---|---|
postgresql |
asyncpg | postgresql | postgres | Sì |
mysql |
aiomysql | mysql | mysql | Sì |
mariadb |
aiomysql | mariadb | mysql | Sì |
singlestore |
— | singlestore | singlestore | Federata |
sqlserver |
aioodbc | sqlserver | tsql | Sì |
oracle |
oracledb | oracle | oracle | Sì |
duckdb |
duckdb | memory | duckdb | Sì |
cockroachdb |
asyncpg (pg wire) | postgresql | postgres | Sì |
yugabytedb |
asyncpg (pg wire) | postgresql | postgres | Sì |
greenplum |
asyncpg (pg wire) | postgresql | postgres | Sì |
tidb |
aiomysql (mysql wire) | mysql | mysql | Sì |
firebird |
— | — (estensione DuckDB) | — | No |
airport |
— | — (estensione DuckDB) | — | No |
saphana |
SQLAlchemy (hana+hdbcli) |
— | — | No |
hiveserver2 |
impyla (HiveServer2 over Thrift) | — | hive | No |
I database compatibili a livello di protocollo riusano il driver JDBC, il driver asincrono nativo e il dialetto di un protocollo di base. CockroachDB, YugabyteDB e Greenplum usano il protocollo PostgreSQL, e TiDB quello MySQL. Servono solo voci di registro, senza nuovo codice di connettore.
firebird (Firebird 3/4/5) e airport (server Arrow Flight) sono tipi di origine registrati che le estensioni della community di DuckDB raggiungono sul posto quando DuckDB è il motore attivo. Non hanno driver diretto né connettore federato.
saphana¶
SAP HANA viene letto tramite il proprio driver e replicato nello store del motore attivo, quindi è raggiungibile su ogni motore. Il modulo richiede host, porta (predefinita 39015), nome utente, password e database; su un sistema multi-tenant il database indica il tenant. Registra tabella elenca gli schemi che l'utente può leggere, escludendo gli schemi di sistema di HANA.
hiveserver2¶
Un endpoint HiveServer2 letto direttamente su Thrift, per un deployment Hive in cui l'endpoint è l'unica cosa raggiungibile. È distinto da hive e hive_s3, che leggono i file e il metastore del warehouse e non passano mai da HiveServer2. Le righe vengono lette attraverso l'endpoint e replicate nello store del motore attivo. Il modulo richiede host, porta (predefinita 10000), nome utente, password e database; federation_hints.auth_mechanism seleziona l'autenticazione (predefinita PLAIN, ad esempio GSSAPI per Kerberos).
Data warehouse cloud¶
| Tipo di origine | Driver diretto | Nome connettore | Dialetto | Mutation | Note |
|---|---|---|---|---|---|
snowflake |
SnowflakeDriver | snowflake | snowflake | Federata | Legge via snowflake-connector-python; atterra una replica; account/warehouse/role in federation_hints |
bigquery |
— | bigquery | bigquery | Federata | Nessun DirectDriver; raggiunta via motore di federazione o ATTACH del motore BigQuery |
databricks |
DatabricksDriver | delta_lake | databricks | Federata | Legge via databricks-sql-connector (Cloud Fetch, Arrow); atterra una replica; http_path richiesto in federation_hints |
redshift |
— | redshift | redshift | Federata | — |
athena |
AthenaDriver | — | athena | Parziale (INSERT sempre; UPDATE/DELETE/MERGE solo su Iceberg) | Query serverless su S3; le istruzioni su singola sorgente vengono eseguite interamente in Athena; i join tra sorgenti diverse atterrano una replica; region obbligatoria in federation_hints |
fabric |
MssqlWarehouseDriver | — | tsql | Federata | Microsoft Fabric Warehouse; T-SQL su TDS, auth Azure AD; atterra una replica |
synapse |
MssqlWarehouseDriver | — | tsql | Federata | Azure Synapse SQL; T-SQL su TDS, auth Azure AD; atterra una replica |
trino |
SQLAlchemyDriver | — | — | Federata | Coordinator Trino/Presto remoto letto via il dialetto SQLAlchemy trino; atterra una replica su qualsiasi motore |
athena¶
Amazon Athena è un servizio di query serverless sui dati in S3, fatturato per byte scansionato. Un'istruzione solo sulle tabelle di Athena va direttamente ad Athena, con filtri, elenchi di colonne e LIMIT spinti fino a essa, così Athena scansiona e fattura solo i byte che servono a quell'istruzione. Un'istruzione che unisce una tabella Athena a qualsiasi altra origine atterra prima una replica completa da Athena. Questo legge l'intera tabella, e Athena la fattura; poi il join gira contro la replica. Le impostazioni Replicate e di protezione dal carico dell'origine decidono ogni quanto avviene un atterraggio, e questa nota è l'unico avviso su quel costo.
Rilevamento. Register Table chiama l'API dei metadati di Athena: ListDatabases, ListTableMetadata e GetTableMetadata. Queste chiamate non eseguono alcuna query, quindi elencare database e tabelle e leggere le colonne di una tabella non costa nulla per byte. Le colonne di una tabella vengono recuperate solo quando selezionate quella tabella, con le chiavi di partizione dopo le colonne normali. array<…>, map<…> e struct<…> diventano text, e un tipo sconosciuto viene rifiutato per nome.
Campi sorgente.
| Campo | Dove | Obbligatorio | Predefinito | Note |
|---|---|---|---|---|
database |
sorgente | Sì | — | Il database Glue interrogato per impostazione predefinita |
username |
sorgente | No | — | ID chiave di accesso AWS; può essere ${secret:…} o ${env:…} |
password |
sorgente | No | — | Chiave di accesso segreta AWS; può essere ${secret:…} o ${env:…} |
region |
federation_hints |
Sì | — | Regione AWS, es. us-east-1 |
workgroup |
federation_hints |
No | primary |
Gruppo di lavoro Athena |
s3_staging_dir |
federation_hints |
No | predefinito del gruppo | Dove Athena scrive i risultati; obbligatorio se il gruppo di lavoro non ne imposta uno |
catalog |
federation_hints |
No | AwsDataCatalog |
Catalogo dati Athena |
role_arn |
federation_hints |
No | — | ARN del ruolo IAM assunto per ogni chiamata |
Entrambi username e password vuoti significa che viene utilizzata la catena di credenziali di boto3: ruolo dell'istanza, variabili d'ambiente (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) o un profilo con nome. Uno senza l'altro viene rifiutato.
Scritture. Ogni tabella registrata accetta INSERT. UPDATE, DELETE e MERGE funzionano solo su una tabella Iceberg, e il formato viene annotato alla registrazione (registered_tables.table_format). Su una tabella Hive, una scrittura che chiede UPDATE, DELETE o MERGE viene rifiutata indicando la tabella e l'operazione. Athena non restituisce righe scritte, quindi una clausola RETURNING viene rifiutata e GraphQL non offre campi di mutation per queste tabelle. I percorsi di scrittura sono INSERT, UPDATE, DELETE e COPY. INSERT … ON CONFLICT, UPDATE … FROM, DELETE … USING e TRUNCATE vengono rifiutati indicando la forma.
- id: analytics-athena
type: athena
database: my_glue_database
username: ${env:ATHENA_ACCESS_KEY_ID}
password: ${env:ATHENA_SECRET_ACCESS_KEY}
federation_hints:
region: us-east-1
workgroup: primary
s3_staging_dir: s3://my-bucket/athena-results/
Con un ruolo assunto per ogni chiamata (catena di credenziali per la sessione base):
- id: analytics-athena
type: athena
database: my_glue_database
federation_hints:
region: us-east-1
role_arn: arn:aws:iam::123456789012:role/AthenaQueryRole
s3_staging_dir: s3://my-bucket/athena-results/
MotherDuck¶
motherduck è un database DuckDB ospitato da MotherDuck. Sul motore DuckDB viene collegato sul posto; sul motore Postgres resta il connettore del foreign data wrapper pg_duckdb.
Connessione. database indica il database MotherDuck, oppure una condivisione tramite il suo indirizzo di condivisione. password è il token MotherDuck, risolto come qualsiasi segreto di origine.
- id: reference
type: motherduck
database: analytics # attached as md:analytics
password: ${secret:<name>} # the MotherDuck token, or a reference to it
Il motore DuckDB esegue ATTACH 'md:<database>'; per una condivisione, database è l'indirizzo della condivisione (_share/...), quindi il collegamento è md:_share/....
- Gestione del token. Il motore accede con
SET GLOBAL motherduck_token = ?, con il token passato come valore associato. Non compare nel testo di alcuna istruzione, quindi nessun log, trace o errore lo contiene. - Nessun token. Un'origine senza
passwordviene rifiutata con il suo nome prima che venga collegato qualcosa (a MotherDuck source signs in with its token (the password field); none is set). Senza questo controllo, l'estensione avvierebbe un accesso interattivo e attenderebbe circa un minuto. Anche un'origine senzadatabaseviene rifiutata. - Un account per motore. Un motore accede a un solo account MotherDuck. Una seconda origine MotherDuck con un token diverso viene rifiutata con il suo nome: "one DuckDB engine reads one MotherDuck account".
- Le scritture passano dal motore, come sul motore Postgres.
L'esecuzione ibrida (duale) non richiede alcuna impostazione. Sul motore DuckDB ogni origine è collegata su un'unica connessione, quindi un'istruzione che unisce una tabella di un'origine file DuckDB con una tabella MotherDuck viene eseguita come un'unica istruzione DuckDB, e MotherDuck decide dove viene eseguita ciascuna parte.
Spostamento dei dati. Con l'esecuzione duale, MotherDuck può spostare righe locali dalla sua parte per le parti che esegue. Un'istruzione di questo tipo può quindi inviare dati dal file locale all'account MotherDuck dell'organizzazione. Non unire un file con dati che devono restare sul nodo a un'origine MotherDuck, a meno che inviarli a quell'account sia accettabile.
Un'origine MotherDuck è servita solo dai motori DuckDB e Postgres. Nessun altro motore ha un connettore per essa, e nulla vi atterra le sue tabelle come repliche.
Analytics / OLAP¶
| Tipo di origine | Driver diretto | Nome connettore | Dialetto | Mutation | Note |
|---|---|---|---|---|---|
clickhouse |
ClickHouseDriver | clickhouse | clickhouse | Federata | Legge via clickhouse-connect (HTTP); secure: "true" in federation_hints per TLS |
druid |
— | druid | druid | No | — |
exasol |
— | exasol | exasol | No | — |
elasticsearch |
HTTP (motori nativi) | elasticsearch (Trino) | — | No | Su Trino il connettore lo legge, le sue proprietà provengono dal DSL di mapping del tipo; su ogni altro motore Provisa legge l'indice via HTTP (indici e mapping per Registra tabella, una scroll read per l'atterraggio) e atterra le righe |
pinot |
— | pinot | — | No | Connettore Trino pinot; pinot.controller-urls = host:porta del controller Pinot |
Data Lake / formati tabella aperti¶
Questi tipi di origine sono solo-federazione. Non hanno né driver diretto né dialetto.
| Tipo di origine | Nome connettore | Time Travel | Note |
|---|---|---|---|
iceberg |
iceberg | Sì (argomento as_of) |
— |
delta_lake |
delta_lake | Sì (argomento as_of) |
— |
hive |
hive | No | — |
hive_s3 |
hive | No | Hive basato su S3 |
NoSQL¶
mongodb, cassandra e redis hanno connettori Trino (redis costruisce le sue proprietà dal DSL di mapping del tipo).
| Tipo di origine | Nome connettore | Mutation |
|---|---|---|
mongodb |
mongodb | No |
cassandra |
cassandra (Trino); lettura CQL tramite cassandra-driver su ogni altro motore | No |
redis |
redis (Trino); lettura redis-py senza HTTP su ogni altro motore | No |
Redis sui motori ospitati. Redis non è disponibile quando il motore dell'organizzazione è uno dei motori predisposti della piattaforma ospitata. Trino legge le tabelle di un catalogo Redis da file di descrizione in una directory propria del catalogo, e a un motore ospitato non se ne può dare una mentre è in esecuzione. Salvare o applicare lì una sorgente Redis viene rifiutato per nome (HTTP 422, sources.redis_needs_engine_files); un deployment self-hosted legge Redis come descritto sopra.
Qdrant¶
qdrant viene letto e scritto dal client proprio di Qdrant, qdrant-client. Qdrant è solo live: una proprietà del tipo di origine, non un'impostazione. Risponde con il suo indice vettoriale e la sua Query API, e nessuna replica può rispondere allo stesso modo, quindi le sue tabelle non vengono mai replicate. Non esiste la scelta Replicate per un'origine Qdrant né per le sue tabelle; un valore replicate o row_materialize su una di esse viene rifiutato.
Connessione. host è l'URL dell'istanza con la sua porta (https://<cluster>.cloud.qdrant.io:6333, oppure un http://qdrant:6333 self-hosted); password è la sua chiave API, un riferimento a un secret risolto quando l'origine viene letta, vuota per un'istanza senza chiave. federation_hints.prefer_grpc: true legge dalla porta gRPC di Qdrant invece che via REST. L'origine ha uno schema, default.
Tabelle di collection. Una collection è una tabella. Registra tabella offre le colonne della collection: id (l'id del punto, la sua chiave, percorso @id), i campi del payload (un campo indicizzato tipizzato dal suo indice, ogni altro come suggeriscono i punti campionati; i campi annidati appiattiti con il loro percorso) e una colonna json per vettore (percorso @vector, o @vector.<name> per un vettore con nome), offerta non selezionata. Una colonna vettoriale indica il modello di embedding registrato di cui contiene gli embedding, scelto tra i modelli le cui dimensioni sono la dimensione del vettore (in Registra tabella e nella scheda Tipo e chiavi dell'editor); scegliere un modello registra il vettore.
Istruzioni su un'origine Qdrant. Un'istruzione che legge una sola collection viene eseguita in Qdrant quando l'API di Qdrant la esprime esattamente:
- le condizioni sul payload diventano un filtro Qdrant (
=,<>,IN, intervalli su numeri e date,IS NULL,AND,OR,NOT); la semantica di NULL di SQL è mantenuta, quindilang <> 'en'salta i punti senzalang; - una condizione solo su
id(=oIN) è unretrieve; ogni altra lettura è unoscrollfiltrato; ORDER BY cosine_similarity(<vector>, <query>) DESC LIMIT kè unaquery_pointssu un vettore la cui distanza è Cosine, con il filtro applicato prima del top-k. Un testo di query viene convertito in embedding dal modello del vettore. La risposta porta un avviso che il top-k è approssimato, scelto tramite l'indice di Qdrant.
Tutto il resto (un aggregato, un GROUP BY, una colonna calcolata o mascherata, un ordinamento diverso dal più simile per primo) è risolto dal motore, come sotto.
Istruzioni risolte dal motore. Quando un'istruzione unisce una collection a un'altra origine, o calcola sulle sue colonne, la parte della collection viene letta in diretta da Qdrant per quell'istruzione e consegnata al motore come righe. La parte comprende le colonne della collection, le condizioni dell'istruzione sulla sola collection (comprese le sue regole di riga) e, quando la collection è letta da sola nel suo SELECT, l'ordinamento per similarità e il LIMIT di quel SELECT. Una parte che Qdrant non può esprimere esattamente viene rifiutata, indicando la tabella e il motivo. Una parte senza condizione, senza chiave e senza top-k leggerebbe l'intera collection e viene rifiutata anch'essa. Una condizione su un parametro ($1) è lasciata al motore.
Scritture. INSERT, UPDATE e DELETE su una collection vengono eseguiti in Qdrant:
INSERT … VALUESindica la colonnaide scrive nuovi punti. Unidche la collection contiene già viene rifiutato per nome. Il controllo legge prima gli id, quindi non è atomico con la scrittura.INSERT … ON CONFLICT (id) DO UPDATE SET c = EXCLUDED.c, …è un upsert. Le colonne che l'istruzione non imposta mantengono i loro valori.UPDATEeDELETEagiscono sui punti selezionati dal loroWHERE, come filtro Qdrant; una condizione senza forma di filtro viene rifiutata.- Un testo scritto in una colonna vettoriale viene convertito in embedding dal modello della colonna. Un vettore scritto in essa deve avere le dimensioni del modello.
Tabelle di ricerca. La ricerca vettoriale è una tabella di ricerca: il suo query_template contiene una richiesta della Query API.
{"collection": "docs", "query": "@query_vector", "using": "text",
"filter": {"must": [{"key": "lang", "match": {"value": "@lang"}}]},
"limit": "@k", "score_threshold": 0.5, "model": "text-embedding-3-small"}
queryè sempre un argomento;using,filter,limit,score_thresholdemodelsono opzionali. Qualsiasi valore diusing,filter,limitoscore_thresholdche sia esattamente"@name"è anch'esso un argomento.- Ogni argomento è un parametro della tabella (
query_arg), convertito rigorosamente nel tipo dichiarato e inserito nella richiesta come valore JSON, mai nel suo testo. L'argomento diqueryèarray(double)ed è obbligatorio. - Un chiamante passa gli argomenti come qualsiasi parametro:
SELECT id, score, title FROM docs_search WHERE _nf_query_vector = '[0.1, 0.2, …]' AND _nf_lang = 'en'in SQL, argomenti di campo in GraphQL. - Un argomento opzionale non dato non filtra nulla: la sua condizione viene omessa dal filtro. Senza
limit, una ricerca restituisce il valore predefinito di Qdrant, 10 punti. - Il filtro è applicato da Qdrant prima di scegliere i suoi punti più vicini, quindi è esatto. Qualsiasi altra condizione dell'istruzione restringe in seguito i punti restituiti dalla ricerca; non ne recupera altri.
scoreè la metrica propria della collection per il vettore cercato: per una collection Cosine, la similarità del coseno. Registra tabella elenca i vettori della collection con dimensione e distanza.modelnomina un modello di embedding registrato (vector_models, admin → AI Models). Un testo dato per la query viene allora incorporato da quel modello, e un vettore dato deve avere le dimensioni di quel modello. Senzamodel, la query deve essere un vettore.
Una ricerca viene letta per istruzione con i suoi argomenti, le sue righe conservate sotto quegli argomenti per il TTL della tabella. Una tabella di ricerca è di sola lettura.
Streaming¶
| Tipo di origine | Meccanismo | Mutation |
|---|---|---|
kafka |
Connettore Kafka federato; schema via Confluent Schema Registry (Avro, Protobuf, JSON Schema), definizione manuale, o inferenza da campione | No; l'output live di una tabella pubblica su Kafka |
websocket |
Feed WebSocket esterno — connette, si iscrive, riceve eventi; risultati materializzati | No |
rss |
Feed RSS 2.0 / Atom — polling, watermark per pubDate/updated; risultati materializzati | No |
Kafka sui motori ospitati. Sui motori predisposti della piattaforma ospitata, una sorgente Kafka deve nominare uno schema registry (schema_registry_url). Una sorgente le cui colonne dei topic sono dichiarate solo dal modello viene rifiutata per nome al salvataggio o all'applicazione (HTTP 422, sources.kafka_needs_schema_registry), perché le sue descrizioni di tabella richiederebbero una directory che non si può dare al motore in esecuzione. Le credenziali SASL raggiungono quei motori in memoria (vedere Sicurezza).
Ricevitore push¶
| Tipo di origine | Meccanismo | Mutation |
|---|---|---|
ingest |
Servizi esterni inviano eventi JSON via POST; risultati materializzati | No |
Grafo e semantico¶
| Tipo di origine | Meccanismo | Mutation |
|---|---|---|
neo4j |
Cypher via API HTTP, risultati messi in cache in PostgreSQL | No |
sparql |
SPARQL 1.1 POST, risultati messi in cache in PostgreSQL | No |
Basate su file¶
Tre meccanismi coprono i file. Le origini a file singolo e il crawler di file usano il campo path invece di host/port. Il terzo, Logs, indica i propri file con un glob nel suo mapping e legge solo file presenti sul server Provisa.
Origini database su file e a file singolo. Un'origine sqlite, duckdb, csv o parquet punta path a un solo file. (Un'origine duckdb tiene la propria posizione nel campo database del modulo.)
| Tipo di origine | Posizioni | Mutation |
|---|---|---|
sqlite |
percorso locale, https://, s3://, gs://, az:// |
Sì su un file locale; nessuna su uno remoto |
duckdb |
percorso locale, https://, s3://, gs://, az:// |
Sì su un file locale; nessuna su uno remoto |
csv |
percorso locale, https://, s3://, gs://, az:// (motore DuckDB) |
No |
parquet |
percorso locale, https://, s3://, gs://, az:// (motore DuckDB) |
No |
Un percorso con qualsiasi altro schema (ad esempio ftp://) viene rifiutato: "DuckDB reads no file at a ftp:// location".
Credenziali per una posizione remota. Inseritele nei federation_hints dell'origine. Ciascuna è limitata al bucket o al container del percorso stesso, quindi le credenziali di un'origine non vengono mai usate per la posizione di un'altra origine. Un'origine che non ne fornisce viene letta come posizione pubblica.
| Posizione | Chiavi federation_hints |
|---|---|
s3:// |
access_key_id, secret_access_key; facoltativi region, endpoint (uno storage compatibile con S3 come R2 o MinIO) |
gs:// |
gcs_access_id, gcs_secret_key (una chiave HMAC) |
az:// |
azure_account_name, azure_account_key |
I file SQLite e DuckDB remoti sono di sola lettura. Il motore DuckDB li collega sul posto con READ_ONLY, e una tabella su uno di essi non offre insert, update o delete. Su un motore che non collega SQLite, un file SQLite remoto viene letto tramite una connessione DuckDB temporanea che lo collega in sola lettura, e le sue righe atterrano come di consueto.
Caricare un file. Nel modulo di un'origine sqlite, duckdb, csv o parquet, caricate il file invece di digitare un percorso. Il file viene scritto su disco in streaming, mai tenuto in memoria, nella cartella dell'organizzazione sotto files/<source id>/<file name>, e il percorso dell'origine viene impostato su di esso.
- Sostituzione. Un nuovo caricamento prende il posto del file precedente, qualunque sia il suo nome, e l'origine viene ripristinata così che la lettura successiva veda il nuovo file.
- Rimozione. Rimuovere l'origine rimuove il file caricato. Nient'altro lo rimuove.
- Chi. Solo i ruoli con la capability
source_registrationpossono caricare. - Limite di dimensione. L'impostazione
sources.max_upload_bytes(ambientePROVISA_SOURCES_MAX_UPLOAD_BYTES) limita il file, 1 GiB (1073741824 byte) per impostazione predefinita, minimo 1, letta all'inizio di un caricamento. Un file più grande viene rifiutato con HTTP 413 indicando il limite, e non ne viene conservato nulla. - Percorso. Finché il percorso è il file caricato dell'origine, il campo percorso del modulo è di sola lettura.
- Nomi. L'id dell'origine e il nome del file possono contenere solo lettere, cifre,
.,_e-. - Archiviazione. Un file caricato viene archiviato come qualsiasi file dell'organizzazione: nella cartella dell'organizzazione, cifrato quando lo è quella cartella, e visibile a ogni nodo su un mount condiviso.
Scritture su un file SQLite o DuckDB locale, e lock. Una scrittura prende il lock del file e non lo attende mai. Quando un altro processo o nodo detiene il file, la scrittura viene rifiutata, indicando l'origine e che il suo file è bloccato.
- SQLite. La scrittura imposta
PRAGMA busy_timeout = 0, quindi un file bloccato fallisce subito. - DuckDB. DuckDB condivide un file solo tra aperture in sola lettura. Per questo il collegamento del motore e la connessione propria dell'origine tengono il file in sola lettura, e ogni nodo lo legge contemporaneamente. Una scrittura sospende i detentori di questo processo, apre il file in lettura e scrittura solo per quell'istruzione e lo chiude. Se l'apertura non riesce a prendere il lock, la scrittura viene rifiutata.
Mount condivisi. In una cartella dell'organizzazione cifrata il cui mount è condiviso da più nodi (impostazione org_files.shared_mount), una scrittura su un file SQLite o DuckDB al suo interno viene rifiutata direttamente, indicando l'origine. Lo strato di cifratura delle cartelle (gocryptfs) non porta i lock dei file agli altri nodi, quindi nulla impedirebbe a due nodi di scrivere contemporaneamente. Le letture funzionano comunque.
Una cartella condivisa senza cifratura deve essere montata con lock che raggiungono ogni nodo: un mount NFS con il locking attivo, non nolock e non local_lock. Nessuna verifica lo controlla; è un requisito di deployment.
Crawler di file (origine files). Punta path a un glob, lo percorre ricorsivamente e registra la directory come catalogo federato di tabelle. Legge molti formati attraverso molti trasporti:
| Formati | Trasporti |
|---|---|
| CSV, TSV, JSON, YAML, Excel (XLS/XLSX), Parquet, Arrow, e documenti convertiti in tabelle — HTML, Markdown, DOCX, PPTX | Filesystem locale, HTTP(S), s3://, hdfs://, ftp:///ftps://, sftp://, iceberg://, SharePoint (REST e Microsoft Graph) |
- id: sales_files
type: files
path: s3://bucket/sales/**/*.csv # glob; local and http(s):// also supported
Qualsiasi origine files può anche dichiarare un crawl nel proprio mapping, con le stesse impostazioni elencate sotto Wikipedia: l'origine legge prima le pagine web e atterra ogni tabella di dati HTML e ogni file di dati collegato nella propria directory come tabella.
Sul motore DuckDB, files viene letto nativamente, con una vista di scansione read_csv_auto per ogni <table>.csv sotto la directory risolta. Su un motore senza un proprio connettore files, le righe atterrano tramite lo stesso server pgwire Calcite fornito con il connettore (pgwire-file) che usano sharepoint e splunk. Vedi Connettori SaaS enterprise più avanti.
Logs¶
Un'origine logs trasforma i file di log sul server Provisa in una tabella. I file si indicano con un percorso completo o con un glob, e si sceglie il formato in cui sono scritti i loro record. Ogni record diventa una riga. Un record che il formato non sa leggere viene conservato, con il testo grezzo e il motivo, mai scartato.
sources:
- id: mac-install-log
type: logs
mapping:
glob: /var/log/install.log* # the live file and its rotated, compressed copies
format: macos_install
Il mapping accetta quattro chiavi e rifiuta qualsiasi altra per nome: glob e format (entrambe obbligatorie), e pattern e record_start, che solo il formato regex accetta.
mapping:
glob: /var/log/myapp/*.log
format: regex
pattern: '(?P<ts>\S+ \S+) (?P<level>[A-Z]+) (?P<message>.*)'
record_start: '\d{4}-\d{2}-\d{2} ' # a stack trace joins the line it follows
Nel modulo Sources, un'origine Logs compare come "Log files" sotto File. Il pulsante Preview mostra i file che il glob seleziona, le colonne che il formato fornisce e le prime 20 righe del file più recente, prima che venga salvato qualsiasi cosa.
Il glob. Deve essere un percorso completo; ~ viene espanso e un percorso relativo viene rifiutato ("name the files by their full path"). La corrispondenza usa glob di Python in modalità non ricorsiva, quindi ** si comporta come * e corrisponde entro un solo livello di directory. Si prendono solo i file regolari, in ordine di nome. Un file vuoto viene saltato finché non ha byte. Il glob può nominare una variabile d'ambiente, come in ${env:LOG_DIR}/*.log. La sorgente conserva il riferimento così come è scritto e lo risolve a ogni lettura dei file; una variabile non impostata viene rifiutata per nome.
Le colonne proprie di ogni riga. Vengono prima delle colonne del formato:
| Colonna | Tipo | Contenuto |
|---|---|---|
_file_id |
text | L'identità del file: un checksum dei suoi primi 4096 byte, decompressi |
_file_name |
text | Il percorso con cui il record è stato letto |
_line_offset |
bigint | Offset in byte della prima riga del record nel testo decompresso |
_entry |
bigint | Quale voce del record è la riga: 0, tranne per un record batch come Records di un file CloudTrail |
_raw |
text | Il record come è stato scritto |
_parse_error |
text | Perché il record, o uno dei suoi valori, non ha potuto essere letto; NULL quando lo è stato |
_landed_at |
timestamp | Quando la riga è atterrata |
_file_id, _line_offset e _entry sono la chiave della riga, quindi un record letto due volte è una sola riga.
Formati e preset. La selezione del formato li raggruppa per famiglia; l'ultima scelta, regex, è il pattern definito da voi.
| Famiglia (etichetta nel modulo) | Nomi dei formati |
|---|---|
| Formati comuni | rfc3164, rfc5424, macos_install, clf, combined, jsonl, logfmt, w3c, cef, gelf |
| Amazon Web Services | aws_alb, aws_nlb, aws_elb, aws_cloudfront, aws_s3_access, aws_cloudtrail, aws_vpc_flow, aws_waf, aws_route53_resolver |
| Database e runtime | postgresql_stderr, postgresql_csvlog, postgresql_jsonlog, mysql_error, mysql_slow, mongodb, redis, log4j2_default, logback_default, python_logging |
gcp_cloud_logging, android_logcat, chrome_net_export |
|
| Container e Linux | kubernetes_cri, journald_json, linux_auditd, linux_dmesg |
| Microsoft | ms_iis, ms_windows_firewall, ms_dns_debug, ms_dhcp_audit, ms_exchange_tracking, ms_sqlserver_errorlog, azure_storage_analytics, azure_monitor, windows_evtx |
| Monitoraggio della sicurezza di rete | zeek_tsv, zeek_json, suricata_eve, snort_fast |
| Server web e proxy | nginx_error, apache_error, haproxy_http, squid_access, envoy_access, caddy_access, traefik_access |
| Personalizzato | regex |
Sono 56 formati integrati più regex.
Come un formato produce le colonne. I formati ottengono le colonne in uno di quattro modi.
- Colonne fisse. La maggior parte dei formati dichiara colonne e tipi.
rfc3164, per esempio, dàtimestamp,facility,severity,host,process,pidemessage;facilityeseveritysono calcolate dal valore<PRI>. Una riga syslog BSD non porta l'anno, quindi l'anno è quello dell'ultima scrittura del file, oppure l'anno precedente se il mese della riga è successivo a quello della scrittura. - Campi nominati dal record.
jsonlelogfmtprendono le colonne dai campi visti nelle prime righe dei file (impostazionelogs.preview_lines, predefinita 1000). Ogni colonna riceve il tipo più stretto in cui rientrano tutti i valori visti: boolean, poi integer, float, timestamp e json, altrimenti text. Un campo che un record successivo porta e che nessuna colonna ha finisce in una colonna jsonextra. - Campi nominati da un'intestazione.
w3ce i preset costruiti su di esso leggono la riga#Fields:di ciascun file;dateetimesi uniscono in un'unica colonnatimestamp.aws_cloudfront,ms_iis,ms_windows_firewall,ms_exchange_trackingezeek_tsvleggono allo stesso modo le righe di intestazione che iniziano con#, eaws_vpc_flowlegge una riga di intestazione che inizia conversion. Il punto in cui è stata letta una riga di intestazione resta con il file, quindi un file letto più avanti mantiene i suoi campi. regex. Ogni gruppo nominato, come(?P<level>…), è una colonna di tipo text.
La schermata Register Table elenca queste colonne con i loro tipi e segna la chiave. Quando le righe vengono lette, ogni valore è convertito nel tipo che la sua colonna ha nella tabella registrata, quindi un tipo cambiato lì è quello applicato. Un valore che la colonna non può contenere diventa NULL e il suo motivo va in _parse_error.
Un record più lungo di logs.max_line_bytes (predefinito 65536) conserva i primi byte in _raw e non viene analizzato; _parse_error ne indica la lunghezza. Un file che inizia con un byte-order mark UTF-8 o UTF-16 è letto in quella codifica, e qualsiasi altro file come UTF-8.
Record su più righe. Un formato con un pattern di inizio record crea un record da una riga che lo soddisfa più tutte le righe successive che non lo soddisfano. Uno stack trace resta con la sua eccezione, e una voce di query lenta resta intera. Le righe prima della prima corrispondenza sono un record a sé; di solito il formato lo rifiuta, e la riga viene conservata. I formati integrati con un inizio record sono rfc3164, macos_install, aws_cloudtrail, postgresql_stderr, postgresql_csvlog, mysql_error, mysql_slow, log4j2_default, logback_default, python_logging, chrome_net_export, ms_sqlserver_errorlog e azure_monitor. Con regex, impostate voi record_start.
Due formati leggono i propri record da soli invece che per riga. kubernetes_cri unisce in un record le righe parziali (P) che il runtime ha spezzato, e windows_evtx legge il file binario .evtx.
Un file ancora in scrittura è letto fino all'ultima riga completa. Per un formato multiriga è letto fino all'inizio del suo ultimo record, che la riga successiva potrebbe continuare. Quando il file resta 2 secondi senza scritture, quell'ultimo record è considerato intero.
Rotazione. Un file è riconosciuto da un checksum dei suoi primi 4096 byte, decompressi, non dal suo nome. Quando system.log viene ruotato in system.log.0 e poi compresso in system.log.0.gz, ogni copia è lo stesso file. La lettura prosegue dal byte raggiunto dall'ultima lettura, quindi nessuna riga è letta due volte.
- La compressione è riconosciuta dai primi byte del file, mai dal nome: gzip e bzip2. Un file compresso è considerato completo.
- Una copia ruotata e la sua gemella compressa, entrambe selezionate, sono lette una volta sola, dalla compressa.
- Un file trovato più corto dell'offset già letto è stato troncato: è un file nuovo, letto dall'inizio. Le righe del vecchio restano.
- I file più corti di 4096 byte che iniziano allo stesso modo si distinguono per inode, quindi restano due file.
- Un file che il glob non seleziona più viene dimenticato; le sue righe restano come storico.
Aggiornamento e consegna live. Una tabella Logs è una replica. Nulla osserva i file. Invece, quando il segnale di cambiamento dell'origine è probe o ttl_probe, la sua sonda calcola un digest di percorso, inode, dimensione e ultima scrittura di ogni file selezionato; quando il digest differisce, viene richiesto un aggiornamento. I tipi di sonda che un'origine Logs accetta sono hash e none.
Un aggiornamento legge solo ciò che è nuovo. Ogni file è letto da dove è arrivato l'ultimo passaggio, e le righe sono scritte per chiave, 5000 alla volta, prima che la nuova posizione sia conservata. Un passaggio che si ferma tra i due passi rilegge quei record, il che lascia le stesse righe. Ogni passaggio marca le proprie righe in _landed_at, la colonna che la consegna live usa come watermark. Una definizione di tabella modificata è una ricostruzione completa che legge ogni file dall'inizio.
Radici consentite. L'impostazione operatore logs.allowed_roots elenca le directory in cui un glob può risolversi. Il valore predefinito è ["/var/log"]; è un elenco nella scheda Security di Admin → Settings, impostabile anche con PROVISA_LOGS_ALLOWED_ROOTS, e una modifica chiede conferma. Ogni radice è risolta dopo l'espansione di ~ e dei symlink.
Si applicano due controlli. La parte fissa del glob, fino al primo carattere jolly, deve risolversi dentro una radice; è controllata quando l'origine viene salvata e a ogni anteprima o lettura. Anche ogni file selezionato deve risolversi dentro una radice una volta seguiti i symlink, quindi un symlink che esce da /var/log viene rifiutato. Il rifiuto indica il percorso, ciò in cui si è risolto e le radici consentite:
/home/me/app.log resolves to /home/me/app.log, outside the directories log files may be read from: /var/log
Al salvataggio il codice è schema.logs_path_refused; in anteprima è un 403 con logs.path_refused.
Rifiuto in hosting (SaaS). Un deployment che ospita molte organizzazioni non legge alcun file di log proprio. Ogni controllo di percorso rifiuta per primo, al salvataggio, in anteprima e in lettura, con "<path>: a server that holds many organizations reads no log file of its own". I file sono del server, non di una singola organizzazione, quindi nessun tenant può leggerli. Il deployment sa se ospita molte organizzazioni dal suo stato di multitenancy.
Dataset Kaggle¶
Kaggle è un sito da cui scaricare file di dati. Un dataset Kaggle che aggiungete si registra come origine files, e lo interrogate come qualsiasi altra origine files.
Aggiungere un dataset. Aprite Sources → Subscriptions → Kaggle. Il modulo procede in due passi, perché la ricerca di dataset di Kaggle richiede un token: il selettore compare solo dopo che il vostro è stato verificato.
- Token. Inserite un token API di Kaggle e fate clic su "Validate". La verifica chiama
POST https://www.kaggle.com/api/v1/datasets/create/newcon un corpo vuoto. Un401significa che il token non è valido, qualsiasi altra risposta che lo è. Kaggle controlla il corpo vuoto prima di creare qualcosa, quindi non viene salvato nulla. - Selettore. Un campo di ricerca interroga
GET /api/v1/datasets/listmentre digitate, e ogni risultato mostra titolo e descrizione del dataset. Sceglietene uno e fate clic su "Add Dataset".
"Add Dataset" scarica il pacchetto da GET /api/v1/datasets/download/{owner}/{ref}, decomprime i suoi file CSV e Parquet in <PROVISA_DATA_DIR>/kaggle/<owner>/<ref>/<file-stem>/<file-name> e crea una origine files il cui path è quella directory radice. Il server non conserva mai il token.
Poi registrate le tabelle che volete nella normale schermata Register Table. Ogni file compare come tabella a sé, come in qualsiasi altra origine files.
Solo CSV e Parquet. Un pacchetto che contiene un file .sqlite o .db viene rifiutato con un errore che lo dice. Vengono preparati solo file CSV e Parquet.
Aggiornare. Quando Kaggle pubblica una nuova versione di un dataset, chiamate la mutation GraphQL refreshKaggleSource con l'ID dell'origine e un token valido. Prepara di nuovo i file sul posto e svuota la cache dell'endpoint dei file, così la query successiva recepisce qualsiasi cambio di schema. kaggle_owner e kaggle_ref, salvati in federation_hints alla creazione dell'origine, indicano quale dataset scaricare.
Nessuno YAML proprio. Un'origine Kaggle si crea solo dal modulo delle origini. Esportata in YAML, compare come type: files con kaggle_owner e kaggle_ref in federation_hints. Per riscaricarla da Kaggle serve l'aggiornamento nella UI o la mutation refreshKaggleSource. In un ambiente isolato, puntate il path dello YAML a una directory preparata da voi.
Wikipedia¶
Wikipedia scansiona le pagine che indichi. Ogni tabella di dati in una pagina, e ogni file CSV, TSV, Excel, JSON o Parquet collegato, diventa una tabella dell'origine. Come Kaggle, si registra come origine files con un crawl nel proprio mapping. Aggiungerla non registra nulla: uno steward registra le tabelle desiderate tramite la normale schermata Registra tabella.
Campi di configurazione.
| Campo | Predefinito | Significato |
|---|---|---|
| Pages to start from | nessuno (obbligatorio) | Uno per riga: il titolo di una pagina, oppure l'indirizzo di una pagina nell'edizione scelta |
| Language edition | en |
Il codice dell'edizione, ad esempio en, de o ja |
| Link depth | 1 |
A quanti link di distanza da una pagina iniziale leggere; 0 legge solo le pagine iniziali |
| Page limit | 25 |
Il massimo di pagine che una scansione legge |
| Contact | vuoto | Inviato con ogni richiesta perché Wikipedia possa contattarti, come richiede la sua policy |
| Follow "See also" links | disattivato | Disattivato esclude quella sezione dall'articolo |
| What this edition calls its "See also" section | vuoto | Richiesto solo quando la casella sopra è disattivata e Provisa non sa indicare la sezione per l'edizione scelta. Il titolo della sezione come lo mostra una pagina, ad esempio Voir aussi |
| Directory for the crawl's files | vuoto | Dove vengono scritte tabelle e file di dati. Vuoto: <PROVISA_DATA_DIR>/crawl/<source id> |
Provisa conosce l'intestazione "See also" di queste edizioni: en, de, es, it, pt, nl, pl, sv, ja, zh, ru. Per qualsiasi altra edizione con "See also" disattivato, il modulo chiede come l'edizione chiami quella sezione; digita il suo titolo come lo mostra una pagina (ad esempio Voir aussi). Un'origine salvata senza di esso viene rifiutata (wikipedia.see_also_heading_needed).
Quali link vengono seguiti. Un link è scelto da ciò che il markup della pagina stessa dice che è, mai da un nome nel suo indirizzo: tali nomi differiscono in ogni edizione linguistica e compaiono anche nei titoli ordinari.
- Seguiti: link ad altri articoli che si trovano nel testo, nelle liste e nelle tabelle di dati dell'articolo stesso. Il segno di markup è
a[rel='mw:WikiLink']. - Non seguiti: pagine non ancora scritte, la pagina stessa, ricerche di libri, pagine di file, altri wiki e siti esterni.
- Non letti affatto (rimossi prima di raccogliere link e tabelle): riquadri di navigazione, riquadri informativi, note e citazioni, categorie, indice, immagini e messaggi di manutenzione.
- Non letta a meno che tu non attivi l'opzione: la sezione "See also".
Solo le tabelle che i redattori contrassegnano come tabelle di dati (table.wikitable) diventano tabelle, e una tabella con meno di 2 righe non viene offerta.
Impostazioni avanzate di scansione. Il modulo le tiene raccolte sotto "Crawl settings" e mostra ciascuna con il suo valore predefinito; lascia un campo vuoto per mantenerlo. In un mapping files gli stessi nomi vanno sotto crawl:, e un nome fuori da questo elenco viene rifiutato.
| Impostazione | Predefinito Wikipedia | Significato |
|---|---|---|
start_urls |
le pagine che hai indicato | Pagine da cui parte la scansione |
max_depth |
1 |
Profondità dei link |
max_pages |
25 |
Limite di pagine |
request_delay |
1 seconds |
Pausa tra le richieste |
user_agent |
Provisa/<version> (+https://provisa.dev) file-crawler, con il tuo contatto aggiunto |
Il nome con cui la scansione si presenta |
content_selector |
#mw-content-text .mw-parser-output |
Selettore CSS: link e tabelle vengono letti solo al suo interno |
remove_selectors |
l'elenco di parti descritto sopra | Selettori CSS per le parti escluse. Indicarlo sostituisce l'intero elenco, regola "See also" compresa |
link_selector |
a[rel='mw:WikiLink'] con esclusioni |
Selettore CSS che un link deve soddisfare per essere seguito |
link_exclude_patterns |
vuoto | Espressioni regolari; un link il cui indirizzo ne contiene una non viene seguito |
follow_external_links |
false |
Segue i link verso altri siti |
allowed_domains |
non impostato | Domini che la scansione può visitare |
table_selector |
table.wikitable |
Selettore CSS che una tabella deve soddisfare |
generate_tables_from_html |
non impostato | Se le tabelle HTML diventano tabelle |
html_table_min_rows |
2 |
Numero minimo di righe in una tabella |
html_table_max_rows |
non impostato | Numero massimo di righe in una tabella |
allowed_file_extensions |
csv, tsv, xlsx, xls, json, parquet |
Tipi di file collegati che diventano tabelle |
html_cache_ttl |
1 days |
Per quanto tempo una scansione viene conservata |
max_html_size |
non impostato | La pagina più grande letta, ad esempio 10MB |
max_data_file_size |
non impostato | Il file di dati più grande letto, ad esempio 100MB |
Nomi delle tabelle. Una tabella acquisita dal crawl si chiama <page>__<name>. Il nome è la didascalia (caption) della tabella o, se manca, l'ultima intestazione sopra di essa; una tabella senza nessuna delle due diventa table_1, table_2 e così via. Entrambe le parti vengono messe in minuscolo, e ogni sequenza di altri caratteri diventa _. Per esempio, una tabella sotto l'intestazione "Tallest buildings" nella pagina List_of_tallest_buildings diventa list_of_tallest_buildings__tallest_buildings. Un file di dati collegato mantiene il proprio nome.
Aggiornamento. Una scansione viene conservata per il periodo di cache, 1 days per impostazione predefinita (html_cache_ttl). Le pagine vengono recuperate di nuovo solo dopo che quel tempo è trascorso.
Osservabilità e altro¶
prometheus ha un connettore Trino (proprietà costruite dal DSL di mapping del tipo). google_sheets è un tipo di origine registrato senza connettore Trino e materializza attraverso la pipeline di cache API.
| Tipo di origine | Nome connettore | Mutation |
|---|---|---|
google_sheets |
— (materializzata) | No |
prometheus |
prometheus | No |
Connettori SaaS enterprise¶
Google Workspace e Microsoft 365 vengono letti da loader propri; vedi Google Workspace e Microsoft 365. SharePoint, Salesforce, ServiceNow, Jira, l'inventario cloud e Splunk vengono letti tramite driver che girano come processo a sé e parlano il protocollo PostgreSQL (pgwire-sharepoint, pgwire-salesforce, pgwire-servicenow, pgwire-jira, pgwire-cloudops, pgwire-splunk), quindi nessuno ha un driver diretto. Sul motore DuckDB l'endpoint del driver viene collegato live tramite l'estensione postgres: Register Table elenca le tabelle del connettore dal catalogo collegato, le query leggono il connettore sul posto, e filtri e proiezioni gli vengono spinti. Ogni altro motore atterra le righe nello store di materializzazione per la federazione. I pacchetti dei driver vengono scaricati per sistema operativo e architettura da una release fissata (pgwire-<connector>-<version>-<os>-<arch>.tar.gz; macOS arm64, Linux x86_64, Windows x86_64). Un operatore può invece indicare, per connettore, una cartella di pacchetto compilata per questo host in Admin → Impostazioni → Server dei connettori (connectors.<connector>.bundle_dir); vedi Admin. Tutti e cinque i connettori Trino (Jira non ne ha) confrontano sempre i nomi senza distinguere maiuscole e minuscole, come fa ciascun prodotto.
sharepoint¶
Le liste SharePoint vengono enumerate come schemi ed esposte come tabelle interrogabili. Due metodi di autenticazione: CLIENT_CREDENTIALS (default) e basato su certificato tramite un certificato PFX. I valori segreti in mapping vengono risolti attraverso il motore dei secret prima di raggiungere il connettore.
| Campo origine | Proprietà connettore | Note |
|---|---|---|
base_url o host |
site-url |
URL del sito SharePoint |
username |
client-id |
Client ID dell'app Azure |
password |
client-secret |
Client secret dell'app Azure |
database |
tenant-id |
UUID del tenant Azure |
mapping.auth_type |
auth-type |
CLIENT_CREDENTIALS (default) o CERTIFICATE |
mapping.certificate_path |
certificate-path |
Percorso PFX quando auth_type: CERTIFICATE — deve essere ASSOLUTO |
mapping.certificate_password |
certificate-password |
Password PFX — la chiave deve essere presente, stringa vuota per un PFX senza password |
L'autenticazione a certificato sui motori diversi da Trino porta due regole aggiuntive, entrambe applicate quando viene costruito l'operando model.json del server pgwire Calcite. certificate_path deve essere assoluto: il server viene eseguito con la directory del proprio bundle come directory di lavoro, quindi un percorso relativo si risolve dentro la cache dei runtime-deps e il PFX non viene trovato. certificate_password deve essere presente in mapping anche quando il PFX non ha password, nel qual caso è la stringa vuota. L'adapter Calcite rifiuta categoricamente una password null, e una chiave assente viene trattata come errore di configurazione anziché letta silenziosamente come password vuota. Un valore mancante o relativo solleva MissingConnectorConfig nominando il campo.
Quando il connettore non espone information_schema.columns, registra la tabella con definizioni di colonna esplicite (ottenute dall'API Microsoft Graph) tramite la mutation registerTable.
- id: hr-sharepoint
type: sharepoint
base_url: https://kenstott.sharepoint.com
username: ${env:SP_CLIENT_ID}
password: ${env:SP_CLIENT_SECRET}
database: ${env:SP_TENANT_ID}
mapping:
auth_type: CLIENT_CREDENTIALS
Autenticazione a certificato, con il percorso assoluto e la password sempre presente:
- id: hr-sharepoint
type: sharepoint
base_url: https://kenstott.sharepoint.com
username: ${env:SP_CLIENT_ID}
database: ${env:SP_TENANT_ID}
mapping:
auth_type: CERTIFICATE
certificate_path: /etc/provisa/certs/sharepoint.pfx
certificate_password: ${env:SP_CERT_PASSWORD}
salesforce¶
Ogni sObject che le credenziali possono interrogare è una tabella che lo steward può registrare (Account, Opportunity, oggetti personalizzati *__c); nulla viene registrato automaticamente. Colonne e tipi provengono dal describe dell'sObject. Filtri, proiezioni, ordinamenti e limiti vengono delegati a Salesforce come SOQL.
Una fonte Salesforce indica l'URL di accesso Dominio personale dell'organizzazione e un set completo di credenziali, scelto con mapping.auth_type. L'URL di accesso è l'URL https://… completo (ad esempio https://acme.my.salesforce.com); un host nudo o qualsiasi altro schema viene rifiutato, nominando il campo. Un set incompleto viene rifiutato quando la fonte viene usata, indicando il valore mancante. Ogni segreto può essere un riferimento ${secret:…} o ${env:…}. Un segreto digitato come valore letterale viene conservato nel vault dell'organizzazione e la fonte ne mantiene il riferimento.
| Campo origine | Proprietà connettore | Note |
|---|---|---|
base_url o host |
login-url |
URL Dominio personale dell'organizzazione (Imposta, poi Dominio personale). login.salesforce.com non funziona con il flusso client credentials |
username |
client-id |
Chiave consumatore dell'app connessa |
password |
client-secret |
Segreto consumatore dell'app connessa |
mapping.auth_type |
— | CLIENT_CREDENTIALS (predefinito), USERNAME_PASSWORD o ACCESS_TOKEN |
mapping.sf_username, mapping.sf_password |
username, password |
Solo USERNAME_PASSWORD, insieme a chiave e segreto consumatore |
mapping.security_token |
security-token |
Solo USERNAME_PASSWORD; facoltativo |
mapping.access_token, mapping.instance_url |
access-token, instance-url |
Solo ACCESS_TOKEN; nessuna chiave o segreto consumatore |
mapping.api_version |
api-version |
Facoltativo, ad esempio v61.0 |
Per CLIENT_CREDENTIALS l'app connessa richiede Enable Client Credentials Flow e un utente Run As; ogni lettura e scrittura viene eseguita con le autorizzazioni di quell'utente. Le organizzazioni create da Summer '23 bloccano per impostazione predefinita il flusso nome utente e password.
Letture. Su Trino la fonte viene letta tramite il catalogo salesforce, che legge soltanto. Su ogni altro motore, il motore collega il server pgwire della fonte e la legge sul posto.
Scritture. INSERT, UPDATE e DELETE su un sObject registrato sono mutazioni: governate, sottoposte ad audit e gestite per ambiente come ogni altra mutazione. Su ogni motore, Trino incluso, una scrittura viene eseguita sul server pgwire della fonte; su Trino quel server è in esecuzione solo per le scritture. Ogni istruzione viene inviata a Salesforce e confermata all'esecuzione, quindi non può essere annullata. Il server non restituisce le righe che scrive: un'istruzione con clausola RETURNING viene rifiutata e GraphQL non offre campi di mutazione per queste tabelle; INSERT, UPDATE, DELETE e COPY in SQL sono i percorsi di scrittura. Una scrittura è una singola istruzione semplice su una tabella: INSERT … ON CONFLICT, UPDATE … FROM, DELETE … USING, MERGE e TRUNCATE vengono rifiutati, nominando la forma. Una fonte SharePoint viene scritta allo stesso modo.
Avvio. Prima di accettare connessioni, il server della fonte legge le colonne di ogni sObject, circa quattro minuti per un'organizzazione che ne ha 1.200. Il costo si paga una volta: i risultati dei describe sono conservati nella directory di stato propria della fonte, sotto la directory dati dell'istanza. Il server viene avviato quando la fonte viene registrata o caricata. Su Trino il catalogo salesforce conserva i propri risultati dei describe, una directory per fonte sotto /data/trino/cache/salesforce-describe nel coordinator, quindi il costo si paga una volta anche lì quando quel percorso è un volume che sopravvive al container: il volume trino_cache dello stack compose, o il volume della cache del coordinator del chart Helm (trino.coordinator.cache.persistence). Finché non accetta connessioni, Registra tabella mostra il connettore come in avvio e una scrittura riceve lo stesso messaggio invece di attendere.
- id: sales-salesforce
type: salesforce
base_url: https://acme.my.salesforce.com
username: ${env:SF_CONSUMER_KEY}
password: ${env:SF_CONSUMER_SECRET}
mapping:
api_version: v61.0
Flusso nome utente e password:
- id: sales-salesforce
type: salesforce
base_url: https://acme.my.salesforce.com
username: ${env:SF_CONSUMER_KEY}
password: ${env:SF_CONSUMER_SECRET}
mapping:
auth_type: USERNAME_PASSWORD
sf_username: ops@acme.com
sf_password: ${env:SF_PASSWORD}
security_token: ${env:SF_SECURITY_TOKEN}
servicenow¶
Ogni tabella dell'istanza è disponibile per la registrazione, e nulla viene registrato automaticamente. Un'istanza molto usata ha migliaia di tabelle, quindi Register Table le raggruppa per tema. Ogni tema è il nome visualizzato di un pacchetto ServiceNow, oppure "(no package)" per una tabella che non appartiene a nessuno. Ogni tabella è elencata come name — label. Selezionare una tabella ne legge le colonne dall'istanza; elencare le tabelle no.
Una fonte ServiceNow indica l'URL completo https://… dell'istanza insieme a nome utente e password. L'URL è preso da base_url o host; un host nudo o qualsiasi altro schema viene rifiutato, nominando il campo (instanceUrl). Un nome utente o una password assenti vengono rifiutati, nominando il campo. Ogni valore può essere un riferimento ${secret:…} o ${env:…}.
| Campo fonte | Proprietà dell'adattatore | Note |
|---|---|---|
base_url o host |
instanceUrl |
URL completo https://… dell'istanza (ad es. https://acme.service-now.com). Un host nudo o un altro schema viene rifiutato |
username |
username |
Nome utente per l'autenticazione di base |
password |
password |
Password per l'autenticazione di base |
Letture. Su Trino la fonte viene letta tramite il catalogo servicenow, che legge soltanto. Su ogni altro motore, il motore collega il server pgwire della fonte e la legge sul posto. Solo le colonne selezionate vengono richieste all'istanza (sysparm_fields). Un filtro viene inviato all'istanza (sysparm_query) solo in una forma che l'adattatore ha verificato su un'istanza reale; ogni altro filtro, e ogni ordinamento e limite, viene applicato alle righe lette.
Un campo di riferimento appare come due colonne: <field> contiene il sys_id del record referenziato e <field>__display contiene il valore di visualizzazione. Un campo vuoto è NULL. Un campo il cui tipo non può essere mappato dall'adattatore genera un errore che nomina la tabella, il campo e il tipo.
La ricerca di tabelle (linguaggio naturale) corrisponde a nome, etichetta e tema di ogni tabella. Una volta registrata una tabella, le sue colonne si uniscono alla ricerca.
Ciclo di vita del server. Il server serve solo le tabelle registrate da esso. Registrare o rimuovere una tabella riavvia il server e ri-provisiona la fonte sul motore; un'istruzione che arriva durante quel riavvio fallisce.
Scritture. INSERT, UPDATE e DELETE su una tabella registrata sono mutazioni: governate, sottoposte ad audit e gestite per ambiente come ogni altra mutazione. Su ogni motore, Trino incluso, una scrittura viene eseguita sul server pgwire della fonte. Il server restituisce la riga che l'istanza contiene dopo ogni scrittura; una clausola RETURNING è quindi accettata e GraphQL offre campi di mutazione per queste tabelle. Una scrittura è una singola istruzione su una tabella: INSERT … ON CONFLICT, UPDATE … FROM, DELETE … USING, MERGE e TRUNCATE vengono rifiutati, nominando la forma.
Import set. Quando una tabella ha una transform map attiva verso di essa, Register Table offre i suoi import set, e lo steward può vincolare la tabella a uno (una tabella con più map nomina ciascuno). Le inserzioni in una tabella vincolata passano attraverso l'Import Set API dell'istanza e la trasformazione di quella map; le inserzioni in una tabella non vincolata, e ogni aggiornamento ed eliminazione, vengono scritte direttamente nella tabella. Una riga che la trasformazione rifiuta viene respinta, nominando la riga; le righe già scritte restano scritte. Il vincolo viene verificato quando la tabella viene salvata: deve essere l'import set di una transform map attiva verso la tabella, su una fonte ServiceNow.
- id: helpdesk-sn
type: servicenow
base_url: https://acme.service-now.com
username: ${env:SN_USERNAME}
password: ${env:SN_PASSWORD}
jira¶
Un sito Jira Cloud viene letto e scritto tramite un proprio adattatore Calcite. Una tabella di una fonte Jira è un modulo: un progetto e un tipo di issue, più i campi che le sue colonne leggono. Register Table elenca ogni modulo di cui l'account può creare una issue, raggruppato per progetto; selezionarne uno legge da Jira i campi di quel modulo, mentre elencare i moduli non lo fa. Ogni modulo ha le colonne key (la sua chiave primaria), id, summary, status, created e updated, poi una colonna per ogni campo scelto, denominata come la denomina l'adattatore: il nome del campo in snake case (Story Points diventa story_points). Un campo il cui nome è già quello di una colonna viene denominato con il suo id di campo (customfield_10099), così che ogni campo possa essere scelto. Un campo utente contiene l'id dell'account; il suo nome visualizzato è un campo a sé (assignee.displayName, colonna assignee_name). Le colonne che Jira scrive da sé sono di sola lettura: key, id, created, updated, il nome visualizzato di un campo utente e un campo di una forma che l'adattatore non conosce, che serve come testo JSON. Registrarla la contrassegna con «Read only: Jira sets it» nella sua descrizione, se non ne ha una, e una scrittura che la imposta viene rifiutata dal server, nominandola.
La tabella di un modulo nomina il suo progetto e il suo tipo di issue nei suoi arguments (project, issue_type), e ogni colonna legge il campo che il suo path nomina (un id di campo o il nome di un campo), oppure il campo con il proprio nome. Una tabella registrata dall'admin ottiene entrambi da Jira; una configurazione li dichiara. Una tabella Jira che fissa altro, o a cui manca uno dei due, viene rifiutata, nominandola.
| Campo fonte | Proprietà dell'adattatore | Note |
|---|---|---|
base_url o host |
baseUrl |
Indirizzo completo https://… del sito (ad es. https://acme.atlassian.net). Un host nudo o un altro schema viene rifiutato |
username |
email |
Email dell'account Atlassian |
password |
apiToken |
Un token API di quell'account, creato su id.atlassian.com in Security, API tokens |
Collegamenti. La tabella issue_links della fonte contiene i collegamenti tra le issue dei suoi moduli: link_id, link_type, outward_key e inward_key, ogni collegamento una sola volta. Una relazione tra due moduli viene dichiarata attraverso di essa come junction, con link_type come discriminante, così che GraphQL e Cypher passino da una issue alla issue collegata per tipo di collegamento.
Letture. Una ricerca per chiave (key =, key IN) viene recuperata per chiave, così che una chiave che non nomina alcuna issue non dà alcuna riga. Un confronto su data o numero viene inviato a Jira come JQL. Ogni altro filtro, e ogni ordinamento, viene applicato alle righe lette: un'uguaglianza su testo, opzione, stato o utente non viene inviata, poiché JQL rifiuta un valore che non nomina nulla dove SQL restituisce zero righe. Un campo a più valori (etichette, selezione multipla, componenti, versioni) è testo che contiene un array JSON, e si scrive allo stesso modo.
Su DuckDB, PostgreSQL e ClickHouse il motore collega il server pgwire della fonte e la legge sul posto. Su Trino la fonte non ha un catalogo proprio e viene caricata tramite il suo server.
Ciclo di vita del server. Il server serve solo i moduli registrati da esso, insieme al suo elenco di moduli, al suo elenco di campi e ai collegamenti. Registrare, modificare o rimuovere un modulo riavvia il server e ri-provisiona la fonte sul motore.
Scritture. INSERT, UPDATE e DELETE su un modulo sono mutazioni, scritte sul server pgwire della fonte su ogni motore. Le righe vengono inserite in lotti da 50; un aggiornamento o un'eliminazione avviene issue per issue, per chiave. Impostare status esegue la transizione del workflow verso quello stato, e viene rifiutato, nominando gli stati raggiungibili, quando il workflow della issue non ne ha. Una issue con sottoattività non viene eliminata. Il server restituisce ogni riga scritta per chiave, quindi RETURNING è accettato e GraphQL offre campi di mutazione. Su issue_links, INSERT crea un collegamento e DELETE ne rimuove uno; UPDATE viene rifiutato.
- id: support-jira
type: jira
base_url: https://acme.atlassian.net
username: ${env:JIRA_EMAIL}
password: ${env:JIRA_API_TOKEN}
tables:
- source_id: support-jira
table: incident
arguments: {project: KAN, issue_type: Incident}
columns:
- {name: key, data_type: varchar, is_primary_key: true}
- {name: summary, data_type: varchar}
- {name: status, data_type: varchar}
- {name: priority, data_type: varchar}
- {name: flagged, data_type: varchar, path: customfield_10021}
cloudops¶
L'inventario cloud presenta le risorse di Azure, AWS e GCP come un unico insieme di tabelle: compute_resources, storage_resources, kubernetes_clusters, database_resources, network_resources, iam_resources, container_registries e compute_security_groups, che collega una risorsa di calcolo ai suoi gruppi di sicurezza. Ogni tabella ha le stesse colonne per i tre cloud, e la colonna cloud_provider indica il cloud da cui proviene ogni riga. La fonte legge soltanto, e le sue tabelle vengono registrate da uno steward come tutte le altre.
Una fonte indica uno o più cloud. Ogni cloud è tutto o niente: un cloud con solo una parte dei valori obbligatori viene rifiutato, indicando ciò che manca, così come una fonte che non indica alcun cloud. La fonte legge esattamente i cloud che indica. Tutti i valori si trovano in mapping; ogni segreto può essere un riferimento ${secret:…} o ${env:…}, Un segreto digitato come valore letterale viene conservato nel vault dell'organizzazione e la fonte ne mantiene il riferimento.
| Cloud | Chiave di mapping |
Proprietà connettore | Note |
|---|---|---|---|
| Azure | azure_tenant_id |
azure.tenant-id |
Obbligatorio |
| Azure | azure_client_id |
azure.client-id |
Obbligatorio; il client ID della registrazione dell'app |
| Azure | azure_client_secret |
azure.client-secret |
Obbligatorio; segreto |
| Azure | azure_subscription_ids |
azure.subscription-ids |
Obbligatorio; separati da virgole |
| AWS | aws_access_key_id |
aws.access-key-id |
Obbligatorio |
| AWS | aws_secret_access_key |
aws.secret-access-key |
Obbligatorio; segreto |
| AWS | aws_region |
aws.region |
Obbligatorio, ad esempio us-east-1 |
| AWS | aws_account_ids |
aws.account-ids |
Obbligatorio; separati da virgole |
| AWS | aws_role_arn |
aws.role-arn |
Facoltativo; un ruolo da assumere per l'accesso cross-account |
| GCP | gcp_credentials_path |
gcp.credentials-path |
Obbligatorio; percorso ASSOLUTO di un file di chiave JSON di un service account |
| GCP | gcp_project_ids |
gcp.project-ids |
Obbligatorio; separati da virgole |
| — | cache_ttl_minutes |
cache.ttl-minutes |
Facoltativo; per quanto tempo un risultato viene riutilizzato prima di interrogare di nuovo i cloud (predefinito dell'adattatore 5) |
Il file della chiave GCP viene aperto da chi legge la fonte. Su Trino è il server Trino, quindi il percorso deve esistere lì. Su ogni altro motore è il server pgwire della fonte sull'host Provisa, la cui directory di lavoro è la propria directory di stato, quindi un percorso relativo viene rifiutato.
Su Trino la fonte viene letta tramite il catalogo cloudops. Su ogni altro motore, il motore collega il server pgwire della fonte e la legge sul posto, come una fonte SharePoint.
- id: cloud-estate
type: cloudops
mapping:
aws_access_key_id: ${env:CLOUDOPS_AWS_ACCESS_KEY_ID}
aws_secret_access_key: ${env:CLOUDOPS_AWS_SECRET_ACCESS_KEY}
aws_region: us-east-1
aws_account_ids: "111111111111,222222222222"
azure_tenant_id: ${env:CLOUDOPS_AZURE_TENANT_ID}
azure_client_id: ${env:CLOUDOPS_AZURE_CLIENT_ID}
azure_client_secret: ${env:CLOUDOPS_AZURE_CLIENT_SECRET}
azure_subscription_ids: "sub-1,sub-2"
splunk¶
I risultati di ricerca Splunk sono interrogabili come tabelle (es. internal_server). L'URL del connettore proviene da base_url, oppure viene costruito come https://{host}:{port} con porta di default 8089. Auth: quando mapping.use_token è true (default), password viene passata come token API; quando false, username e password vengono passate come credenziali separate.
| Campo origine | Proprietà connettore | Note |
|---|---|---|
base_url / host + port |
url |
base_url, altrimenti https://host:port (porta default 8089) |
password |
token o password |
token quando use_token: true |
username |
user |
solo quando use_token: false |
database |
app |
restringe a un'app Splunk |
mapping.datamodel_filter |
datamodel-filter |
filtra a un data model |
mapping.disable_ssl_validation |
disable-ssl-validation |
per certificati self-signed |
Sul percorso di replica pgwire (tutti i motori tranne Trino) le stesse quattro impostazioni facoltative diventano le chiavi di operando Calcite model.json app, token/username+password, datamodelFilter e disableSslValidation. Le ultime due prendono i tipi in cui SplunkSchemaFactory le converte, una stringa e un booleano.
- id: ops-splunk
type: splunk
host: splunk
port: 8089
password: ${env:SPLUNK_TOKEN}
mapping:
use_token: true
disable_ssl_validation: true
Google Workspace e Microsoft 365¶
google_workspace e microsoft_365 leggono posta, calendari e attività degli account che un'origine indica, e li atterrano come repliche. Non sono connettori Calcite: ciascuno ha un loader proprio che chiama l'API del provider, Gmail, Google Calendar e Google Tasks da una parte e Microsoft Graph dall'altra. Il modulo li elenca sotto Enterprise come "Google Workspace (Gmail)" e "Microsoft 365 (Outlook, Exchange)".
Tabelle. I due tipi producono le stesse tabelle canoniche: stessi nomi, tipi di colonna e ordine. Un'unione dei due non richiede cast né rinomine; una colonna per cui un provider non ha valore è NULL. Le tabelle sono elencate nello schema default, e ogni riga inizia con l'account da cui è stata letta. Un'origine offre le tabelle delle risorse che legge:
| Risorsa | Tabelle | Scritte da istruzioni |
|---|---|---|
mail |
messages, message_recipients, folders, message_folders, threads, attachments |
No |
calendar |
calendars, events, event_attendees |
events, event_attendees |
tasks |
task_lists, tasks |
tasks |
Le colonne sono elencate nello schema canonico della posta. events contiene eventi singoli, eventi master di serie con la ricorrenza come righe RFC 5545, ed eccezioni; non contiene mai le occorrenze espanse di una serie.
Registrazione. L'amministratore di un'organizzazione inserisce prima il client dell'organizzazione per la piattaforma sotto Admin → Email: un client ID e un secret, più l'ID della directory (tenant) o il dominio per Microsoft 365. Chi aggiunge un'origine non lo vede né lo digita. Un'origine assume poi una di due forme:
- Una casella. L'origine indica un indirizzo in
accounts. Il suo titolare fa clic su Connect nel modulo e approva il client dell'organizzazione. L'approvazione risultante è conservata nello store di stato dell'organizzazione, non nelle impostazioni dell'origine. In Google Workspace questa forma dichiara anchesign_in: google_account. - Le caselle dell'organizzazione. L'origine dichiara
mailboxescome esattamente uno traeveryone: true,group: <address or id>olist: [addresses]. Non contiene credenziali e nessuno la connette. L'organizzazione legge con un accesso proprio, e solo dopo che l'amministratore ha attivato "Read the organization's mailboxes". Su Google quell'accesso è una chiave di service account con delega a livello di dominio, più un indirizzo amministratore per le chiamate alla directory. Su Microsoft è il grant client-credentials della registrazione dell'app.
Con la seconda forma, una tabella contiene le righe di ogni casella. Un ruolo che può leggere la tabella legge ogni casella, a meno che una regola di riga sulla colonna account lo limiti.
sources:
- id: team-mail
type: microsoft_365
mapping:
resources: [mail, calendar, tasks]
mailboxes:
group: support@acme.com
mail_since: "2026-01-01"
calendar_check_days: 30 # required when calendar is read
calendar_send_updates: false
Impostazioni. Ogni modulo rifiuta per nome una chiave che non legge.
Chiave mapping |
Tipo | Significato |
|---|---|---|
resources |
entrambi | Quali tra mail, calendar, tasks l'origine legge; almeno uno |
accounts |
entrambi | L'indirizzo dell'unica casella (forma a una casella) |
mailboxes |
entrambi | everyone, group o list (forma organizzazione) |
sign_in |
google_workspace |
google_account (forma a una casella) |
mail_since |
entrambi | YYYY-MM-DD; nulla di ricevuto prima viene conservato |
mail_content |
google_workspace |
full, oppure headers per soli header ed etichette (scope gmail.metadata di Gmail). Obbligatorio quando si legge la posta |
mail_search |
google_workspace |
Una ricerca come si digita nella casella di ricerca di Gmail |
mail_labels |
google_workspace |
Nomi di etichette; si legge solo la posta che le porta tutte |
mail_include_spam_trash |
google_workspace |
false per impostazione predefinita |
calendar_send_updates |
entrambi | Se una scrittura avvisa i partecipanti di un evento; disattivato per impostazione predefinita |
calendar_check_days |
microsoft_365 |
Giorni prima e dopo oggi che il controllo delle modifiche esamina; un numero intero, 1 o più, obbligatorio quando si legge il calendario |
Google rifiuta una ricerca sotto gmail.metadata, quindi mail_content: headers con mail_search o mail_since viene rifiutato.
Scope. Un accesso chiede solo gli scope delle risorse selezionate:
| Risorsa | Google Workspace | Microsoft 365 (delegated) |
|---|---|---|
mail |
gmail.readonly, oppure gmail.metadata per i soli header |
Mail.Read |
calendar |
calendar.calendarlist.readonly, calendar.events |
Calendars.ReadWrite |
tasks |
tasks |
Tasks.ReadWrite |
| ogni origine | userinfo.email |
offline_access, User.Read |
Per la forma organizzazione, Microsoft richiede i permessi applicativi Mail.Read e User.Read.All, più Calendars.ReadWrite e Tasks.ReadWrite.All per calendari e attività. Il service account di Google è autorizzato per gli scope in DELEGATED_SCOPES, che includono anche la lettura di utenti e membri di gruppi della directory.
Aggiornamento e freschezza. I tipi di sonda che queste origini accettano sono hash e none, e il loro loader fornisce il token di cambiamento:
- Gmail: un digest dell'history id corrente di ogni casella, una chiamata
users.getProfileper casella. - Posta Microsoft: ogni delta link conservato, seguito per un elemento.
- Calendari e attività Google: il tag e l'ora dell'ultima modifica di ogni calendario; il tag di ogni lista di attività, e l'id e l'ora di aggiornamento di ogni attività.
- Calendari e attività Microsoft: le righe di modifica di ogni casella, entro
calendar_check_daysprima e dopo oggi.
Un controllo che non può essere eseguito richiede una lettura; non riporta mai "invariato". Le tabelle di calendari e attività sono lette per intero a ogni aggiornamento. Le repliche della posta di un'origine Microsoft 365 o Google Workspace sono mantenute sul posto: ogni parte di una casella è scritta man mano che arriva e può essere interrogata subito. Un'origine Google Workspace che restringe con mail_search è riletta per intero a ogni aggiornamento, perché solo Google può dire cosa una ricerca seleziona.
Scritture. INSERT, UPDATE e DELETE su events, event_attendees e tasks vanno al provider attraverso un unico percorso di scrittura per entrambi i tipi. Un UPDATE o DELETE indica le sue righe per provider, account e id; un partecipante è indicato da event_id e address. Una singola occorrenza di una serie è indicata da series_id e original_start_at. Una scrittura che imposta una colonna fuori dall'insieme scrivibile della tabella viene rifiutata per nome. Google scrive con sendUpdates=all quando calendar_send_updates è attivo e con sendUpdates=none quando è disattivo. Microsoft Graph non può fare una simile modifica in silenzio, quindi una modifica ai partecipanti di una riunione viene rifiutata, indicando l'impostazione.
Esclusioni. La posta è di sola lettura; nessuna istruzione modifica una casella. calendars, task_lists e ogni tabella di posta non accettano scritture. Spam e Cestino di Gmail sono esclusi a meno che mail_include_spam_trash sia attivo. L'API Tasks di Google conserva solo il giorno di scadenza di un'attività, e non ha promemoria.
Origini API¶
Registra qualsiasi endpoint HTTP come tabella interrogabile.
| Tipo API | Discovery | Inferenza colonne |
|---|---|---|
openapi |
Parsing della spec OpenAPI | Primitivi → nativo, oggetti → JSONB |
graphql_remote |
Introspezione dello schema | Primitivi → nativo, oggetti → JSONB |
grpc_remote |
Server reflection | Primitivi → nativo, oggetti → JSONB |
Le risposte API vengono recuperate, messe in cache in PostgreSQL (TTL configurabile), ed esposte come tipi GraphQL. Le tabelle in cache partecipano a query federate come qualsiasi altra origine.
Regole JSONB: Le colonne complesse (oggetti, array) memorizzate come JSONB non sono filtrabili. L'accesso ai sotto-campi usa l'estrazione ->> in SQL. Le relazioni vengono dichiarate tra tabelle usando colonne FK scalari. Le colonne blob JSONB non sono target di join. Usa la promozione JSONB per convertire campi annidati in colonne scalari native quando è necessario filtrare o unirsi su di essi.
GitHub e GitLab¶
GitHub e GitLab sono origini con marchio, gestite da graphql_remote: ciascuna compare nel modulo Sources con il proprio nome e si registra come origine GraphQL remota che ne registra il marchio. Il modulo richiede un personal access token e nient'altro. L'endpoint (https://api.github.com/graphql, https://gitlab.com/api/graphql) e lo schema sono forniti con Provisa, quindi lo schema non viene sottoposto a introspezione alla registrazione. Il token viene verificato quando l'origine viene aggiunta. Aggiungere l'origine non registra tabelle: ogni tabella offerta dallo schema è disponibile sotto il namespace dell'origine (gh, gl), e lo steward registra quelle desiderate. Un campo che il token non può leggere viene escluso da una tabella quando questa viene registrata.
Stripe¶
Stripe è un'origine con marchio portata da openapi, costruita come GitHub e GitLab sono costruiti su graphql_remote. Compare nel modulo Sources come Stripe, sotto Enterprise. Si registra come origine OpenAPI la cui riga registra il marchio in federation_hints e il cui percorso della specifica è brand:stripe.
Cosa raggiunge. L'API REST di Stripe, tramite la specifica OpenAPI pubblicata da Stripe (stripe/openapi, spec3.json). La specifica è distribuita con Provisa, fissata a una versione e compressa con gzip, e non viene scaricata alla registrazione. Quella distribuita è la versione API 2026-09-30.endive con 425 percorsi. Ogni chiamata porta Stripe-Version impostato su quella versione, quindi ogni risposta ha la forma che la specifica descrive, qualunque sia la versione predefinita dell'account. I campi che Stripe restituisce ma che la specifica omette sono aggiunti da stripe.additions.json.
Come per qualsiasi origine OpenAPI, ogni operazione GET è una tabella offerta e ogni altra operazione è un comando offerto. Aggiungere l'origine non ne registra nessuna: lo steward registra quelle volute.
Autenticazione. Il modulo chiede una sola cosa, un token di accesso (BRAND_AUTH: token). Usate una chiave API segreta o limitata (restricted) di Stripe. Il token è inviato come bearer token ed è verificato una volta con GET /v1/balance prima che l'origine sia aggiunta. Un token rifiutato fa fallire la registrazione con openapi.credential_rejected; uno mancante la fa fallire con openapi.credential_required.
Aggiornamento e freschezza. Una tabella Stripe è una replica, come ogni tabella OpenAPI. Le sue risposte in cache durano per il cache_ttl dell'origine, che una richiesta di registrazione imposta a 300 secondi se non dice altrimenti. Si applicano i tipi di sonda OpenAPI. Un preset può indicare il parametro che un aggiornamento usa per recuperare solo ciò che è cambiato. Il marchio Stripe non ne indica nessuno, quindi ogni aggiornamento legge le sue tabelle per intero.
Esclusioni. L'integrazione di pagamento deprecata di Stripe basata su token e Sources è lasciata fuori dalla specifica distribuita. L'elenco è sotto Esclusioni su un'origine costruita da una specifica. Le exclusions proprie dell'operatore si aggiungono a quelle del marchio.
stripe compare anche in SourceType tra i tipi stub di Supabase wrappers. Un'origine Stripe aggiunta tramite il modulo è un'origine openapi, non quello stub.
Esclusioni su un'origine costruita da una specifica¶
Una specifica OpenAPI pubblicata descrive tutto ciò che l'API remota accetta, compreso ciò che il suo fornitore ha dichiarato obsoleto e ciò che un deployment non deve mai inviare. Un'origine OpenAPI può avere delle esclusioni: elementi della specifica che l'origine non offre.
| Escluso | Indicato da | Effetto |
|---|---|---|
| Un'operazione | method e path |
Non elencata per la registrazione come tabella o come comando |
| Un argomento di richiesta | name, su una operazione (method e path) oppure, senza di essi, su ogni operazione che lo ha |
Non tra gli argomenti dell'operazione, non mostrato, mai inviato |
Le esclusioni si impostano sotto exclusions nel mapping dell'origine:
sources:
- id: shop-api
type: openapi
path: https://shop.example.com/openapi.json
mapping:
exclusions:
operations:
- {method: POST, path: /v1/tokens}
arguments:
- {name: source, method: POST, path: /v1/customers}
- {name: legacy_token} # on every operation that has it
Vengono applicate alla specifica quando viene caricata, quindi l'elenco di registrazione, i comandi generati e la richiesta inviata vedono tutti una specifica in cui gli elementi esclusi non esistono. Si può escludere una proprietà del body e un parametro di query, header o cookie; un parametro di percorso (path) no, perché fa parte dell'indirizzo dell'operazione.
Un'esclusione che nomina un'operazione o un argomento che la specifica non ha fa fallire il caricamento della specifica di quell'origine e nomina ogni voce obsoleta, così un elenco che non corrisponde più a una specifica più recente non passa inosservato.
Un'origine con marchio fornisce proprie esclusioni come parte della sua curatela, e quelle dell'operatore vi si aggiungono. L'origine Stripe esclude l'integrazione di pagamento obsoleta di Stripe basata su token e source: la creazione di un addebito (charge), le API Tokens e Sources, l'aggiunta di una carta o di un conto bancario a un cliente a partire da un token, e gli argomenti source, card, bank_account e default_card delle scritture di clienti e di pagamenti di fatture. I pagamenti si effettuano tramite Payment Intents, Payment Methods e Setup Intents, che l'origine offre. La lettura e l'eliminazione dei metodi di pagamento che un account ha già restano disponibili.
GovData¶
Dati aperti del governo statunitense. L'accesso è partizionato per raggruppamento subject.
A un'origine vengono assegnati uno o più subject, e ogni subject porta gli schemi dell'adattatore elencati sotto. Uno schema può appartenere a più di un subject (census). Gli schemi ref e geo sono schemi linker: dati di riferimento e geografia attraverso cui si uniscono le tabelle di ogni subject, quindi ogni origine li serve qualunque subject le sia stato assegnato. L'elenco è dichiarato una sola volta sul server ed è vincolato, da un test, agli schemi serviti dal bundle dell'adattatore fissato.
| Subject | Schemi esposti |
|---|---|
COMMERCE |
sec, patents, cftc |
ECONOMY |
econ, econ_reference, ag, banking, fiscal, housing, transport |
EDUCATION |
census, edu, research |
HEALTH |
health |
CYBER |
cyber_threat, cyber_vuln |
PUBLIC_SAFETY |
crime, disasters |
ENVIRONMENT |
lands, environment |
WEATHER |
weather |
ENERGY |
energy |
GOVERNMENT |
fedregister, fec, officials, law |
DEMOGRAPHICS |
census |
| ogni origine | ref, geo |
ALL è la scorciatoia di una sottoscrizione per tutti i subject; non è una scelta nel modulo Sources.
sources:
- id: federal-commerce
type: govdata
subject: COMMERCE
domain_id: federal-analytics
description: U.S. commerce and securities data
| Campo | Richiesto | Default | Descrizione |
|---|---|---|---|
id |
Sì | — | Identificatore univoco |
subject |
Sì | — | Uno dei valori subject sopra |
domain_id |
Sì | — | Dominio a cui appartiene questa origine |
description |
No | "" |
Descrizione leggibile |
Un'origine AskAmerica creata nell'admin richiede una sola credenziale, la chiave API. Provisa presenta la chiave all'API di AskAmerica e riceve le credenziali di storage a breve durata e in sola lettura con cui i dati vengono letti; la chiave viene rifiutata lì, per nome, se è sconosciuta o revocata.
L'origine è poi un'origine Postgres-wire come le origini SharePoint, Salesforce e inventario cloud: Provisa avvia per essa il server pgwire-govdata integrato, con la chiave e quelle credenziali nell'ambiente del server, e ogni motore raggiunge quel server come raggiunge quello di un'origine sorella. DuckDB, Postgres e ClickHouse lo collegano e leggono le sue tabelle sul posto, quindi una tabella AskAmerica si unisce alle tabelle di altre origini, sta sotto le view ed è consegnata come qualsiasi altra. Trino non ha un connettore per essa e legge una replica atterrata tramite il server: una copia della tabella conservata nello store di materializzazione, aggiornata quanto il suo ultimo refresh secondo le impostazioni di cache e replica della tabella, anziché letta al momento della query. Una tabella viene letta dallo schema dell'adattatore che nomina (sec, econ e così via).
Il server monta i suoi schemi dall'object storage prima di accettare una connessione, il che richiede minuti al primo avvio. Viene avviato quando l'origine viene salvata; finché non è pronto, il modulo Registra tabella segnala che l'origine è ancora in avvio.
Dove risiede la password di un'origine¶
La password di un'origine non viene mai memorizzata insieme al resto delle sue impostazioni di
connessione. La riga sources del control plane porta una colonna password_ref che contiene ciò
che è stato inserito come password: il valore stesso, oppure un riferimento come
${env:PG_PASSWORD} o ${secret:SNOWFLAKE_KEY}. Viene risolto nel momento in cui l'origine viene
contattata, all'interno dell'organizzazione in cui la richiesta è in esecuzione.
Un valore senza ${…} al suo interno viene usato così com'è. ${env:VAR} legge l'ambiente di
processo del deployment e non richiede alcun binding. ${secret:NAME} nomina un secret di
proprietà di un'organizzazione, quindi si risolve solo all'interno delle operazioni proprie di
quell'organizzazione: le seam di introspezione admin e il terminale di query che ogni superficie
raggiunge stabiliscono quel binding.
Il campo contiene ciò che vi è stato inserito, comunque l'origine sia stata registrata:
- Da config. Il file è il record: Provisa copia il valore o il riferimento in
password_refalla lettera. - Dal modulo Sources. Ciò che viene digitato nel campo password viene memorizzato alla lettera,
valore o riferimento che sia. Provisa non scrive nulla nel vault dell'organizzazione per esso e non
inventa alcun nome di secret. Per tenere una credenziale fuori dalla riga del control plane, crea
tu stesso il secret (vedi Secrets) e digita
${secret:NAME}nel campo.
Un valore digitato come valore viene memorizzato nel database del control plane così come digitato. Riscrivere la password sostituisce il testo del campo. Eliminare l'origine rimuove la sua riga e nulla dal vault: un secret che hai creato è tuo.
password_ref non viaggia tra ambienti. Un branch o un ambiente copiato fornisce i
propri valori di connessione, e il vault che un riferimento nomina appartiene a qualunque ambiente
lo abbia fornito.
Data profiler¶
Un data profiler è un'origine che contiene un nome, una pianificazione e i valori predefiniti di esecuzione, e nient'altro. Non viene mai sottoposta a introspezione. Vi si aggiungono tabelle di altre origini, e ogni esecuzione le misura e scrive le sue righe nel control plane dell'organizzazione, dove vengono lette come le tabelle di qualsiasi altra origine. Vedi Test data per creare un profiler e aggiungervi tabelle.
Controlli di qualità dei dati¶
Un checker di qualità dei dati è semplicemente un altro tipo di origine. Il suo output di scansione sono dati: ogni risultato di controllo è un'osservazione, quindi viene depositato per il percorso ordinario delle origini e riceve ciò che riceve ogni altra origine, da cadenza, freschezza, eventi e derivazione fino a governance, RLS, griglia ed esportazione.
Sono supportati due checker, e scegliere tra loro è una questione di licenza quanto di funzionalità.
| Tipo di origine | Dialetto di contratto | Extra | Licenza | Piano cloud ospitato |
|---|---|---|---|---|
soda |
Soda contract YAML | pip install .[soda] (soda-postgres) |
Elastic License 2.0 | Rifiutato — vedi sotto |
great_expectations |
Expectation suite JSON | pip install .[gx] (great-expectations[postgresql]) |
Apache 2.0 | Consentito |
La Elastic License 2.0 vieta di fornire il software a terzi come servizio ospitato o gestito, ed eseguire Soda nel piano SaaS per conto di un tenant sarebbe esattamente questo. config/capabilities.yaml lo registra come cloud_eligible: false sull'opzione soda, e il piano ospitato legge quel flag. Un deployment ospitato che vuole Soda punta a un endpoint Soda che l'operatore gestisce in proprio.
Provisa non include né collega nulla. La scansione gira in un interprete figlio (python -m provisa.dq.worker), l'unico punto in cui si importa soda_core o great_expectations. Un checker source-available quindi non raggiunge mai il processo server, e se un checker va in crash si porta via un sottoprocesso e lascia in pace l'event loop.
L'origine punta all'endpoint pgwire di Provisa stesso. È così che un solo driver postgres può controllare una tabella basata su Snowflake o Iceberg: il checker scansiona la vista federata, mai il sistema sottostante. Su quella connessione si applica la policy, quindi l'identità della scansione si dichiara esplicitamente. Se fosse ereditata, un insieme di righe filtrato potrebbe far passare un controllo senza che nessuno se ne accorga.
sources:
- id: dq
type: soda
domain_id: sales-analytics
description: Soda contract scans over the governed estate
mapping:
host: localhost
port: 5439 # Provisa's pgwire endpoint
database: provisa
user: dq_scanner # the scan identity, declared explicitly
password: ${env:PROVISA_DQ_PASSWORD}
Una tabella di risultati per contratto, e il contratto è tutta la registrazione. La tabella porta dq_contract, il testo del contratto parola per parola, e nient'altro sulla sua forma. Colonne, watermark e promozioni derivano tutti da lì.
tables:
- source_id: dq
schema_name: quality
table_name: orders_scan
domain_id: sales-analytics
change_signal: ttl_probe
cache_ttl: 3600
columns:
- name: scan_id # declared only to carry visible_to; replaced at parse
visible_to: [analyst, admin]
dq_contract: |
dataset: provisa/sales/orders
columns:
- name: customer_id
checks:
- missing:
threshold:
metric: percent
must_be_less_than: 1
checks:
- row_count:
must_be_greater_than: 0
Da quel testo la registrazione ricava quattro cose:
- Derivazione. Il contratto nomina già il proprio dataset di destinazione, quindi la registrazione lo analizza come
extract_inputsanalizza l'SQL e lo risolve nella tabella governata. C'è una sola definizione e nessuna seconda copia che possa divergere. Un contratto che nomina un dataset non governato fallisce in modo evidente alla registrazione, così non deposita mai righe che nessuno ha chiesto. - Colonne. L'involucro del risultato appartiene al checker: 16 colonne fornite, da
scan_idadiagnostics. Delle colonne dichiarate si legge solo ilvisible_to, che deve essere uguale su tutte, e poi vengono sostituite. - Watermark.
scan_timediventa il watermark, il che rende il deposito un append. La cronologia delle scansioni si accumula senza un sottosistema di cronologia. - Promozioni.
freshness_max_timestampedataset_rows_testedvengono promossi dal jsonbdiagnosticscome colonne tipizzate. Potete aggiungerne altre come su qualsiasi altra colonna jsonb.
I tempi non richiedono campi nuovi. change_signal più cache_ttl stabiliscono ogni quanto si interroga, mv_debounce_quiet e mv_debounce_max_delay raccolgono una raffica di modifiche a monte in una sola scansione, una granularità di calendario la rende periodica e expected_events trattiene la scansione finché i suoi input non sono freschi su tutta la finestra. Il ciclo di polling è lo scheduler delle scansioni.
outcome vale pass, fail, warn, error o skipped. Nessuno di questi impone nulla da solo. Se volete un'imposizione, dichiaratela a parte: un preflight, o una MV sui risultati depositati. Un'osservazione depositata non deve essere deterministica, quindi qui vanno bene controlli che non potrebbero mai governare un preflight, come un punteggio di anomalia, una variazione su una finestra mobile o la freschezza misurata rispetto ad adesso.
Il contratto si scrive nella UI, nel pannello qualità dei dati della pagina di modifica della tabella, e il testo grezzo del contratto lì presente è sempre la fonte di verità. Un'esecuzione di prova applica il contratto alla tabella live e mostra gli esiti senza depositarli. Usatela per scovare un contratto il cui nome di dataset si è risolto in un punto inatteso e che altrimenti depositerebbe solo righe superate.
Connettori personalizzati¶
Un operatore può dare ai motori di federazione nativi (Postgres, DuckDB e ClickHouse) accesso a un nuovo tipo di origine dichiarando un connettore per esso in config/custom_connectors.yaml. Non serve codice.
Così i motori nativi, che girano senza cluster, ottengono la stessa estensibilità guidata dalla configurazione che il motore Trino ha al proprio livello. Lì un connettore JDBC generico viene parametrizzato per tipo di origine, ogni tipo riceve un corpo .properties di catalogo e Provisa aggiunge i propri plugin di connettore Trino (Splunk, SharePoint, Calcite).
Il file viene fornito vuoto. I connettori integrati coprono ciò che funziona subito, e tutto ciò che sta in questo file lo scrive l'operatore. Per usare un altro percorso, per esempio nei test, impostate PROVISA_CUSTOM_CONNECTORS.
Tipi di descrittore¶
| Motore | Tipo | Meccanismo | Cosa fornisce il descrittore |
|---|---|---|---|
postgres |
pg_fdw |
SQL/MED (standard ISO) | extension, server_options, user_mapping, supports_import, table_options, remote_schema |
duckdb |
duckdb_attach |
INSTALL/LOAD + ATTACH | extension, probe_symbol, attach_template, remote_schema |
duckdb |
duckdb_scan |
INSTALL/LOAD + vista scanner | extension, probe_symbol, scan_template |
clickhouse |
clickhouse_database |
CREATE DATABASE ENGINE=… (espone automaticamente ogni tabella remota) |
ch_engine, engine_template |
clickhouse |
clickhouse_table |
CREATE TABLE ENGINE=… per tabella (colonne dal registro) |
ch_engine, engine_template (può portare {table}) |
clickhouse |
clickhouse_scan |
CREATE TABLE ENGINE=…, ClickHouse inferisce lo schema |
ch_engine, engine_template |
Postgres è generico. SQL/MED è uno standard ISO, quindi ogni FDW conforme condivide la stessa forma di DDL: CREATE SERVER … FOREIGN DATA WRAPPER <fdw> OPTIONS(…), opzionalmente CREATE USER MAPPING, poi o IMPORT FOREIGN SCHEMA (quando supports_import: true) oppure un CREATE FOREIGN TABLE esplicito per tabella (quando false). Un descrittore pg_fdw fornisce solo la variazione per-FDW: nome dell'estensione, chiavi delle opzioni server, chiavi di user-mapping, flag di import, opzioni tabella. Qualsiasi FDW conforme allo standard è quindi pilotabile solo da configurazione.
DuckDB supporta due meccanismi. Un'estensione che espone un catalogo via ATTACH usa duckdb_attach; una che espone una table-function di lettura usa duckdb_scan. Un'estensione che non rientra in nessuno dei due schemi non è supportata.
ClickHouse supporta tre meccanismi, uno per ciascuna forma di motore di integrazione: un motore DATABASE relazionale che espone automaticamente ogni tabella remota (clickhouse_database, es. Redis/MySQL), un motore per tabella le cui colonne sono fornite dal registro (clickhouse_table, es. il bridge JDBC/ODBC; l'engine_template può portare un placeholder {table} che il runtime lega), e un motore file/lake/URL il cui schema ClickHouse inferisce (clickhouse_scan, es. HDFS/URL). SQLite (motore DATABASE, file, nessun server) e Hudi (lakehouse, zero-copy) sono forniti out-of-the-box.
Un valore kind sconosciuto fallisce in modo evidente all'avvio, così un refuso in un descrittore non lascia mai un tipo di origine irraggiungibile senza dirlo.
Gating della sonda (probe)¶
La disponibilità viene verificata al momento dell'attach contro il catalogo di discovery standard di ciascun motore:
- Postgres — controlla
pg_extension, poipg_available_extensions. - DuckDB — esegue
INSTALL/LOADe controlladuckdb_functions()per ilprobe_symboldichiarato. - ClickHouse — controlla
system.table_enginesper ilch_enginedichiarato; l'assenza dal build fallisce in modo esplicito.
Un'estensione dichiarata che non è installabile fallisce in modo esplicito. Nessun salto silenzioso, nessun fallback. Un connettore la cui sonda fallisce semplicemente non è attivo per quel deployment.
Variabili di template¶
Ogni valore server_options, valore user_mapping, attach_template e scan_template può usare placeholder {field}. Campi disponibili:
{id}, {host}, {port}, {database}, {username}, {password}, {path}, {schema_name}, {table_name}, più qualsiasi chiave di federation_hints. I template di attach di DuckDB ricevono anche {alias}, l'alias di catalogo interno che Provisa dà al database collegato.
Un template che referenzia un campo sconosciuto fallisce in modo esplicito al momento dell'attach, facendo emergere un mismatch descrittore/origine prima che DDL rotto raggiunga il motore.
Esempi¶
Postgres — MongoDB via mongo_fdw (nessun import di schema; colonne fornite per tabella)
# config/custom_connectors.yaml
connectors:
- engine: postgres
source_type: mongodb
kind: pg_fdw
extension: mongo_fdw
mechanism: attach_r
server_options:
address: "{host}"
port: "{port}"
user_mapping:
username: "{username}"
password: "{password}"
supports_import: false
table_options:
database: "{database}"
collection: "{table_name}"
DuckDB — file Excel via read_xlsx (scan table-function)
- engine: duckdb
source_type: xlsx
kind: duckdb_scan
extension: excel
install_from_community: false
probe_symbol: read_xlsx
scan_template: "read_xlsx('{path}')"
Con l'uno o l'altro descrittore in atto, registrare un'origine con il source_type dichiarato viene instradato attraverso il connettore personalizzato, soggetto a una sonda riuscita. Non è necessaria alcuna altra modifica di configurazione.
Warehouse come origini nominate¶
Snowflake, Databricks e ClickHouse possono essere registrati come origini nominate indipendentemente da quale motore di federazione è attivo.
Una volta registrato, il warehouse viene letto tramite il DirectDriver dell'origine, che atterra una replica nello store di materializzazione del motore attivo, e la query gira contro quella replica. Sul consueto percorso diretto (asyncpg, aiomysql) il motore viene saltato del tutto. Qui il motore esegue comunque la query, ma contro una replica locale invece di passare in rete fino al warehouse a ogni richiesta.
Le letture sono Arrow-native dove il warehouse lo supporta: Databricks usa Cloud Fetch, Snowflake usa fetch_arrow_table, e ClickHouse usa l'interfaccia HTTP columnar nativa.
I parametri di connessione estesi che i campi standard host/port/username/password non possono portare vanno in federation_hints:
sources:
- id: my-databricks
type: databricks
host: my-workspace.azuredatabricks.net
password: ${env:DATABRICKS_TOKEN}
federation_hints:
http_path: /sql/1.0/warehouses/xxxx # required — the SQL Warehouse connection detail
- id: my-snowflake
type: snowflake
host: org.snowflakecomputing.com
username: svc_provisa
password: ${env:SNOWFLAKE_PASSWORD}
federation_hints:
account: myorg-myaccount # required — Snowflake account identifier
warehouse: COMPUTE_WH # optional — virtual warehouse to use
role: PROVISA_ROLE # optional — Snowflake role
- id: my-clickhouse
type: clickhouse
host: ch.example.com
port: 8123
database: analytics
username: default
password: ${env:CLICKHOUSE_PASSWORD}
federation_hints:
secure: "true" # optional — enables TLS on the HTTP interface
La registrazione come origine nominata è indipendente dalla selezione dello stesso warehouse come motore di federazione. Un'origine Snowflake su un motore DuckDB atterra una replica in DuckDB, non in Snowflake.
I dati oggetto e lake nel cloud (file parquet, csv, iceberg e delta_lake su S3, GCS o R2) sono un tipo di origine a parte. Si collegano sul posto quando il motore attivo ha un connettore ATTACH per quel tipo, quindi non si atterra alcuna replica e il motore scansiona direttamente l'object storage. Anche le credenziali di queste origini vanno in federation_hints:
sources:
- id: r2-events
type: parquet
path: s3://my-bucket/events/2026/*.parquet
federation_hints:
access_key_id: ${env:R2_ACCESS_KEY}
secret_access_key: ${env:R2_SECRET}
account_id: ${env:R2_ACCOUNT_ID} # Cloudflare R2 account (S3-compatible)
Campi di configurazione dell'origine¶
Tutte le origini condividono un insieme comune di campi.
| Campo | Richiesto | Default | Descrizione |
|---|---|---|---|
id |
Sì | — | Identificatore univoco; alfanumerico con trattini/underscore |
type |
Sì | — | Tipo di origine (vedi tabelle sopra) |
host |
No | "" |
Hostname o IP |
port |
No | 0 |
Numero di porta |
database |
No | "" |
Nome del database |
username |
No | "" |
Nome utente |
password |
No | "" |
Password; usa ${env:VAR} o ${secret:NAME} invece di un letterale (vedi sotto) |
path |
No | null |
Percorso file o URI cloud per origini basate su file e object/lake |
base_url |
No | null |
URL base per origini OpenAPI |
pool_min |
No | 1 |
Dimensione minima del connection pool |
pool_max |
No | 5 |
Dimensione massima del connection pool |
use_pgbouncer |
No | false |
Instrada le connessioni attraverso PgBouncer |
pgbouncer_port |
No | 6432 |
Porta PgBouncer |
cache_enabled |
No | true |
Abilita la cache delle risposte API |
cache_ttl |
No | null |
TTL della cache in secondi; eredita il default globale quando null |
cache_catalog |
No | null |
Catalogo federato per la cache API; di default il catalogo dell'origine stessa |
cache_schema |
No | api_cache |
Schema all'interno del catalogo di cache |
naming_convention |
No | null |
Sovrascrive la convenzione di naming globale per questa origine |
federation_hints |
No | {} |
Proprietà di sessione passate al motore di federazione, e parametri di connessione estesi per origini warehouse |
mapping |
No | {} |
Impostazioni di connettore specifiche per tipo per origini NoSQL e SaaS (es. auth_type di SharePoint, auth_type di Salesforce, use_token di Splunk) |
allowed_domains |
No | [] |
Restringe l'origine a domini specifici; vuoto = senza restrizioni |
description |
No | "" |
Descrizione leggibile |
Origini Kafka¶
I topic Kafka vengono configurati separatamente sotto kafka_sources, indicizzati dall'id di origine di un'origine kafka registrata.
kafka_sources:
- id: kafka-support
topics:
- id: tickets
topic: support.tickets
domain_id: sales-analytics
description: "Inbound support tickets"
default_window: 1h
columns:
- name: id
- name: subject
- name: status
- name: created_at
| Campo | Descrizione |
|---|---|
id |
Deve corrispondere all'id di un'origine con type: kafka |
topics[].id |
Nome logico per questo topic all'interno di Provisa |
topics[].topic |
Nome del topic Kafka |
topics[].domain_id |
Dominio a cui appartiene questo topic |
topics[].description |
Descrizione leggibile |
topics[].default_window |
Finestra temporale di default per query con finestra (es. 1h) |
topics[].columns |
Definizioni di colonna per lo schema del topic |
Visibilità delle colonne¶
Il campo visible_to su ogni colonna è una lista di ID di ruolo che possono vedere quella colonna.
columns:
- name: email
visible_to: [admin] # only admin role sees this column
- name: region
visible_to: [admin, analyst] # both roles see this column
Le colonne omesse dalla lista visible_to di un ruolo non appaiono nello schema GraphQL di quel ruolo e non possono essere interrogate o referenziate nei filtri.
Relazioni¶
Le relazioni collegano due tabelle registrate e appaiono come campi annidati in GraphQL.
relationships:
- id: orders-to-customers
source_table_id: orders
target_table_id: customers
source_column: customer_id
target_column: id
cardinality: many-to-one
| Campo | Richiesto | Descrizione |
|---|---|---|
id |
Sì | Identificatore univoco per questa relazione |
source_table_id |
Sì | Tabella che detiene la chiave esterna |
target_table_id |
Sì | Tabella referenziata; vuoto per relazioni calcolate |
source_column |
Sì | Colonna sulla tabella sorgente |
target_column |
Sì | Colonna sulla tabella target; vuoto per relazioni calcolate |
cardinality |
Sì | many-to-one o one-to-many |
materialize |
No | Crea automaticamente una vista materializzata per join cross-source. Su un arco basato su una giunzione la vista copre la traversata a due salti, non un join diretto |
refresh_interval |
No | Intervallo di refresh della MV in secondi (default: 300) |
target_function_name |
No | Nome della funzione DB per relazioni calcolate |
function_arg |
No | Quale argomento della funzione riceve il valore della colonna sorgente |
alias |
No | Tipo di relazione leggibile (es. WORKS_FOR) |
graphql_alias |
No | Nomina il campo SDL che questa relazione espone sul tipo padre. Quando assente, il nome viene derivato dal field_name della tabella target e dalla cardinalità della relazione. |
disable_cypher |
No | Quando true, esclude questa relazione dagli archi grafo Cypher |
source_json_key |
No | Estrae questa chiave dalla colonna sorgente come oggetto JSON prima del JOIN |
via_table |
No | Nome della tabella registrata della giunzione che questo arco attraversa. Valorizzarlo rende l'arco basato su una giunzione; lasciarlo vuoto lo lascia un arco su chiave esterna |
via_source_column |
No | Colonna della giunzione accoppiata a source_column. Separata da virgole e posizionale per una chiave composta |
via_target_column |
No | Colonna della giunzione accoppiata a target_column |
via_type_column |
No | Colonna discriminante, quando una stessa giunzione porta più tipi di relazione |
via_type_value |
No | Il valore del discriminante a cui questo arco è fissato |
via_label_source |
No | Quale designazione dà il nome al tipo Cypher: column (il valore del discriminante), table (il nome della tabella di giunzione) o fixed (l'alias dichiarato). Tutte vengono portate in UPPER_SNAKE_CASE |
Relazioni basate su una tabella di giunzione¶
Potete dichiarare una tabella associativa come relazione Cypher a pieno titolo, invece che come nodo. Le sue colonne diventano allora gli attributi della relazione:
relationships:
- id: pets-bonded-pair
source_table_id: pets
target_table_id: pets
source_column: id
target_column: id
cardinality: one-to-many
via_table: pet_companions
via_source_column: pet_id
via_target_column: companion_pet_id
via_type_column: relation_type
via_type_value: bonded pair
via_label_source: column
La tabella di giunzione è una tabella registrata come le altre, e va registrata prima che una relazione possa nominarla. Dichiarate una relazione per ogni valore del discriminatore. Tre righe su pet_companions danno BONDED_PAIR, LITTERMATE e SHARES_ENCLOSURE come tre tipi Cypher distinti, ciascuno con le altre colonne della riga di giunzione come proprietà dell'arco. La configurazione demo fornita dichiara esattamente questo.
Un arco di giunzione esiste solo in Cypher. L'emettitore di join GraphQL costruisce la sua clausola ON per una sola coppia di colonne e non ha posto per il secondo salto, quindi gli archi di giunzione restano fuori dall'SDL generato e da pg_constraint. La tabella di giunzione resta interrogabile come proprio campo radice. Viene tolta dal lato nodi dello schema del grafo Cypher, così non compare mai come etichetta di nodo.
materialize: true funziona anche su un arco di giunzione. Ciò che materializza è l'intero attraversamento: la vista contiene il salto di origine, il salto di giunzione, il discriminatore e le colonne proprie della giunzione accanto a quelle della destinazione. La giunzione è una terza gamba del join, quindi se l'arco attraversa più origini si giudica su tutte e tre le tabelle. Una giunzione che sta in un'origine diversa dalle due tabelle che collega viene materializzata anche quando quelle due condividono l'origine. Ogni dichiarazione materializza un tipo di arco, quindi una vista costruita per bonded pair non risponde mai a un attraversamento littermate.
Valori di cardinalità:
many-to-one: ogni riga di origine corrisponde a una riga di destinazione (FK verso PK)one-to-many: ogni riga di origine corrisponde a più righe di destinazione (l'inverso)
Regole di sicurezza a livello di riga¶
Le regole RLS iniettano clausole WHERE al momento della query, ambito a un ruolo e opzionalmente a una tabella o dominio.
rls_rules:
- table_id: orders # applies to orders table only
role_id: analyst
filter: "region = current_setting('provisa.user_region')"
description: Analysts read only their own region's orders.
- domain_id: sales-analytics # applies to every table in domain
role_id: analyst
filter: "tenant_id = current_setting('provisa.tenant_id')"
description: Analysts read only their own tenant's rows.
Quando esistono sia una regola a livello di dominio che una a livello di tabella per lo stesso ruolo, la regola a livello di tabella ha la precedenza.
| Campo | Richiesto | Descrizione |
|---|---|---|
table_id |
Condizionale | Tabella a cui applicare la regola; mutuamente esclusivo con domain_id |
domain_id |
Condizionale | Dominio a cui applicare la regola; si applica a tutte le tabelle nel dominio |
role_id |
Sì | Ruolo a cui si applica questa regola |
filter |
Sì | Predicato SQL iniettato in WHERE; può referenziare variabili di sessione |
description |
Sì | A cosa serve la regola, come la legge una revisione degli accessi |
Funzioni e webhook¶
Funzioni DB¶
Traccia una funzione database ed esponila come query o mutation GraphQL.
Le origini database possono anche scoprire automaticamente le loro stored procedure e funzioni dal catalogo del vendor (pg_proc, information_schema.routines, o equivalenti del vendor), eliminando la necessità di registrare ciascuna a mano. La discovery legge prokind e provolatile: le funzioni immutabili/stabili si registrano come relazioni parametrizzate (gli argomenti della procedura diventano parametri di query, la stessa forma delle tabelle OpenAPI GET), e le procedure volatili si registrano come mutation/funzioni tracciate. Le routine scoperte passano attraverso la governance Stage-2 in modo identico a quelle registrate a mano.
functions:
- name: get_customers_by_region
source_id: sales-pg
schema: public
function_name: get_customers_by_region
returns: customers
domain_id: sales-analytics
description: "Returns customers filtered by region"
visible_to: [admin, analyst]
kind: query
arguments:
- name: p_region
type: String
| Campo | Richiesto | Default | Descrizione |
|---|---|---|---|
name |
Sì | — | Nome del campo GraphQL |
source_id |
Sì | — | Origine contenente la funzione |
schema |
No | public |
Schema del database |
function_name |
Sì | — | Nome effettivo della funzione database |
returns |
Sì | — | ID della tabella registrata restituita dalla funzione |
arguments |
No | [] |
Elenco di definizioni argomento {name, type} |
visible_to |
No | [] |
Ruoli a cui è assegnata la funzione (vuoto: ogni ruolo). Una mutation richiede inoltre il diritto write |
domain_id |
No | "" |
Dominio a cui appartiene questa funzione |
description |
No | null |
Descrizione del campo GraphQL |
kind |
No | mutation |
"query" o "mutation" |
Webhook¶
Esponi un endpoint HTTP esterno come query o mutation GraphQL.
webhooks:
- name: notify_support
url: http://localhost:9999/notify
method: POST
timeout_ms: 3000
domain_id: sales-analytics
description: "Send a support notification"
visible_to: [admin]
kind: mutation
arguments:
- name: message
type: String
| Campo | Richiesto | Default | Descrizione |
|---|---|---|---|
name |
Sì | — | Nome del campo GraphQL |
url |
Sì | — | URL dell'endpoint webhook |
method |
No | POST |
Metodo HTTP |
timeout_ms |
No | 5000 |
Timeout della richiesta in millisecondi |
returns |
No | null |
ID della tabella registrata, o null per tipo inline |
inline_return_type |
No | [] |
Elenco di campi {name, type} per forme di ritorno personalizzate |
arguments |
No | [] |
Elenco di definizioni argomento {name, type} |
visible_to |
No | [] |
Ruoli che possono chiamare questo webhook |
domain_id |
No | "" |
Dominio a cui appartiene questo webhook |
description |
No | null |
Descrizione del campo GraphQL |
kind |
No | mutation |
"query" o "mutation" |
Autenticazione¶
L'autenticazione viene configurata sotto la chiave auth.
| Provider | Descrizione |
|---|---|
none |
Nessuna autenticazione; tutte le richieste trattate come il default_role |
firebase |
Firebase Authentication; richiede project_id e service_account_key |
keycloak |
Keycloak OIDC |
oauth |
OAuth 2.0 generico |
simple |
Nome utente/password senza un provider esterno |
auth:
provider: firebase
assignments_source: provisa # "claims" or "provisa"
default_role: analyst
default_assignments:
- role_id: analyst
domain_id: "*"
firebase:
project_id: ${env:FIREBASE_PROJECT_ID}
service_account_key: ${env:FIREBASE_SERVICE_ACCOUNT_KEY}
assignments_source: claims legge le assegnazioni di ruolo dalle claim JWT. assignments_source: provisa le legge dallo store di assegnazioni proprio di Provisa.
Routing di esecuzione¶
Esecuzione diretta. Una query che tocca una sola origine relazionale va direttamente al driver proprio di quell'origine, in meno di 100 ms. Un tipo di origine segue questo percorso quando Provisa conosce sia il suo dialetto SQL sia il suo connettore.
Esecuzione federata. Una query su più origini, o su un'origine senza driver diretto, passa dal motore di federazione. Provisa ne include uno integrato; per i deployment grandi, puntatelo al vostro cluster compatibile.
Statistiche. Quando una tabella viene registrata, Provisa esegue ANALYZE su ogni tabella pubblicata, così l'ottimizzatore basato sui costi ha conteggi di righe, frazioni di null, valori distinti e min/max. Se fallisce, l'errore viene registrato nel log e la registrazione prosegue.
Origini grafo e semantiche¶
Neo4j¶
Registra un database a grafo Neo4j come origine interrogabile. Gli steward scrivono query Cypher che proiettano valori scalari; Provisa mette in cache i risultati e li espone come tipi GraphQL.
Le query Cypher devono usare accessi alle proprietà nella clausola RETURN (RETURN n.id AS id, n.name AS name). La registrazione rifiuta una query che restituisce oggetti nodo.
Registrazione da file di configurazione¶
Dichiara un'origine neo4j e le sue tabelle in YAML. Ogni tabella richiede un query_template (il Cypher che produce le sue righe) e colonne tipizzate. La chiave query_template non è valida sotto nessun altro tipo di origine.
Un'origine richiede host, port e database. Le righe vengono recuperate con un POST di {"statements": [{"statement": <cypher>}]} a /db/<database>/tx/commit (l'API delle transazioni HTTP di Neo4j). Una risposta con un elenco errors non vuoto viene trattata come query fallita, non come risultato vuoto.
Ogni colonna richiede data_type. Il loader mappa i tipi di config al tipo di colonna API usato al momento della query:
data_type di config |
Tipo API |
|---|---|
varchar, text, string, char |
string |
integer, int, bigint, smallint |
integer |
float, double, real, decimal, numeric, number |
number |
boolean, bool |
boolean |
json, jsonb |
jsonb |
varchar(N) e decimal(10,2) sono accettati, e si usa il tipo di base prima della parentesi.
La registrazione persiste una riga api_sources e una riga api_endpoints per tabella, cosicché le tabelle sopravvivono a un riavvio senza rileggere il file. Gli endpoint REST admin sotto /admin/sources/neo4j scrivono le stesse righe.
sources:
- id: graph
type: neo4j
host: neo4j
port: 7474
database: neo4j
cache_ttl: 300
tables:
- source_id: graph
schema: neo4j
table: person_skills
query_template: >-
MATCH (p:Person)-[:HAS_SKILL]->(s:Skill)
RETURN p.name AS name, s.skill AS skill, p.experience AS years
columns:
- name: name
data_type: varchar
- name: skill
data_type: varchar
- name: years
data_type: integer
Registra tabella nella UI¶
Un'origine neo4j non ha tabelle da elencare, quindi il modulo Registra tabella chiede la tabella invece di offrirne una.
- Scegli l'origine neo4j e un dominio. I selettori di schema e tabella, la checkbox di discovery e il selettore del watermark non appaiono; l'origine non viene mai introspezionata.
- Digita un nome tabella e il Cypher. Il Cypher deve proiettare scalari (
RETURN a.name AS name); una proiezione che restituisce un nodo o una lista viene segnalata come errore. - Premi Anteprima. Il modulo esegue il Cypher con
LIMIT 5tramite la query GraphQLneo4jPreviewe compila l'elenco delle colonne dalle righe restituite, tipizzate cometext,integer,double,booleanojson. Un'anteprima fallita mantiene il Cypher nell'editor e mostra il messaggio. - Regola visibilità, alias o mascheramento come per qualsiasi tabella, poi registra. Il modulo rifiuta l'invio finché un'anteprima non ha tipizzato le colonne, e il server rifiuta una tabella neo4j priva di Cypher (
schema.neo4j_query_required).
Il Cypher viene memorizzato con la tabella come queryTemplate, appare nella vista di lettura della tabella, e persiste esattamente come fa una registrazione da file di configurazione: una riga api_sources e una riga api_endpoints che il riavvio successivo idrata. Modificare la tabella ripersiste un Cypher modificato.
Registrazione REST admin¶
# Register via admin API (no YAML config required)
POST /admin/sources/neo4j
{
"source_id": "graph",
"host": "neo4j",
"port": 7474,
"database": "neo4j"
}
# Register a table (preview + validate before persisting)
POST /admin/sources/neo4j/graph/tables
{
"table_name": "person_skills",
"cypher": "MATCH (p:Person)-[:HAS_SKILL]->(s:Skill) RETURN p.name AS name, s.skill AS skill, p.experience AS years",
"ttl": 300
}
L'endpoint di preview (POST /admin/sources/neo4j/{id}/preview) restituisce righe di esempio e blocca la registrazione se il Cypher restituisce oggetti nodo.
SPARQL¶
Registra qualsiasi triplestore conforme a SPARQL 1.1 (Apache Jena Fuseki, Virtuoso, Stardog, ecc.) come origine interrogabile.
Le query devono essere query SELECT. I nomi delle variabili nella clausola SELECT diventano automaticamente nomi di colonna.
# Register via admin API
POST /admin/sources/sparql
{
"source_id": "knowledge-graph",
"endpoint_url": "http://fuseki:3030/ds/sparql",
"default_graph_uri": "http://example.org/graph"
}
# Register a table (executes LIMIT 5 probe to validate and infer columns)
POST /admin/sources/sparql/knowledge-graph/tables
{
"table_name": "product_categories",
"sparql_query": "SELECT ?product ?label ?category WHERE { ?product a :Product ; rdfs:label ?label ; :hasCategory ?category . }",
"ttl": 600
}
Entrambi i connettori usano la pipeline di cache delle origini API. I risultati vengono memorizzati in PostgreSQL con TTL configurabile, rendendoli disponibili per JOIN federati cross-source.
Registrazione da file di configurazione e da UI¶
L'host di un'origine sparql è l'URL del suo endpoint SPARQL (il modulo Sources lo memorizza allo stesso modo). Ogni tabella sotto di essa porta query_template, un SELECT le cui variabili sono le colonne; ogni binding è text.
sources:
- id: sparql-demo
type: sparql
host: http://localhost:23030/provisa/query
tables:
- source_id: sparql-demo
domain_id: shelter
schema: sparql
table: volunteer
query_template: >-
PREFIX s: <http://provisa.dev/shelter#>
SELECT ?volunteer_id ?name WHERE { ?v a s:Volunteer ; s:id ?volunteer_id ; s:name ?name }
columns:
- { name: volunteer_id, data_type: text, visible_to: [org_admin] }
- { name: name, data_type: text, visible_to: [org_admin] }
Registra tabella funziona allo stesso modo di Neo4j: scegli l'origine, digita un nome tabella e il SELECT, premi Anteprima (la query sparqlPreview lo esegue con LIMIT 5 e compila l'elenco delle colonne), poi registra. La registrazione persiste una riga api_sources e una riga api_endpoints (POST form-encoded al percorso dell'endpoint, normalizzatore sparql_bindings), le stesse righe che scrive una registrazione da file di configurazione, e il motore nativo atterra le righe tramite la stessa catena di fetch di Neo4j.
Esempi di connessione¶
PostgreSQL¶
- id: sales-pg
type: postgresql
host: postgres
port: 5432
database: provisa
username: provisa
password: ${env:PG_PASSWORD}
Snowflake¶
- id: analytics-sf
type: snowflake
host: org.snowflakecomputing.com
port: 443
database: ANALYTICS
username: svc_provisa
password: ${env:SNOWFLAKE_PASSWORD}
federation_hints:
account: myorg-myaccount
warehouse: COMPUTE_WH
Databricks¶
- id: lakehouse-db
type: databricks
host: my-workspace.azuredatabricks.net
password: ${env:DATABRICKS_TOKEN}
federation_hints:
http_path: /sql/1.0/warehouses/xxxx
MongoDB¶
- id: reviews-mongo
type: mongodb
host: mongodb
port: 27017
database: provisa
username: ""
password: ""
Query cross-source¶
{
orders(where: {region: {eq: "us"}}) {
id
amount
customers { # PostgreSQL
name
email
}
productReviews { # MongoDB (federated)
rating
comment
}
}
}
Le porzioni a singola origine vengono instradate direttamente. I JOIN cross-source federano con coercizione di tipo automatica.