Przejdź do głównej zawartości

Adapter SQL nad Apache Ossie

Saiku dostarcza adapter SQL Calcite, który czyta YAML Apache Ossie — ten sam format, który eksporter Ossie produkuje z twojej schemy Mondrian — i udostępnia go jako odpytywalną powierzchnię JDBC. Wskaż na nią dowolnego klienta SQL i możesz SELECT-ować ze swoich zbiorów danych Ossie dokładnie tak, jakby były prawdziwymi tabelami bazy. Pod maską Calcite planuje zapytanie i pcha je w dół do twojej faktycznej hurtowni jako natywny SQL.

Co dostajesz

  • Standardowy SQL nad twoimi zbiorami danych Ossie — cokolwiek Calcite rozumie, co jest dużym nadzbiorem ANSI SQL.
  • Pushdown do hurtowni. Calcite czyta zapytanie, planuje je względem twojej hurtowni (Postgres, Snowflake, H2, cokolwiek) i emituje natywny SQL. Agregaty działają w hurtowni, nie w JVM.
  • Natywnie JDBC. Działa z dowolnym klientem JDBC: dbt, DBeaver, Tableau, Power BI, psql, JetBrains DataGrip, własny kod.
  • Zmiany nazw Ossie przeżywają. Jeśli twój eksporter wyemitował nazwy zbiorów danych, które nie pasują do bazowych tabel hurtowni (przez pole source Ossie), adapter przezroczyście mapuje z powrotem — SELECT * FROM CUSTOMERS staje się SELECT * FROM public.dim_customer_v2 w hurtowni.

Szybki start

1. Wyprodukuj YAML Ossie

Okno terminala
saiku ossie-export --in saiku-home/data/Pharma.xml --out pharma.ossie.yaml

Zobacz Eksport do Apache Ossie po tabelę mapowania i przykład.

2. Napisz model połączenia Calcite

Połączenia JDBC Calcite przyjmują model połączenia JSON, który mówi Calcite, którą fabrykę zinstancjonować:

{
"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"
}
}]
}

Klucze operanda:

KluczWymaganyOpis
ossieYamltakBezwzględna ścieżka do pliku YAML Ossie.
modelNamenieNazwa wpisu semantic_model[] do udostępnienia, gdy dokument niesie kilka. Domyślnie pierwszy.
jdbcUrlmocno zalecanyURL JDBC hurtowni. Bez niego tabele rejestrują się, ale zapytania zwracają zero wierszy.
jdbcUser, jdbcPasswordwedle potrzebPoświadczenia hurtowni.

3. Połącz się

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));
}

Lub z dowolnego klienta JDBC — URL połączenia jdbc:calcite:model=/path/to/model.json.

4. Albo uruchom to jako endpoint sieciowy

Dla zdalnych klientów użyj podkomendy CLI saiku sql-serve. Udostępnia ona dwa endpointy — endpoint Apache Avatica dla klientów świadomych Avatiki ORAZ natywny endpoint na drucie Postgres dla psql / pgAdmin / Tableau / DBeaver / dbt-postgres. Włącz jeden albo oba:

Okno terminala
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

Serwer loguje oba URL-e przy starcie. Klienci łączą się następnie przez:

Avatica:

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

Drut Postgres (dowolny natywny klient PG):

Okno terminala
# 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

Zapytania parametryzowane przez PreparedStatement.setString(1, ...) itd. działają od ręki.

Co działa dziś

Funkcja SQLStatusUwagi
SELECT ze zbioru danychPchane w dół do hurtowni.
WHERE na kolumnach zbioru danychPchane w dół.
GROUP BY + agregatyAgregaty działają w hurtowni.
Jawny JOIN … ON między zbiorami danychPredykat złączenia pcha się w dół.
ORDER BY, LIMITPchane w dół tam, gdzie dialekt hurtowni to obsługuje.
information_schema / DatabaseMetaData.getTables()Zbiory danych, metryki ORAZ widoki złączeń wykrywalne przez narzędzia BI.
Mapowanie zmian nazw (nazwa zbioru danych Ossie → tabela hurtowni przez source)Przezroczyste.
Fallback bez rozróżniania wielkości liter (H2 UPPER vs Postgres lower)Adapter próbuje wszystkich wielkości.
Skalarny SELECT metrykiSELECT * FROM PHARMA.TOTAL_QUANTITYRozwija ANSI_SQL metryki względem jej macierzystego zbioru danych. Typ zwracany wyprowadzony z typu kolumny bazowej (brak zgrubnych zgadywań DOUBLE/BIGINT).
Widoki złączeń relacjiSELECT ... FROM PHARMA.FACT_PHARMA_JOIN_PRESCRIBER ...Jeden widok na relationship Ossie, nazwany <from>_JOIN_<to>. Predykat JOIN żyje w YAML, nie w zapytaniu. Calcite pcha całość w dół jako pojedynczy JOIN — brak narzutu w runtime.
Auto-wstrzykiwane złączeniaSELECT c.x, SUM(o.y) FROM ORDERS o, CUSTOMERS c GROUP BY c.x (bez klauzuli JOIN)Niestandardowa reguła planera Calcite wykrywa złączenie kartezjańskie między dwoma zbiorami danych Ossie i wstrzykuje predykat ON z relacji. Ten sam wynik co ręczne napisanie JOIN. Zobacz „Auto-wstrzykiwane złączenia” niżej.
Metryki tylko-MDX (członkowie obliczani)✅ (niewidoczne)Poprawnie nieudostępniane na powierzchni SQL. Żyją w Mondrianie.

