Migration von Looker (LookML)
Der LookML-Importer lädt Looker-LookML-Modelle als Mondrian-4-Schemas in Saiku, sodass Sie OLAP/MDX darauf ausführen können. Er ist ein Migrationsbeschleuniger mit hartem Sicherheits-Gate, kein verlustfreier Konverter – jedes Konstrukt wird in eines von drei Urteilen klassifiziert:
- CLEAN – auf ein Mondrian-Element portiert.
- DEGRADE – portiert, aber eine Fähigkeit ging verloren (und wird präzise benannt).
- REFUSE – nicht portiert, mit einer exakten Diagnose des Warum.
Wie es funktioniert
Der Importer ist eine vierstufige Pipeline:
- Parse – die
.lkml-Dateien werden in ein Modell eingelesen (ein eingebauter, gehärteter LookML-Parser). - Classify – ein statisches Sicherheits-Gate markiert jeden Explore und jedes Feld als CLEAN / DEGRADE / REFUSE, ohne Warehouse-Zugriff.
- Transpile – ein Mondrian-4-Schema für die CLEAN/DEGRADE-Untermenge ausgeben (als YAML, direkt von Saiku ladbar), plus eine Provenance-Map, welches LookML-Feld welches Schema-Element erzeugt hat.
- Report – ein Coverage-Report (Markdown + JSON), der jedes Konstrukt einteilt, mit zusammenfassenden Quoten.
Nur Konstrukte, die das Gate akzeptiert, erreichen jemals den Transpiler – eine abgelehnte, still-falsche Measure kann also niemals in den ausgegebenen Cube gelangen.
Den Importer ausführen
Der Importer wird als das Tool mondrian.lookml.report.LookmlReportCli bereitgestellt (auf dieselbe Weise, wie die Schema-CLI SchemaCli umschließt). Richten Sie ihn auf eine einzelne .lkml-Datei oder ein ganzes Projektverzeichnis – Verzeichnisse werden rekursiv nach .lkml-Dateien durchsucht.
lookml-report report <path> [-o report.md] [--json report.json] [--fail-on-refuse]| Flag | Wirkung |
|---|---|
| (keiner) | Schreibt den Markdown-Report nach stdout. |
-o <file> | Schreibt den Markdown-Report in eine Datei. |
--json <file> | Schreibt zusätzlich den maschinenlesbaren JSON-Report. |
--fail-on-refuse | Gibt den Report trotzdem aus, beendet sich dann mit Nicht-Null, wenn etwas abgelehnt wurde – ein CI-Gate für “blockiere die Migration, bis die Refuse-Liste leer ist.” |
Exit-Codes: 0 Erfolg · 1 ungültige Argumente · 2 der Pfad fehlt/ist nicht lesbar, nichts war parsebar, oder --fail-on-refuse hat eine Ablehnung gesehen.
Was der Report enthält
- Zusammenfassende Metriken – CLEAN / DEGRADE / REFUSE-Zählungen und Prozentsätze auf beiden Explore- und Feldebenen. Dieses Verhältnis ist die Schlagzeilenzahl für die Migrationsbereitschaft.
- Pro-Konstrukt-Buckets – jeder Explore und jedes Feld wird unter Clean / Degrade / Refuse mit einem präzisen Grund aufgeführt, mit dem erzeugten M4-Element (für clean/degrade) oder der verlorenen Fähigkeit und einem Link zum Saiku-Feature, das eine Ablehnung aufheben würde.
- Unparseable / skipped files – vollständige Transparenz über alles, was nicht aufgenommen wurde.
Multi-Datei-Projekte
Wenn Sie den Importer auf ein Projekt-Verzeichnis richten, löst er das gesamte Projekt auf, nicht nur eine Datei:
- Jede
.lkmlwird rekursiv entdeckt und unabhängig geparst (nicht parsbare Dateien und*.dashboard.lkmlwerden aufgeführt, niemals fatal). - Die parsbaren Top-Level-Objekte werden zusammengeführt, dann löst ein Flatten-Durchlauf Cross-File-Referenzen in ein einziges Modell auf, bevor klassifiziert wird:
include:– durch das Zusammenführen erfüllt;extends:– die Basis wird kopiert, dann überschreiben die eigenen Eigenschaften des erweiternden Objekts;- Refinements (
+view/+explore/+model) – über die Basis gelegt (Skalare überschreiben;dimension/measure/joinwerden nach Namen zusammengeführt); @{constant}– wird ausconstant:-Blöcken substituiert.
Dies behebt Fehlklassifizierungen, bei denen eine Measure oder ein Feld erst nach einem Refinement additiv, Liquid oder zeilengesichert wird – auf einem echten Projekt hat das Hunderte von Feldern von einer Literal-Text-Vermutung auf das korrekte Urteil verschoben.
Import aus einer Live-Looker-Instanz (Explore JSON)
Mit einer Looker-Instanz mit Zugangsdaten können Sie das rohe .lkml-Parsing überspringen und die bereits aufgelösten Metadaten eines Explores importieren:
lookml-report report --explore-json explore.jsonExportieren Sie das lookml_model_explore-JSON des Explores aus der Looker-API. Da Looker bereits alle extends/Refinements/Konstanten/Liquid angewendet hat, überspringt diese Eingabe den Flatten-Durchlauf und fließt direkt in dieselbe Klassifizierer → Transpiler → Report-Pipeline und produziert denselben Coverage-Report wie das äquivalente .lkml. Trade-Off: erfordert API-Zugriff und ist pro Explore, ergänzt also den Offline-Pfad (auf-ein-Git-Repo-zeigen) – statt ihn zu ersetzen.
Was portiert wird
Das meiste eines sauberen Stern-/Schneeflocken-LookML-Modells wird direkt konvertiert. Der Importer ordnet zu:
| LookML | Mondrian-4 | Urteil |
|---|---|---|
explore (single-base Stern/Schneeflocke) | <Cube> mit einer <MeasureGroup> und konformen Dimensionen | CLEAN |
explore mit Join über mehrere Fakten-Basen (konform) | ein <Cube> mit einer <MeasureGroup> pro Fakt-Basis über gemeinsame konforme Dimensionen | CLEAN |
join: { relationship: many_to_one | one_to_one, type: left_outer } | ForeignKeyLink (degenerierte Dims → FactLink) | CLEAN |
Bridge two-hop: Fact one_to_many (oder many_to_many) → Bridge-View → many_to_one-Dimension-View | <BridgeLink> (full-count De-Dup) + die Dimension als konforme Dimension | CLEAN |
measure: { type: sum | count | min | max | average | count_distinct } | <Measure> mit dem passenden Aggregator | CLEAN |
measure: { type: median | percentile } | aggregator="median" / aggregator="percentile" | DEGRADE – benötigt ein PERCENTILE_CONT-fähiges Backend |
Gefilterte Measure (filters: Gleichheit, kein Liquid) | berechneter Member | CLEAN |
dimension (+ value_format / value_format_name, label, description) | Attribut / Level (+ formatString – Looker-benannte Formate wie usd, percent_2, decimal_0 werden in Mondrian-Masken übersetzt; ein unbekanntes benanntes Format wird wörtlich mit einem DEGRADE-Hinweis behalten – plus caption, description) | CLEAN |
dimension: { type: tier } / dimension_group: { type: duration } | native <Tier> / <Duration> | CLEAN |
parameter (bounded: getypt, allowed_values) | <QueryParameter> | CLEAN |
measure: { type: sum_distinct | average_distinct }, sql_distinct_key löst auf eine Spalte in der eigenen View der Measure auf (inkl. nicht-Primärschlüssel) | <Measure> mit Distinct-Granularität auf Measure-Ebene (distinctKeyColumn) – auf diesem Schlüssel dedupliziert vor dem Aggregieren; reduziert sich zu einer einfachen sum/avg, wenn der Schlüssel der Primärschlüssel der View ist | CLEAN |
Bounded Liquid: {% parameter %}, {% condition %}, {{ _user_attributes['x'] }} | <QueryParameter> / Predicate-Grant-Bindung | DEGRADE |
access_filter auf einem modellierten Dimensions-Schlüssel | Role-Member-Grant | CLEAN |
access_filter auf einer beliebigen Fact-Spalte | Predicate-Grant-Rolle + gebundener Parameter | DEGRADE |
derived_table | eine SQL-gestützte physische Tabelle (<Query>) | DEGRADE – Persistenzpolitik verloren |
drill_fields | Drillthrough-RETURN-Set, als Cube-Annotation getragen (M4 hat kein <DrillThrough>-Schema-Element – es ist eine Laufzeit-DRILLTHROUGH … RETURN-Anweisung) | CLEAN |
aggregate_table | (nicht konvertiert – Saiku regeneriert Aggregate) | DEGRADE |
Eine sum/average-Measure, die über einen one_to_many-Join expandiert (fans out), wird (CLEAN) portiert, wenn die Basis-View einen Primärschlüssel deklariert, weil Saikus Fan-Out-sichere symmetrische Aggregation auf dieser Granularität dedupliziert. Ohne deklarierten Schlüssel wird sie abgelehnt, statt das Risiko des Doppelzählens einzugehen.
Many-to-Many-Joins (Bridge-Dimensionen). Der Importer erkennt den kanonischen LookML-Bridge-Two-Hop – ein Fact mit one_to_many (oder many_to_many) zu einer Bridge-View, die wiederum many_to_one zu einer Dimension-View joint – und mappt ihn auf einen Mondrian-<BridgeLink>, statt den Explore als nicht-Stern abzulehnen. Die durch den Bridge erreichte Dimension wird zu einer normalen konformen Dimension, und Measures auf dem Fact geben die deduplizierte (full-count) Summe zurück, nicht die expandierte. Ein Bridge wird nur ausgegeben, wenn jeder Hop sich auf einen einspaltigen Schlüssel reduziert und die Fact-View eine primary_key: yes-Granularität deklariert; ein zusammengesetzter/mehrdeutiger Join-Schlüssel oder ein Fact ohne Primärschlüssel wird abgelehnt gelassen, statt einen still-falschen Cube zu produzieren. Da LookML keine Allokationsgewichtung trägt, verwenden Bridges standardmäßig die full-count-Deduplizierung.
from:-aliasierte Joins. In LookML wird ein joined-Feld immer über den Join-Namen referenziert, während from: (oder view_name:) nur die zugrundeliegende physische View austauscht. Der Importer matcht den sql_on ${name.column} jedes Joins gegen den Join-Namen – löst die Spalten und Tabelle der Dimension aus der zugrundeliegenden View auf – sodass ein Join wie join: current_subscription_state { from: logical_subscriptions; sql_on: ${fact.fk} = ${current_subscription_state.id} } als konforme Dimension benannt nach dem Join CLEAN portiert. Zwei Joins, die from: auf dieselbe Basis-View setzen, werden zu zwei separaten konformen Dimensionen (z. B. account_csm und account_owner über einer account-Tabelle). Was noch degradiert (DEGRADE_JOIN_SQL_ON_UNPARSEABLE): ein sql_on, das keine einspaltige Gleichheit auf jeder Seite ist – Konstanten-/Metadaten-Joins (${meta.col} = 'literal') und zusammengesetzte oder Ausdrucks-Joins (mit AND verkettete mehrspaltige Schlüssel, coalesce(...), ::date-Casts) werden degradiert gehalten, statt auf einen einzelnen, still-falschen Schlüssel reduziert.
Was abgelehnt wird (und warum)
| Abgelehnt | Warum | Weg nach vorn |
|---|---|---|
Berechnetes Liquid ({% if %} / Schleifen / assign / berechnete {{ }} in SQL) | Zur Laufzeit generiertes SQL ist ein Korrektheits-/Sicherheitsloch, das wir nicht importieren werden | Bounded {% parameter %} / {% condition %} / {{ _user_attributes['x'] }} portiert jetzt (DEGRADE) |
Fan-Out sum/average ohne deklarierbare Granularität | Würde stillschweigend doppelt zählen | Fügen Sie eine primary_key: yes-Dimension auf der Basis-View hinzu |
Nicht-Stern-Topologien: full_outer / cross-Joins oder ein many_to_many, dessen Bridge-Two-Hop nicht wiederhergestellt werden kann (zusammengesetzter/mehrdeutiger Join-Schlüssel oder die Fact-View hat keinen primary_key) | Brechen strukturell / expandieren unkontrolliert, oder es gibt keine einspaltige Granularität, auf der dedupliziert werden kann | Für ein Many-to-Many: Geben Sie der Fact-View einen primary_key: yes und einspaltige Join-Schlüssel, damit der Importer einen Bridge ausgeben kann (der wiederherstellbare Two-Hop portiert jetzt automatisch) |
type: sum_distinct / average_distinct, dessen sql_distinct_key ein cross-view (${other_view.field}), Fremdschlüssel- oder Ausdrucks-Schlüssel ist | Eine Distinct-Granularität auf Measure-Ebene kann nur auf einer Spalte in der eigenen Fact-View der Measure deduplizieren; ein Cross-View-Schlüssel braucht den Bridge-Two-Hop | Modellieren Sie den Join mit einer Bridge-Dimension (Many-to-Many) |
type: list | Kein multidimensionales Äquivalent | — |
Mondrian++-Erweiterungen erweitern die Abdeckung
Saikus semantische Erweiterungen existieren teilweise, um die Refuse-Liste zu verkleinern – jede einzelne verwandelt eine frühere Ablehnung in einen sauberen Port:
- Symmetrische (Fan-Out-sichere) Aggregation, Bridge-Dimensionen (Many-to-Many) und Distinct-Granularität auf Measure-Ebene – siehe Fortgeschritten.
- median / percentile-Aggregatoren, native Tier / Duration-Dimensionstypen – siehe Dimensionen und Cubes und Measures.
- Bounded Query-Context-Parameter und Predicate-basierte Zeilensicherheit – siehe Zugriffskontrolle.
Mit weiteren Erweiterungen klassifiziert dasselbe LookML-Modell sauberer – führen Sie den Report erneut aus, um zu sehen, wie sich die Quote verbessert.
Realweltabdeckung
Validiert gegen einen Korpus öffentlicher LookML-Projekte (offizielle Looker-Blocks plus Produktiv-Community-Modelle, ~900 .lkml-Dateien): ~99,8 % der in Scope befindlichen Dateien parsen, jedes Projekt produziert einen projektweiten Report, und über ~20.000 Felder in echten Modellen ist die Abdeckung ~97,6 % CLEAN, ~1,2 % DEGRADE, ~1,2 % REFUSE. Die verbleibenden Ablehnungen werden dominiert von echtem berechnetem Liquid (inhärent dynamisch – ein “wird per Design nicht portieren”, kein Bug).
Numerische Äquivalenz validieren
Der Coverage-Report sagt Ihnen, was portiert; er sagt Ihnen nicht, ob der konvertierte Cube die gleichen Zahlen wie Looker zurückgibt. Ein CLEAN-Port, der falsch summiert, ist das schlimmste Ergebnis – daher überprüft eine separate Äquivalenz-Harness den konvertierten Cube gegen eine Live-Looker-Instanz – das Migrations-Analogon zum engine-seitigen Calcite-Parity-Guard.
Gegeben eine Query-Spezifikation (ein Explore plus die Dimensions- und Measure-Felder), führt die Harness die Abfrage auf beide Arten aus und vergleicht die Ergebnisse:
- Looker-Seite –
POST /api/4.0/logingefolgt von/api/4.0/queries/run/jsonliefert die Zeilen als Orakel. Richten Sie es auf Ihre Instanz mit drei Einstellungen (System-Properties oder Umgebungsvariablen – niemals einchecken):LOOKER_BASE_URL,LOOKER_CLIENT_ID,LOOKER_CLIENT_SECRET(ein Looker-API3-Schlüssel). Ohne alle drei ist der Client inert und die Harness bleibt vollständig offline. - Saiku-Seite – dieselbe Spezifikation wird über die Provenance-Map des Transpilers in MDX über den konvertierten Cube umgeschrieben (Measures auf Spalten, Dimensionsebenen auf Zeilen). Felder, die der Importer nicht CLEAN konvertiert hat, werden als skipped aufgeführt und nie still verglichen.
- Vergleich – Zeilen werden über ihr Dimensionsschlüssel-Tupel ausgerichtet und Measures werden innerhalb einer relativen Toleranz verglichen (Standard
1e-6). Divergenzen werden inROW_COUNT,DIMENSION_SEToderMEASURE_VALUEkategorisiert und benennen nur das Feld und die Kategorie – niemals die zugrundeliegenden Werte (keine Daten in Logs). Ein sauberer Lauf meldet einen Match mit null Divergenzen.
Die Harness validiert auch die Zeilensicherheit: Ein durch einen Looker-access_filter eingeschränktes Ergebnis wird gegen den konvertierten Cube verglichen, der unter dem entsprechenden Rollen-Grant abgefragt wird, was bestätigt, dass die eingeschränkten Zahlen übereinstimmen.
Einschränkungen (v1)
- Cross-Datei-Auflösung von
include:,extends:/Refinements und@{}-Konstanten wird durch einen Flatten-Durchlauf behandelt (siehe Multi-Datei-Projekte); eine Referenz, deren Basis oder Konstante außerhalb der entdeckten Dateimenge liegt, wird als Diagnose berichtet und as-parsed belassen, statt aufgelöst. Für ein vollständig vor-aufgelöstes Modell verwenden Sie das Explore-JSON-Frontend. - Konforme Multi-Basis-Explores portieren in einen Cube mit einer
<MeasureGroup>pro Fakt-Basis; v1 verbindet jede sekundäre Fakt-Gruppe nur mit den konformen Dimensionen ihrer eigenensql_on-Schlüssel – Base-View-degenerierte Dimensionen und Cross-Factcopy/no_link-Verdrahtung werden noch nicht synthetisiert. *.dashboard.lkml(YAML-strukturierte Looker-Dashboards) werden übersprungen – sie sind nicht Teil des Cube-Modells.
Siehe YAML-Schemas für das Format, das der Importer ausgibt, und die Schema-CLI zum Konvertieren oder Linten des Ergebnisses, bevor es in Saiku deployt wird.