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.Authorization;
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
///
[Authorize("App.SqlQueryManager")]
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 sqlText = input.QueryText.Trim();
var sqlUpper = sqlText.ToUpperInvariant();
// Check if this is a DDL command (CREATE/ALTER/DROP for VIEW/PROCEDURE/FUNCTION)
bool isDDLCommand =
sqlUpper.Contains("CREATE VIEW") || sqlUpper.Contains("ALTER VIEW") ||
sqlUpper.Contains("CREATE PROCEDURE") || sqlUpper.Contains("CREATE PROC") ||
sqlUpper.Contains("ALTER PROCEDURE") || sqlUpper.Contains("ALTER PROC") ||
sqlUpper.Contains("CREATE FUNCTION") || sqlUpper.Contains("ALTER FUNCTION") ||
sqlUpper.Contains("DROP VIEW") || sqlUpper.Contains("DROP PROCEDURE") ||
sqlUpper.Contains("DROP PROC") || sqlUpper.Contains("DROP FUNCTION");
if (isDDLCommand)
{
// For DDL commands, only validate syntax without executing
try
{
// Try to parse/validate the SQL using SET PARSEONLY
var validationSql = $"SET PARSEONLY ON;\n{sqlText}\nSET PARSEONLY OFF;";
await _sqlExecutorService.ExecuteNonQueryAsync(validationSql, input.DataSourceCode);
return new SqlQueryExecutionResultDto
{
Success = true,
Message = "SQL syntax is valid. Use Save button to save and Deploy button to create in SQL Server.",
Data = new List