2025-12-05 14:56:39 +00:00
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 ;
2025-12-05 22:38:21 +00:00
using Microsoft.AspNetCore.Authorization ;
2025-12-05 14:56:39 +00:00
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>
2025-12-05 22:38:21 +00:00
[Authorize("App.SqlQueryManager")]
2025-12-05 14:56:39 +00:00
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 ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
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 ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
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 )
{
2025-12-05 22:38:21 +00:00
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
2025-12-05 14:56:39 +00:00
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 ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
// Update execution statistics
query . ExecutionCount + + ;
query . LastExecutedAt = DateTime . UtcNow ;
await _queryRepository . UpdateAsync ( query , autoSave : true ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
return MapExecutionResult ( result ) ;
}
#endregion
#region Stored Procedure Operations
public async Task < SqlStoredProcedureDto > UpdateStoredProcedureAsync ( Guid id , UpdateSqlStoredProcedureDto input )
{
var procedure = await _procedureRepository . GetAsync ( id ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
procedure . DisplayName = input . DisplayName ;
procedure . Description = input . Description ;
procedure . ProcedureBody = input . ProcedureBody ;
procedure . Category = input . Category ;
2025-12-05 22:38:21 +00:00
procedure . IsDeployed = false ;
procedure . LastDeployedAt = null ;
2025-12-05 14:56:39 +00:00
var updated = await _procedureRepository . UpdateAsync ( procedure , autoSave : true ) ;
return ObjectMapper . Map < SqlStoredProcedure , SqlStoredProcedureDto > ( updated ) ;
}
public async Task DeleteStoredProcedureAsync ( Guid id )
{
2025-12-05 22:38:21 +00:00
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
}
2025-12-05 14:56:39 +00:00
await _procedureRepository . DeleteAsync ( id ) ;
}
public async Task < SqlQueryExecutionResultDto > DeployStoredProcedureAsync ( DeployStoredProcedureDto input )
{
var procedure = await _procedureRepository . GetAsync ( input . Id ) ;
2025-12-05 22:38:21 +00:00
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 ) ;
}
2025-12-05 14:56:39 +00:00
2025-12-05 22:38:21 +00:00
return MapExecutionResult ( result ) ;
}
catch ( Exception ex )
2025-12-05 14:56:39 +00:00
{
2025-12-05 22:38:21 +00:00
return new SqlQueryExecutionResultDto
{
Success = false ,
Message = $"Deploy failed: {ex.Message}" ,
Data = new List < object > ( ) ,
RowsAffected = 0 ,
ExecutionTimeMs = 0
} ;
2025-12-05 14:56:39 +00:00
}
}
#endregion
#region View Operations
public async Task < SqlViewDto > UpdateViewAsync ( Guid id , UpdateSqlViewDto input )
{
var view = await _viewRepository . GetAsync ( id ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
view . DisplayName = input . DisplayName ;
view . Description = input . Description ;
view . ViewDefinition = input . ViewDefinition ;
view . Category = input . Category ;
2025-12-05 22:38:21 +00:00
view . IsDeployed = false ;
view . LastDeployedAt = null ;
2025-12-05 14:56:39 +00:00
var updated = await _viewRepository . UpdateAsync ( view , autoSave : true ) ;
return ObjectMapper . Map < SqlView , SqlViewDto > ( updated ) ;
}
public async Task DeleteViewAsync ( Guid id )
{
2025-12-05 22:38:21 +00:00
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
}
2025-12-05 14:56:39 +00:00
await _viewRepository . DeleteAsync ( id ) ;
}
public async Task < SqlQueryExecutionResultDto > DeployViewAsync ( DeployViewDto input )
{
var view = await _viewRepository . GetAsync ( input . Id ) ;
2025-12-05 22:38:21 +00:00
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 ) ;
}
2025-12-05 14:56:39 +00:00
2025-12-05 22:38:21 +00:00
return MapExecutionResult ( result ) ;
}
catch ( Exception ex )
2025-12-05 14:56:39 +00:00
{
2025-12-05 22:38:21 +00:00
return new SqlQueryExecutionResultDto
{
Success = false ,
Message = $"Deploy failed: {ex.Message}" ,
Data = new List < object > ( ) ,
RowsAffected = 0 ,
ExecutionTimeMs = 0
} ;
2025-12-05 14:56:39 +00:00
}
}
#endregion
#region Function Operations
public async Task < SqlFunctionDto > UpdateFunctionAsync ( Guid id , UpdateSqlFunctionDto input )
{
var function = await _functionRepository . GetAsync ( id ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
function . DisplayName = input . DisplayName ;
function . Description = input . Description ;
function . FunctionBody = input . FunctionBody ;
function . Category = input . Category ;
2025-12-05 22:38:21 +00:00
function . IsDeployed = false ;
function . LastDeployedAt = null ;
2025-12-05 14:56:39 +00:00
var updated = await _functionRepository . UpdateAsync ( function , autoSave : true ) ;
return ObjectMapper . Map < SqlFunction , SqlFunctionDto > ( updated ) ;
}
public async Task DeleteFunctionAsync ( Guid id )
{
2025-12-05 22:38:21 +00:00
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
}
2025-12-05 14:56:39 +00:00
await _functionRepository . DeleteAsync ( id ) ;
}
public async Task < SqlQueryExecutionResultDto > DeployFunctionAsync ( DeployFunctionDto input )
{
var function = await _functionRepository . GetAsync ( input . Id ) ;
2025-12-05 22:38:21 +00:00
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 ) ;
}
2025-12-05 14:56:39 +00:00
2025-12-05 22:38:21 +00:00
return MapExecutionResult ( result ) ;
}
catch ( Exception ex )
2025-12-05 14:56:39 +00:00
{
2025-12-05 22:38:21 +00:00
return new SqlQueryExecutionResultDto
{
Success = false ,
Message = $"Deploy failed: {ex.Message}" ,
Data = new List < object > ( ) ,
RowsAffected = 0 ,
ExecutionTimeMs = 0
} ;
2025-12-05 14:56:39 +00:00
}
}
#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 ) ;
2025-12-05 22:38:21 +00:00
2025-12-05 14:56:39 +00:00
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
2025-12-05 22:38:21 +00:00
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" ;
}
2025-12-05 14:56:39 +00:00
#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
}