Zum Inhalt springen

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 CUSTOMERS wird am Warehouse zu SELECT * FROM public.dim_customer_v2.

Schnellstart

1. Das Ossie-YAML produzieren

Terminal-Fenster
saiku ossie-export --in saiku-home/data/Pharma.xml --out pharma.ossie.yaml

Siehe 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üsselErforderlichBeschreibung
ossieYamljaAbsoluter Pfad zur Ossie-YAML-Datei.
modelNameneinName des semantic_model[]-Eintrags, der bereitgestellt werden soll, wenn das Dokument mehrere trägt. Standardmäßig der erste.
jdbcUrldringend empfohlenWarehouse-JDBC-URL. Ohne sie registrieren Tabellen, aber Abfragen geben null Zeilen zurück.
jdbcUser, jdbcPasswordnach BedarfWarehouse-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:

Terminal-Fenster
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 5432

Der Server loggt beim Start beide URLs. Clients verbinden sich dann via:

Avatica:

jdbc:avatica:remote:url=http://localhost:8765
# with serialization=protobuf

Postgres-Wire (jeder native PG-Client):

Terminal-Fenster
# psql
PGSSLMODE=disable psql -h localhost -p 5432 saiku
# JDBC — pgjdbc defaults (extended query mode) work; simple mode is also fine
jdbc:postgresql://localhost:5432/saiku?sslmode=disable

Parametrisierte Abfragen via PreparedStatement.setString(1, ...) etc. funktionieren out of the box.

Was heute funktioniert

SQL-FeatureStatusAnmerkungen
SELECT aus einem DatasetIns Warehouse hinuntergeschoben.
WHERE auf Dataset-SpaltenHinuntergeschoben.
GROUP BY + AggregateAggregate laufen im Warehouse.
Expliziter JOIN … ON zwischen DatasetsJoin-Prädikat schiebt hinunter.
ORDER BY, LIMITHinuntergeschoben, 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-SELECTSELECT * FROM PHARMA.TOTAL_QUANTITYExpandiert das ANSI_SQL der Metrik gegen ihr Home-Dataset. Rückgabetyp aus dem zugrundeliegenden Spaltentyp abgeleitet (keine groben DOUBLE/BIGINT-Schätzungen).
Relationship-Join-ViewsSELECT ... 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 JoinsSELECT 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-JoinsFROM 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 über jdbc: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 (sodass psql / libpq-Clients nativ verbinden) ist #1386.

Durchgearbeitetes Beispiel — Pharma-Cube

Gegeben das Pharma-Ossie-YAML, das der Exporter produziert:

version: 0.2.0.dev0
semantic_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_count
FROM "Pharma Rx".fact_pharma f
JOIN "Pharma Rx"."Prescriber" p ON f.prescriberkey = p.prescriberkey
GROUP BY p.prescribername
ORDER BY units DESC
LIMIT 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_count
FROM "Pharma Rx".fact_pharma_JOIN_Prescriber
GROUP BY prescribername
ORDER BY units DESC
LIMIT 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 TOTAL
FROM "Pharma Rx".fact_pharma o, "Pharma Rx"."Prescriber" c
GROUP 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 TOTAL
FROM "Pharma Rx".fact_pharma o JOIN "Pharma Rx"."Prescriber" c ON o.prescriberkey = c.prescriberkey
GROUP 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-relationship muss jedes auto-gejointe Paar verknüpfen. Mehrere Kandidaten lösen AmbiguousJoinException mit 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