SchemaRepositoryImpl.ts•8.44 kB
import { DatabaseSchema, TableInfo, ColumnInfo } from '../../domain/schema/entities/Schema.js';
import { DatabaseConnectionManager } from '../../database/connection/connection.manager.js';
import { Logger } from '../../core/logger.js';
import { SchemaRepository } from '../../domain/schema/services/SchemaDomainService.js';
import { SchemaStatistics } from '../../application/use-cases/GetSchemaStatisticsUseCase.js';
export class SchemaRepositoryImpl implements SchemaRepository {
  private schemaCache: Map<string, DatabaseSchema> = new Map();
  constructor(
    private readonly dbManager: DatabaseConnectionManager,
    private readonly logger: Logger
  ) {}
  async getSchema(databaseName: string): Promise<DatabaseSchema> {
    try {
      // Get current database name if not provided
      if (!databaseName || databaseName === 'default') {
        const connection = this.dbManager.getCurrentConnection();
        const dbResult = await connection.executeQuery('SELECT DB_NAME() as dbName');
        databaseName = dbResult.recordset?.[0]?.dbName || 'WMS';
      }
      // Check cache first
      const cacheKey = databaseName;
      const cachedSchema = this.schemaCache.get(cacheKey);
      if (cachedSchema) {
        this.logger.info('Returning cached schema', { databaseName });
        return cachedSchema;
      }
      // If not in cache, refresh and return
      return await this.refreshSchema(databaseName);
    } catch (error) {
      this.logger.error('Failed to get schema', { error });
      throw error;
    }
  }
  async getTables(pattern?: string): Promise<any[]> {
    try {
      const connection = this.dbManager.getCurrentConnection();
      let query = `
        SELECT 
          TABLE_NAME as name,
          TABLE_SCHEMA as tableSchema,
          TABLE_TYPE as type
        FROM INFORMATION_SCHEMA.TABLES
        WHERE TABLE_TYPE = 'BASE TABLE'
      `;
      if (pattern) {
        query += ` AND TABLE_NAME LIKE '%${pattern}%'`;
      }
      const result = await connection.executeQuery(query);
      return result.recordset || [];
    } catch (error) {
      this.logger.error('Failed to get tables', { error });
      throw error;
    }
  }
  async getTableInfo(tableName: string): Promise<any> {
    try {
      this.logger.info('Getting table info', { tableName });
      const adapter = this.dbManager.getCurrentConnection();
      const tableInfo = await adapter.getTableInfo(tableName);
      this.logger.info('Retrieved table info', {
        tableName,
        columnCount: tableInfo.columns?.length || 0,
        primaryKeyCount: tableInfo.primaryKeys?.length || 0,
      });
      return tableInfo;
    } catch (error) {
      this.logger.error('Failed to get table info', { tableName, error });
      throw error;
    }
  }
  async getSchemaStatistics(includeSystemTables: boolean = false): Promise<SchemaStatistics> {
    try {
      const connection = this.dbManager.getCurrentConnection();
      // Get table count
      let tableQuery = `
        SELECT COUNT(*) as count 
        FROM INFORMATION_SCHEMA.TABLES 
        WHERE TABLE_TYPE = 'BASE TABLE'
      `;
      if (!includeSystemTables) {
        tableQuery += ` AND TABLE_SCHEMA != 'sys'`;
      }
      const tableResult = await connection.executeQuery(tableQuery);
      const totalTables = tableResult.recordset?.[0]?.count || 0;
      // Get view count
      const viewResult = await connection.executeQuery(`
        SELECT COUNT(*) as count 
        FROM INFORMATION_SCHEMA.VIEWS
      `);
      const totalViews = viewResult.recordset?.[0]?.count || 0;
      // Get procedure count
      const procResult = await connection.executeQuery(`
        SELECT COUNT(*) as count 
        FROM INFORMATION_SCHEMA.ROUTINES 
        WHERE ROUTINE_TYPE = 'PROCEDURE'
      `);
      const totalProcedures = procResult.recordset?.[0]?.count || 0;
      // Get function count
      const funcResult = await connection.executeQuery(`
        SELECT COUNT(*) as count 
        FROM INFORMATION_SCHEMA.ROUTINES 
        WHERE ROUTINE_TYPE = 'FUNCTION'
      `);
      const totalFunctions = funcResult.recordset?.[0]?.count || 0;
      // Get column count
      const columnResult = await connection.executeQuery(`
        SELECT COUNT(*) as count 
        FROM INFORMATION_SCHEMA.COLUMNS
      `);
      const totalColumns = columnResult.recordset?.[0]?.count || 0;
      // Get index count (approximate)
      const indexResult = await connection.executeQuery(`
        SELECT COUNT(*) as count 
        FROM sys.indexes 
        WHERE type > 0
      `);
      const totalIndexes = indexResult.recordset?.[0]?.count || 0;
      // Calculate average columns per table
      const averageColumnsPerTable = totalTables > 0 ? totalColumns / totalTables : 0;
      // Get largest table by column count
      const largestTableResult = await connection.executeQuery(`
        SELECT TOP 1 
          t.TABLE_NAME as name,
          t.TABLE_TYPE as type,
          COUNT(c.COLUMN_NAME) as columnCount
        FROM INFORMATION_SCHEMA.TABLES t
        LEFT JOIN INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME
        WHERE t.TABLE_TYPE = 'BASE TABLE'
        GROUP BY t.TABLE_NAME, t.TABLE_TYPE
        ORDER BY COUNT(c.COLUMN_NAME) DESC
      `);
      const largestTable = largestTableResult.recordset?.[0]
        ? {
            name: largestTableResult.recordset[0].name,
            type: largestTableResult.recordset[0].type,
            columns: [], // 실제 컬럼 정보는 별도 조회 필요
          }
        : undefined;
      return {
        totalTables,
        totalViews,
        totalProcedures,
        totalFunctions,
        totalColumns,
        totalIndexes,
        averageColumnsPerTable: Math.round(averageColumnsPerTable * 100) / 100,
        largestTable,
      };
    } catch (error) {
      this.logger.error('Failed to get schema statistics', { error });
      throw error;
    }
  }
  async refreshSchema(databaseName: string): Promise<DatabaseSchema> {
    this.logger.info('Refreshing schema from database', { databaseName });
    try {
      const adapter = this.dbManager.getCurrentConnection();
      const tables = await adapter.getTables();
      const schema = new DatabaseSchema(databaseName);
      // Add tables to schema
      if (tables && tables.length > 0) {
        this.logger.info('Processing tables', { tableCount: tables.length });
        for (const table of tables) {
          try {
            const tableInfo: TableInfo = {
              name: table.name,
              schema: table.schema,
              type: table.type === 'system_table' ? 'table' : table.type,
            };
            schema.addTable(tableInfo);
            // Get columns using adapter's getTableInfo method
            try {
              const detailedTableInfo = await adapter.getTableInfo(table.name);
              if (detailedTableInfo.columns) {
                const columns: ColumnInfo[] = detailedTableInfo.columns.map((col: any) => ({
                  name: col.name,
                  type: col.dataType || col.type,
                  nullable: col.isNullable,
                  defaultValue: col.defaultValue,
                  isPrimaryKey: col.isPrimaryKey || false,
                  isForeignKey: col.isForeignKey || false,
                }));
                schema.addColumns(table.name, columns);
              }
            } catch (columnError) {
              this.logger.warn('Failed to get columns for table', {
                tableName: table.name,
                error: columnError,
              });
            }
          } catch (tableError) {
            this.logger.error('Failed to process table', { table, error: tableError });
          }
        }
      } else {
        this.logger.warn('No tables found from adapter', {
          tableCount: tables.length,
        });
      }
      // Cache the schema
      this.schemaCache.set(databaseName, schema);
      this.logger.info('Schema refreshed successfully', {
        databaseName,
        tableCount: schema.getTableCount(),
      });
      return schema;
    } catch (error) {
      this.logger.error('Failed to refresh schema', {
        databaseName,
        error: error instanceof Error ? error.message : String(error),
      });
      throw error;
    }
  }
  clearCache(): void {
    this.schemaCache.clear();
    this.logger.info('Schema cache cleared');
  }
}