ktx/packages/cli/test/connectors/mysql/dialect.test.ts
Andrey Avtomonov 00cdf2de90
refactor: enforce ktx naming and AGENTS.md compliance sweep (#289)
Align the tree with AGENTS.md/CLAUDE.md conventions:

- Rewrite user-facing strings, docs, and tests to lowercase `ktx`
  (no bare uppercase `KTX` tokens remain outside literal identifiers).
- Drop the legacy `historicSql` migration path and its now-unused
  helpers, per the no-backward-compat rule.
- Remove `as unknown as` / `any` casts: narrow `BaseTool` generics to
  `z.ZodObject`, add a typed `createLookerClient`, and delete the dead
  `getParametersSchema`/`toAnthropicFormat` pre-AI-SDK helpers.
- Use `InvalidArgumentError` for Commander parse failures.
- Finish the adapter→connector prose conversion in the `ktx.yaml` docs
  while keeping the literal `adapters` config key.
2026-06-11 13:49:45 +02:00

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