Skip to main content
Glama
SchemaAnalyzer.test.js13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const Column_1 = require("../entities/Column"); const ForeignKey_1 = require("../entities/ForeignKey"); const Index_1 = require("../entities/Index"); const TableInfo_1 = require("../entities/TableInfo"); const SchemaAnalyzer_1 = require("./SchemaAnalyzer"); (0, vitest_1.describe)('SchemaAnalyzer', () => { let analyzer; let usersTable; let postsTable; let logsTable; let userView; (0, vitest_1.beforeEach)(() => { analyzer = new SchemaAnalyzer_1.SchemaAnalyzer(); // Users table - well-designed with PK usersTable = new TableInfo_1.TableInfo('users', 'table', [ new Column_1.Column('id', 'INTEGER', true, false), new Column_1.Column('name', 'TEXT', false, false), new Column_1.Column('email', 'TEXT', false, true), ], [new Index_1.Index('idx_users_email', 'users', ['email'], true)]); // Posts table - has FK and index postsTable = new TableInfo_1.TableInfo('posts', 'table', [ new Column_1.Column('id', 'INTEGER', true, false), new Column_1.Column('user_id', 'INTEGER', false, false), new Column_1.Column('title', 'TEXT', false, false), new Column_1.Column('content', 'TEXT', false, true), ], [new Index_1.Index('idx_posts_user_id', 'posts', ['user_id'])], [new ForeignKey_1.ForeignKey('posts', 'user_id', 'users', 'id', 'CASCADE')]); // Logs table - no PK (problematic) logsTable = new TableInfo_1.TableInfo('logs', 'table', [ new Column_1.Column('timestamp', 'TEXT', false, true), new Column_1.Column('message', 'TEXT', false, true), ]); // User view userView = new TableInfo_1.TableInfo('user_view', 'view', [ new Column_1.Column('id', 'INTEGER'), new Column_1.Column('name', 'TEXT'), ]); }); (0, vitest_1.describe)('analyzeSchema()', () => { (0, vitest_1.it)('should analyze overall schema statistics', () => { const result = analyzer.analyzeSchema([usersTable, postsTable, logsTable, userView]); (0, vitest_1.expect)(result.totalTables).toBe(4); (0, vitest_1.expect)(result.totalColumns).toBe(11); (0, vitest_1.expect)(result.tablesWithPrimaryKeys).toBe(2); // users, posts (0, vitest_1.expect)(result.tablesWithoutPrimaryKeys).toBe(2); // logs, user_view (0, vitest_1.expect)(result.tablesWithForeignKeys).toBe(1); // posts (0, vitest_1.expect)(result.tablesWithIndexes).toBe(2); // users, posts (0, vitest_1.expect)(result.totalIndexes).toBe(2); (0, vitest_1.expect)(result.totalForeignKeys).toBe(1); (0, vitest_1.expect)(result.totalViews).toBe(1); }); (0, vitest_1.it)('should handle empty table array', () => { const result = analyzer.analyzeSchema([]); (0, vitest_1.expect)(result.totalTables).toBe(0); (0, vitest_1.expect)(result.totalColumns).toBe(0); (0, vitest_1.expect)(result.tablesWithPrimaryKeys).toBe(0); }); (0, vitest_1.it)('should count all columns correctly', () => { const result = analyzer.analyzeSchema([usersTable, postsTable]); (0, vitest_1.expect)(result.totalColumns).toBe(7); // 3 + 4 }); }); (0, vitest_1.describe)('analyzeTable()', () => { (0, vitest_1.it)('should analyze table with primary key', () => { const analysis = analyzer.analyzeTable(usersTable); (0, vitest_1.expect)(analysis.tableName).toBe('users'); (0, vitest_1.expect)(analysis.columnCount).toBe(3); (0, vitest_1.expect)(analysis.hasPrimaryKey).toBe(true); (0, vitest_1.expect)(analysis.primaryKeyColumns).toEqual(['id']); (0, vitest_1.expect)(analysis.requiredColumns).toEqual(['id', 'name']); (0, vitest_1.expect)(analysis.foreignKeyCount).toBe(0); (0, vitest_1.expect)(analysis.indexCount).toBe(1); (0, vitest_1.expect)(analysis.isView).toBe(false); }); (0, vitest_1.it)('should analyze table without primary key', () => { const analysis = analyzer.analyzeTable(logsTable); (0, vitest_1.expect)(analysis.hasPrimaryKey).toBe(false); (0, vitest_1.expect)(analysis.primaryKeyColumns).toEqual([]); }); (0, vitest_1.it)('should analyze table with foreign keys', () => { const analysis = analyzer.analyzeTable(postsTable); (0, vitest_1.expect)(analysis.foreignKeyCount).toBe(1); (0, vitest_1.expect)(analysis.hasPrimaryKey).toBe(true); }); (0, vitest_1.it)('should identify views', () => { const analysis = analyzer.analyzeTable(userView); (0, vitest_1.expect)(analysis.isView).toBe(true); }); }); (0, vitest_1.describe)('analyzeColumnTypes()', () => { (0, vitest_1.it)('should count column type distribution', () => { const typeDistribution = analyzer.analyzeColumnTypes([usersTable, postsTable]); (0, vitest_1.expect)(typeDistribution.get('INTEGER')).toBe(3); // id columns (0, vitest_1.expect)(typeDistribution.get('TEXT')).toBe(4); // name, email, title, content }); (0, vitest_1.it)('should handle empty tables', () => { const typeDistribution = analyzer.analyzeColumnTypes([]); (0, vitest_1.expect)(typeDistribution.size).toBe(0); }); (0, vitest_1.it)('should aggregate types across multiple tables', () => { const table1 = new TableInfo_1.TableInfo('t1', 'table', [ new Column_1.Column('id', 'INTEGER', true), new Column_1.Column('name', 'TEXT'), ]); const table2 = new TableInfo_1.TableInfo('t2', 'table', [ new Column_1.Column('id', 'INTEGER', true), new Column_1.Column('value', 'REAL'), ]); const typeDistribution = analyzer.analyzeColumnTypes([table1, table2]); (0, vitest_1.expect)(typeDistribution.get('INTEGER')).toBe(2); (0, vitest_1.expect)(typeDistribution.get('TEXT')).toBe(1); (0, vitest_1.expect)(typeDistribution.get('REAL')).toBe(1); }); }); (0, vitest_1.describe)('identifyProblematicTables()', () => { (0, vitest_1.it)('should identify tables without primary keys', () => { const problematic = analyzer.identifyProblematicTables([ usersTable, postsTable, logsTable, ]); (0, vitest_1.expect)(problematic.length).toBe(1); (0, vitest_1.expect)(problematic[0].name).toBe('logs'); }); (0, vitest_1.it)('should identify tables with unindexed foreign keys', () => { const badTable = new TableInfo_1.TableInfo('comments', 'table', [ new Column_1.Column('id', 'INTEGER', true), new Column_1.Column('post_id', 'INTEGER'), new Column_1.Column('text', 'TEXT'), ], [], // No indexes! [new ForeignKey_1.ForeignKey('comments', 'post_id', 'posts', 'id')]); const problematic = analyzer.identifyProblematicTables([usersTable, badTable]); (0, vitest_1.expect)(problematic.length).toBe(1); (0, vitest_1.expect)(problematic[0].name).toBe('comments'); }); (0, vitest_1.it)('should return empty array for well-designed schema', () => { const problematic = analyzer.identifyProblematicTables([usersTable, postsTable]); (0, vitest_1.expect)(problematic.length).toBe(0); }); (0, vitest_1.it)('should not flag views as problematic', () => { const problematic = analyzer.identifyProblematicTables([userView]); // Views without PKs are acceptable (0, vitest_1.expect)(problematic.length).toBe(0); }); }); (0, vitest_1.describe)('analyzeNullability()', () => { (0, vitest_1.it)('should analyze nullable columns', () => { const analysis = analyzer.analyzeNullability(usersTable); (0, vitest_1.expect)(analysis.nullableColumns.length).toBe(1); // email (0, vitest_1.expect)(analysis.nullableColumns[0].name).toBe('email'); (0, vitest_1.expect)(analysis.requiredColumns.length).toBe(2); // id, name (0, vitest_1.expect)(analysis.nullablePercentage).toBeCloseTo(33.33, 1); }); (0, vitest_1.it)('should handle all required columns', () => { const strictTable = new TableInfo_1.TableInfo('strict', 'table', [ new Column_1.Column('id', 'INTEGER', true, false), new Column_1.Column('name', 'TEXT', false, false), ]); const analysis = analyzer.analyzeNullability(strictTable); (0, vitest_1.expect)(analysis.nullableColumns.length).toBe(0); (0, vitest_1.expect)(analysis.requiredColumns.length).toBe(2); (0, vitest_1.expect)(analysis.nullablePercentage).toBe(0); }); (0, vitest_1.it)('should handle all nullable columns', () => { const analysis = analyzer.analyzeNullability(logsTable); (0, vitest_1.expect)(analysis.nullableColumns.length).toBe(2); (0, vitest_1.expect)(analysis.requiredColumns.length).toBe(0); (0, vitest_1.expect)(analysis.nullablePercentage).toBe(100); }); }); (0, vitest_1.describe)('getTablesByComplexity()', () => { (0, vitest_1.it)('should sort tables by complexity', () => { const sorted = analyzer.getTablesByComplexity([usersTable, postsTable, logsTable]); // posts: 4 columns + 1 index + 1 FK = 6 // users: 3 columns + 1 index + 0 FK = 4 // logs: 2 columns + 0 index + 0 FK = 2 (0, vitest_1.expect)(sorted[0].table.name).toBe('posts'); (0, vitest_1.expect)(sorted[0].complexity).toBe(6); (0, vitest_1.expect)(sorted[1].table.name).toBe('users'); (0, vitest_1.expect)(sorted[1].complexity).toBe(4); (0, vitest_1.expect)(sorted[2].table.name).toBe('logs'); (0, vitest_1.expect)(sorted[2].complexity).toBe(2); }); (0, vitest_1.it)('should handle single table', () => { const sorted = analyzer.getTablesByComplexity([usersTable]); (0, vitest_1.expect)(sorted.length).toBe(1); (0, vitest_1.expect)(sorted[0].table.name).toBe('users'); }); (0, vitest_1.it)('should handle empty array', () => { const sorted = analyzer.getTablesByComplexity([]); (0, vitest_1.expect)(sorted.length).toBe(0); }); }); (0, vitest_1.describe)('identifyOrphanedTables()', () => { (0, vitest_1.it)('should identify tables with no relationships', () => { const orphaned = analyzer.identifyOrphanedTables([usersTable, postsTable, logsTable]); (0, vitest_1.expect)(orphaned.length).toBe(1); (0, vitest_1.expect)(orphaned[0].name).toBe('logs'); }); (0, vitest_1.it)('should not flag tables with incoming foreign keys', () => { // users is referenced by posts, so not orphaned const orphaned = analyzer.identifyOrphanedTables([usersTable, postsTable]); (0, vitest_1.expect)(orphaned.length).toBe(0); }); (0, vitest_1.it)('should not flag tables with outgoing foreign keys', () => { // posts references users, so not orphaned const orphaned = analyzer.identifyOrphanedTables([postsTable]); (0, vitest_1.expect)(orphaned.length).toBe(0); }); (0, vitest_1.it)('should not flag views as orphaned', () => { const orphaned = analyzer.identifyOrphanedTables([userView]); (0, vitest_1.expect)(orphaned.length).toBe(0); }); (0, vitest_1.it)('should handle complex relationship networks', () => { const commentsTable = new TableInfo_1.TableInfo('comments', 'table', [new Column_1.Column('id', 'INTEGER', true), new Column_1.Column('post_id', 'INTEGER')], [], [new ForeignKey_1.ForeignKey('comments', 'post_id', 'posts', 'id')]); const tagsTable = new TableInfo_1.TableInfo('tags', 'table', [ new Column_1.Column('id', 'INTEGER', true), new Column_1.Column('name', 'TEXT'), ]); const orphaned = analyzer.identifyOrphanedTables([ usersTable, postsTable, commentsTable, tagsTable, ]); // Only tags is orphaned (no FK in/out) (0, vitest_1.expect)(orphaned.length).toBe(1); (0, vitest_1.expect)(orphaned[0].name).toBe('tags'); }); }); }); //# sourceMappingURL=SchemaAnalyzer.test.js.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/semanticintent/semantic-d1-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server