Docs / Build Workflow

Soporte de Malloy

La versión de Malloy que corre Looky

Looky shippea una versión específica y pineada de Malloy con los adapters de BigQuery, Postgres y MySQL en la misma versión. Cualquier sintaxis de Malloy más allá de lo que esa versión soporta no se entiende; cualquier feature agregado en releases posteriores de Malloy no está disponible hasta que Looky actualice.

No hay extensiones específicas de Looky a Malloy. El dialecto que escribís es lo que Malloy mismo documenta — ni más, ni menos.

Adapters soportados

  • BigQuery — conecta con un JSON key de service account.
  • Postgres — conecta con un connection string libpq (host / port / database, más flags TLS o de pool) y un secret de user/password.
  • MySQL — conecta con un connection string mysql://host:port/database y un secret de user/password. Dos detalles: las conexiones no van encriptadas (todavía no hay opción TLS), y MySQL no tiene un tipo boolean real, así que las columnas boolean vuelven como números — casteá explícitamente cuando necesites un filtro true/false.

No hay otros adapters bundleados. Mirá Sources para el schema YAML del source por adapter.

Cómo se cargan los models

El content de cada workspace está rooteado en workspaces/<billing>/<slug>/content/. Looky resuelve los archivos .malloy relativos a ese root.

Las declaraciones import "..." dentro de un model se resuelven contra archivos .malloy hermanos en el mismo directorio. Usá imports para compartir una declaración de source base entre múltiples models de dominio — declará el source y sus joins una vez, extendelo por tópico.

# models/orders_base.malloy — base compartida
##! experimental.parameters

source: orders_base() is bigquery.table('...order_items') extend {
  join_one: products on product_id = products.id
  measure: revenue is sum(sale_price)
}

# models/revenue.malloy — model de dominio extiende la base
##! experimental.parameters
import "orders_base.malloy"

source: revenue() is orders_base() extend {
  view: by_category is {
    group_by: products.category
    aggregate: revenue
  }
}

Cada model necesita el pragma ##! experimental.parameters arriba y paréntesis en cada declaración de source (y en cada referencia a un source) — incluso cuando el source no toma parámetros. Los paréntesis vacíos () declaran la lista de parámetros.

Aliases de source dentro de los models

Un model Malloy usa un alias de conexión cuando llama a alias.table(...) o alias.sql(...). Looky matchea cada alias contra las declaraciones de runtime/sources.runtime.yml del workspace.

  • Si el model usa un alias, Looky lo elige automático.
  • Si el model usa varios aliases (directo o vía imports), el run tiene que decir cuál usar; si no, se rechaza.

Mirá Sources para la sintaxis de declaración de alias por adapter.

Parámetros con nombre

Los valores de filtro y cross-filter llegan a una query a través de parámetros con nombre declarados en la signature del source, entre los paréntesis. Cada parámetro lleva un nombre, un tipo y un default — p_country::string is "all".

Hay una regla de naming: los parámetros cuyo nombre Malloy empieza con p_ tienen el prefix strippeado para el nombre externo. Un parámetro del model declarado como p_start_date se setea mandando start_date desde el filtro del dashboard.

##! experimental.parameters

source: orders(
  p_country::string  is "all",
  p_start_date::date is @2024-01-01
) is bigquery.table('...') extend {

  view: revenue is {
    where:
      (p_country = "all" or country = p_country)
      and created_at::date >= p_start_date
    aggregate:
      revenue is sum(sale_price)
  }
}

# el filtro / pill manda
params:
  country: "MX"
  start_date: "2024-12-01"

Los parámetros pueden referenciarse de dos maneras dentro del cuerpo del source:

  • Expresiones Malloy — escribí p_country, p_start_date directo en where:, aggregate:, etc. (el ejemplo de arriba).
  • Placeholders @param en SQL crudo — cuando el cuerpo del source se construye desde SQL crudo (connection.sql("""…""")), usá placeholders @param dentro del bloque SQL. Cada @param tiene que tener una declaración matcheante en la signature del source; si no, validate falla con un error claro.

Parámetros de date y timestamp en Postgres y MySQL

