Sql Query Manager düzenlemesi
This commit is contained in:
parent
4a7f2ee853
commit
9b2c1c25ee
11 changed files with 417 additions and 83 deletions
|
|
@ -42,6 +42,11 @@ public interface ISqlObjectManagerAppService : IApplicationService
|
||||||
// Database Metadata Operations
|
// Database Metadata Operations
|
||||||
Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName);
|
Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the SQL definition/body of a native SQL Server object (Stored Procedure, View, or Function)
|
||||||
|
/// </summary>
|
||||||
|
Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName);
|
||||||
|
|
||||||
// Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy
|
// Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy
|
||||||
Task<SmartSaveResultDto> SmartSaveAsync(SmartSaveInputDto input);
|
Task<SmartSaveResultDto> SmartSaveAsync(SmartSaveInputDto input);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ public class SqlFunctionDto : FullAuditedEntityDto<Guid>
|
||||||
public bool IsDeployed { get; set; }
|
public bool IsDeployed { get; set; }
|
||||||
public DateTime? LastDeployedAt { get; set; }
|
public DateTime? LastDeployedAt { get; set; }
|
||||||
public string Parameters { get; set; }
|
public string Parameters { get; set; }
|
||||||
|
public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateSqlFunctionDto
|
public class CreateSqlFunctionDto
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ public class SqlStoredProcedureDto : FullAuditedEntityDto<Guid>
|
||||||
public bool IsDeployed { get; set; }
|
public bool IsDeployed { get; set; }
|
||||||
public DateTime? LastDeployedAt { get; set; }
|
public DateTime? LastDeployedAt { get; set; }
|
||||||
public string Parameters { get; set; }
|
public string Parameters { get; set; }
|
||||||
|
public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateSqlStoredProcedureDto
|
public class CreateSqlStoredProcedureDto
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ public class SqlViewDto : FullAuditedEntityDto<Guid>
|
||||||
public bool IsDeployed { get; set; }
|
public bool IsDeployed { get; set; }
|
||||||
public DateTime? LastDeployedAt { get; set; }
|
public DateTime? LastDeployedAt { get; set; }
|
||||||
public bool WithSchemaBinding { get; set; }
|
public bool WithSchemaBinding { get; set; }
|
||||||
|
public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateSqlViewDto
|
public class CreateSqlViewDto
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Erp.SqlQueryManager.Application.Contracts;
|
using Erp.SqlQueryManager.Application.Contracts;
|
||||||
using Erp.SqlQueryManager.Domain.Entities;
|
using Erp.SqlQueryManager.Domain.Entities;
|
||||||
|
|
@ -100,67 +102,14 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Get all stored procedures for this data source
|
// Get all stored procedures for this data source (custom + native merged)
|
||||||
var procedures = await _procedureRepository.GetListAsync();
|
result.StoredProcedures = await GetMergedStoredProceduresAsync(dataSourceCode);
|
||||||
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
|
// Get all views for this data source (custom + native merged)
|
||||||
var views = await _viewRepository.GetListAsync();
|
result.Views = await GetMergedViewsAsync(dataSourceCode);
|
||||||
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
|
// Get all functions for this data source (custom + native merged)
|
||||||
var functions = await _functionRepository.GetListAsync();
|
result.Functions = await GetMergedFunctionsAsync(dataSourceCode);
|
||||||
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
|
// Get all database tables
|
||||||
result.Tables = await GetTablesAsync(dataSourceCode);
|
result.Tables = await GetTablesAsync(dataSourceCode);
|
||||||
|
|
@ -214,6 +163,244 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
||||||
return tables;
|
return tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<SqlStoredProcedureDto>> GetMergedStoredProceduresAsync(string dataSourceCode)
|
||||||
|
{
|
||||||
|
// Get custom stored procedures from database
|
||||||
|
var customProcedures = await _procedureRepository.GetListAsync();
|
||||||
|
var customList = customProcedures
|
||||||
|
.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,
|
||||||
|
IsCustom = true
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get native stored procedures from SQL Server
|
||||||
|
var nativeQuery = @"
|
||||||
|
SELECT
|
||||||
|
SCHEMA_NAME(p.schema_id) AS SchemaName,
|
||||||
|
p.name AS ProcedureName,
|
||||||
|
p.create_date AS CreatedDate,
|
||||||
|
p.modify_date AS ModifiedDate
|
||||||
|
FROM
|
||||||
|
sys.procedures p
|
||||||
|
WHERE
|
||||||
|
p.is_ms_shipped = 0
|
||||||
|
ORDER BY
|
||||||
|
SCHEMA_NAME(p.schema_id), p.name";
|
||||||
|
|
||||||
|
var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode);
|
||||||
|
var nativeList = new List<SqlStoredProcedureDto>();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var schemaName = dict["SchemaName"]?.ToString() ?? "dbo";
|
||||||
|
var procName = dict["ProcedureName"]?.ToString() ?? "";
|
||||||
|
|
||||||
|
// Skip if already exists in custom list
|
||||||
|
if (!customList.Any(c => c.SchemaName == schemaName && c.ProcedureName == procName))
|
||||||
|
{
|
||||||
|
// Generate deterministic GUID from schema and name to ensure uniqueness
|
||||||
|
var uniqueId = GenerateDeterministicGuid($"SP_{dataSourceCode}_{schemaName}_{procName}");
|
||||||
|
|
||||||
|
nativeList.Add(new SqlStoredProcedureDto
|
||||||
|
{
|
||||||
|
Id = uniqueId,
|
||||||
|
SchemaName = schemaName,
|
||||||
|
ProcedureName = procName,
|
||||||
|
DisplayName = $"[{schemaName}].[{procName}]",
|
||||||
|
DataSourceCode = dataSourceCode,
|
||||||
|
IsCustom = false,
|
||||||
|
IsDeployed = true // Native objects are already deployed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge and return
|
||||||
|
return customList.Concat(nativeList).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<SqlViewDto>> GetMergedViewsAsync(string dataSourceCode)
|
||||||
|
{
|
||||||
|
// Get custom views from database
|
||||||
|
var customViews = await _viewRepository.GetListAsync();
|
||||||
|
var customList = customViews
|
||||||
|
.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,
|
||||||
|
IsCustom = true
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get native views from SQL Server
|
||||||
|
var nativeQuery = @"
|
||||||
|
SELECT
|
||||||
|
SCHEMA_NAME(v.schema_id) AS SchemaName,
|
||||||
|
v.name AS ViewName,
|
||||||
|
v.create_date AS CreatedDate,
|
||||||
|
v.modify_date AS ModifiedDate
|
||||||
|
FROM
|
||||||
|
sys.views v
|
||||||
|
WHERE
|
||||||
|
v.is_ms_shipped = 0
|
||||||
|
ORDER BY
|
||||||
|
SCHEMA_NAME(v.schema_id), v.name";
|
||||||
|
|
||||||
|
var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode);
|
||||||
|
var nativeList = new List<SqlViewDto>();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var schemaName = dict["SchemaName"]?.ToString() ?? "dbo";
|
||||||
|
var viewName = dict["ViewName"]?.ToString() ?? "";
|
||||||
|
|
||||||
|
// Skip if already exists in custom list
|
||||||
|
if (!customList.Any(c => c.SchemaName == schemaName && c.ViewName == viewName))
|
||||||
|
{
|
||||||
|
// Generate deterministic GUID from schema and name to ensure uniqueness
|
||||||
|
var uniqueId = GenerateDeterministicGuid($"VIEW_{dataSourceCode}_{schemaName}_{viewName}");
|
||||||
|
|
||||||
|
nativeList.Add(new SqlViewDto
|
||||||
|
{
|
||||||
|
Id = uniqueId,
|
||||||
|
SchemaName = schemaName,
|
||||||
|
ViewName = viewName,
|
||||||
|
DisplayName = $"[{schemaName}].[{viewName}]",
|
||||||
|
DataSourceCode = dataSourceCode,
|
||||||
|
IsCustom = false,
|
||||||
|
IsDeployed = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return customList.Concat(nativeList).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<SqlFunctionDto>> GetMergedFunctionsAsync(string dataSourceCode)
|
||||||
|
{
|
||||||
|
// Get custom functions from database
|
||||||
|
var customFunctions = await _functionRepository.GetListAsync();
|
||||||
|
var customList = customFunctions
|
||||||
|
.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,
|
||||||
|
IsCustom = true
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get native functions from SQL Server
|
||||||
|
var nativeQuery = @"
|
||||||
|
SELECT
|
||||||
|
SCHEMA_NAME(o.schema_id) AS SchemaName,
|
||||||
|
o.name AS FunctionName,
|
||||||
|
o.create_date AS CreatedDate,
|
||||||
|
o.modify_date AS ModifiedDate,
|
||||||
|
CASE o.type
|
||||||
|
WHEN 'FN' THEN 'Scalar'
|
||||||
|
WHEN 'IF' THEN 'InlineTableValued'
|
||||||
|
WHEN 'TF' THEN 'TableValued'
|
||||||
|
ELSE 'Unknown'
|
||||||
|
END AS FunctionType
|
||||||
|
FROM
|
||||||
|
sys.objects o
|
||||||
|
WHERE
|
||||||
|
o.type IN ('FN', 'IF', 'TF')
|
||||||
|
AND o.is_ms_shipped = 0
|
||||||
|
ORDER BY
|
||||||
|
SCHEMA_NAME(o.schema_id), o.name";
|
||||||
|
|
||||||
|
var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode);
|
||||||
|
var nativeList = new List<SqlFunctionDto>();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var schemaName = dict["SchemaName"]?.ToString() ?? "dbo";
|
||||||
|
var funcName = dict["FunctionName"]?.ToString() ?? "";
|
||||||
|
|
||||||
|
// Skip if already exists in custom list
|
||||||
|
if (!customList.Any(c => c.SchemaName == schemaName && c.FunctionName == funcName))
|
||||||
|
{
|
||||||
|
var funcTypeStr = dict["FunctionType"]?.ToString() ?? "Scalar";
|
||||||
|
var funcType = funcTypeStr == "Scalar" ? SqlFunctionType.ScalarFunction :
|
||||||
|
funcTypeStr == "InlineTableValued" ? SqlFunctionType.InlineTableValuedFunction :
|
||||||
|
SqlFunctionType.TableValuedFunction;
|
||||||
|
|
||||||
|
// Generate deterministic GUID from schema and name to ensure uniqueness
|
||||||
|
var uniqueId = GenerateDeterministicGuid($"FUNC_{dataSourceCode}_{schemaName}_{funcName}");
|
||||||
|
|
||||||
|
nativeList.Add(new SqlFunctionDto
|
||||||
|
{
|
||||||
|
Id = uniqueId,
|
||||||
|
SchemaName = schemaName,
|
||||||
|
FunctionName = funcName,
|
||||||
|
DisplayName = $"[{schemaName}].[{funcName}]",
|
||||||
|
DataSourceCode = dataSourceCode,
|
||||||
|
FunctionType = funcType,
|
||||||
|
IsCustom = false,
|
||||||
|
IsDeployed = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return customList.Concat(nativeList).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
#region Query Operations
|
#region Query Operations
|
||||||
|
|
||||||
public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input)
|
public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input)
|
||||||
|
|
@ -551,6 +738,34 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
||||||
|
|
||||||
#region Database Metadata Operations
|
#region Database Metadata Operations
|
||||||
|
|
||||||
|
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 System.Collections.Generic.IDictionary<string, object>;
|
||||||
|
if (row != null && row.ContainsKey("Definition"))
|
||||||
|
{
|
||||||
|
return row["Definition"]?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName)
|
public async Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName)
|
||||||
{
|
{
|
||||||
ValidateTenantAccess();
|
ValidateTenantAccess();
|
||||||
|
|
@ -838,4 +1053,21 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Helper Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a deterministic GUID from a string input using MD5 hash
|
||||||
|
/// This ensures same input always produces same GUID
|
||||||
|
/// </summary>
|
||||||
|
private static Guid GenerateDeterministicGuid(string input)
|
||||||
|
{
|
||||||
|
using (var md5 = MD5.Create())
|
||||||
|
{
|
||||||
|
var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
|
||||||
|
return new Guid(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
|
||||||
.Ignore(x => x.LastExecutedAt)
|
.Ignore(x => x.LastExecutedAt)
|
||||||
.Ignore(x => x.ExecutionCount);
|
.Ignore(x => x.ExecutionCount);
|
||||||
|
|
||||||
CreateMap<SqlStoredProcedure, SqlStoredProcedureDto>();
|
CreateMap<SqlStoredProcedure, SqlStoredProcedureDto>().Ignore(x => x.IsCustom);
|
||||||
CreateMap<CreateSqlStoredProcedureDto, SqlStoredProcedure>()
|
CreateMap<CreateSqlStoredProcedureDto, SqlStoredProcedure>()
|
||||||
.IgnoreFullAuditedObjectProperties()
|
.IgnoreFullAuditedObjectProperties()
|
||||||
.Ignore(x => x.Id)
|
.Ignore(x => x.Id)
|
||||||
|
|
@ -34,6 +34,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
|
||||||
.Ignore(x => x.Status)
|
.Ignore(x => x.Status)
|
||||||
.Ignore(x => x.IsDeployed)
|
.Ignore(x => x.IsDeployed)
|
||||||
.Ignore(x => x.LastDeployedAt);
|
.Ignore(x => x.LastDeployedAt);
|
||||||
|
|
||||||
CreateMap<UpdateSqlStoredProcedureDto, SqlStoredProcedure>()
|
CreateMap<UpdateSqlStoredProcedureDto, SqlStoredProcedure>()
|
||||||
.IgnoreFullAuditedObjectProperties()
|
.IgnoreFullAuditedObjectProperties()
|
||||||
.Ignore(x => x.Id)
|
.Ignore(x => x.Id)
|
||||||
|
|
@ -45,7 +46,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
|
||||||
.Ignore(x => x.IsDeployed)
|
.Ignore(x => x.IsDeployed)
|
||||||
.Ignore(x => x.LastDeployedAt);
|
.Ignore(x => x.LastDeployedAt);
|
||||||
|
|
||||||
CreateMap<SqlView, SqlViewDto>();
|
CreateMap<SqlView, SqlViewDto>().Ignore(x => x.IsCustom);
|
||||||
CreateMap<CreateSqlViewDto, SqlView>()
|
CreateMap<CreateSqlViewDto, SqlView>()
|
||||||
.IgnoreFullAuditedObjectProperties()
|
.IgnoreFullAuditedObjectProperties()
|
||||||
.Ignore(x => x.Id)
|
.Ignore(x => x.Id)
|
||||||
|
|
@ -64,7 +65,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
|
||||||
.Ignore(x => x.IsDeployed)
|
.Ignore(x => x.IsDeployed)
|
||||||
.Ignore(x => x.LastDeployedAt);
|
.Ignore(x => x.LastDeployedAt);
|
||||||
|
|
||||||
CreateMap<SqlFunction, SqlFunctionDto>();
|
CreateMap<SqlFunction, SqlFunctionDto>().Ignore(x => x.IsCustom);
|
||||||
CreateMap<CreateSqlFunctionDto, SqlFunction>()
|
CreateMap<CreateSqlFunctionDto, SqlFunction>()
|
||||||
.IgnoreFullAuditedObjectProperties()
|
.IgnoreFullAuditedObjectProperties()
|
||||||
.Ignore(x => x.Id)
|
.Ignore(x => x.Id)
|
||||||
|
|
|
||||||
|
|
@ -10327,6 +10327,12 @@
|
||||||
"tr": "Şablon Kullan",
|
"tr": "Şablon Kullan",
|
||||||
"en": "Use Template"
|
"en": "Use Template"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Platform.NativeObjectViewOnly",
|
||||||
|
"tr": "Yerel Nesne Sadece Görüntüle",
|
||||||
|
"en": "Native Object View Only"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Platform.Error",
|
"key": "App.Platform.Error",
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export interface SqlFunctionDto extends FullAuditedEntityDto<string> {
|
||||||
isDeployed: boolean
|
isDeployed: boolean
|
||||||
lastDeployedAt?: string
|
lastDeployedAt?: string
|
||||||
parameters: string
|
parameters: string
|
||||||
|
isCustom: boolean // true = stored in database, false = native SQL Server object
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateSqlFunctionDto {
|
export interface CreateSqlFunctionDto {
|
||||||
|
|
@ -132,6 +133,7 @@ export interface SqlStoredProcedureDto extends FullAuditedEntityDto<string> {
|
||||||
isDeployed: boolean
|
isDeployed: boolean
|
||||||
lastDeployedAt?: string
|
lastDeployedAt?: string
|
||||||
parameters: string
|
parameters: string
|
||||||
|
isCustom: boolean // true = stored in database, false = native SQL Server object
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateSqlStoredProcedureDto {
|
export interface CreateSqlStoredProcedureDto {
|
||||||
|
|
@ -171,6 +173,7 @@ export interface SqlViewDto extends FullAuditedEntityDto<string> {
|
||||||
isDeployed: boolean
|
isDeployed: boolean
|
||||||
lastDeployedAt?: string
|
lastDeployedAt?: string
|
||||||
withSchemaBinding: boolean
|
withSchemaBinding: boolean
|
||||||
|
isCustom: boolean // true = stored in database, false = native SQL Server object
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateSqlViewDto {
|
export interface CreateSqlViewDto {
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,16 @@ export class SqlObjectManagerService {
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
getNativeObjectDefinition = (dataSourceCode: string, schemaName: string, objectName: string, config?: Partial<Config>) =>
|
||||||
|
apiService.fetchData<string, void>(
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/app/sql-object-manager/native-object-definition',
|
||||||
|
params: { dataSourceCode, schemaName, objectName },
|
||||||
|
},
|
||||||
|
{ apiName: this.apiName, ...config },
|
||||||
|
)
|
||||||
|
|
||||||
// Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy
|
// Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy
|
||||||
smartSave = (input: { sqlText: string; dataSourceCode: string; name?: string; description?: string }, config?: Partial<Config>) =>
|
smartSave = (input: { sqlText: string; dataSourceCode: string; name?: string; description?: string }, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<{ objectType: string; objectId: string; deployed: boolean; message: string }, typeof input>(
|
apiService.fetchData<{ objectType: string; objectId: string; deployed: boolean; message: string }, typeof input>(
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ const SqlQueryManager = () => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleObjectSelect = useCallback(
|
const handleObjectSelect = useCallback(
|
||||||
(object: SqlObject | null, objectType: SqlObjectType | null) => {
|
async (object: SqlObject | null, objectType: SqlObjectType | null) => {
|
||||||
if (state.isDirty) {
|
if (state.isDirty) {
|
||||||
if (!confirm(translate('::App.Platform.UnsavedChangesConfirmation'))) {
|
if (!confirm(translate('::App.Platform.UnsavedChangesConfirmation'))) {
|
||||||
return
|
return
|
||||||
|
|
@ -124,16 +124,76 @@ const SqlQueryManager = () => {
|
||||||
if (object) {
|
if (object) {
|
||||||
if (objectType === 1) {
|
if (objectType === 1) {
|
||||||
// Query
|
// Query
|
||||||
content = (object as SqlQueryDto).queryText
|
content = (object as SqlQueryDto).queryText || ''
|
||||||
} else if (objectType === 2) {
|
} else if (objectType === 2) {
|
||||||
// Stored Procedure
|
// Stored Procedure
|
||||||
content = (object as SqlStoredProcedureDto).procedureBody
|
const sp = object as SqlStoredProcedureDto
|
||||||
|
if (sp.isCustom) {
|
||||||
|
content = sp.procedureBody || ''
|
||||||
|
} else {
|
||||||
|
// Native SP - fetch definition from SQL Server
|
||||||
|
try {
|
||||||
|
const result = await sqlObjectManagerService.getNativeObjectDefinition(
|
||||||
|
state.selectedDataSource || '',
|
||||||
|
sp.schemaName,
|
||||||
|
sp.procedureName
|
||||||
|
)
|
||||||
|
content = result.data || ''
|
||||||
|
} catch (error) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||||
|
{translate('::App.Platform.FailedToLoadDefinition')}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-center' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (objectType === 3) {
|
} else if (objectType === 3) {
|
||||||
// View
|
// View
|
||||||
content = (object as SqlViewDto).viewDefinition
|
const view = object as SqlViewDto
|
||||||
|
if (view.isCustom) {
|
||||||
|
content = view.viewDefinition || ''
|
||||||
|
} else {
|
||||||
|
// Native View - fetch definition from SQL Server
|
||||||
|
try {
|
||||||
|
const result = await sqlObjectManagerService.getNativeObjectDefinition(
|
||||||
|
state.selectedDataSource || '',
|
||||||
|
view.schemaName,
|
||||||
|
view.viewName
|
||||||
|
)
|
||||||
|
content = result.data || ''
|
||||||
|
} catch (error) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||||
|
{translate('::App.Platform.FailedToLoadDefinition')}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-center' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (objectType === 4) {
|
} else if (objectType === 4) {
|
||||||
// Function
|
// Function
|
||||||
content = (object as SqlFunctionDto).functionBody
|
const func = object as SqlFunctionDto
|
||||||
|
if (func.isCustom) {
|
||||||
|
content = func.functionBody || ''
|
||||||
|
} else {
|
||||||
|
// Native Function - fetch definition from SQL Server
|
||||||
|
try {
|
||||||
|
const result = await sqlObjectManagerService.getNativeObjectDefinition(
|
||||||
|
state.selectedDataSource || '',
|
||||||
|
func.schemaName,
|
||||||
|
func.functionName
|
||||||
|
)
|
||||||
|
content = result.data || ''
|
||||||
|
} catch (error) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||||
|
{translate('::App.Platform.FailedToLoadDefinition')}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-center' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +208,7 @@ const SqlQueryManager = () => {
|
||||||
isSaved: false,
|
isSaved: false,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
[state.isDirty, translate],
|
[state.isDirty, state.selectedDataSource, translate],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleEditorChange = useCallback((value: string | undefined) => {
|
const handleEditorChange = useCallback((value: string | undefined) => {
|
||||||
|
|
@ -345,7 +405,7 @@ GO`,
|
||||||
const handleUseTemplateFromDialog = useCallback(
|
const handleUseTemplateFromDialog = useCallback(
|
||||||
(templateContent: string, templateType: string) => {
|
(templateContent: string, templateType: string) => {
|
||||||
// Check if editor has content
|
// Check if editor has content
|
||||||
const hasUserContent = state.editorContent.trim() && state.isDirty
|
const hasUserContent = state.editorContent?.trim() && state.isDirty
|
||||||
|
|
||||||
if (hasUserContent) {
|
if (hasUserContent) {
|
||||||
// Ask for confirmation
|
// Ask for confirmation
|
||||||
|
|
@ -365,7 +425,7 @@ GO`,
|
||||||
const templateContent = template || getTemplateContent(templateType)
|
const templateContent = template || getTemplateContent(templateType)
|
||||||
|
|
||||||
// Check if editor has content and it's not from a previous template
|
// Check if editor has content and it's not from a previous template
|
||||||
const hasUserContent = state.editorContent.trim() && state.isDirty
|
const hasUserContent = state.editorContent?.trim() && state.isDirty
|
||||||
|
|
||||||
if (hasUserContent) {
|
if (hasUserContent) {
|
||||||
// Ask for confirmation
|
// Ask for confirmation
|
||||||
|
|
@ -405,7 +465,7 @@ GO`,
|
||||||
|
|
||||||
// Seçili text varsa onu, yoksa tüm editor içeriğini kullan
|
// Seçili text varsa onu, yoksa tüm editor içeriğini kullan
|
||||||
const selectedText = editorRef.current?.getSelectedText() || ''
|
const selectedText = editorRef.current?.getSelectedText() || ''
|
||||||
const queryToExecute = selectedText.trim() || state.editorContent.trim()
|
const queryToExecute = selectedText.trim() || state.editorContent?.trim() || ''
|
||||||
|
|
||||||
if (!queryToExecute) {
|
if (!queryToExecute) {
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -471,7 +531,7 @@ GO`,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.editorContent.trim()) {
|
if (!state.editorContent?.trim()) {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||||
{translate('::App.Platform.PleaseEnterContentToSave')}
|
{translate('::App.Platform.PleaseEnterContentToSave')}
|
||||||
|
|
@ -771,7 +831,7 @@ GO`,
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={
|
disabled={
|
||||||
!state.selectedDataSource ||
|
!state.selectedDataSource ||
|
||||||
!state.editorContent.trim() ||
|
!state.editorContent?.trim() ||
|
||||||
(state.isSaved && !state.isDirty) ||
|
(state.isSaved && !state.isDirty) ||
|
||||||
!state.executionResult?.success
|
!state.executionResult?.success
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,10 @@ const SqlObjectExplorer = ({
|
||||||
const deployInfo = p.isDeployed && p.lastDeployedAt
|
const deployInfo = p.isDeployed && p.lastDeployedAt
|
||||||
? ` (${new Date(p.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
|
? ` (${new Date(p.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
|
||||||
: '';
|
: '';
|
||||||
|
const customBadge = p.isCustom ? ' 📝' : ' 🗄️';
|
||||||
return {
|
return {
|
||||||
id: p.id || '',
|
id: p.id!,
|
||||||
label: `${p.displayName || p.procedureName}${p.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
|
label: `${p.displayName || p.procedureName}${customBadge}${p.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
objectType: 2 as SqlObjectType,
|
objectType: 2 as SqlObjectType,
|
||||||
data: p,
|
data: p,
|
||||||
|
|
@ -140,9 +141,10 @@ const SqlObjectExplorer = ({
|
||||||
const deployInfo = v.isDeployed && v.lastDeployedAt
|
const deployInfo = v.isDeployed && v.lastDeployedAt
|
||||||
? ` (${new Date(v.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
|
? ` (${new Date(v.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
|
||||||
: '';
|
: '';
|
||||||
|
const customBadge = v.isCustom ? ' 📝' : ' 🗄️';
|
||||||
return {
|
return {
|
||||||
id: v.id || '',
|
id: v.id!,
|
||||||
label: `${v.displayName || v.viewName}${v.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
|
label: `${v.displayName || v.viewName}${customBadge}${v.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
objectType: 3 as SqlObjectType,
|
objectType: 3 as SqlObjectType,
|
||||||
data: v,
|
data: v,
|
||||||
|
|
@ -160,9 +162,10 @@ const SqlObjectExplorer = ({
|
||||||
const deployInfo = f.isDeployed && f.lastDeployedAt
|
const deployInfo = f.isDeployed && f.lastDeployedAt
|
||||||
? ` (${new Date(f.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
|
? ` (${new Date(f.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
|
||||||
: '';
|
: '';
|
||||||
|
const customBadge = f.isCustom ? ' 📝' : ' 🗄️';
|
||||||
return {
|
return {
|
||||||
id: f.id || '',
|
id: f.id!,
|
||||||
label: `${f.displayName || f.functionName}${f.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
|
label: `${f.displayName || f.functionName}${customBadge}${f.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
objectType: 4 as SqlObjectType,
|
objectType: 4 as SqlObjectType,
|
||||||
data: f,
|
data: f,
|
||||||
|
|
@ -289,7 +292,12 @@ const SqlObjectExplorer = ({
|
||||||
// Column clicked - do nothing or show info
|
// Column clicked - do nothing or show info
|
||||||
return
|
return
|
||||||
} else if (node.type === 'object' && node.data) {
|
} else if (node.type === 'object' && node.data) {
|
||||||
if (node.objectType) {
|
// Check if it's a table - generate SELECT template
|
||||||
|
if (node.id.startsWith('table-') && onTemplateSelect) {
|
||||||
|
const table = node.data as any
|
||||||
|
const selectQuery = `SELECT TOP 10 * \nFROM ${table.fullName || `[${table.schemaName}].[${table.tableName}]`};`
|
||||||
|
onTemplateSelect(selectQuery, 'table-select')
|
||||||
|
} else if (node.objectType) {
|
||||||
onObjectSelect(node.data, node.objectType)
|
onObjectSelect(node.data, node.objectType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +506,7 @@ const SqlObjectExplorer = ({
|
||||||
className="fixed z-50 bg-white dark:bg-gray-800 shadow-lg rounded border border-gray-200 dark:border-gray-700 py-1"
|
className="fixed z-50 bg-white dark:bg-gray-800 shadow-lg rounded border border-gray-200 dark:border-gray-700 py-1"
|
||||||
style={{ top: contextMenu.y, left: contextMenu.x }}
|
style={{ top: contextMenu.y, left: contextMenu.x }}
|
||||||
>
|
>
|
||||||
{contextMenu.node?.type === 'object' && contextMenu.node?.objectType && (
|
{contextMenu.node?.type === 'object' && contextMenu.node?.objectType && contextMenu.node?.data?.isCustom && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
|
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
|
||||||
|
|
@ -532,6 +540,12 @@ const SqlObjectExplorer = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{contextMenu.node?.type === 'object' && contextMenu.node?.objectType && !contextMenu.node?.data?.isCustom && (
|
||||||
|
<div className="px-4 py-2 text-xs text-gray-500 italic">
|
||||||
|
{translate('::App.Platform.NativeObjectViewOnly')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{contextMenu.node?.type === 'folder' && (
|
{contextMenu.node?.type === 'folder' && (
|
||||||
<button
|
<button
|
||||||
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
|
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue