Saltearse al contenido

Migrar desde Looker (LookML)

El importador de LookML carga modelos LookML de Looker en Saiku como schemas Mondrian-4 para que pueda ejecutar OLAP/MDX sobre ellos. Es un acelerador de migración con una puerta de seguridad estricta, no un conversor sin pérdidas — cada construcción se clasifica en uno de tres veredictos:

  • CLEAN — portado a un elemento de Mondrian.
  • DEGRADE — portado, pero se perdió una capacidad (y se nombra con precisión).
  • REFUSE — no portado, con un diagnóstico exacto del por qué.

Cómo funciona

El importador es un pipeline de cuatro etapas:

  1. Parsear — leer los archivos .lkml en un modelo (un parser de LookML vendado y endurecido).
  2. Clasificar — una puerta de seguridad estática marca cada explore y campo CLEAN / DEGRADE / REFUSE, sin acceso al data warehouse.
  3. Transpilar — emitir un schema Mondrian-4 para el subconjunto CLEAN/DEGRADE (como YAML, cargable directamente por Saiku), más un mapa de procedencia de qué campo LookML produjo qué elemento del schema.
  4. Informar — un informe de cobertura (Markdown + JSON) categorizando cada construcción, con razones de resumen.

Solo las construcciones que la puerta acepta llegan al transpilador, así que una medida rechazada y silenciosamente incorrecta nunca puede filtrarse al cubo emitido.

Ejecutar el importador

El importador se expone como la herramienta mondrian.lookml.report.LookmlReportCli (de la misma forma que la CLI de schema envuelve a SchemaCli). Apúntelo a un único archivo .lkml o a todo un directorio de proyecto — los directorios se escanean recursivamente en busca de archivos .lkml.

Ventana de terminal
lookml-report report <path> [-o report.md] [--json report.json] [--fail-on-refuse]
FlagEfecto
(ninguno)Imprime el informe Markdown a stdout.
-o <file>Escribe el informe Markdown a un archivo.
--json <file>También escribe el informe JSON legible por máquina.
--fail-on-refuseTodavía emite el informe, luego sale con código distinto de cero si algo fue rechazado — una puerta CI para “bloquear la migración hasta que la lista de rechazos esté vacía”.

Códigos de salida: 0 éxito · 1 argumentos incorrectos · 2 la ruta falta/no es legible, nada fue parseable, o --fail-on-refuse vio un rechazo.

Qué contiene el informe

  • Métricas de resumen — recuentos y porcentajes CLEAN / DEGRADE / REFUSE a ambas granularidades de explore y campo. Esta razón es el número titular de preparación para la migración.
  • Categorías por construcción — cada explore y campo listado bajo Clean / Degrade / Refuse con una razón precisa, el elemento M4 producido (para clean/degrade) o la capacidad perdida, y un enlace a la funcionalidad de Saiku que levantaría un rechazo.
  • Archivos no parseables / omitidos — transparencia total sobre cualquier cosa no ingerida.

Proyectos multi-archivo

Apunte el importador a un directorio de proyecto y resuelve el proyecto entero, no solo un archivo:

  1. Cada .lkml se descubre recursivamente y se parsea independientemente (los archivos no parseables y *.dashboard.lkml se listan, nunca fatales).
  2. Los objetos de nivel superior parseables se fusionan, luego una pasada de aplanado resuelve referencias cross-archivo en un modelo antes de la clasificación:
    • include: — satisfecho por la fusión;
    • extends: — la base se copia, luego las propiedades propias del objeto que extiende sobrescriben;
    • refinamientos (+view / +explore / +model) — superpuestos sobre la base (escalares sobrescriben; dimension / measure / join se fusionan por nombre);
    • @{constant} — sustituido desde bloques constant:.

Esto arregla clasificaciones erróneas donde una medida o campo solo se vuelve aditivo, Liquid o asegurado por fila tras un refinamiento — en un proyecto real esto movió cientos de campos desde una suposición de texto literal al veredicto correcto.

Importar desde una instancia Looker en vivo (Explore JSON)

Con una instancia de Looker con credenciales puede omitir el parseo de .lkml cruda e importar los metadatos ya resueltos de un explore:

Ventana de terminal
lookml-report report --explore-json explore.json

Exporte el JSON lookml_model_explore del explore desde la API de Looker. Como Looker ya ha aplicado todos los extends/refinamientos/constantes/Liquid, esta entrada omite la pasada de aplanado y se alimenta directamente al mismo pipeline de clasificador → transpilador → informe, produciendo el mismo informe de cobertura que el .lkml equivalente. Compensación: requiere acceso a la API y es por explore, así que complementa — en lugar de reemplazar — la ruta offline de apuntar-a-un-repo-git.

Qué se porta

La mayor parte de un modelo LookML estrella/copo de nieve limpio se convierte directamente. El importador mapea:

