sozsoft-platform/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs
2026-03-02 21:31:49 +03:00

254 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sozsoft.SqlQueryManager.Application.Contracts;
using Sozsoft.SqlQueryManager.Domain.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Application.Services;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.SqlQueryManager.Application;
/// <summary>
/// Executes T-SQL against configured data sources and exposes database metadata.
/// Does not persist SQL objects (queries, procedures, views, functions) to its own tables.
/// </summary>
[Authorize("App.SqlQueryManager")]
public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerAppService
{
private readonly ISqlExecutorService _sqlExecutorService;
private readonly ISqlTemplateProvider _templateProvider;
private readonly ICurrentTenant _currentTenant;
private readonly IHttpContextAccessor _httpContextAccessor;
public SqlObjectManagerAppService(
ISqlExecutorService sqlExecutorService,
ISqlTemplateProvider templateProvider,
ICurrentTenant currentTenant,
IHttpContextAccessor httpContextAccessor)
{
_sqlExecutorService = sqlExecutorService;
_templateProvider = templateProvider;
_currentTenant = currentTenant;
_httpContextAccessor = httpContextAccessor;
}
private string GetTenantFromHeader()
{
return _httpContextAccessor.HttpContext?
.Request?
.Headers["__tenant"]
.FirstOrDefault();
}
private void ValidateTenantAccess()
{
var headerTenant = GetTenantFromHeader();
var currentTenantName = _currentTenant.Name;
if (_currentTenant.IsAvailable)
{
if (headerTenant != currentTenantName)
{
throw new Volo.Abp.UserFriendlyException($"Tenant mismatch. Header tenant '{headerTenant}' does not match current tenant '{currentTenantName}'.");
}
}
}
public async Task<SqlObjectExplorerDto> GetAllObjectsAsync(string dataSourceCode)
{
ValidateTenantAccess();
var result = new SqlObjectExplorerDto();
result.Tables = await GetTablesAsync(dataSourceCode);
result.Views = await GetNativeObjectsAsync(dataSourceCode, "V");
result.StoredProcedures = await GetNativeObjectsAsync(dataSourceCode, "P");
result.Functions = await GetNativeObjectsAsync(dataSourceCode, "FN", "IF", "TF");
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<List<SqlNativeObjectDto>> GetNativeObjectsAsync(string dataSourceCode, params string[] objectTypes)
{
var typeList = string.Join(",", objectTypes.Select(t => $"'{t}'"));
var query = $@"
SELECT
SCHEMA_NAME(o.schema_id) AS SchemaName,
o.name AS ObjectName
FROM
sys.objects o
WHERE
o.type IN ({typeList})
AND o.is_ms_shipped = 0
ORDER BY
SCHEMA_NAME(o.schema_id), o.name";
var result = await _sqlExecutorService.ExecuteQueryAsync(query, dataSourceCode);
var objects = new List<SqlNativeObjectDto>();
if (result.Success && result.Data != null)
{
foreach (var row in result.Data)
{
var dict = row as IDictionary<string, object>;
if (dict != null)
{
objects.Add(new SqlNativeObjectDto
{
SchemaName = dict["SchemaName"]?.ToString() ?? "dbo",
ObjectName = dict["ObjectName"]?.ToString() ?? ""
});
}
}
}
return objects;
}
private async Task<List<DatabaseTableDto>> 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<DatabaseTableDto>();
if (result.Success && result.Data != null)
{
foreach (var row in result.Data)
{
var dict = row as IDictionary<string, object>;
if (dict != null)
{
tables.Add(new DatabaseTableDto
{
SchemaName = dict["SchemaName"]?.ToString() ?? "dbo",
TableName = dict["TableName"]?.ToString() ?? ""
});
}
}
}
return tables;
}
public async Task<SqlQueryExecutionResultDto> ExecuteQueryAsync(ExecuteSqlQueryDto input)
{
ValidateTenantAccess();
var result = await _sqlExecutorService.ExecuteQueryAsync(
input.QueryText,
input.DataSourceCode,
input.Parameters);
return MapExecutionResult(result);
}
public async Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName)
{
ValidateTenantAccess();
var query = @"
SELECT OBJECT_DEFINITION(OBJECT_ID(@ObjectName)) AS Definition";
var fullObjectName = $"[{schemaName}].[{objectName}]";
var result = await _sqlExecutorService.ExecuteQueryAsync(
query.Replace("@ObjectName", $"'{fullObjectName}'"),
dataSourceCode);
if (result.Success && result.Data != null)
{
var dataList = result.Data.ToList();
if (dataList.Count > 0)
{
var row = dataList[0] as IDictionary<string, object>;
if (row != null && row.ContainsKey("Definition"))
{
var definition = row["Definition"]?.ToString() ?? string.Empty;
// Replace first CREATE keyword with ALTER so the user edits, not recreates
definition = System.Text.RegularExpressions.Regex.Replace(
definition,
@"^\s*CREATE\s+",
"ALTER ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
return definition;
}
}
}
return string.Empty;
}
public async Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName)
{
ValidateTenantAccess();
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<DatabaseColumnDto>();
if (result.Success && result.Data != null)
{
foreach (var row in result.Data)
{
var dict = row as IDictionary<string, object>;
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;
}
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
};
}
}