Postgres y MySQL comparten una limitación conocida cuando Malloy bindea un parámetro de date o timestamp nativo en algunos shapes de query. El camino confiable en ambos es el patrón @param en SQL crudo: armá el source desde connection.sql("""…"""), referenciá placeholders @param dentro del SQL, y declará cada placeholder en la signature del source.

##! experimental.parameters

source: orders(
  p_date_from::date is null,
  p_date_to::date   is null
) is postgres.sql("""
  select *
  from orders
  where (@date_from::date is null or created_at::date >= @date_from::date)
    and (@date_to::date   is null or created_at::date <= @date_to::date)
""") extend {
  view: revenue is {
    aggregate: revenue is sum(sale_price)
  }
}

Looky sustituye los placeholders @date_from / @date_to con valores literales (o NULL tipado cuando el dashboard no proveyó un valor). En BigQuery y MySQL el mismo patrón funciona — la sustitución es dialect-aware. Mirá la comparación de adapters de source para la lista completa de divergencias.

Cache y parámetros

Cada entry cacheada está scoped a una query en un model con una combinación de parámetro específica. Un usuario filtrando por "MX" obtiene un resultado cacheado para esa combinación específica; un usuario filtrando por "AR" dispara una entry de cache separada la primera vez, después hace hit al cache en cargas posteriores.

Editar el archivo .malloy invalida cada entry cacheada para queries adentro en el próximo request. Mirá Cache para el shape del cache sidecar.

Patrones trabajados

Parámetro string con un sentinel "all"

##! experimental.parameters

source: orders(
  p_status::string is "all"
) is bigquery.table('...') extend {
  view: detail is {
    where: p_status = "all" or status = p_status
    select: *
  }
}

Par de parámetros de date range (estilo expresión Malloy)

##! experimental.parameters

source: orders(
  p_date_from::date is @2024-01-01,
  p_date_to::date   is @2024-12-31
) is bigquery.table('...') extend {
  view: revenue is {
    where:
      created_at::date >= p_date_from
      and created_at::date <= p_date_to
    aggregate: revenue is sum(sale_price)
  }
}

Par de parámetros de date range (estilo @param en SQL crudo)

##! experimental.parameters

source: orders(
  p_date_from::date is null,
  p_date_to::date   is null
) is postgres.sql("""
  select *
  from orders
  where (@date_from::date is null or created_at::date >= @date_from::date)
    and (@date_to::date   is null or created_at::date <= @date_to::date)
""") extend {
  view: revenue is {
    aggregate: revenue is sum(sale_price)
  }
}

Los placeholders @date_from / @date_to se sustituyen en tiempo de run con los valores del filtro (o con NULL tipado cuando el dashboard no proveyó un valor).

Parámetro string de mes

##! experimental.parameters

source: orders(
  p_month::string is "2024-12"
) is bigquery.table('...') extend {
  view: monthly is {
    where: format_datetime('%Y-%m', created_at) = p_month
    aggregate: revenue is sum(sale_price)
  }
}

Errores comunes

  • Falta ##! experimental.parameters o faltan () en el source. Validate falla con unsupported_model_shape. Agregá el pragma arriba del archivo y paréntesis en cada declaración source: (y cada referencia a ella).
  • SQL crudo @param sin una declaración matcheante en la signature del source. Validate falla con unbound_param nombrando el parámetro faltante. Agregalo a los paréntesis, ej. p_date_from::date is null.
  • Mismatch de nombre de parámetro entre filtro y model. El filtro manda el nombre externo; el model recibe el nombre Malloy con prefix (p_). Auditá los dos lados.
  • La query falla cuando el parámetro no está seteado. Declará un default en el model así la query igual corre. Para semántica "all", defaulteá a un valor sentinel que el where-clause trate como no-op.
  • El model usa un feature de Malloy que el engine no conoce. Looky pinea una versión de Malloy. Features más nuevos fallan en compile time. Quedate con features documentados en esa versión.
  • Dos models en el mismo dashboard declaran nombres de parámetro distintos para la misma dimension. Los clicks de cross-filter en esa dimension solo estrechan el model que usa el nombre matcheante. Estandarizá los nombres de parámetro entre los models en el mismo dashboard.
  • El cache devuelve datos stale. El cache está keyed por combinación de parámetro; si la data cambió pero la combinación de parámetro es la misma, la entry cacheada gana hasta que expire su TTL (o cambie el archivo del model).