Migration depuis Looker (LookML)
L’importeur LookML charge les modèles LookML Looker dans Saiku en tant que schemas Mondrian-4 afin que vous puissiez exécuter OLAP/MDX sur eux. C’est un accélérateur de migration avec une porte de sécurité stricte, pas un convertisseur sans perte — chaque construction est classifiée dans l’un des trois verdicts :
- CLEAN — porté à un élément Mondrian.
- DEGRADE — porté, mais une capacité a été perdue (et nommée précisément).
- REFUSE — non porté, avec un diagnostic exact du pourquoi.
Comment ça fonctionne
L’importeur est un pipeline en quatre étapes :
- Parse — lit les fichiers
.lkmldans un modèle (un parseur LookML vendoré et durci). - Classify — une porte de sécurité statique marque chaque explore et champ CLEAN / DEGRADE / REFUSE, sans accès à l’entrepôt.
- Transpile — émet un schema Mondrian-4 pour le sous-ensemble CLEAN/DEGRADE (sous forme YAML, chargeable directement par Saiku), plus une carte de provenance de quel champ LookML a produit quel élément de schema.
- Report — un rapport de couverture (Markdown + JSON) classifiant chaque construction, avec des ratios récapitulatifs.
Seules les constructions que la porte accepte atteignent le transpileur, donc une mesure refusée et silencieusement fausse ne peut jamais fuiter dans le cube émis.
Exécuter l’importeur
L’importeur est exposé comme l’outil mondrian.lookml.report.LookmlReportCli (de la même manière que la CLI de schema enveloppe SchemaCli). Pointez-le sur un seul fichier .lkml ou tout un répertoire de projet — les répertoires sont scannés récursivement pour les fichiers .lkml.
lookml-report report <path> [-o report.md] [--json report.json] [--fail-on-refuse]| Flag | Effet |
|---|---|
| (aucun) | Imprime le rapport Markdown sur stdout. |
-o <file> | Écrit le rapport Markdown dans un fichier. |
--json <file> | Écrit également le rapport JSON lisible par machine. |
--fail-on-refuse | Émet quand même le rapport, puis sort en non-zero si quelque chose a été refusé — une porte CI pour « bloquer la migration jusqu’à ce que la liste de refus soit vide ». |
Codes de sortie : 0 succès · 1 mauvais arguments · 2 le chemin est manquant/illisible, rien n’était parsable, ou --fail-on-refuse a vu un refus.
Ce que le rapport contient
- Métriques récapitulatives — comptes et pourcentages CLEAN / DEGRADE / REFUSE à la fois au niveau explore et champ. Ce ratio est le nombre principal de l’état de préparation à la migration.
- Buckets par construction — chaque explore et champ listé sous Clean / Degrade / Refuse avec une raison précise, l’élément M4 produit (pour clean/degrade) ou la capacité perdue, et un lien vers la fonctionnalité Saiku qui lèverait un refus.
- Fichiers non parsables / sautés — transparence totale sur tout ce qui n’est pas ingéré.
Projets multi-fichiers
Pointez l’importeur sur un répertoire de projet et il résout le projet entier, pas juste un fichier :
- Chaque
.lkmlest découvert récursivement et parsé indépendamment (les fichiers non parsables et*.dashboard.lkmlsont listés, jamais fatals). - Les objets de niveau supérieur parsables sont fusionnés, puis une passe d’aplatissement résout les références inter-fichiers dans un seul modèle avant la classification :
include:— satisfait par la fusion ;extends:— la base est copiée, puis les propres propriétés de l’objet étendant remplacent ;- raffinements (
+view/+explore/+model) — superposés à la base (les scalaires remplacent ;dimension/measure/joinfusionnent par nom) ; @{constant}— substitué depuis les blocsconstant:.
Cela corrige les mauvaises classifications où une mesure ou un champ ne devient additif, Liquid ou sécurisé par ligne qu’après un raffinement — sur un vrai projet, cela a déplacé des centaines de champs d’une devinette texte-littéral au verdict correct.
Importer depuis une instance Looker en direct (Explore JSON)
Avec une instance Looker avec identifiants, vous pouvez sauter le parsing .lkml brut et importer les métadonnées déjà résolues d’un explore :
lookml-report report --explore-json explore.jsonExportez le JSON lookml_model_explore de l’explore depuis l’API Looker. Parce que Looker a déjà appliqué tous les extends/raffinements/constantes/Liquid, cette entrée saute la passe d’aplatissement et alimente directement le même classifieur → transpileur → pipeline de rapport, produisant le même rapport de couverture que le .lkml équivalent. Compromis : cela nécessite l’accès API et c’est par explore, donc cela complète — plutôt que remplace — le chemin offline pointez-sur-un-repo-git.
Ce qui porte
La plupart d’un modèle LookML star/snowflake propre se convertit directement. L’importeur mappe :
| LookML | Mondrian-4 | Verdict |
|---|---|---|
explore (single-base star/snowflake) | <Cube> avec un <MeasureGroup> et des dimensions conformes | CLEAN |
explore joignant plusieurs bases de faits (conformes) | un <Cube> avec un <MeasureGroup> par base de faits sur des dimensions conformes partagées | CLEAN |
join: { relationship: many_to_one | one_to_one, type: left_outer } | ForeignKeyLink (dims dégénérées → FactLink) | CLEAN |
bridge two-hop : fait one_to_many (ou many_to_many) → vue pont → vue dimension many_to_one | <BridgeLink> (de-dup full-count) + la dimension comme dimension conforme | CLEAN |
measure: { type: sum | count | min | max | average | count_distinct } | <Measure> avec l’agrégateur correspondant | CLEAN |
measure: { type: median | percentile } | aggregator="median" / aggregator="percentile" | DEGRADE — nécessite un backend supportant PERCENTILE_CONT |
mesure filtrée (filters: égalité, pas de Liquid) | membre calculé | CLEAN |
dimension (+ value_format / value_format_name, label, description) | attribut / niveau (+ formatString — les formats nommés Looker comme usd, percent_2, decimal_0 sont traduits en masques Mondrian ; un format nommé inconnu est gardé verbatim avec une note DEGRADE — plus caption, description) | CLEAN |
dimension: { type: tier } / dimension_group: { type: duration } | <Tier> / <Duration> natifs | CLEAN |
parameter (borné : typé, allowed_values) | <QueryParameter> | CLEAN |
measure: { type: sum_distinct | average_distinct }, sql_distinct_key se résout en une colonne dans la propre vue de la mesure (y compris une non-clé-primaire) | <Measure> avec un grain distinct au niveau de la mesure (distinctKeyColumn) — dédupliqué sur cette clé avant l’agrégation ; se réduit à un sum/avg simple quand la clé est la clé primaire de la vue | CLEAN |
Liquid borné : {% parameter %}, {% condition %}, {{ _user_attributes['x'] }} | <QueryParameter> / liaison par autorisation par prédicat | DEGRADE |
access_filter sur une clé de dimension modélisée | autorisation de membre de rôle | CLEAN |
access_filter sur une colonne de fait arbitraire | rôle avec autorisation par prédicat + paramètre lié | DEGRADE |
derived_table | une table physique soutenue par SQL (<Query>) | DEGRADE — politique de persistance abandonnée |
drill_fields | ensemble RETURN de drillthrough, porté comme une annotation de cube (M4 n’a pas d’élément de schema <DrillThrough> — c’est une instruction runtime DRILLTHROUGH … RETURN) | CLEAN |
aggregate_table | (non converti — Saiku régénère les agrégats) | DEGRADE |
Une mesure sum/average qui fan out à travers une jointure one_to_many est portée (CLEAN) lorsque la vue de base déclare une clé primaire, car l’agrégation symétrique fan-out-safe de Saiku déduplique sur ce grain. Sans clé déclarée, elle est refusée plutôt que de risquer un double-comptage.
Jointures plusieurs-à-plusieurs (dimensions pont). L’importeur reconnaît le bridge two-hop LookML canonique — un fait joint one_to_many (ou many_to_many) à une vue pont, qui à son tour joint many_to_one à une vue dimension — et le mappe à un <BridgeLink> Mondrian au lieu de refuser l’explore comme non-étoile. La dimension atteinte via le pont devient une dimension conforme normale, et les mesures sur le fait renvoient le total dédupliqué (full-count), pas le fanned-out. Un pont n’est émis que lorsque chaque saut se réduit à une clé mono-colonne et que la vue de fait déclare un grain primary_key: yes ; une clé de jointure composée/ambiguë, ou un fait sans clé primaire, est laissé refusé plutôt que de produire un cube silencieusement faux. Puisque LookML ne porte pas de poids d’allocation, les ponts par défaut à la déduplication full-count.
Jointures aliasées from:. En LookML, un champ joint est toujours référencé par le nom de jointure, tandis que from: (ou view_name:) ne fait que substituer la vue physique sous-jacente. L’importeur fait correspondre sql_on ${name.column} de chaque jointure au nom de jointure — résolvant les colonnes et la table de la dimension depuis la vue sous-jacente — donc une jointure comme join: current_subscription_state { from: logical_subscriptions; sql_on: ${fact.fk} = ${current_subscription_state.id} } porte CLEAN comme une dimension conforme nommée pour la jointure. Deux jointures qui from: la même vue de base deviennent deux dimensions conformes distinctes (par ex. account_csm et account_owner sur une table account). Ce qui dégrade encore (DEGRADE_JOIN_SQL_ON_UNPARSEABLE) : un sql_on qui n’est pas une égalité mono-colonne de chaque côté — les jointures constantes/métadonnées (${meta.col} = 'literal') et les jointures composées ou expressions (clés multi-colonnes enchaînées par AND, coalesce(...), casts ::date) sont gardées dégradées plutôt que réduites à une seule clé silencieusement fausse.
Ce qui est refusé (et pourquoi)
| Refusé | Pourquoi | Voie à suivre |
|---|---|---|
Liquid calculé ({% if %} / boucles / assign / {{ }} calculé dans le SQL) | Le SQL généré à l’exécution est un trou de correction/sécurité que nous n’importerons pas | {% parameter %} / {% condition %} / {{ _user_attributes['x'] }} bornés portent maintenant (DEGRADE) |
sum/average fan-out sans grain déclarable | Double-compterait silencieusement | Ajoutez une dimension primary_key: yes sur la vue de base |
Topologies non-étoile : jointures full_outer / cross, ou un many_to_many dont le bridge two-hop ne peut pas être récupéré (clé de jointure composée/ambiguë, ou la vue de fait n’a pas de primary_key) | Cassent structurellement / fan out de manière incontrôlable, ou il n’y a pas de grain mono-colonne sur lequel dédupliquer | Pour un plusieurs-à-plusieurs : donnez à la vue de fait un primary_key: yes et des clés de jointure mono-colonnes pour que l’importeur puisse émettre un pont (le two-hop récupérable porte maintenant automatiquement) |
type: sum_distinct / average_distinct dont sql_distinct_key est une clé inter-vues (${other_view.field}), une clé étrangère, ou une expression | Un grain distinct au niveau de la mesure ne peut dédupliquer que sur une colonne dans la propre vue de fait de la mesure ; une clé inter-vues nécessite le bridge two-hop | Modélisez la jointure avec une dimension pont (plusieurs-à-plusieurs) |
type: list | Pas d’équivalent multidimensionnel | — |
Les extensions Mondrian++ élargissent la couverture
Les extensions sémantiques de Saiku existent en partie pour réduire la liste de refus — chacune transforme un ancien refus en un port propre :
- Agrégation symétrique (fan-out-safe), dimensions pont (plusieurs-à-plusieurs) et grain distinct au niveau de la mesure — voir Avancé.
- Agrégateurs médiane / percentile, types de dimension natifs tier / duration — voir Dimensions et Cubes et mesures.
- Paramètres de contexte de requête bornés et sécurité de lignes par prédicat — voir Contrôle d’accès.
À mesure que plus d’extensions arrivent, le même modèle LookML classifie plus proprement — relancez le rapport pour voir le ratio s’améliorer.
Couverture réelle
Validé contre un corpus de projets LookML publics (blocs Looker officiels plus modèles communautaires de production, ~900 fichiers .lkml) : ~99,8 % des fichiers in-scope parsent, chaque projet produit un rapport sur tout le projet, et sur ~20 000 champs dans des modèles réels, la couverture est de ~97,6 % CLEAN, ~1,2 % DEGRADE, ~1,2 % REFUSE. Les refus résiduels sont dominés par du Liquid véritablement calculé (intrinsèquement dynamique — un « ne portera pas par conception » plutôt qu’un bug).
Valider l’équivalence numérique
Le rapport de couverture vous dit ce qui porte ; il ne vous dit pas si le cube converti renvoie les mêmes nombres que Looker. Un port CLEAN qui totalise faux est le pire résultat, donc un harness d’équivalence séparé vérifie le cube converti contre une instance Looker en direct — l’analogue côté migration de la garde de parité Calcite côté moteur.
Étant donné une spec de requête (un explore plus les champs de dimension et de mesure), le harness exécute la requête des deux côtés et compare les résultats :
- Côté Looker —
POST /api/4.0/loginpuis/api/4.0/queries/run/jsonrenvoie les lignes comme oracle. Pointez-le sur votre instance avec trois paramètres (propriétés système ou variables d’environnement — ne les commitez jamais) :LOOKER_BASE_URL,LOOKER_CLIENT_ID,LOOKER_CLIENT_SECRET(une clé API3 Looker). Sans tous les trois, le client est inerte et le harness reste entièrement offline. - Côté Saiku — la même spec est réécrite en MDX sur le cube converti à l’aide de la carte de provenance du transpileur (mesures sur colonnes, niveaux de dimension sur lignes). Les champs que l’importeur n’a pas convertis CLEAN sont listés comme skipped, jamais silencieusement comparés.
- Comparaison — les lignes sont alignées par leur tuple de clé de dimension et les mesures comparées dans une tolérance relative (par défaut
1e-6). Les divergences sont catégoriséesROW_COUNT,DIMENSION_SETouMEASURE_VALUEet ne nomment que le champ et la catégorie — jamais les valeurs sous-jacentes (pas de données dans les logs). Une exécution propre rapporte une correspondance avec zéro divergence.
Le harness valide également la sécurité de lignes : un résultat Looker restreint par access_filter est comparé au cube converti interrogé sous l’autorisation de rôle correspondante, confirmant que les nombres restreints correspondent.
Limitations (v1)
- La résolution inter-fichiers d’
include:, d’extends:/raffinements, et de constantes@{}est gérée par une passe d’aplatissement (voir Projets multi-fichiers) ; une référence dont la base ou la constante est en dehors de l’ensemble de fichiers découverts est signalée comme un diagnostic et laissée tel-que-parsé plutôt que résolue. Pour un modèle entièrement pré-résolu, utilisez le front-end Explore-JSON. - Les explores multi-base conformes portent à un cube avec un
<MeasureGroup>par base de faits ; v1 lie chaque groupe de fait secondaire uniquement aux dimensions conformes que ses propres cléssql_onlient — les dimensions dégénérées de vue de base et le câblagecopy/no_linkinter-faits ne sont pas encore synthétisés. - Les
*.dashboard.lkml(dashboards Looker structurés en YAML) sont sautés — ils ne font pas partie du modèle de cube.
Voir Schemas YAML pour le format que l’importeur émet, et la CLI de schema pour convertir ou linter le résultat avant de le déployer dans Saiku.