Jeszcze nieobsługiwane

Śledzone na epicu Ossie/SQL:

  • SSL/TLS + uwierzytelnianie na obu endpointach. Obecnie anonimowe + plaintext. Podstawa przed jakimkolwiek wdrożeniem produkcyjnym.
  • Parametry/wyniki formatu binarnego na endpoincie na drucie PG — Bind teraz poprawnie dekoduje big-endian INT2/INT4/INT8 (większość wywołań setInt/setLong narzędzi BI), ale pełne dekodowanie binarne sterowane typem (BOOL/DATE/NUMERIC/TIMESTAMP względem zadeklarowanych OID-ów parametrów instrukcji) to kontynuacja.
  • Zawieszanie portala — Execute zawsze zwraca wszystkie wiersze niezależnie od żądania maxRows klienta. W porządku dla interaktywnych zapytań BI; ma znaczenie dla paginacji dużych kursorów.
  • Trójstronne auto-złączeniaFROM A, B, C, gdzie A↔B i B↔C oba istnieją. Zagnieżdżone przepisania powinny kaskadować, ale nie zostało to jeszcze zweryfikowane w testach.
  • Złączenia międzyschemowe — łączenie zbioru danych Ossie z tabelą nie-Ossie (z innej podschemy Calcite). Reguła auto-złączenia rezygnuje, gdy obie strony należą do różnych schem.
  • Schemat połączenia jdbc:saiku://. Dziś użytkownicy przechodzą przez jdbc:calcite: + plik model.json. Sterownik JDBC first-party przychodzi z pracą nad drutem Postgres.
  • Protokół na drucie Postgres. Dziś powierzchnia jest tylko-JDBC. jdbc:saiku: na drucie (aby klienci psql / libpq łączyli się natywnie) to #1386.

Przykład — kostka Pharma

Mając YAML Ossie Pharma, który eksporter produkuje:

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]

Możesz odpytywać go tak:

-- 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;

Wszystkie pięć działają całkowicie w Postgresie przez pushdown JDBC Calcite — JVM nigdy nie widzi pojedynczych wierszy dla agregatów.

Auto-wstrzykiwane złączenia

Użytkownicy nie muszą pamiętać, które kolumny łączą ORDERS z CUSTOMERS. Wystarczy wylistować oba zbiory danych w FROM, a Calcite wstrzyknie za ciebie predykat ON:

-- 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;

Przepisanie zdarza się podczas fazy optymalizacji Calcite — całość nadal pcha się w dół do hurtowni jako pojedyncze zapytanie JOIN.

Kiedy reguła się uruchamia

  • Złączenie musi być kartezjańskie. Jawne klauzule JOIN … ON … nigdy nie są nadpisywane — użytkownik poprosił o konkretny predykat i to szanujemy.
  • Wszystkie tabele muszą być zbiorami danych będącymi własnością Ossie w tej samej OssieSchema. Tabele międzyschemowe i nie-Ossie pozostawiają zapytanie niezmienione.
  • Dokładnie jedna relationship Ossie musi łączyć każdą auto-łączoną parę. Wielu kandydatów podnosi AmbiguousJoinException z listą nazw kandydatów — użytkownik musi dodać jawne ON, aby wybrać jednego. Cicho-błędne-wyniki to tryb awarii, przed którym się chronimy.

Wsparcie N-stronne

FROM A, B, C, … (trzy lub więcej tabel w jednym kartezjaniku) też się auto-łączy. Reguła schodzi w dół przez zagnieżdżone Joins, aby dosięgnąć każdego surowego TableScan, po czym buduje świeży lewostronny łańcuch złączeń używając relacji, które łączą każdą nową tabelę z już-złączonym zbiorem. Zapytania w skali Pharma względem faktu + wielu wymiarów działają bez klauzul JOIN.

Kiedy się nie uruchamia

  • Brak relacji między dwoma zbiorami danych, które należałoby połączyć → kartezjanik pozostaje (prawdopodobnie nie to, czego chce użytkownik, ale uczciwie).
  • Self-joins (FROM A a, A b) → pozostawione jako kartezjanik.
  • Złączenia międzyschemowe mieszające tabele Ossie z nie-Ossie.

Widoki złączeń

Dla każdej relationship Ossie adapter rejestruje pre-zmaterializowany widok nazwany <from>_JOIN_<to>. SQL widoku to JOIN dwóch zbiorów danych na from_columns / to_columns relacji, a jego typ wiersza to konkatenacja kolumn obu zbiorów danych (z numerycznymi sufiksami przy kolizjach).

Mając ten fragment Ossie:

relationships:
- name: fact_pharma_to_Prescriber
from: fact_pharma
to: Prescriber
from_columns: [prescriberkey]
to_columns: [prescriberkey]

Adapter udostępnia PHARMA.fact_pharma_JOIN_Prescriber z unią kolumn obu tabel. Użytkownicy piszą:

SELECT * FROM "Pharma Rx".fact_pharma_JOIN_Prescriber WHERE prescribername LIKE 'Dr%';

a Calcite pcha w dół SELECT * FROM fact_pharma JOIN dim_prescriber ON fact_pharma.prescriberkey = dim_prescriber.prescriberkey WHERE prescribername LIKE 'Dr%' — brak narzutu w runtime nad ręcznym napisaniem JOIN.

Relacje wielokolumnowe są obsługiwane: predykat ON łączy przez AND każdą parę from_columns[i] = to_columns[i].

Powiązane