using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Erp.SqlQueryManager.Application.Contracts; using Erp.SqlQueryManager.Domain.Entities; using Erp.SqlQueryManager.Domain.Services; using Erp.SqlQueryManager.Domain.Shared; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; namespace Erp.SqlQueryManager.Application; /// /// Unified service for SQL Object Explorer /// Combines all SQL objects into a single endpoint /// public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerAppService { private readonly IRepository _queryRepository; private readonly IRepository _procedureRepository; private readonly IRepository _viewRepository; private readonly IRepository _functionRepository; private readonly ISqlExecutorService _sqlExecutorService; private readonly ISqlTemplateProvider _templateProvider; public SqlObjectManagerAppService( IRepository queryRepository, IRepository procedureRepository, IRepository viewRepository, IRepository functionRepository, ISqlExecutorService sqlExecutorService, ISqlTemplateProvider templateProvider) { _queryRepository = queryRepository; _procedureRepository = procedureRepository; _viewRepository = viewRepository; _functionRepository = functionRepository; _sqlExecutorService = sqlExecutorService; _templateProvider = templateProvider; } public async Task GetAllObjectsAsync(string dataSourceCode) { var result = new SqlObjectExplorerDto(); // Get all queries for this data source var queries = await _queryRepository.GetListAsync(); result.Queries = queries .Where(q => q.DataSourceCode == dataSourceCode) .Select(q => new SqlQueryDto { Id = q.Id, Code = q.Code, Name = q.Name, Description = q.Description, QueryText = q.QueryText, DataSourceCode = q.DataSourceCode, Status = q.Status, Category = q.Category, Tags = q.Tags, IsModifyingData = q.IsModifyingData, Parameters = q.Parameters, ExecutionCount = q.ExecutionCount, LastExecutedAt = q.LastExecutedAt }) .ToList(); // Get all stored procedures for this data source var procedures = await _procedureRepository.GetListAsync(); result.StoredProcedures = procedures .Where(p => p.DataSourceCode == dataSourceCode) .Select(p => new SqlStoredProcedureDto { Id = p.Id, ProcedureName = p.ProcedureName, SchemaName = p.SchemaName, DisplayName = p.DisplayName, Description = p.Description, ProcedureBody = p.ProcedureBody, DataSourceCode = p.DataSourceCode, Category = p.Category, Parameters = p.Parameters, IsDeployed = p.IsDeployed, LastDeployedAt = p.LastDeployedAt }) .ToList(); // Get all views for this data source var views = await _viewRepository.GetListAsync(); result.Views = views .Where(v => v.DataSourceCode == dataSourceCode) .Select(v => new SqlViewDto { Id = v.Id, ViewName = v.ViewName, SchemaName = v.SchemaName, DisplayName = v.DisplayName, Description = v.Description, ViewDefinition = v.ViewDefinition, DataSourceCode = v.DataSourceCode, Category = v.Category, WithSchemaBinding = v.WithSchemaBinding, IsDeployed = v.IsDeployed, LastDeployedAt = v.LastDeployedAt }) .ToList(); // Get all functions for this data source var functions = await _functionRepository.GetListAsync(); result.Functions = functions .Where(f => f.DataSourceCode == dataSourceCode) .Select(f => new SqlFunctionDto { Id = f.Id, FunctionName = f.FunctionName, SchemaName = f.SchemaName, DisplayName = f.DisplayName, Description = f.Description, FunctionType = f.FunctionType, FunctionBody = f.FunctionBody, ReturnType = f.ReturnType, DataSourceCode = f.DataSourceCode, Category = f.Category, Parameters = f.Parameters, IsDeployed = f.IsDeployed, LastDeployedAt = f.LastDeployedAt }) .ToList(); // Get all database tables result.Tables = await GetTablesAsync(dataSourceCode); // Get all templates result.Templates = _templateProvider.GetAvailableQueryTemplates() .Select(t => new SqlTemplateDto { Type = t.Type, Name = t.Name, Description = t.Description, Template = _templateProvider.GetQueryTemplate(t.Type) }) .ToList(); return result; } private async Task> GetTablesAsync(string dataSourceCode) { var query = @" SELECT SCHEMA_NAME(t.schema_id) AS SchemaName, t.name AS TableName FROM sys.tables t WHERE t.is_ms_shipped = 0 ORDER BY SCHEMA_NAME(t.schema_id), t.name"; var result = await _sqlExecutorService.ExecuteQueryAsync(query, dataSourceCode); var tables = new List(); if (result.Success && result.Data != null) { foreach (var row in result.Data) { var dict = row as System.Collections.Generic.IDictionary; if (dict != null) { tables.Add(new DatabaseTableDto { SchemaName = dict["SchemaName"]?.ToString() ?? "dbo", TableName = dict["TableName"]?.ToString() ?? "" }); } } } return tables; } #region Query Operations public async Task CreateQueryAsync(CreateSqlQueryDto input) { var query = ObjectMapper.Map(input); query.Status = SqlQueryStatus.Draft; var created = await _queryRepository.InsertAsync(query, autoSave: true); return ObjectMapper.Map(created); } public async Task UpdateQueryAsync(Guid id, UpdateSqlQueryDto input) { var query = await _queryRepository.GetAsync(id); query.Name = input.Name; query.Description = input.Description; query.QueryText = input.QueryText; query.Category = input.Category; query.Tags = input.Tags; var updated = await _queryRepository.UpdateAsync(query, autoSave: true); return ObjectMapper.Map(updated); } public async Task DeleteQueryAsync(Guid id) { await _queryRepository.DeleteAsync(id); } public async Task ExecuteQueryAsync(ExecuteSqlQueryDto input) { var result = await _sqlExecutorService.ExecuteQueryAsync(input.QueryText, input.DataSourceCode); return MapExecutionResult(result); } public async Task ExecuteSavedQueryAsync(Guid id) { var query = await _queryRepository.GetAsync(id); var result = await _sqlExecutorService.ExecuteQueryAsync(query.QueryText, query.DataSourceCode); // Update execution statistics query.ExecutionCount++; query.LastExecutedAt = DateTime.UtcNow; await _queryRepository.UpdateAsync(query, autoSave: true); return MapExecutionResult(result); } #endregion #region Stored Procedure Operations public async Task UpdateStoredProcedureAsync(Guid id, UpdateSqlStoredProcedureDto input) { var procedure = await _procedureRepository.GetAsync(id); procedure.DisplayName = input.DisplayName; procedure.Description = input.Description; procedure.ProcedureBody = input.ProcedureBody; procedure.Category = input.Category; var updated = await _procedureRepository.UpdateAsync(procedure, autoSave: true); return ObjectMapper.Map(updated); } public async Task DeleteStoredProcedureAsync(Guid id) { await _procedureRepository.DeleteAsync(id); } public async Task DeployStoredProcedureAsync(DeployStoredProcedureDto input) { var procedure = await _procedureRepository.GetAsync(input.Id); var result = await _sqlExecutorService.DeployStoredProcedureAsync( procedure.ProcedureBody, procedure.DataSourceCode ); if (result.Success) { procedure.IsDeployed = true; procedure.LastDeployedAt = DateTime.UtcNow; await _procedureRepository.UpdateAsync(procedure, autoSave: true); } return MapExecutionResult(result); } #endregion #region View Operations public async Task UpdateViewAsync(Guid id, UpdateSqlViewDto input) { var view = await _viewRepository.GetAsync(id); view.DisplayName = input.DisplayName; view.Description = input.Description; view.ViewDefinition = input.ViewDefinition; view.Category = input.Category; var updated = await _viewRepository.UpdateAsync(view, autoSave: true); return ObjectMapper.Map(updated); } public async Task DeleteViewAsync(Guid id) { await _viewRepository.DeleteAsync(id); } public async Task DeployViewAsync(DeployViewDto input) { var view = await _viewRepository.GetAsync(input.Id); var result = await _sqlExecutorService.DeployViewAsync( view.ViewDefinition, view.DataSourceCode ); if (result.Success) { view.IsDeployed = true; view.LastDeployedAt = DateTime.UtcNow; await _viewRepository.UpdateAsync(view, autoSave: true); } return MapExecutionResult(result); } #endregion #region Function Operations public async Task UpdateFunctionAsync(Guid id, UpdateSqlFunctionDto input) { var function = await _functionRepository.GetAsync(id); function.DisplayName = input.DisplayName; function.Description = input.Description; function.FunctionBody = input.FunctionBody; function.Category = input.Category; var updated = await _functionRepository.UpdateAsync(function, autoSave: true); return ObjectMapper.Map(updated); } public async Task DeleteFunctionAsync(Guid id) { await _functionRepository.DeleteAsync(id); } public async Task DeployFunctionAsync(DeployFunctionDto input) { var function = await _functionRepository.GetAsync(input.Id); var result = await _sqlExecutorService.DeployFunctionAsync( function.FunctionBody, function.DataSourceCode ); if (result.Success) { function.IsDeployed = true; function.LastDeployedAt = DateTime.UtcNow; await _functionRepository.UpdateAsync(function, autoSave: true); } return MapExecutionResult(result); } #endregion #region Database Metadata Operations public async Task> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName) { var query = $@" SELECT c.name AS ColumnName, TYPE_NAME(c.user_type_id) AS DataType, c.is_nullable AS IsNullable, c.max_length AS MaxLength FROM sys.columns c INNER JOIN sys.tables t ON c.object_id = t.object_id INNER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE s.name = '{schemaName}' AND t.name = '{tableName}' ORDER BY c.column_id"; var result = await _sqlExecutorService.ExecuteQueryAsync(query, dataSourceCode); var columns = new List(); if (result.Success && result.Data != null) { foreach (var row in result.Data) { var dict = row as System.Collections.Generic.IDictionary; if (dict != null) { columns.Add(new DatabaseColumnDto { ColumnName = dict["ColumnName"]?.ToString() ?? "", DataType = dict["DataType"]?.ToString() ?? "", IsNullable = dict["IsNullable"] is bool b && b, MaxLength = dict["MaxLength"] != null ? int.Parse(dict["MaxLength"].ToString()) : null }); } } } return columns; } #endregion #region Helper Methods private SqlQueryExecutionResultDto MapExecutionResult(SqlExecutionResult result) { return new SqlQueryExecutionResultDto { Success = result.Success, Message = result.Message, Data = result.Data, RowsAffected = result.RowsAffected, ExecutionTimeMs = result.ExecutionTimeMs, Metadata = result.Metadata }; } #endregion }