Adaptador SQL sobre Apache Ossie
O Saiku distribui um adaptador SQL Calcite que lê YAML Apache Ossie — o mesmo formato que o exportador Ossie produz a partir do seu schema Mondrian — e o expõe como uma superfície JDBC consultável. Aponte qualquer cliente SQL para ele e você pode dar SELECT nos seus datasets Ossie exatamente como se fossem tabelas de banco reais. Por baixo, o Calcite planeja a query e a empurra para seu warehouse real como SQL nativo.
O que você ganha
- SQL padrão sobre seus datasets Ossie — qualquer coisa que o Calcite entenda, o que é um grande superset de ANSI SQL.
- Pushdown para o warehouse. O Calcite lê a query, a planeja contra seu warehouse (Postgres, Snowflake, H2, o que for) e emite SQL nativo. Agregados rodam no warehouse, não na JVM.
- JDBC-nativo. Funciona com qualquer cliente JDBC: dbt, DBeaver, Tableau, Power BI,
psql, JetBrains DataGrip, código customizado. - Renomes Ossie sobrevivem. Se seu exportador emitiu nomes de dataset que não casam com as tabelas do warehouse subjacente (via o field
sourcedo Ossie), o adaptador mapeia de volta transparentemente —SELECT * FROM CUSTOMERSviraSELECT * FROM public.dim_customer_v2no warehouse.
Início rápido
1. Produza o YAML Ossie
saiku ossie-export --in saiku-home/data/Pharma.xml --out pharma.ossie.yamlVeja Exportando para Apache Ossie para a tabela de mapeamento e exemplo trabalhado.
2. Escreva um connect model do Calcite
Conexões JDBC do Calcite recebem um connect model JSON que diz ao Calcite qual 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" } }]}Chaves de operand:
| Chave | Obrigatório | Descrição |
|---|---|---|
ossieYaml | sim | Path absoluto para o arquivo YAML Ossie. |
modelName | não | Nome da entrada semantic_model[] a expor quando o documento carrega várias. Default para a primeira. |
jdbcUrl | fortemente encorajado | URL JDBC do warehouse. Sem ela, tabelas registram mas queries retornam zero linhas. |
jdbcUser, jdbcPassword | conforme necessário | Credenciais do 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));}Ou de qualquer cliente JDBC — URL de conexão jdbc:calcite:model=/path/to/model.json.
4. Ou rode como um endpoint de rede
Para clientes remotos, use o subcomando CLI saiku sql-serve. Ele expõe dois endpoints — um endpoint Apache Avatica para clientes Avatica-aware E um endpoint de wire Postgres nativo para psql / pgAdmin / Tableau / DBeaver / dbt-postgres. Habilite qualquer um ou ambos:
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 5432O servidor loga ambas as URLs no startup. Clientes então conectam via:
Avatica:
jdbc:avatica:remote:url=http://localhost:8765# with serialization=protobufWire Postgres (qualquer cliente PG nativo):
# psqlPGSSLMODE=disable psql -h localhost -p 5432 saiku
# JDBC — pgjdbc defaults (extended query mode) work; simple mode is also finejdbc:postgresql://localhost:5432/saiku?sslmode=disableQueries parametrizadas via PreparedStatement.setString(1, ...) etc. funcionam out of the box.
O que funciona hoje
| Recurso SQL | Status | Notas |
|---|---|---|
SELECT de um dataset | ✅ | Empurrado para o warehouse. |
WHERE em colunas do dataset | ✅ | Empurrado. |
GROUP BY + agregados | ✅ | Agregados rodam no warehouse. |
JOIN … ON explícito entre datasets | ✅ | Predicado de join empurra para baixo. |
ORDER BY, LIMIT | ✅ | Empurrado onde o dialeto do warehouse suporta. |
information_schema / DatabaseMetaData.getTables() | ✅ | Datasets, metrics E views de join descobríveis por ferramentas BI. |
Mapeamento de rename (nome de dataset Ossie → tabela do warehouse via source) | ✅ | Transparente. |
| Fallback case-insensitive (H2 UPPER vs Postgres lower) | ✅ | O adaptador tenta todos os casos. |
SELECT de métrica escalar — SELECT * FROM PHARMA.TOTAL_QUANTITY | ✅ | Expande o ANSI_SQL da métrica contra seu dataset de origem. Tipo de retorno derivado do tipo da coluna subjacente (sem palpites grosseiros de DOUBLE/BIGINT). |
Views de join de relationship — SELECT ... FROM PHARMA.FACT_PHARMA_JOIN_PRESCRIBER ... | ✅ | Uma view por relationship Ossie, nomeada <from>_JOIN_<to>. O predicado JOIN vive no YAML, não na query. O Calcite empurra tudo para baixo como um único JOIN — sem overhead de runtime. |
Joins auto-injetados — SELECT c.x, SUM(o.y) FROM ORDERS o, CUSTOMERS c GROUP BY c.x (sem cláusula JOIN) | ✅ | Regra de planner customizada do Calcite detecta o join cartesiano entre dois datasets Ossie e injeta o predicado ON a partir da relationship. Mesmo resultado de escrever o JOIN à mão. Veja “Joins auto-injetados” abaixo. |
| Métricas só-MDX (calculated members) | ✅ (invisível) | Corretamente não expostas na superfície SQL. Elas vivem no Mondrian. |
Ainda não suportado
Rastreado no épico Ossie/SQL:
- SSL/TLS + auth em ambos os endpoints. Atualmente anônimo + plaintext. Requisito básico antes de qualquer deploy de prod.
- Parâmetros/resultados em formato binário no endpoint PG-wire — o Bind agora decodifica INT2/INT4/INT8 big-endian corretamente (a maioria das chamadas
setInt/setLongde ferramentas BI) mas a decodificação binária completa dirigida por tipo (BOOL/DATE/NUMERIC/TIMESTAMP contra os OIDs de parâmetro declarados do statement) é um follow-up. - Suspensão de portal — Execute sempre retorna todas as linhas independentemente do pedido
maxRowsdo cliente. Bom para queries BI interativas; importa para paginação de cursor grande. - Auto-joins de três vias —
FROM A, B, Conde A↔B e B↔C ambos existem. Reescritas aninhadas deveriam cascatear mas isso ainda não foi verificado em testes. - Joins cross-schema — unir um dataset Ossie com uma tabela não-Ossie (de um sub-schema Calcite diferente). A regra de auto-join desiste quando os dois lados pertencem a schemas diferentes.
- Esquema de connect
jdbc:saiku://. Hoje usuários passam porjdbc:calcite:+ um arquivo model.json. Um driver JDBC de primeira parte vem com o trabalho de wire Postgres. - Protocolo de wire Postgres. Hoje a superfície é só-JDBC.
jdbc:saiku:no wire (para que clientespsql/libpqconectem nativamente) é #1386.
Exemplo trabalhado — cubo Pharma
Dado o YAML Ossie do Pharma que o exportador produz:
version: 0.2.0.dev0semantic_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]Você pode consultá-lo assim:
-- 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_countFROM "Pharma Rx".fact_pharma fJOIN "Pharma Rx"."Prescriber" p ON f.prescriberkey = p.prescriberkeyGROUP BY p.prescribernameORDER BY units DESCLIMIT 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_countFROM "Pharma Rx".fact_pharma_JOIN_PrescriberGROUP BY prescribernameORDER BY units DESCLIMIT 20;Todas as cinco rodam inteiramente no Postgres via o pushdown JDBC do Calcite — a JVM nunca vê linhas individuais para os agregados.
Joins auto-injetados
Usuários não precisam lembrar quais colunas ligam ORDERS a CUSTOMERS. Apenas liste ambos os datasets em FROM e o Calcite injetará o predicado ON para você:
-- User writes:SELECT c.REGION, SUM(o.AMOUNT) AS TOTALFROM "Pharma Rx".fact_pharma o, "Pharma Rx"."Prescriber" cGROUP 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 TOTALFROM "Pharma Rx".fact_pharma o JOIN "Pharma Rx"."Prescriber" c ON o.prescriberkey = c.prescriberkeyGROUP BY c.REGION;A reescrita acontece durante a fase de otimização do Calcite — tudo ainda empurra para baixo ao warehouse como uma única query JOIN.
Quando a regra dispara
- O join deve ser cartesiano. Cláusulas
JOIN … ON …explícitas nunca são sobrescritas — o usuário pediu um predicado específico e nós o respeitamos. - Todas as tabelas devem ser datasets Ossie-owned no mesmo OssieSchema. Tabelas cross-schema e não-Ossie deixam a query inalterada.
- Exatamente uma
relationshipOssie deve ligar cada par sendo auto-unido. Múltiplos candidatos levantamAmbiguousJoinExceptioncom a lista de nomes candidatos — o usuário deve adicionar um ON explícito para escolher um. Resultados-silenciosamente-errados é o modo de falha contra o qual estamos nos protegendo.
Suporte a N vias
FROM A, B, C, … (três ou mais tabelas em um único cartesiano) também auto-une. A regra caminha para baixo através de Joins aninhados para alcançar cada TableScan cru, depois constrói uma cadeia de join left-deep fresca usando as relationships que ligam cada nova tabela ao conjunto já-unido. Queries em escala Pharma contra fact + múltiplas dims funcionam sem cláusulas JOIN.
Quando não dispara
- Sem relationship entre dois datasets que precisariam ser ligados → o cartesiano permanece (provavelmente não o que o usuário quer, mas honesto).
- Self-joins (
FROM A a, A b) → deixados como cartesiano. - Joins cross-schema misturando tabelas Ossie com não-Ossie.
Views de join
Para cada relationship Ossie, o adaptador registra uma view pré-materializada nomeada <from>_JOIN_<to>. O SQL da view é o JOIN dos dois datasets nas from_columns / to_columns da relationship, e seu row type é a concatenação das colunas de ambos os datasets (com sufixos numéricos em colisões).
Dado este fragmento Ossie:
relationships:- name: fact_pharma_to_Prescriber from: fact_pharma to: Prescriber from_columns: [prescriberkey] to_columns: [prescriberkey]O adaptador expõe PHARMA.fact_pharma_JOIN_Prescriber com a união das colunas de ambas as tabelas. Usuários escrevem:
SELECT * FROM "Pharma Rx".fact_pharma_JOIN_Prescriber WHERE prescribername LIKE 'Dr%';e o Calcite empurra para baixo SELECT * FROM fact_pharma JOIN dim_prescriber ON fact_pharma.prescriberkey = dim_prescriber.prescriberkey WHERE prescribername LIKE 'Dr%' — sem overhead de runtime sobre escrever o JOIN à mão.
Relationships multi-coluna são suportadas: o predicado ON faz AND de cada par from_columns[i] = to_columns[i].
Relacionado
- Exportando para Apache Ossie — como produzir o arquivo YAML que este adaptador lê.
- Anotações semânticas do Saiku — as anotações que sobrevivem no YAML Ossie e (numa fatia futura) dirigem a resolução de metric/dimension no momento da query.
- Framework de adaptador do Apache Calcite — o planner subjacente.
- Repositório Apache Ossie — spec upstream.