QueryDomainService.ts•5.87 kB
import { QueryRepository } from '../repositories/QueryRepository.js';
import { QueryExecutor } from '../repositories/QueryExecutor.js';
import { Query, QueryResult } from '../entities/Query.js';
import { QueryId } from '../value-objects/QueryId.js';
import { QueryString } from '../value-objects/QueryString.js';
import { QueryExecutionError } from '../errors/QueryExecutionError.js';
import { QueryExecutedEvent } from '../events/QueryExecutedEvent.js';
import { EventBus } from '../../../infrastructure/events/EventBus.js';
import { EventTypes } from '../../../infrastructure/events/EventTypes.js';
/**
 * QueryDomainService
 *
 * Domain service for query-related operations.
 * Implements domain logic for query creation, validation, and execution.
 */
export class QueryDomainService {
  private eventBus: EventBus;
  constructor(
    private readonly queryRepository: QueryRepository,
    private readonly queryExecutor: QueryExecutor
  ) {
    this.eventBus = EventBus.getInstance();
  }
  /**
   * Creates a new query with validation.
   * @param sql The SQL query string
   * @param parameters Query parameters
   * @returns Promise with the query ID
   */
  async createQuery(sql: string, parameters: any[] = []): Promise<{ id: string }> {
    try {
      // Create value objects
      const queryString = new QueryString(sql);
      const queryId = new QueryId();
      // Create and validate query entity
      const query = new Query(queryId, queryString, parameters);
      if (!query.validate()) {
        throw new Error('Invalid query');
      }
      // Store query
      await this.queryRepository.save(query);
      return { id: queryId.value };
    } catch (error) {
      throw error instanceof Error ? error : new Error('Failed to create query');
    }
  }
  /**
   * Executes a query by ID.
   * @param queryId The ID of the query to execute
   * @returns Promise with query result
   */
  async executeQuery(queryId: string): Promise<QueryResult> {
    try {
      // Get query from repository
      const query = await this.queryRepository.findById(new QueryId(queryId));
      if (!query) {
        throw new Error(`Query with ID ${queryId} not found`);
      }
      // Execute query
      const startTime = Date.now();
      const result = await this.queryExecutor.execute(query.sql, query.parameters);
      const executionTime = Date.now() - startTime;
      // Update query statistics
      query.updateStatistics(executionTime);
      await this.queryRepository.save(query);
      // Create result with execution time
      const queryResult: QueryResult = {
        rows: result.rows,
        rowCount: result.rowCount,
        executionTime,
        columns: result.columns,
      };
      // Publish query executed event
      const event = new QueryExecutedEvent(
        queryId,
        query.sql,
        executionTime,
        result.rowCount,
        true
      );
      this.eventBus.publish(EventTypes.QUERY_EXECUTED, event);
      // Check if query is slow and publish slow query event if needed
      if (event.isSlow()) {
        this.eventBus.publish(EventTypes.QUERY_SLOW, event);
      }
      return queryResult;
    } catch (error) {
      // Create and publish query failed event
      const failedEvent = new QueryExecutedEvent(
        queryId,
        '',
        0,
        0,
        false,
        error instanceof Error ? error.message : 'Unknown error'
      );
      this.eventBus.publish(EventTypes.QUERY_FAILED, failedEvent);
      // Throw domain-specific error
      throw new QueryExecutionError(
        queryId,
        error instanceof Error ? error : new Error(String(error))
      );
    }
  }
  /**
   * Gets query statistics by ID.
   * @param queryId The ID of the query
   * @returns Promise with query statistics
   */
  async getQueryStatistics(queryId: string): Promise<any> {
    const query = await this.queryRepository.findById(new QueryId(queryId));
    if (!query) {
      throw new Error(`Query with ID ${queryId} not found`);
    }
    return query.statistics;
  }
  async executeBatchQueries(queryIds: QueryId[]): Promise<QueryResult[]> {
    const queries = await Promise.all(queryIds.map(id => this.queryRepository.findById(id)));
    const validQueries = queries.filter((q): q is Query => q !== null);
    if (validQueries.length !== queryIds.length) {
      throw new Error('Some queries not found');
    }
    const startTime = Date.now();
    const results = await this.queryExecutor.executeBatch(validQueries.map(q => q.sql));
    const totalExecutionTime = Date.now() - startTime;
    // Update statistics for each query
    const avgExecutionTime = totalExecutionTime / validQueries.length;
    await Promise.all(
      validQueries.map(async query => {
        query.updateStatistics(avgExecutionTime);
        await this.queryRepository.save(query);
      })
    );
    return results;
  }
  async optimizeQuery(queryId: QueryId): Promise<string> {
    const query = await this.queryRepository.findById(queryId);
    if (!query) {
      throw new Error(`Query with id ${queryId.value} not found`);
    }
    // Basic query optimization suggestions
    const sql = query.sql.toLowerCase();
    const suggestions: string[] = [];
    if (sql.includes('select *')) {
      suggestions.push('Consider selecting only needed columns instead of using SELECT *');
    }
    if (sql.includes('where') && !sql.includes('index')) {
      suggestions.push('Consider adding indexes for WHERE clause columns');
    }
    if (sql.includes('order by') && !sql.includes('limit')) {
      suggestions.push('Consider adding LIMIT clause when using ORDER BY');
    }
    return suggestions.join('; ') || 'Query appears to be optimized';
  }
  private generateQueryId(): string {
    return `query_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}