Skip to content

Commit

Permalink
fix(server-core): Handle empty query in getSqlGenerator (#9270)
Browse files Browse the repository at this point in the history
api-gateway calls getSqlGenerator with empty query and concrete data source to initialize SQL API
But because query is empty `sqlGenerator.dataSource` can be undefined, and it would trigger re-creating query with new data source, which would be `default`
  • Loading branch information
mcheshkov authored Feb 26, 2025
1 parent 5c47335 commit 350a438
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 11 deletions.
29 changes: 18 additions & 11 deletions packages/cubejs-server-core/src/core/CompilerApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,26 @@ export class CompilerApi {
throw new Error(`Unknown dbType: ${dbType}`);
}

// sqlGenerator.dataSource can return undefined for query without members
// Queries like this are used by api-gateway to initialize SQL API
// At the same time, those queries should use concrete dataSource, so we should be good to go with it
dataSource = compilers.compiler.withQuery(sqlGenerator, () => sqlGenerator.dataSource);
const _dbType = await this.getDbType(dataSource);
if (dataSource !== 'default' && dbType !== _dbType) {
// TODO consider more efficient way than instantiating query
sqlGenerator = await this.createQueryByDataSource(
compilers,
query,
dataSource,
_dbType
);
if (dataSource !== undefined) {
const _dbType = await this.getDbType(dataSource);
if (dataSource !== 'default' && dbType !== _dbType) {
// TODO consider more efficient way than instantiating query
sqlGenerator = await this.createQueryByDataSource(
compilers,
query,
dataSource,
_dbType
);

if (!sqlGenerator) {
throw new Error(`Can't find dialect for '${dataSource}' data source: ${_dbType}`);
if (!sqlGenerator) {
throw new Error(
`Can't find dialect for '${dataSource}' data source: ${_dbType}`
);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`multidb SQL pushdown queries to different data sources: Products 1`] = `
Array [
Object {
"name": "apples",
},
]
`;

exports[`multidb SQL pushdown queries to different data sources: Suppliers 1`] = `
Array [
Object {
"company": "Fruits Inc",
},
]
`;

exports[`multidb query: query 1`] = `
Array [
Object {
Expand Down
64 changes: 64 additions & 0 deletions packages/cubejs-testing/test/smoke-multidb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MysqlDBRunner, PostgresDBRunner } from '@cubejs-backend/testing-shared'
import cubejs, { CubeApi } from '@cubejs-client/core';
// eslint-disable-next-line import/no-extraneous-dependencies
import { afterAll, beforeAll, expect, jest } from '@jest/globals';
import { Client as PgClient } from 'pg';
import { BirdBox, getBirdbox } from '../src';
import {
DEFAULT_API_TOKEN,
Expand All @@ -11,12 +12,43 @@ import {
JEST_BEFORE_ALL_DEFAULT_TIMEOUT,
} from './smoke-tests';

// TODO: Random port?
const pgPort = 5656;
let connectionId = 0;

async function createPostgresClient(user: string, password: string) {
connectionId++;
const currentConnId = connectionId;

console.debug(`[pg] new connection ${currentConnId}`);

const conn = new PgClient({
database: 'db',
port: pgPort,
host: '127.0.0.1',
user,
password,
ssl: false,
});
conn.on('error', (err) => {
console.log(err);
});
conn.on('end', () => {
console.debug(`[pg] end ${currentConnId}`);
});

await conn.connect();

return conn;
}

describe('multidb', () => {
jest.setTimeout(60 * 5 * 1000);
let db: StartedTestContainer;
let db2: StartedTestContainer;
let birdbox: BirdBox;
let client: CubeApi;
let connection: PgClient;

beforeAll(async () => {
db = await PostgresDBRunner.startContainer({});
Expand All @@ -39,6 +71,9 @@ describe('multidb', () => {
CUBEJS_DB_USER2: 'root',
CUBEJS_DB_PASS2: 'Test1test',

CUBEJS_PG_SQL_PORT: `${pgPort}`,
CUBESQL_SQL_PUSH_DOWN: 'true',

...DEFAULT_CONFIG,
},
{
Expand All @@ -49,6 +84,7 @@ describe('multidb', () => {
client = cubejs(async () => DEFAULT_API_TOKEN, {
apiUrl: birdbox.configuration.apiUrl,
});
connection = await createPostgresClient('admin', 'admin_password');
}, JEST_BEFORE_ALL_DEFAULT_TIMEOUT);

afterAll(async () => {
Expand All @@ -69,4 +105,32 @@ describe('multidb', () => {
});
expect(response.rawData()).toMatchSnapshot('query');
});

test('SQL pushdown queries to different data sources: Products', async () => {
const res = await connection.query(`
SELECT
name
FROM
Products
WHERE
LOWER(name) = 'apples'
GROUP BY
1
`);
expect(res.rows).toMatchSnapshot();
});

test('SQL pushdown queries to different data sources: Suppliers', async () => {
const res = await connection.query(`
SELECT
company
FROM
Suppliers
WHERE
LOWER(company) = 'fruits inc'
GROUP BY
1
`);
expect(res.rows).toMatchSnapshot();
});
});

0 comments on commit 350a438

Please sign in to comment.