mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
* feat(mysql): implement columnStats using INFORMATION_SCHEMA.STATISTICS Enable column cardinality statistics for the MySQL connector by querying INFORMATION_SCHEMA.STATISTICS, which provides index-based cardinality estimates without requiring additional permissions. - Add generateColumnStatisticsQuery() to KtxMysqlDialect - Add getColumnStatistics() and columnStats() to KtxMysqlScanConnector - Flip columnStats capability from false to true - Add MysqlStatsRow and KtxMysqlColumnStatisticsResult interfaces - Add tests for dialect query generation and connector stats retrieval - Update dialect conformance fixture for mysql * fix(mysql): filter to leading index columns to avoid inflated cardinality Add AND SEQ_IN_INDEX = 1 to INFORMATION_SCHEMA.STATISTICS query to ensure only leading index columns are returned. For composite indexes, non-leading columns report the cardinality of the index prefix rather than the column's own distinct count, which inflates distinctCount. Add regression test asserting SEQ_IN_INDEX = 1 is present in the query. * fix: add trailing newline to dialect.test.ts --------- Co-authored-by: Andrey Avtomonov <andreybavt@gmail.com>
61 lines
2.9 KiB
TypeScript
61 lines
2.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { KtxMysqlDialect } from '../../../src/connectors/mysql/dialect.js';
|
|
|
|
describe('KtxMysqlDialect', () => {
|
|
const dialect = new KtxMysqlDialect();
|
|
|
|
it('quotes identifiers and formats database-qualified table names', () => {
|
|
expect(dialect.quoteIdentifier('orders')).toBe('`orders`');
|
|
expect(dialect.quoteIdentifier('odd`name')).toBe('`odd``name`');
|
|
expect(dialect.formatTableName({ catalog: null, db: 'analytics', name: 'orders' })).toBe(
|
|
'`analytics`.`orders`',
|
|
);
|
|
expect(dialect.formatTableName({ catalog: null, db: null, name: 'orders' })).toBe('`orders`');
|
|
});
|
|
|
|
it('maps native MySQL types to KTX dimension types', () => {
|
|
expect(dialect.mapToDimensionType('tinyint(1)')).toBe('boolean');
|
|
expect(dialect.mapToDimensionType('int')).toBe('number');
|
|
expect(dialect.mapToDimensionType('decimal(10,2)')).toBe('number');
|
|
expect(dialect.mapToDimensionType('timestamp')).toBe('time');
|
|
expect(dialect.mapToDimensionType('varchar(255)')).toBe('string');
|
|
expect(dialect.mapToDimensionType('json')).toBe('string');
|
|
expect(dialect.mapToDimensionType('')).toBe('string');
|
|
});
|
|
|
|
it('builds sampling, distinct-value, and pagination SQL', () => {
|
|
expect(dialect.generateSampleQuery('`analytics`.`orders`', 25, ['id', 'status'])).toBe(
|
|
'SELECT `id`, `status` FROM `analytics`.`orders` LIMIT 25',
|
|
);
|
|
expect(dialect.generateColumnSampleQuery('`analytics`.`orders`', 'status', 10)).toBe(
|
|
"SELECT `status` FROM `analytics`.`orders` WHERE `status` IS NOT NULL AND TRIM(CAST(`status` AS CHAR)) != '' LIMIT 10",
|
|
);
|
|
expect(dialect.generateDistinctValuesQuery('`analytics`.`orders`', '`status`', 5)).toContain(
|
|
'SELECT DISTINCT CAST(`status` AS CHAR) AS val',
|
|
);
|
|
expect(dialect.getLimitOffsetClause(10, 20)).toBe('LIMIT 10 OFFSET 20');
|
|
});
|
|
|
|
|
|
it('generates column statistics query using INFORMATION_SCHEMA.STATISTICS', () => {
|
|
const sql = dialect.generateColumnStatisticsQuery('analytics', 'orders');
|
|
expect(sql).not.toBeNull();
|
|
expect(sql).toContain('INFORMATION_SCHEMA.STATISTICS');
|
|
expect(sql).toContain("TABLE_SCHEMA = 'analytics'");
|
|
expect(sql).toContain("TABLE_NAME = 'orders'");
|
|
expect(sql).toContain('CARDINALITY IS NOT NULL');
|
|
expect(sql).toContain('column_name');
|
|
expect(sql).toContain('estimated_cardinality');
|
|
});
|
|
|
|
it('filters to leading index columns only (SEQ_IN_INDEX = 1) to avoid inflated cardinality from composite indexes', () => {
|
|
const sql = dialect.generateColumnStatisticsQuery('analytics', 'orders');
|
|
expect(sql).toContain('SEQ_IN_INDEX = 1');
|
|
});
|
|
|
|
it('escapes single quotes in schema and table names for statistics query', () => {
|
|
const sql = dialect.generateColumnStatisticsQuery("andy's_db", "o'rders");
|
|
expect(sql).toContain("TABLE_SCHEMA = 'andy''s_db'");
|
|
expect(sql).toContain("TABLE_NAME = 'o''rders'");
|
|
});
|
|
});
|