feat(athena): first-class AWS Athena warehouse identity (SL + BI mapping) (#332)

* feat: support Athena warehouse identity

* fix: support Athena and DuckDB BI table parsing
This commit is contained in:
Andrey Avtomonov 2026-07-02 15:10:29 +02:00 committed by GitHub
parent fe7e6bd1fa
commit f310391da5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 252 additions and 45 deletions

View file

@ -960,6 +960,28 @@ describe('validateWithProposedSource', () => {
);
});
it('uses the Athena sqlglot dialect when validating Athena sources', async () => {
pythonPort.validateSources.mockResolvedValue({
data: { errors: [], warnings: [] },
});
service = new SemanticLayerService(configService as never, connectionCatalog('ATHENA'), pythonPort);
await service.validateWithProposedSource('conn-1', {
name: 'orders',
table: 'analytics.orders',
grain: ['id'],
columns: [{ name: 'id', type: 'number' }],
joins: [],
measures: [],
});
expect(pythonPort.validateSources).toHaveBeenCalledWith(
expect.objectContaining({
dialect: 'athena',
}),
);
});
it('composes a bare overlay with its manifest base before validating', async () => {
const schemaPath = 'semantic-layer/conn-1/_schema/core.yaml';
const listFilesImpl = (dir: string): Promise<{ files: string[] }> => {
@ -1326,6 +1348,44 @@ describe('validateWithProposedSource', () => {
});
});
describe('executeQuery dialect resolution', () => {
it('passes the Athena sqlglot dialect to the Python query engine for Athena connections', async () => {
pythonPort.query.mockResolvedValue({ data: { sql: 'SELECT * FROM orders' } });
const catalog = connectionCatalog('ATHENA');
catalog.executeQuery.mockResolvedValue({ headers: ['id'], rows: [[1]], totalRows: 1 });
const configService = {
listFiles: vi.fn().mockResolvedValue({
files: ['semantic-layer/conn-1/orders.yaml'],
}),
readFile: vi.fn().mockResolvedValue({
content: [
'name: orders',
'table: analytics.orders',
'grain:',
' - id',
'columns:',
' - name: id',
' type: number',
'joins: []',
'measures:',
' - name: count',
' expr: count(*)',
].join('\n'),
}),
};
const service = new SemanticLayerService(configService as never, catalog, pythonPort);
await service.executeQuery('conn-1', { measures: ['count'] } as never);
expect(pythonPort.query).toHaveBeenCalledWith(
expect.objectContaining({
dialect: 'athena',
}),
);
expect(catalog.executeQuery).toHaveBeenCalledWith('conn-1', 'SELECT * FROM orders');
});
});
describe('findDanglingSegmentRefs', () => {
it('returns empty when every measure segment resolves', () => {
const source = {