LookMLMondrian-4Veredicto
explore (estrella/copo de nieve de base única)<Cube> con un <MeasureGroup> y dimensiones conformadasCLEAN
explore uniendo múltiples bases de hechos (conformadas)un <Cube> con un <MeasureGroup> por base de hechos sobre dimensiones conformadas compartidasCLEAN
join: { relationship: many_to_one | one_to_one, type: left_outer }ForeignKeyLink (dims degeneradas → FactLink)CLEAN
puente de dos saltos: hechos one_to_many (o many_to_many) → view puente → view dimensión many_to_one<BridgeLink> (de-dup de recuento completo) + la dimensión como una dimensión conformadaCLEAN
measure: { type: sum | count | min | max | average | count_distinct }<Measure> con el agregador coincidenteCLEAN
measure: { type: median | percentile }aggregator="median" / aggregator="percentile"DEGRADE — necesita un backend con capacidad PERCENTILE_CONT
medida filtrada (filters: igualdad, sin Liquid)miembro calculadoCLEAN
dimension (+ value_format / value_format_name, label, description)atributo / nivel (+ formatString — los formatos nombrados de Looker como usd, percent_2, decimal_0 se traducen a máscaras de Mondrian; un formato nombrado desconocido se conserva literalmente con una nota DEGRADE — más caption, description)CLEAN
dimension: { type: tier } / dimension_group: { type: duration }<Tier> / <Duration> nativosCLEAN
parameter (acotado: tipado, allowed_values)<QueryParameter>CLEAN
measure: { type: sum_distinct | average_distinct }, sql_distinct_key resuelve a una columna en la propia view de la medida (incl. una no clave primaria)<Measure> con una granularidad distinct a nivel de medida (distinctKeyColumn) — deduplicado en esa clave antes de agregar; colapsa a un sum/avg plano cuando la clave es la clave primaria de la viewCLEAN
Liquid acotado: {% parameter %}, {% condition %}, {{ _user_attributes['x'] }}binding <QueryParameter> / predicate-grantDEGRADE
access_filter en una clave de dimensión modeladagrant de miembro de RoleCLEAN
access_filter en una columna de hecho arbitrariaRole predicate-grant + parámetro enlazadoDEGRADE
derived_tableuna tabla física respaldada por SQL (<Query>)DEGRADE — política de persistencia descartada
drill_fieldsconjunto RETURN del drillthrough, llevado como anotación de cubo (M4 no tiene elemento de schema <DrillThrough> — es una sentencia DRILLTHROUGH … RETURN en tiempo de ejecución)CLEAN
aggregate_table(no convertido — Saiku regenera agregados)DEGRADE

Una medida sum/average que se expande a través de un join one_to_many se porta (CLEAN) cuando la view base declara una clave primaria, porque la agregación simétrica segura para fan-out de Saiku deduplica en esa granularidad. Sin una clave declarada se rechaza en lugar de arriesgar doble conteo.

Joins many-to-many (dimensiones puente). El importador reconoce el puente canónico LookML de dos saltos — un hecho unido one_to_many (o many_to_many) a una view puente, que a su vez se une many_to_one a una view dimensión — y lo mapea a un <BridgeLink> de Mondrian en lugar de rechazar el explore como no estrella. La dimensión alcanzada a través del puente se convierte en una dimensión conformada normal, y las medidas en los hechos devuelven el total deduplicado (recuento completo), no el expandido. Un puente se emite solo cuando cada salto se reduce a una clave de una columna y la view de hechos declara una granularidad primary_key: yes; una clave de join compuesta/ambigua, o una view de hechos sin clave primaria, se deja rechazada en lugar de producir un cubo silenciosamente incorrecto. Como LookML no lleva peso de asignación, los puentes por defecto se deduplican por recuento completo.

Joins con alias from:. En LookML un campo unido siempre se referencia por el nombre del join, mientras que from: (o view_name:) solo intercambia la view física subyacente. El importador empareja el sql_on ${name.column} de cada join contra el nombre del join — resolviendo las columnas y tabla de la dimensión desde la view subyacente — así que un join como join: current_subscription_state { from: logical_subscriptions; sql_on: ${fact.fk} = ${current_subscription_state.id} } se porta CLEAN como una dimensión conformada con el nombre del join. Dos joins que from: la misma view base se convierten en dos dimensiones conformadas distintas (por ejemplo, account_csm y account_owner sobre una tabla account). Lo que aún degrada (DEGRADE_JOIN_SQL_ON_UNPARSEABLE): un sql_on que no es una igualdad de una sola columna en cada lado — joins constantes/de metadatos (${meta.col} = 'literal') y joins compuestos o de expresión (claves multi-columna encadenadas con AND, coalesce(...), casts ::date) se mantienen degradados en lugar de reducirse a una única clave silenciosamente incorrecta.

Qué se rechaza (y por qué)

