Saltearse al contenido

Adaptador SQL sobre Apache Ossie

Saiku entrega un adaptador SQL de Calcite que lee YAML de Apache Ossie — el mismo formato que el exportador de Ossie produce a partir de su schema Mondrian — y lo expone como una superficie JDBC consultable. Apunte cualquier cliente SQL a él y podrá hacer SELECT desde sus datasets Ossie exactamente como si fueran tablas reales de base de datos. Por debajo, Calcite planifica la consulta y la empuja a su warehouse real como SQL nativo.

Qué obtiene

  • SQL estándar sobre sus datasets Ossie — cualquier cosa que Calcite entienda, que es un gran superconjunto de ANSI SQL.
  • Pushdown al warehouse. Calcite lee la consulta, la planifica contra su warehouse (Postgres, Snowflake, H2, lo que sea), y emite SQL nativo. Los agregados corren en el warehouse, no en la JVM.
  • Nativo de JDBC. Funciona con cualquier cliente JDBC: dbt, DBeaver, Tableau, Power BI, psql, JetBrains DataGrip, código personalizado.
  • Los renombrados de Ossie sobreviven. Si su exportador emitió nombres de dataset que no coinciden con las tablas subyacentes del warehouse (mediante el campo source de Ossie), el adaptador mapea de vuelta de forma transparente — SELECT * FROM CUSTOMERS se convierte en SELECT * FROM public.dim_customer_v2 en el warehouse.

Inicio rápido

1. Produzca el YAML Ossie

Ventana de terminal
saiku ossie-export --in saiku-home/data/Pharma.xml --out pharma.ossie.yaml

Vea Exportar a Apache Ossie para la tabla de mapeo y el ejemplo trabajado.

2. Escriba un modelo de conexión Calcite

Las conexiones JDBC de Calcite toman un modelo de conexión JSON que le dice a Calcite qué factory instanciar:

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

Claves del operand:

ClaveRequeridaDescripción
ossieYamlRuta absoluta al archivo YAML Ossie.
modelNamenoNombre de la entrada semantic_model[] a exponer cuando el documento lleva varias. Por defecto la primera.
jdbcUrlmuy recomendadaURL JDBC del warehouse. Sin ella, las tablas se registran pero las consultas devuelven cero filas.
jdbcUser, jdbcPasswordsegún sea necesarioCredenciales del warehouse.

3. Conecte

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

O desde cualquier cliente JDBC — URL de conexión jdbc:calcite:model=/path/to/model.json.

4. O ejecútelo como un endpoint de red

Para clientes remotos, use el subcomando CLI saiku sql-serve. Expone dos endpoints — un endpoint Apache Avatica para clientes conscientes de Avatica Y un endpoint nativo del protocolo de cable de Postgres para psql / pgAdmin / Tableau / DBeaver / dbt-postgres. Active uno o ambos:

Ventana de terminal
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

El servidor registra ambas URLs al arranque. Los clientes entonces se conectan mediante:

Avatica:

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

Cable de Postgres (cualquier cliente PG nativo):

Ventana de terminal
# 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

Las consultas parametrizadas mediante PreparedStatement.setString(1, ...) etc. funcionan de fábrica.

Qué funciona hoy

Característica SQLEstadoNotas
SELECT desde un datasetEmpujado al warehouse.
WHERE en columnas del datasetEmpujado.
GROUP BY + agregadosLos agregados corren en el warehouse.
JOIN … ON explícito entre datasetsEl predicado del join se empuja.
ORDER BY, LIMITEmpujado donde el dialecto del warehouse lo soporta.
information_schema / DatabaseMetaData.getTables()Datasets, métricas Y vistas de join descubribles por herramientas BI.
Mapeo de renombrado (nombre de dataset Ossie → tabla del warehouse vía source)Transparente.
Fallback insensible a mayúsculas (H2 UPPER vs Postgres lower)El adaptador prueba todos los casos.
SELECT de métrica escalarSELECT * FROM PHARMA.TOTAL_QUANTITYExpande el ANSI_SQL de la métrica contra su dataset de origen. Tipo de retorno derivado del tipo de la columna subyacente (sin conjeturas gruesas de DOUBLE/BIGINT).
Vistas de join de relaciónSELECT ... FROM PHARMA.FACT_PHARMA_JOIN_PRESCRIBER ...Una vista por relationship de Ossie, nombrada <from>_JOIN_<to>. El predicado del JOIN vive en el YAML, no en la consulta. Calcite empuja todo como un único JOIN — sin sobrecarga en tiempo de ejecución.
Joins auto-inyectadosSELECT c.x, SUM(o.y) FROM ORDERS o, CUSTOMERS c GROUP BY c.x (sin cláusula JOIN)Una regla de planificador de Calcite personalizada detecta el join cartesiano entre dos datasets Ossie e inyecta el predicado ON a partir de la relación. Mismo resultado que escribir el JOIN a mano. Vea “Joins auto-inyectados” abajo.
Métricas MDX-only (miembros calculados)✅ (invisible)Correctamente no expuestas en la superficie SQL. Viven en Mondrian.

