825 lines
31 KiB
C#
825 lines
31 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Unified service for SQL Object Explorer
|
|
/// Combines all SQL objects into a single endpoint
|
|
/// </summary>
|
|
[Authorize("App.SqlQueryManager")]
|
|
public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerAppService
|
|
{
|
|
private readonly IRepository<SqlQuery, Guid> _queryRepository;
|
|
private readonly IRepository<SqlStoredProcedure, Guid> _procedureRepository;
|
|
private readonly IRepository<SqlView, Guid> _viewRepository;
|
|
private readonly IRepository<SqlFunction, Guid> _functionRepository;
|
|
private readonly ISqlExecutorService _sqlExecutorService;
|
|
private readonly ISqlTemplateProvider _templateProvider;
|
|
|
|
public SqlObjectManagerAppService(
|
|
IRepository<SqlQuery, Guid> queryRepository,
|
|
IRepository<SqlStoredProcedure, Guid> procedureRepository,
|
|
IRepository<SqlView, Guid> viewRepository,
|
|
IRepository<SqlFunction, Guid> functionRepository,
|
|
ISqlExecutorService sqlExecutorService,
|
|
ISqlTemplateProvider templateProvider)
|
|
{
|
|
_queryRepository = queryRepository;
|
|
_procedureRepository = procedureRepository;
|
|
_viewRepository = viewRepository;
|
|
_functionRepository = functionRepository;
|
|
_sqlExecutorService = sqlExecutorService;
|
|
_templateProvider = templateProvider;
|
|
}
|
|
|
|
public async Task<SqlObjectExplorerDto> 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<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 System.Collections.Generic.IDictionary<string, object>;
|
|
if (dict != null)
|
|
{
|
|
tables.Add(new DatabaseTableDto
|
|
{
|
|
SchemaName = dict["SchemaName"]?.ToString() ?? "dbo",
|
|
TableName = dict["TableName"]?.ToString() ?? ""
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return tables;
|
|
}
|
|
|
|
#region Query Operations
|
|
|
|
public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input)
|
|
{
|
|
var query = ObjectMapper.Map<CreateSqlQueryDto, SqlQuery>(input);
|
|
query.Status = SqlQueryStatus.Draft;
|
|
|
|
var created = await _queryRepository.InsertAsync(query, autoSave: true);
|
|
return ObjectMapper.Map<SqlQuery, SqlQueryDto>(created);
|
|
}
|
|
|
|
public async Task<SqlQueryDto> 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<SqlQuery, SqlQueryDto>(updated);
|
|
}
|
|
|
|
public async Task DeleteQueryAsync(Guid id)
|
|
{
|
|
await _queryRepository.DeleteAsync(id);
|
|
}
|
|
|
|
public async Task<SqlQueryExecutionResultDto> 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<object>(),
|
|
RowsAffected = 0,
|
|
ExecutionTimeMs = 0
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new SqlQueryExecutionResultDto
|
|
{
|
|
Success = false,
|
|
Message = $"SQL syntax error: {ex.Message}",
|
|
Data = new List<object>(),
|
|
RowsAffected = 0,
|
|
ExecutionTimeMs = 0
|
|
};
|
|
}
|
|
}
|
|
|
|
// For DML commands (SELECT, INSERT, UPDATE, DELETE), execute normally
|
|
var result = await _sqlExecutorService.ExecuteQueryAsync(input.QueryText, input.DataSourceCode);
|
|
return MapExecutionResult(result);
|
|
}
|
|
|
|
public async Task<SqlQueryExecutionResultDto> 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<SqlStoredProcedureDto> 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;
|
|
procedure.IsDeployed = false;
|
|
procedure.LastDeployedAt = null;
|
|
|
|
var updated = await _procedureRepository.UpdateAsync(procedure, autoSave: true);
|
|
return ObjectMapper.Map<SqlStoredProcedure, SqlStoredProcedureDto>(updated);
|
|
}
|
|
|
|
public async Task DeleteStoredProcedureAsync(Guid id)
|
|
{
|
|
var procedure = await _procedureRepository.GetAsync(id);
|
|
|
|
// Drop stored procedure from SQL Server (always try, regardless of IsDeployed flag)
|
|
try
|
|
{
|
|
var dropSql = $"IF OBJECT_ID('[{procedure.SchemaName}].[{procedure.ProcedureName}]', 'P') IS NOT NULL DROP PROCEDURE [{procedure.SchemaName}].[{procedure.ProcedureName}]";
|
|
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, procedure.DataSourceCode);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors if object doesn't exist in database
|
|
}
|
|
|
|
await _procedureRepository.DeleteAsync(id);
|
|
}
|
|
|
|
public async Task<SqlQueryExecutionResultDto> DeployStoredProcedureAsync(DeployStoredProcedureDto input)
|
|
{
|
|
var procedure = await _procedureRepository.GetAsync(input.Id);
|
|
|
|
try
|
|
{
|
|
// Önce DROP işlemi yap (varsa)
|
|
var dropSql = $"IF OBJECT_ID('[{procedure.SchemaName}].[{procedure.ProcedureName}]', 'P') IS NOT NULL DROP PROCEDURE [{procedure.SchemaName}].[{procedure.ProcedureName}]";
|
|
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, procedure.DataSourceCode);
|
|
|
|
// Sonra CREATE işlemi yap
|
|
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);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new SqlQueryExecutionResultDto
|
|
{
|
|
Success = false,
|
|
Message = $"Deploy failed: {ex.Message}",
|
|
Data = new List<object>(),
|
|
RowsAffected = 0,
|
|
ExecutionTimeMs = 0
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region View Operations
|
|
|
|
public async Task<SqlViewDto> 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;
|
|
view.IsDeployed = false;
|
|
view.LastDeployedAt = null;
|
|
|
|
var updated = await _viewRepository.UpdateAsync(view, autoSave: true);
|
|
return ObjectMapper.Map<SqlView, SqlViewDto>(updated);
|
|
}
|
|
|
|
public async Task DeleteViewAsync(Guid id)
|
|
{
|
|
var view = await _viewRepository.GetAsync(id);
|
|
|
|
// Drop view from SQL Server (always try, regardless of IsDeployed flag)
|
|
try
|
|
{
|
|
var dropSql = $"IF OBJECT_ID('[{view.SchemaName}].[{view.ViewName}]', 'V') IS NOT NULL DROP VIEW [{view.SchemaName}].[{view.ViewName}]";
|
|
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, view.DataSourceCode);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors if object doesn't exist in database
|
|
}
|
|
|
|
await _viewRepository.DeleteAsync(id);
|
|
}
|
|
|
|
public async Task<SqlQueryExecutionResultDto> DeployViewAsync(DeployViewDto input)
|
|
{
|
|
var view = await _viewRepository.GetAsync(input.Id);
|
|
|
|
try
|
|
{
|
|
// Önce DROP işlemi yap (varsa)
|
|
var dropSql = $"IF OBJECT_ID('[{view.SchemaName}].[{view.ViewName}]', 'V') IS NOT NULL DROP VIEW [{view.SchemaName}].[{view.ViewName}]";
|
|
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, view.DataSourceCode);
|
|
|
|
// Sonra CREATE işlemi yap
|
|
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);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new SqlQueryExecutionResultDto
|
|
{
|
|
Success = false,
|
|
Message = $"Deploy failed: {ex.Message}",
|
|
Data = new List<object>(),
|
|
RowsAffected = 0,
|
|
ExecutionTimeMs = 0
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Function Operations
|
|
|
|
public async Task<SqlFunctionDto> 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;
|
|
function.IsDeployed = false;
|
|
function.LastDeployedAt = null;
|
|
|
|
var updated = await _functionRepository.UpdateAsync(function, autoSave: true);
|
|
return ObjectMapper.Map<SqlFunction, SqlFunctionDto>(updated);
|
|
}
|
|
|
|
public async Task DeleteFunctionAsync(Guid id)
|
|
{
|
|
var function = await _functionRepository.GetAsync(id);
|
|
|
|
// Drop function from SQL Server (always try, regardless of IsDeployed flag)
|
|
try
|
|
{
|
|
var dropSql = $"IF OBJECT_ID('[{function.SchemaName}].[{function.FunctionName}]', 'FN') IS NOT NULL DROP FUNCTION [{function.SchemaName}].[{function.FunctionName}]";
|
|
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, function.DataSourceCode);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors if object doesn't exist in database
|
|
}
|
|
|
|
await _functionRepository.DeleteAsync(id);
|
|
}
|
|
|
|
public async Task<SqlQueryExecutionResultDto> DeployFunctionAsync(DeployFunctionDto input)
|
|
{
|
|
var function = await _functionRepository.GetAsync(input.Id);
|
|
|
|
try
|
|
{
|
|
// Önce DROP işlemi yap (varsa)
|
|
var dropSql = $"IF OBJECT_ID('[{function.SchemaName}].[{function.FunctionName}]', 'FN') IS NOT NULL DROP FUNCTION [{function.SchemaName}].[{function.FunctionName}]";
|
|
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, function.DataSourceCode);
|
|
|
|
// Sonra CREATE işlemi yap
|
|
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);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new SqlQueryExecutionResultDto
|
|
{
|
|
Success = false,
|
|
Message = $"Deploy failed: {ex.Message}",
|
|
Data = new List<object>(),
|
|
RowsAffected = 0,
|
|
ExecutionTimeMs = 0
|
|
};
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Database Metadata Operations
|
|
|
|
public async Task<List<DatabaseColumnDto>> 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<DatabaseColumnDto>();
|
|
if (result.Success && result.Data != null)
|
|
{
|
|
foreach (var row in result.Data)
|
|
{
|
|
var dict = row as System.Collections.Generic.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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public async Task<SmartSaveResultDto> SmartSaveAsync(SmartSaveInputDto input)
|
|
{
|
|
var result = new SmartSaveResultDto();
|
|
var sqlText = input.SqlText.Trim();
|
|
var sqlUpper = sqlText.ToUpperInvariant();
|
|
|
|
try
|
|
{
|
|
// Analyze SQL to determine object type
|
|
if (sqlUpper.Contains("CREATE VIEW") || sqlUpper.Contains("ALTER VIEW"))
|
|
{
|
|
// Extract view name
|
|
var viewName = input.Name;
|
|
var schemaName = "dbo";
|
|
var displayName = input.Name;
|
|
|
|
// Check if view already exists
|
|
var existingView = (await _viewRepository.GetListAsync())
|
|
.FirstOrDefault(v => v.ViewName == viewName && v.DataSourceCode == input.DataSourceCode);
|
|
|
|
SqlView view;
|
|
if (existingView != null)
|
|
{
|
|
// Update existing view
|
|
existingView.DisplayName = displayName;
|
|
existingView.ViewDefinition = sqlText;
|
|
existingView.IsDeployed = false;
|
|
existingView.LastDeployedAt = null;
|
|
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
existingView.Description = input.Description;
|
|
}
|
|
view = await _viewRepository.UpdateAsync(existingView, autoSave: true);
|
|
result.Message = $"View '{viewName}' updated successfully. Use Deploy button to deploy changes to SQL Server.";
|
|
}
|
|
else
|
|
{
|
|
// Create new view
|
|
view = new SqlView(
|
|
GuidGenerator.Create(),
|
|
viewName, // ViewName from SQL
|
|
schemaName,
|
|
displayName, // DisplayName from user input
|
|
sqlText,
|
|
input.DataSourceCode
|
|
);
|
|
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
view.Description = input.Description;
|
|
}
|
|
|
|
await _viewRepository.InsertAsync(view, autoSave: true);
|
|
result.Message = $"View '{viewName}' saved successfully. Use Deploy button to deploy to SQL Server.";
|
|
}
|
|
|
|
result.ObjectType = "View";
|
|
result.ObjectId = view.Id;
|
|
result.Deployed = view.IsDeployed;
|
|
}
|
|
else if (sqlUpper.Contains("CREATE PROCEDURE") || sqlUpper.Contains("CREATE PROC") ||
|
|
sqlUpper.Contains("ALTER PROCEDURE") || sqlUpper.Contains("ALTER PROC"))
|
|
{
|
|
// Extract procedure name
|
|
var procName = input.Name;
|
|
var schemaName = "dbo";
|
|
var displayName = input.Name;
|
|
|
|
// Check if procedure already exists
|
|
var existingProcedure = (await _procedureRepository.GetListAsync())
|
|
.FirstOrDefault(p => p.ProcedureName == procName && p.DataSourceCode == input.DataSourceCode);
|
|
|
|
SqlStoredProcedure procedure;
|
|
if (existingProcedure != null)
|
|
{
|
|
// Update existing procedure
|
|
existingProcedure.DisplayName = displayName;
|
|
existingProcedure.ProcedureBody = sqlText;
|
|
existingProcedure.IsDeployed = false;
|
|
existingProcedure.LastDeployedAt = null;
|
|
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
existingProcedure.Description = input.Description;
|
|
}
|
|
procedure = await _procedureRepository.UpdateAsync(existingProcedure, autoSave: true);
|
|
result.Message = $"Stored Procedure '{procName}' updated successfully. Use Deploy button to deploy changes to SQL Server.";
|
|
}
|
|
else
|
|
{
|
|
// Create new procedure
|
|
procedure = new SqlStoredProcedure(
|
|
GuidGenerator.Create(),
|
|
procName, // ProcedureName from SQL
|
|
schemaName,
|
|
displayName, // DisplayName from user input
|
|
sqlText,
|
|
input.DataSourceCode
|
|
);
|
|
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
procedure.Description = input.Description;
|
|
}
|
|
|
|
await _procedureRepository.InsertAsync(procedure, autoSave: true);
|
|
result.Message = $"Stored Procedure '{procName}' saved successfully. Use Deploy button to deploy to SQL Server.";
|
|
}
|
|
|
|
result.ObjectType = "StoredProcedure";
|
|
result.ObjectId = procedure.Id;
|
|
result.Deployed = procedure.IsDeployed;
|
|
}
|
|
else if (sqlUpper.Contains("CREATE FUNCTION") || sqlUpper.Contains("ALTER FUNCTION"))
|
|
{
|
|
// Extract function name
|
|
var funcName = input.Name;
|
|
var schemaName = "dbo";
|
|
var returnType = "NVARCHAR(MAX)"; // Default, can be extracted from SQL
|
|
var displayName = input.Name;
|
|
|
|
// Check if function already exists
|
|
var existingFunction = (await _functionRepository.GetListAsync())
|
|
.FirstOrDefault(f => f.FunctionName == funcName && f.DataSourceCode == input.DataSourceCode);
|
|
|
|
SqlFunction function;
|
|
if (existingFunction != null)
|
|
{
|
|
// Update existing function
|
|
existingFunction.DisplayName = displayName;
|
|
existingFunction.FunctionBody = sqlText;
|
|
existingFunction.IsDeployed = false;
|
|
existingFunction.LastDeployedAt = null;
|
|
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
existingFunction.Description = input.Description;
|
|
}
|
|
function = await _functionRepository.UpdateAsync(existingFunction, autoSave: true);
|
|
result.Message = $"Function '{funcName}' updated successfully. Use Deploy button to deploy changes to SQL Server.";
|
|
}
|
|
else
|
|
{
|
|
// Create new function
|
|
function = new SqlFunction(
|
|
GuidGenerator.Create(),
|
|
funcName, // FunctionName from SQL
|
|
schemaName,
|
|
displayName, // DisplayName from user input
|
|
SqlFunctionType.ScalarFunction,
|
|
sqlText,
|
|
returnType,
|
|
input.DataSourceCode
|
|
);
|
|
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
function.Description = input.Description;
|
|
}
|
|
|
|
await _functionRepository.InsertAsync(function, autoSave: true);
|
|
result.Message = $"Function '{funcName}' saved successfully. Use Deploy button to deploy to SQL Server.";
|
|
}
|
|
|
|
result.ObjectType = "Function";
|
|
result.ObjectId = function.Id;
|
|
result.Deployed = function.IsDeployed;
|
|
}
|
|
else
|
|
{
|
|
// Default to Query (SELECT, INSERT, UPDATE, DELETE, etc.)
|
|
var queryName = input.Name ?? $"Query_{DateTime.Now:yyyyMMddHHmmss}";
|
|
var queryCode = queryName.Replace(" ", "_");
|
|
|
|
// Check if query already exists
|
|
var existingQuery = (await _queryRepository.GetListAsync())
|
|
.FirstOrDefault(q => q.Code == queryCode && q.DataSourceCode == input.DataSourceCode);
|
|
|
|
SqlQuery query;
|
|
if (existingQuery != null)
|
|
{
|
|
// Update existing query
|
|
existingQuery.Name = queryName;
|
|
existingQuery.QueryText = sqlText;
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
existingQuery.Description = input.Description;
|
|
}
|
|
query = await _queryRepository.UpdateAsync(existingQuery, autoSave: true);
|
|
result.Message = $"Query '{queryName}' updated successfully";
|
|
}
|
|
else
|
|
{
|
|
// Create new query
|
|
query = new SqlQuery(
|
|
GuidGenerator.Create(),
|
|
queryCode,
|
|
queryName,
|
|
sqlText,
|
|
input.DataSourceCode
|
|
);
|
|
|
|
if (!string.IsNullOrEmpty(input.Description))
|
|
{
|
|
query.Description = input.Description;
|
|
}
|
|
|
|
await _queryRepository.InsertAsync(query, autoSave: true);
|
|
result.Message = $"Query '{queryName}' saved successfully";
|
|
}
|
|
|
|
result.ObjectType = "Query";
|
|
result.ObjectId = query.Id;
|
|
result.Deployed = false; // Queries are not deployed;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new Volo.Abp.UserFriendlyException($"Failed to save SQL object: {ex.Message}");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private string ExtractObjectName(string sql, params string[] keywords)
|
|
{
|
|
var sqlUpper = sql.ToUpperInvariant();
|
|
|
|
foreach (var keyword in keywords)
|
|
{
|
|
var createIndex = sqlUpper.IndexOf($"CREATE {keyword}", StringComparison.OrdinalIgnoreCase);
|
|
var alterIndex = sqlUpper.IndexOf($"ALTER {keyword}", StringComparison.OrdinalIgnoreCase);
|
|
var startIndex = Math.Max(createIndex, alterIndex);
|
|
|
|
if (startIndex >= 0)
|
|
{
|
|
startIndex = sqlUpper.IndexOf(keyword, startIndex, StringComparison.OrdinalIgnoreCase) + keyword.Length;
|
|
var endIndex = sql.IndexOfAny(new[] { ' ', '\r', '\n', '\t', '(', '[' }, startIndex);
|
|
|
|
if (endIndex > startIndex)
|
|
{
|
|
var name = sql.Substring(startIndex, endIndex - startIndex).Trim();
|
|
// Remove schema prefix if exists
|
|
if (name.Contains("."))
|
|
{
|
|
name = name.Substring(name.LastIndexOf('.') + 1);
|
|
}
|
|
// Remove square brackets
|
|
name = name.Replace("[", "").Replace("]", "");
|
|
return name;
|
|
}
|
|
}
|
|
}
|
|
|
|
return "UnnamedObject";
|
|
}
|
|
|
|
#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
|
|
}
|