RechazadoPor quéCamino a seguir
Liquid computado ({% if %} / loops / assign / {{ }} computado en SQL)El SQL generado en tiempo de ejecución es un agujero de corrección/seguridad que no importaremosEl {% parameter %} / {% condition %} / {{ _user_attributes['x'] }} acotado ahora se porta (DEGRADE)
Fan-out sum/average sin granularidad declarableDoblaría el conteo silenciosamenteAñada una dimensión primary_key: yes en la view base
Topologías no estrella: joins full_outer / cross, o un many_to_many cuyo puente de dos saltos no se puede recuperar (clave de join compuesta/ambigua, o la view de hechos no tiene primary_key)Se rompen estructuralmente / se expanden incontrolablemente, o no hay una granularidad de una columna sobre la que deduplicarPara un many-to-many: dele a la view de hechos un primary_key: yes y claves de join de una sola columna para que el importador pueda emitir un puente (el dos-saltos recuperable ahora se porta automáticamente)
type: sum_distinct / average_distinct cuya sql_distinct_key es una cross-view (${other_view.field}), clave foránea, o clave de expresiónUna granularidad distinct a nivel de medida solo puede deduplicar en una columna en la propia view de hechos de la medida; una clave cross-view necesita el puente de dos saltosModele el join con una dimensión puente (many-to-many)
type: listSin equivalente multidimensional

Las extensiones Mondrian++ amplían la cobertura

Las extensiones semánticas de Saiku existen en parte para estrechar la lista de rechazos — cada una convierte un antiguo rechazo en un porte limpio:

  • Agregación simétrica (segura para fan-out), dimensiones puente (many-to-many) y granularidad distinct a nivel de medida — consulte Avanzado.
  • Agregadores mediana / percentil, tipos de dimensión nativos tier / duration — consulte Dimensiones y Cubos y medidas.
  • Parámetros acotados de contexto de consulta y seguridad de fila basada en predicado — consulte Control de acceso.

A medida que aterrizan más extensiones, el mismo modelo LookML se clasifica más limpio — vuelva a ejecutar el informe para ver la razón mejorar.

Cobertura del mundo real

Validado contra un corpus de proyectos LookML públicos (bloques oficiales de Looker más modelos comunitarios de producción, ~900 archivos .lkml): ~99,8% de archivos en alcance parsean, cada proyecto produce un informe de proyecto completo, y a través de ~20 000 campos en modelos reales la cobertura es ~97,6% CLEAN, ~1,2% DEGRADE, ~1,2% REFUSE. Los rechazos residuales están dominados por Liquid genuinamente computado (inherentemente dinámico — un “no se portará por diseño” en lugar de un bug).

Validar la equivalencia numérica

El informe de cobertura le dice qué se porta; no le dice si el cubo convertido devuelve los mismos números que Looker. Un porte CLEAN que totaliza mal es el peor resultado, así que un harness de equivalencia separado verifica el cubo convertido contra una instancia de Looker en vivo — el análogo de migración del guard de paridad de Calcite del lado del motor.

Dado una especificación de consulta (un explore más los campos de dimensión y medida), el harness ejecuta la consulta de ambas formas y compara los resultados:

  • Lado LookerPOST /api/4.0/login luego /api/4.0/queries/run/json devuelve las filas como el oráculo. Apúntelo a su instancia con tres ajustes (propiedades de sistema o variables de entorno — nunca las comita): LOOKER_BASE_URL, LOOKER_CLIENT_ID, LOOKER_CLIENT_SECRET (una clave API3 de Looker). Sin las tres el cliente está inerte y el harness permanece completamente offline.
  • Lado Saiku — la misma especificación se reescribe a MDX sobre el cubo convertido usando el mapa de procedencia del transpilador (medidas en columnas, niveles de dimensión en filas). Los campos que el importador no convirtió CLEAN se listan como omitidos, nunca comparados silenciosamente.
  • Comparación — las filas se alinean por su tupla de clave de dimensión y las medidas se comparan dentro de una tolerancia relativa (por defecto 1e-6). Las divergencias se categorizan ROW_COUNT, DIMENSION_SET o MEASURE_VALUE y nombran solo el campo y la categoría — nunca los valores subyacentes (sin datos en logs). Una ejecución limpia reporta una coincidencia con cero divergencias.

El harness también valida la seguridad de fila: un resultado restringido por access_filter de Looker se compara contra el cubo convertido consultado bajo el grant de rol correspondiente, confirmando que los números restringidos coinciden.

Limitaciones (v1)

  • La resolución cross-archivo de include:, extends:/refinamientos y constantes @{} la gestiona una pasada de aplanado (consulte Proyectos multi-archivo); una referencia cuya base o constante está fuera del conjunto de archivos descubierto se informa como diagnóstico y se deja como parseada en lugar de resolverse. Para un modelo completamente preresuelto, use el front-end Explore-JSON.
  • Los explores multi-base conformados se portan a un cubo con un <MeasureGroup> por base de hechos; v1 enlaza cada grupo de hechos secundario solo a las dimensiones conformadas a las que apuntan sus propias claves sql_on — las dimensiones degeneradas de la view base y el cableado cross-fact copy/no_link aún no se sintetizan.
  • *.dashboard.lkml (dashboards Looker estructurados como YAML) se omiten — no son parte del modelo de cubo.

Consulte Schemas YAML para el formato que emite el importador, y la CLI de schema para convertir o validar el resultado antes de desplegarlo en Saiku.