Todavía no soportado

Rastreado en el epic Ossie/SQL:

  • SSL/TLS + auth en ambos endpoints. Actualmente anónimo + texto plano. Requisito básico antes de cualquier despliegue de producción.
  • Parámetros/resultados en formato binario en el endpoint de cable PG — Bind ahora decodifica INT2/INT4/INT8 big-endian correctamente (la mayoría de las llamadas setInt/setLong de herramientas BI) pero la decodificación binaria completa dirigida por tipo (BOOL/DATE/NUMERIC/TIMESTAMP contra los OID de parámetro declarados de la sentencia) es un seguimiento.
  • Suspensión de portal — Execute siempre devuelve todas las filas independientemente de la petición maxRows del cliente. Bien para consultas BI interactivas; importa para la paginación de cursores grandes.
  • Auto-joins de tres víasFROM A, B, C donde A↔B y B↔C ambos existen. Las reescrituras anidadas deberían encadenarse pero esto aún no se ha verificado en tests.
  • Joins entre schemas — unir un dataset Ossie con una tabla no-Ossie (de un sub-schema de Calcite diferente). La regla de auto-join se abstiene cuando los dos lados pertenecen a schemas diferentes.
  • Esquema de conexión jdbc:saiku://. Hoy los usuarios pasan por jdbc:calcite: + un archivo model.json. Un driver JDBC de primera parte viene con el trabajo de cable de Postgres.
  • Protocolo de cable de Postgres. Hoy la superficie es solo JDBC. jdbc:saiku: en el cable (para que los clientes psql / libpq conecten de forma nativa) es #1386.

Ejemplo trabajado — cubo Pharma

Dado el YAML Ossie de Pharma que produce el exportador:

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]

Puede consultarlo así:

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

Las cinco corren enteramente en Postgres mediante el pushdown JDBC de Calcite — la JVM nunca ve filas individuales para los agregados.

Joins auto-inyectados

Los usuarios no necesitan recordar qué columnas enlazan ORDERS con CUSTOMERS. Simplemente liste ambos datasets en FROM y Calcite inyectará el predicado ON por usted:

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

La reescritura ocurre durante la fase de optimización de Calcite — todo sigue empujándose al warehouse como una única consulta JOIN.

Cuándo se dispara la regla

  • El join debe ser cartesiano. Las cláusulas JOIN … ON … explícitas nunca se sobrescriben — el usuario pidió un predicado específico y lo respetamos.
  • Todas las tablas deben ser datasets propiedad de Ossie en el mismo OssieSchema. Las tablas entre schemas y no-Ossie dejan la consulta sin cambios.
  • Exactamente una relationship de Ossie debe enlazar cada par que se auto-une. Múltiples candidatos lanzan AmbiguousJoinException con la lista de nombres candidatos — el usuario debe añadir un ON explícito para elegir uno. Los resultados silenciosamente-incorrectos son el modo de fallo contra el que nos protegemos.

Soporte N-vías

FROM A, B, C, … (tres o más tablas en un único cartesiano) también se auto-une. La regla desciende a través de los Joins anidados para alcanzar cada TableScan cruda, luego construye una cadena de join left-deep fresca usando las relaciones que enlazan cada nueva tabla al conjunto ya unido. Las consultas a escala Pharma contra hechos + múltiples dims funcionan sin cláusulas JOIN.

Cuándo no se dispara

  • Sin relación entre dos datasets que necesitarían enlazarse → el cartesiano permanece (probablemente no lo que el usuario quiere, pero honesto).
  • Self-joins (FROM A a, A b) → se dejan como cartesianos.
  • Joins entre schemas mezclando tablas Ossie con no-Ossie.

Vistas de join

Para cada relationship de Ossie, el adaptador registra una vista pre-materializada nombrada <from>_JOIN_<to>. El SQL de la vista es el JOIN de los dos datasets sobre los from_columns / to_columns de la relación, y su tipo de fila es la concatenación de las columnas de ambos datasets (con sufijos numéricos en las colisiones).

Dado este fragmento Ossie:

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

El adaptador expone PHARMA.fact_pharma_JOIN_Prescriber con la unión de las columnas de ambas tablas. Los usuarios escriben:

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

y Calcite empuja SELECT * FROM fact_pharma JOIN dim_prescriber ON fact_pharma.prescriberkey = dim_prescriber.prescriberkey WHERE prescribername LIKE 'Dr%' — sin sobrecarga en tiempo de ejecución respecto a escribir el JOIN a mano.

Las relaciones multi-columna están soportadas: el predicado ON hace AND de cada par from_columns[i] = to_columns[i].

Relacionado