SQL-Adapter über Apache Ossie
Saiku liefert einen Calcite-SQL-Adapter, der Apache-Ossie-YAML liest — dasselbe Format, das der Ossie-Exporter aus Ihrem Mondrian-Schema produziert — und es als abfragbare JDBC-Oberfläche bereitstellt. Zeigen Sie irgendeinen SQL-Client darauf, und Sie können aus Ihren Ossie-Datasets SELECTen, genau als wären sie echte Datenbanktabellen. Unter der Haube plant Calcite die Abfrage und schiebt sie als natives SQL in Ihr tatsächliches Warehouse hinunter.
Was Sie bekommen
- Standard-SQL über Ihren Ossie-Datasets — alles, was Calcite versteht, was eine große Obermenge von ANSI-SQL ist.
- Pushdown ins Warehouse. Calcite liest die Abfrage, plant sie gegen Ihr Warehouse (Postgres, Snowflake, H2, was auch immer) und emittiert natives SQL. Aggregate laufen im Warehouse, nicht in der JVM.
- JDBC-nativ. Funktioniert mit jedem JDBC-Client: dbt, DBeaver, Tableau, Power BI,
psql, JetBrains DataGrip, eigener Code. - Ossie-Renames überleben. Wenn Ihr Exporter Dataset-Namen emittiert hat, die nicht mit den zugrundeliegenden Warehouse-Tabellen übereinstimmen (via Ossies
source-Feld), mappt der Adapter transparent zurück —SELECT * FROM CUSTOMERSwird am Warehouse zuSELECT * FROM public.dim_customer_v2.
Schnellstart
1. Das Ossie-YAML produzieren
saiku ossie-export --in saiku-home/data/Pharma.xml --out pharma.ossie.yamlSiehe Export nach Apache Ossie für die Mapping-Tabelle und das durchgearbeitete Beispiel.
2. Ein Calcite-Connect-Model schreiben
Calcite-JDBC-Verbindungen nehmen ein JSON-Connect-Model, das Calcite mitteilt, welche Factory zu instanziieren ist:
{ "version": "1.0", "defaultSchema": "PHARMA", "schemas": [{ "name": "PHARMA", "type": "custom", "factory": "org.saiku.sql.adapter.OssieSchemaFactory", "operand": { "ossieYaml": "/absolute/path/to/pharma.ossie.yaml", "jdbcUrl": "jdbc:postgresql://localhost:5432/warehouse", "jdbcUser": "app", "jdbcPassword": "changeme" } }]}Operand-Schlüssel:
| Schlüssel | Erforderlich | Beschreibung |
|---|---|---|
ossieYaml | ja | Absoluter Pfad zur Ossie-YAML-Datei. |
modelName | nein | Name des semantic_model[]-Eintrags, der bereitgestellt werden soll, wenn das Dokument mehrere trägt. Standardmäßig der erste. |
jdbcUrl | dringend empfohlen | Warehouse-JDBC-URL. Ohne sie registrieren Tabellen, aber Abfragen geben null Zeilen zurück. |
jdbcUser, jdbcPassword | nach Bedarf | Warehouse-Anmeldedaten. |
3. Verbinden
Properties p = new Properties();p.put("model", "/path/to/model.json");p.put("caseSensitive", "false");try (Connection c = DriverManager.getConnection("jdbc:calcite:", p)) { var rs = c.createStatement().executeQuery( "SELECT REGION, COUNT(*) FROM PHARMA.PRESCRIBER GROUP BY REGION"); while (rs.next()) System.out.println(rs.getString(1) + " → " + rs.getInt(2));}Oder von jedem JDBC-Client — Verbindungs-URL jdbc:calcite:model=/path/to/model.json.
4. Oder als Netzwerk-Endpunkt betreiben
Für Remote-Clients verwenden Sie das saiku sql-serve-CLI-Subcommand. Es stellt zwei Endpunkte bereit — einen Apache-Avatica-Endpunkt für Avatica-fähige Clients UND einen nativen Postgres-Wire-Endpunkt für psql / pgAdmin / Tableau / DBeaver / dbt-postgres. Aktivieren Sie einen oder beide:
saiku sql-serve \ --ossie pharma.ossie.yaml \ --schema PHARMA \ --jdbc-url jdbc:postgresql://warehouse:5432/prod \ --jdbc-user app --jdbc-password changeme \ --port 8765 \ --pg-port 5432Der Server loggt beim Start beide URLs. Clients verbinden sich dann via:
Avatica:
jdbc:avatica:remote:url=http://localhost:8765# with serialization=protobufPostgres-Wire (jeder native PG-Client):
# psqlPGSSLMODE=disable psql -h localhost -p 5432 saiku
# JDBC — pgjdbc defaults (extended query mode) work; simple mode is also finejdbc:postgresql://localhost:5432/saiku?sslmode=disableParametrisierte Abfragen via PreparedStatement.setString(1, ...) etc. funktionieren out of the box.
Was heute funktioniert
| SQL-Feature | Status | Anmerkungen |
|---|---|---|
SELECT aus einem Dataset | ✅ | Ins Warehouse hinuntergeschoben. |
WHERE auf Dataset-Spalten | ✅ | Hinuntergeschoben. |
GROUP BY + Aggregate | ✅ | Aggregate laufen im Warehouse. |
Expliziter JOIN … ON zwischen Datasets | ✅ | Join-Prädikat schiebt hinunter. |
ORDER BY, LIMIT | ✅ | Hinuntergeschoben, wo der Warehouse-Dialekt es unterstützt. |
information_schema / DatabaseMetaData.getTables() | ✅ | Datasets, Metriken UND Join-Views durch BI-Tools auffindbar. |
Rename-Mapping (Ossie-Dataset-Name → Warehouse-Tabelle via source) | ✅ | Transparent. |
| Case-insensitiver Fallback (H2 UPPER vs Postgres lower) | ✅ | Adapter probiert alle Fälle. |
Skalares Metrik-SELECT — SELECT * FROM PHARMA.TOTAL_QUANTITY | ✅ | Expandiert das ANSI_SQL der Metrik gegen ihr Home-Dataset. Rückgabetyp aus dem zugrundeliegenden Spaltentyp abgeleitet (keine groben DOUBLE/BIGINT-Schätzungen). |
Relationship-Join-Views — SELECT ... FROM PHARMA.FACT_PHARMA_JOIN_PRESCRIBER ... | ✅ | Eine View pro Ossie-relationship, benannt <from>_JOIN_<to>. Das JOIN-Prädikat lebt im YAML, nicht in der Abfrage. Calcite schiebt das Ganze als einzelnen JOIN hinunter — kein Laufzeit-Overhead. |
Auto-injizierte Joins — SELECT c.x, SUM(o.y) FROM ORDERS o, CUSTOMERS c GROUP BY c.x (keine JOIN-Klausel) | ✅ | Benutzerdefinierte Calcite-Planner-Regel erkennt den kartesischen Join zwischen zwei Ossie-Datasets und injiziert das ON-Prädikat aus der Beziehung. Gleiches Ergebnis wie das Schreiben des JOIN von Hand. Siehe „Auto-injizierte Joins” unten. |
| MDX-only-Metriken (Calculated Members) | ✅ (unsichtbar) | Korrekterweise nicht auf der SQL-Oberfläche freigelegt. Sie leben in Mondrian. |
Noch nicht unterstützt
Verfolgt auf dem Ossie/SQL-Epic:
- SSL/TLS + Auth auf beiden Endpunkten. Derzeit anonym + Klartext. Table-Stakes vor jedem Prod-Deployment.
- Binärformat-Parameter/-Ergebnisse auf dem PG-Wire-Endpunkt — Bind dekodiert jetzt big-endian INT2/INT4/INT8 korrekt (die meisten BI-Tool-
setInt/setLong-Aufrufe), aber vollständige typgesteuerte Binärdekodierung (BOOL/DATE/NUMERIC/TIMESTAMP gegen die deklarierten Parameter-OIDs des Statements) ist ein Follow-up. - Portal-Suspension — Execute gibt immer alle Zeilen zurück, unabhängig von der
maxRows-Anfrage des Clients. In Ordnung für interaktive BI-Abfragen; wichtig für Large-Cursor-Pagination. - Drei-Wege-Auto-Joins —
FROM A, B, C, wo A↔B und B↔C beide existieren. Verschachtelte Rewrites sollten kaskadieren, aber das wurde in Tests noch nicht verifiziert. - Cross-Schema-Joins — das Joinen eines Ossie-Datasets mit einer Nicht-Ossie-Tabelle (aus einem anderen Calcite-Sub-Schema). Die Auto-Join-Regel steigt aus, wenn die beiden Seiten zu verschiedenen Schemas gehören.
jdbc:saiku://-Connect-Schema. Heute gehen Nutzer überjdbc:calcite:+ eine model.json-Datei. Ein First-Party-JDBC-Treiber kommt mit der Postgres-Wire-Arbeit.- Postgres-Wire-Protokoll. Heute ist die Oberfläche nur-JDBC.
jdbc:saiku:auf dem Wire (sodasspsql/libpq-Clients nativ verbinden) ist #1386.
Durchgearbeitetes Beispiel — Pharma-Cube
Gegeben das Pharma-Ossie-YAML, das der Exporter produziert:
version: 0.2.0.dev0semantic_model:- name: Pharma Rx datasets: - name: fact_pharma source: public.fact_pharma - name: Prescriber source: public.dim_prescriber primary_key: [prescriberkey] relationships: - name: fact_pharma_to_Prescriber from: fact_pharma to: Prescriber from_columns: [prescriberkey] to_columns: [prescriberkey]Sie können es abfragen wie:
-- Simple dataset scan.SELECT * FROM "Pharma Rx".fact_pharma LIMIT 10;
-- Aggregate — pushed down as SUM() to Postgres.SELECT SUM(quantity_units) AS total_units FROM "Pharma Rx".fact_pharma;
-- Scalar metric SELECT — same result as above, but the aggregate-- expression lives in the Ossie YAML instead of the query.SELECT * FROM "Pharma Rx"."Quantity";
-- Join via the relationship's foreign key.SELECT p.prescribername, SUM(f.quantity_units) AS units, COUNT(*) AS rx_countFROM "Pharma Rx".fact_pharma fJOIN "Pharma Rx"."Prescriber" p ON f.prescriberkey = p.prescriberkeyGROUP BY p.prescribernameORDER BY units DESCLIMIT 20;
-- Same query using the pre-materialised join view — the ON predicate-- lives in the Ossie YAML. Users don't need to remember which columns-- link fact_pharma to Prescriber.SELECT prescribername, SUM(quantity_units) AS units, COUNT(*) AS rx_countFROM "Pharma Rx".fact_pharma_JOIN_PrescriberGROUP BY prescribernameORDER BY units DESCLIMIT 20;Alle fünf laufen vollständig in Postgres via Calcites JDBC-Pushdown — die JVM sieht für die Aggregate nie einzelne Zeilen.
Auto-injizierte Joins
Nutzer müssen sich nicht merken, welche Spalten ORDERS mit CUSTOMERS verknüpfen. Listen Sie einfach beide Datasets in FROM auf, und Calcite injiziert das ON-Prädikat für Sie:
-- User writes:SELECT c.REGION, SUM(o.AMOUNT) AS TOTALFROM "Pharma Rx".fact_pharma o, "Pharma Rx"."Prescriber" cGROUP BY c.REGION;
-- The adapter's OssieAutoJoinRule detects the Cartesian join between two datasets-- in the same Ossie schema, looks up the relationship, and rewrites to:SELECT c.REGION, SUM(o.AMOUNT) AS TOTALFROM "Pharma Rx".fact_pharma o JOIN "Pharma Rx"."Prescriber" c ON o.prescriberkey = c.prescriberkeyGROUP BY c.REGION;Das Rewrite passiert während Calcites Optimierungsphase — das Ganze schiebt sich weiterhin als einzelne JOIN-Abfrage ins Warehouse hinunter.
Wann die Regel feuert
- Der Join muss kartesisch sein. Explizite
JOIN … ON …-Klauseln werden nie überschrieben — der Nutzer hat ein spezifisches Prädikat verlangt, und wir respektieren es. - Alle Tabellen müssen Ossie-eigene Datasets im selben OssieSchema sein. Cross-Schema- und Nicht-Ossie-Tabellen lassen die Abfrage unverändert.
- Genau eine Ossie-
relationshipmuss jedes auto-gejointe Paar verknüpfen. Mehrere Kandidaten lösenAmbiguousJoinExceptionmit der Liste der Kandidatennamen aus — der Nutzer muss ein explizites ON hinzufügen, um eines zu wählen. Still-falsche-Ergebnisse sind der Fehlermodus, gegen den wir uns absichern.
N-Wege-Unterstützung
FROM A, B, C, … (drei oder mehr Tabellen in einem einzelnen Kartesischen) auto-joint auch. Die Regel läuft durch verschachtelte Joins hinab, um jeden rohen TableScan zu erreichen, und baut dann eine frische left-deep-Join-Kette unter Verwendung der Beziehungen, die jede neue Tabelle in die bereits gejointe Menge verknüpfen. Pharma-skalige Abfragen gegen Fact + mehrere Dims funktionieren ohne JOIN-Klauseln.
Wann sie nicht feuert
- Keine Beziehung zwischen zwei Datasets, die verknüpft werden müssten → Kartesisch bleibt (wahrscheinlich nicht, was der Nutzer will, aber ehrlich).
- Self-Joins (
FROM A a, A b) → als Kartesisch belassen. - Cross-Schema-Joins, die Ossie-Tabellen mit Nicht-Ossie-Tabellen mischen.
Join-Views
Für jede Ossie-relationship registriert der Adapter eine vor-materialisierte View namens <from>_JOIN_<to>. Das SQL der View ist der JOIN der beiden Datasets auf den from_columns / to_columns der Beziehung, und ihr Row-Type ist die Verkettung der Spalten beider Datasets (mit numerischen Suffixen bei Kollisionen).
Gegeben dieses Ossie-Fragment:
relationships:- name: fact_pharma_to_Prescriber from: fact_pharma to: Prescriber from_columns: [prescriberkey] to_columns: [prescriberkey]Der Adapter legt PHARMA.fact_pharma_JOIN_Prescriber mit der Vereinigung der Spalten beider Tabellen offen. Nutzer schreiben:
SELECT * FROM "Pharma Rx".fact_pharma_JOIN_Prescriber WHERE prescribername LIKE 'Dr%';und Calcite schiebt SELECT * FROM fact_pharma JOIN dim_prescriber ON fact_pharma.prescriberkey = dim_prescriber.prescriberkey WHERE prescribername LIKE 'Dr%' hinunter — kein Laufzeit-Overhead gegenüber dem Schreiben des JOIN von Hand.
Multi-Column-Beziehungen werden unterstützt: Das ON-Prädikat verknüpft jedes from_columns[i] = to_columns[i]-Paar mit AND.
Verwandt
- Export nach Apache Ossie — wie die YAML-Datei produziert wird, die dieser Adapter liest.
- Saiku-Semantik-Annotationen — die Annotationen, die in das Ossie-YAML überleben und (in einer zukünftigen Scheibe) die Metrik-/Dimensionsauflösung zur Abfragezeit antreiben.
- Apache-Calcite-Adapter-Framework — der zugrundeliegende Planner.
- Apache-Ossie-Repository — Upstream-Spezifikation.