From 932ee406b11b6bafa79a9dae250965c1f4e536a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:56:39 +0300 Subject: [PATCH] =?UTF-8?q?Sql=20Object=20AppServis=20ba=C5=9Fl=C4=B1?= =?UTF-8?q?=C4=9F=C4=B1nda=20t=C3=BCm=20appservisler=20birle=C5=9Ftirildi.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DatabaseObjectDto.cs | 16 + .../ISqlFunctionAppService.cs | 29 -- .../ISqlObjectManagerAppService.cs | 44 ++ .../ISqlQueryAppService.cs | 39 -- .../ISqlStoredProcedureAppService.cs | 29 -- .../ISqlTemplateAppService.cs | 34 -- .../ISqlViewAppService.cs | 29 -- .../SqlObjectExplorerDto.cs | 39 ++ .../SqlFunctionAppService.cs | 139 ------ .../SqlObjectManagerAppService.cs | 417 ++++++++++++++++++ .../SqlQueryAppService.cs | 124 ------ .../SqlStoredProcedureAppService.cs | 134 ------ .../SqlTemplateAppService.cs | 59 --- .../SqlViewAppService.cs | 133 ------ .../Seeds/LanguagesData.json | 6 + ui/src/proxy/sql-query-manager/models.ts | 24 + ui/src/services/sql-query-manager.service.ts | 392 +++------------- .../views/sqlQueryManager/SqlQueryManager.tsx | 41 +- .../components/SqlObjectExplorer.tsx | 266 ++++++++--- 19 files changed, 840 insertions(+), 1154 deletions(-) create mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/DatabaseObjectDto.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlFunctionAppService.cs create mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlQueryAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlStoredProcedureAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlTemplateAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlViewAppService.cs create mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlObjectExplorerDto.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlFunctionAppService.cs create mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlStoredProcedureAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlTemplateAppService.cs delete mode 100644 api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlViewAppService.cs diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/DatabaseObjectDto.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/DatabaseObjectDto.cs new file mode 100644 index 00000000..1546937a --- /dev/null +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/DatabaseObjectDto.cs @@ -0,0 +1,16 @@ +namespace Erp.SqlQueryManager.Application.Contracts; + +public class DatabaseTableDto +{ + public string SchemaName { get; set; } + public string TableName { get; set; } + public string FullName => $"{SchemaName}.{TableName}"; +} + +public class DatabaseColumnDto +{ + public string ColumnName { get; set; } + public string DataType { get; set; } + public bool IsNullable { get; set; } + public int? MaxLength { get; set; } +} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlFunctionAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlFunctionAppService.cs deleted file mode 100644 index 99179ef5..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlFunctionAppService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Erp.SqlQueryManager.Application.Contracts; - -public interface ISqlFunctionAppService : ICrudAppService< - SqlFunctionDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlFunctionDto, - UpdateSqlFunctionDto> -{ - /// - /// Deploy function to database - /// - Task DeployAsync(DeployFunctionDto input); - - /// - /// Check if function exists in database - /// - Task CheckExistsAsync(Guid id); - - /// - /// Drop function from database - /// - Task DropAsync(Guid id); -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs new file mode 100644 index 00000000..432cbe9b --- /dev/null +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Erp.SqlQueryManager.Application.Contracts; + +/// +/// Unified service for SQL Object Explorer and CRUD operations +/// +public interface ISqlObjectManagerAppService : IApplicationService +{ + /// + /// Get all SQL objects for Object Explorer (Queries, SPs, Views, Functions, Tables, Templates) + /// + /// Data source code to filter objects + /// Combined response with all object types + Task GetAllObjectsAsync(string dataSourceCode); + + // Query Operations + Task CreateQueryAsync(CreateSqlQueryDto input); + Task UpdateQueryAsync(Guid id, UpdateSqlQueryDto input); + Task DeleteQueryAsync(Guid id); + Task ExecuteQueryAsync(ExecuteSqlQueryDto input); + Task ExecuteSavedQueryAsync(Guid id); + + // Stored Procedure Operations + Task UpdateStoredProcedureAsync(Guid id, UpdateSqlStoredProcedureDto input); + Task DeleteStoredProcedureAsync(Guid id); + Task DeployStoredProcedureAsync(DeployStoredProcedureDto input); + + // View Operations + Task UpdateViewAsync(Guid id, UpdateSqlViewDto input); + Task DeleteViewAsync(Guid id); + Task DeployViewAsync(DeployViewDto input); + + // Function Operations + Task UpdateFunctionAsync(Guid id, UpdateSqlFunctionDto input); + Task DeleteFunctionAsync(Guid id); + Task DeployFunctionAsync(DeployFunctionDto input); + + // Database Metadata Operations + Task> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName); +} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlQueryAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlQueryAppService.cs deleted file mode 100644 index f7a0e794..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlQueryAppService.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Erp.SqlQueryManager.Application.Contracts; - -public interface ISqlQueryAppService : ICrudAppService< - SqlQueryDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlQueryDto, - UpdateSqlQueryDto> -{ - /// - /// Execute a SQL query - /// - Task ExecuteQueryAsync(ExecuteSqlQueryDto input); - - /// - /// Execute a saved query by ID - /// - Task ExecuteSavedQueryAsync(Guid id); - - /// - /// Validate SQL query syntax - /// - Task<(bool IsValid, string ErrorMessage)> ValidateQueryAsync(string sql); - - /// - /// Activate query - /// - Task ActivateAsync(Guid id); - - /// - /// Archive query - /// - Task ArchiveAsync(Guid id); -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlStoredProcedureAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlStoredProcedureAppService.cs deleted file mode 100644 index 48edcb87..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlStoredProcedureAppService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Erp.SqlQueryManager.Application.Contracts; - -public interface ISqlStoredProcedureAppService : ICrudAppService< - SqlStoredProcedureDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlStoredProcedureDto, - UpdateSqlStoredProcedureDto> -{ - /// - /// Deploy stored procedure to database - /// - Task DeployAsync(DeployStoredProcedureDto input); - - /// - /// Check if procedure exists in database - /// - Task CheckExistsAsync(Guid id); - - /// - /// Drop procedure from database - /// - Task DropAsync(Guid id); -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlTemplateAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlTemplateAppService.cs deleted file mode 100644 index 9f192b33..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlTemplateAppService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Erp.SqlQueryManager.Domain.Shared; -using Volo.Abp.Application.Services; - -namespace Erp.SqlQueryManager.Application.Contracts; - -public interface ISqlTemplateAppService : IApplicationService -{ - /// - /// Get all available query templates - /// - Task> GetQueryTemplatesAsync(); - - /// - /// Get stored procedure template - /// - Task GetStoredProcedureTemplateAsync(string procedureName, string schemaName = "dbo"); - - /// - /// Get view template - /// - Task GetViewTemplateAsync(string viewName, string schemaName = "dbo", bool withSchemaBinding = false); - - /// - /// Get function template - /// - Task GetFunctionTemplateAsync(string functionName, SqlFunctionType functionType, string schemaName = "dbo"); - - /// - /// Get specific query template - /// - Task GetQueryTemplateAsync(string templateType); -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlViewAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlViewAppService.cs deleted file mode 100644 index 4fb02584..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlViewAppService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Threading.Tasks; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Erp.SqlQueryManager.Application.Contracts; - -public interface ISqlViewAppService : ICrudAppService< - SqlViewDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlViewDto, - UpdateSqlViewDto> -{ - /// - /// Deploy view to database - /// - Task DeployAsync(DeployViewDto input); - - /// - /// Check if view exists in database - /// - Task CheckExistsAsync(Guid id); - - /// - /// Drop view from database - /// - Task DropAsync(Guid id); -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlObjectExplorerDto.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlObjectExplorerDto.cs new file mode 100644 index 00000000..c3440dde --- /dev/null +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlObjectExplorerDto.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace Erp.SqlQueryManager.Application.Contracts; + +/// +/// Combined DTO for Object Explorer containing all SQL objects +/// +public class SqlObjectExplorerDto +{ + /// + /// SQL Queries + /// + public List Queries { get; set; } = new(); + + /// + /// Stored Procedures + /// + public List StoredProcedures { get; set; } = new(); + + /// + /// Views + /// + public List Views { get; set; } = new(); + + /// + /// Functions + /// + public List Functions { get; set; } = new(); + + /// + /// Database Tables + /// + public List Tables { get; set; } = new(); + + /// + /// Query Templates + /// + public List Templates { get; set; } = new(); +} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlFunctionAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlFunctionAppService.cs deleted file mode 100644 index 2ce9b3bc..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlFunctionAppService.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Threading.Tasks; -using Erp.SqlQueryManager.Application.Contracts; -using Erp.SqlQueryManager.Domain.Entities; -using Erp.SqlQueryManager.Domain.Services; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Erp.SqlQueryManager.Application; - -public class SqlFunctionAppService : CrudAppService< - SqlFunction, - SqlFunctionDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlFunctionDto, - UpdateSqlFunctionDto>, ISqlFunctionAppService -{ - private readonly ISqlExecutorService _sqlExecutorService; - - public SqlFunctionAppService( - IRepository repository, - ISqlExecutorService sqlExecutorService) : base(repository) - { - _sqlExecutorService = sqlExecutorService; - } - - public override async Task CreateAsync(CreateSqlFunctionDto input) - { - var entity = new SqlFunction( - GuidGenerator.Create(), - input.FunctionName, - input.SchemaName ?? "dbo", - input.DisplayName, - input.FunctionType, - input.FunctionBody, - input.ReturnType, - input.DataSourceCode, - CurrentTenant.Id) - { - Description = input.Description, - Category = input.Category, - Parameters = input.Parameters - }; - - await Repository.InsertAsync(entity); - return ObjectMapper.Map(entity); - } - - public override async Task UpdateAsync(Guid id, UpdateSqlFunctionDto input) - { - var entity = await Repository.GetAsync(id); - - entity.DisplayName = input.DisplayName; - entity.Description = input.Description; - entity.UpdateBody(input.FunctionBody); - entity.ReturnType = input.ReturnType; - entity.Category = input.Category; - entity.Parameters = input.Parameters; - - await Repository.UpdateAsync(entity); - return ObjectMapper.Map(entity); - } - - public async Task DeployAsync(DeployFunctionDto input) - { - var function = await Repository.GetAsync(input.Id); - - // Drop if exists and requested - if (input.DropIfExists) - { - var objectType = function.FunctionType.ToString().ToUpperInvariant().Replace("FUNCTION", "_FUNCTION"); - await _sqlExecutorService.DropObjectAsync( - function.FunctionName, - $"SQL_{objectType}", - function.DataSourceCode, - function.SchemaName); - } - - // Deploy the function - var result = await _sqlExecutorService.DeployFunctionAsync( - function.FunctionBody, - function.DataSourceCode); - - if (result.Success) - { - function.MarkAsDeployed(); - await Repository.UpdateAsync(function); - } - - return MapExecutionResult(result); - } - - public async Task CheckExistsAsync(Guid id) - { - var function = await Repository.GetAsync(id); - var objectType = function.FunctionType.ToString().ToUpperInvariant().Replace("FUNCTION", "_FUNCTION"); - - return await _sqlExecutorService.CheckObjectExistsAsync( - function.FunctionName, - $"SQL_{objectType}", - function.DataSourceCode, - function.SchemaName); - } - - public async Task DropAsync(Guid id) - { - var function = await Repository.GetAsync(id); - var objectType = function.FunctionType.ToString().ToUpperInvariant().Replace("FUNCTION", "_FUNCTION"); - - var result = await _sqlExecutorService.DropObjectAsync( - function.FunctionName, - $"SQL_{objectType}", - function.DataSourceCode, - function.SchemaName); - - if (result.Success) - { - function.IsDeployed = false; - await Repository.UpdateAsync(function); - } - - return MapExecutionResult(result); - } - - 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 - }; - } -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs new file mode 100644 index 00000000..b0fdba7e --- /dev/null +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs @@ -0,0 +1,417 @@ +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.Mvc; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace Erp.SqlQueryManager.Application; + +/// +/// Unified service for SQL Object Explorer +/// Combines all SQL objects into a single endpoint +/// +public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerAppService +{ + private readonly IRepository _queryRepository; + private readonly IRepository _procedureRepository; + private readonly IRepository _viewRepository; + private readonly IRepository _functionRepository; + private readonly ISqlExecutorService _sqlExecutorService; + private readonly ISqlTemplateProvider _templateProvider; + + public SqlObjectManagerAppService( + IRepository queryRepository, + IRepository procedureRepository, + IRepository viewRepository, + IRepository functionRepository, + ISqlExecutorService sqlExecutorService, + ISqlTemplateProvider templateProvider) + { + _queryRepository = queryRepository; + _procedureRepository = procedureRepository; + _viewRepository = viewRepository; + _functionRepository = functionRepository; + _sqlExecutorService = sqlExecutorService; + _templateProvider = templateProvider; + } + + public async Task GetAllObjectsAsync(string dataSourceCode) + { + var result = new SqlObjectExplorerDto(); + + // Get all queries for this data source + var queries = await _queryRepository.GetListAsync(); + result.Queries = queries + .Where(q => q.DataSourceCode == dataSourceCode) + .Select(q => new SqlQueryDto + { + Id = q.Id, + Code = q.Code, + Name = q.Name, + Description = q.Description, + QueryText = q.QueryText, + DataSourceCode = q.DataSourceCode, + Status = q.Status, + Category = q.Category, + Tags = q.Tags, + IsModifyingData = q.IsModifyingData, + Parameters = q.Parameters, + ExecutionCount = q.ExecutionCount, + LastExecutedAt = q.LastExecutedAt + }) + .ToList(); + + // Get all stored procedures for this data source + var procedures = await _procedureRepository.GetListAsync(); + result.StoredProcedures = procedures + .Where(p => p.DataSourceCode == dataSourceCode) + .Select(p => new SqlStoredProcedureDto + { + Id = p.Id, + ProcedureName = p.ProcedureName, + SchemaName = p.SchemaName, + DisplayName = p.DisplayName, + Description = p.Description, + ProcedureBody = p.ProcedureBody, + DataSourceCode = p.DataSourceCode, + Category = p.Category, + Parameters = p.Parameters, + IsDeployed = p.IsDeployed, + LastDeployedAt = p.LastDeployedAt + }) + .ToList(); + + // Get all views for this data source + var views = await _viewRepository.GetListAsync(); + result.Views = views + .Where(v => v.DataSourceCode == dataSourceCode) + .Select(v => new SqlViewDto + { + Id = v.Id, + ViewName = v.ViewName, + SchemaName = v.SchemaName, + DisplayName = v.DisplayName, + Description = v.Description, + ViewDefinition = v.ViewDefinition, + DataSourceCode = v.DataSourceCode, + Category = v.Category, + WithSchemaBinding = v.WithSchemaBinding, + IsDeployed = v.IsDeployed, + LastDeployedAt = v.LastDeployedAt + }) + .ToList(); + + // Get all functions for this data source + var functions = await _functionRepository.GetListAsync(); + result.Functions = functions + .Where(f => f.DataSourceCode == dataSourceCode) + .Select(f => new SqlFunctionDto + { + Id = f.Id, + FunctionName = f.FunctionName, + SchemaName = f.SchemaName, + DisplayName = f.DisplayName, + Description = f.Description, + FunctionType = f.FunctionType, + FunctionBody = f.FunctionBody, + ReturnType = f.ReturnType, + DataSourceCode = f.DataSourceCode, + Category = f.Category, + Parameters = f.Parameters, + IsDeployed = f.IsDeployed, + LastDeployedAt = f.LastDeployedAt + }) + .ToList(); + + // Get all database tables + result.Tables = await GetTablesAsync(dataSourceCode); + + // Get all templates + result.Templates = _templateProvider.GetAvailableQueryTemplates() + .Select(t => new SqlTemplateDto + { + Type = t.Type, + Name = t.Name, + Description = t.Description, + Template = _templateProvider.GetQueryTemplate(t.Type) + }) + .ToList(); + + return result; + } + + private async Task> GetTablesAsync(string dataSourceCode) + { + var query = @" + SELECT + SCHEMA_NAME(t.schema_id) AS SchemaName, + t.name AS TableName + FROM + sys.tables t + WHERE + t.is_ms_shipped = 0 + ORDER BY + SCHEMA_NAME(t.schema_id), t.name"; + + var result = await _sqlExecutorService.ExecuteQueryAsync(query, dataSourceCode); + + var tables = new List(); + if (result.Success && result.Data != null) + { + foreach (var row in result.Data) + { + var dict = row as System.Collections.Generic.IDictionary; + if (dict != null) + { + tables.Add(new DatabaseTableDto + { + SchemaName = dict["SchemaName"]?.ToString() ?? "dbo", + TableName = dict["TableName"]?.ToString() ?? "" + }); + } + } + } + + return tables; + } + + #region Query Operations + + public async Task CreateQueryAsync(CreateSqlQueryDto input) + { + var query = ObjectMapper.Map(input); + query.Status = SqlQueryStatus.Draft; + + var created = await _queryRepository.InsertAsync(query, autoSave: true); + return ObjectMapper.Map(created); + } + + public async Task UpdateQueryAsync(Guid id, UpdateSqlQueryDto input) + { + var query = await _queryRepository.GetAsync(id); + + query.Name = input.Name; + query.Description = input.Description; + query.QueryText = input.QueryText; + query.Category = input.Category; + query.Tags = input.Tags; + + var updated = await _queryRepository.UpdateAsync(query, autoSave: true); + return ObjectMapper.Map(updated); + } + + public async Task DeleteQueryAsync(Guid id) + { + await _queryRepository.DeleteAsync(id); + } + + public async Task ExecuteQueryAsync(ExecuteSqlQueryDto input) + { + var result = await _sqlExecutorService.ExecuteQueryAsync(input.QueryText, input.DataSourceCode); + return MapExecutionResult(result); + } + + public async Task 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 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; + + var updated = await _procedureRepository.UpdateAsync(procedure, autoSave: true); + return ObjectMapper.Map(updated); + } + + public async Task DeleteStoredProcedureAsync(Guid id) + { + await _procedureRepository.DeleteAsync(id); + } + + public async Task DeployStoredProcedureAsync(DeployStoredProcedureDto input) + { + var procedure = await _procedureRepository.GetAsync(input.Id); + 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); + } + + #endregion + + #region View Operations + + public async Task 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; + + var updated = await _viewRepository.UpdateAsync(view, autoSave: true); + return ObjectMapper.Map(updated); + } + + public async Task DeleteViewAsync(Guid id) + { + await _viewRepository.DeleteAsync(id); + } + + public async Task DeployViewAsync(DeployViewDto input) + { + var view = await _viewRepository.GetAsync(input.Id); + 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); + } + + #endregion + + #region Function Operations + + public async Task 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; + + var updated = await _functionRepository.UpdateAsync(function, autoSave: true); + return ObjectMapper.Map(updated); + } + + public async Task DeleteFunctionAsync(Guid id) + { + await _functionRepository.DeleteAsync(id); + } + + public async Task DeployFunctionAsync(DeployFunctionDto input) + { + var function = await _functionRepository.GetAsync(input.Id); + 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); + } + + #endregion + + #region Database Metadata Operations + + public async Task> 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(); + if (result.Success && result.Data != null) + { + foreach (var row in result.Data) + { + var dict = row as System.Collections.Generic.IDictionary; + 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 + + #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 +} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryAppService.cs deleted file mode 100644 index c0d55036..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryAppService.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Erp.SqlQueryManager.Application.Contracts; -using Erp.SqlQueryManager.Domain.Entities; -using Erp.SqlQueryManager.Domain.Services; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Erp.SqlQueryManager.Application; - -public class SqlQueryAppService : CrudAppService< - SqlQuery, - SqlQueryDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlQueryDto, - UpdateSqlQueryDto>, ISqlQueryAppService -{ - private readonly ISqlExecutorService _sqlExecutorService; - - public SqlQueryAppService( - IRepository repository, - ISqlExecutorService sqlExecutorService) : base(repository) - { - _sqlExecutorService = sqlExecutorService; - } - - public override async Task CreateAsync(CreateSqlQueryDto input) - { - var entity = new SqlQuery( - GuidGenerator.Create(), - input.Code, - input.Name, - input.QueryText, - input.DataSourceCode, - CurrentTenant.Id) - { - Description = input.Description, - Category = input.Category, - Tags = input.Tags, - IsModifyingData = input.IsModifyingData, - Parameters = input.Parameters - }; - - await Repository.InsertAsync(entity); - return ObjectMapper.Map(entity); - } - - public override async Task UpdateAsync(Guid id, UpdateSqlQueryDto input) - { - var entity = await Repository.GetAsync(id); - - entity.Name = input.Name; - entity.Description = input.Description; - entity.UpdateQueryText(input.QueryText); - entity.DataSourceCode = input.DataSourceCode; - entity.Category = input.Category; - entity.Tags = input.Tags; - entity.IsModifyingData = input.IsModifyingData; - entity.Parameters = input.Parameters; - - await Repository.UpdateAsync(entity); - return ObjectMapper.Map(entity); - } - - public async Task ExecuteQueryAsync(ExecuteSqlQueryDto input) - { - var result = input.QueryText.TrimStart().StartsWith("SELECT", StringComparison.OrdinalIgnoreCase) - ? await _sqlExecutorService.ExecuteQueryAsync(input.QueryText, input.DataSourceCode, input.Parameters) - : await _sqlExecutorService.ExecuteNonQueryAsync(input.QueryText, input.DataSourceCode, input.Parameters); - - return MapExecutionResult(result); - } - - public async Task ExecuteSavedQueryAsync(Guid id) - { - var query = await Repository.GetAsync(id); - - var result = query.IsModifyingData - ? await _sqlExecutorService.ExecuteNonQueryAsync(query.QueryText, query.DataSourceCode) - : await _sqlExecutorService.ExecuteQueryAsync(query.QueryText, query.DataSourceCode); - - // Update execution statistics - query.MarkAsExecuted(); - await Repository.UpdateAsync(query); - - return MapExecutionResult(result); - } - - public async Task<(bool IsValid, string ErrorMessage)> ValidateQueryAsync(string sql) - { - return await _sqlExecutorService.ValidateSqlAsync(sql); - } - - public async Task ActivateAsync(Guid id) - { - var entity = await Repository.GetAsync(id); - entity.Activate(); - await Repository.UpdateAsync(entity); - } - - public async Task ArchiveAsync(Guid id) - { - var entity = await Repository.GetAsync(id); - entity.Archive(); - await Repository.UpdateAsync(entity); - } - - 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 - }; - } -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlStoredProcedureAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlStoredProcedureAppService.cs deleted file mode 100644 index 2208577c..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlStoredProcedureAppService.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Threading.Tasks; -using Erp.SqlQueryManager.Application.Contracts; -using Erp.SqlQueryManager.Domain.Entities; -using Erp.SqlQueryManager.Domain.Services; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Erp.SqlQueryManager.Application; - -public class SqlStoredProcedureAppService : CrudAppService< - SqlStoredProcedure, - SqlStoredProcedureDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlStoredProcedureDto, - UpdateSqlStoredProcedureDto>, ISqlStoredProcedureAppService -{ - private readonly ISqlExecutorService _sqlExecutorService; - - public SqlStoredProcedureAppService( - IRepository repository, - ISqlExecutorService sqlExecutorService) : base(repository) - { - _sqlExecutorService = sqlExecutorService; - } - - public override async Task CreateAsync(CreateSqlStoredProcedureDto input) - { - var entity = new SqlStoredProcedure( - GuidGenerator.Create(), - input.ProcedureName, - input.SchemaName ?? "dbo", - input.DisplayName, - input.ProcedureBody, - input.DataSourceCode, - CurrentTenant.Id) - { - Description = input.Description, - Category = input.Category, - Parameters = input.Parameters - }; - - await Repository.InsertAsync(entity); - return ObjectMapper.Map(entity); - } - - public override async Task UpdateAsync(Guid id, UpdateSqlStoredProcedureDto input) - { - var entity = await Repository.GetAsync(id); - - entity.DisplayName = input.DisplayName; - entity.Description = input.Description; - entity.UpdateBody(input.ProcedureBody); - entity.Category = input.Category; - entity.Parameters = input.Parameters; - - await Repository.UpdateAsync(entity); - return ObjectMapper.Map(entity); - } - - public async Task DeployAsync(DeployStoredProcedureDto input) - { - var procedure = await Repository.GetAsync(input.Id); - - // Drop if exists and requested - if (input.DropIfExists) - { - await _sqlExecutorService.DropObjectAsync( - procedure.ProcedureName, - "SQL_STORED_PROCEDURE", - procedure.DataSourceCode, - procedure.SchemaName); - } - - // Deploy the procedure - var result = await _sqlExecutorService.DeployStoredProcedureAsync( - procedure.ProcedureBody, - procedure.DataSourceCode); - - if (result.Success) - { - procedure.MarkAsDeployed(); - await Repository.UpdateAsync(procedure); - } - - return MapExecutionResult(result); - } - - public async Task CheckExistsAsync(Guid id) - { - var procedure = await Repository.GetAsync(id); - - return await _sqlExecutorService.CheckObjectExistsAsync( - procedure.ProcedureName, - "SQL_STORED_PROCEDURE", - procedure.DataSourceCode, - procedure.SchemaName); - } - - public async Task DropAsync(Guid id) - { - var procedure = await Repository.GetAsync(id); - - var result = await _sqlExecutorService.DropObjectAsync( - procedure.ProcedureName, - "SQL_STORED_PROCEDURE", - procedure.DataSourceCode, - procedure.SchemaName); - - if (result.Success) - { - procedure.IsDeployed = false; - await Repository.UpdateAsync(procedure); - } - - return MapExecutionResult(result); - } - - 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 - }; - } -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlTemplateAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlTemplateAppService.cs deleted file mode 100644 index 3387159f..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlTemplateAppService.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Erp.SqlQueryManager.Application.Contracts; -using Erp.SqlQueryManager.Domain.Services; -using Erp.SqlQueryManager.Domain.Shared; -using Microsoft.AspNetCore.Mvc; -using Volo.Abp.Application.Services; - -namespace Erp.SqlQueryManager.Application; - -public class SqlTemplateAppService : ApplicationService, ISqlTemplateAppService -{ - private readonly ISqlTemplateProvider _templateProvider; - - public SqlTemplateAppService(ISqlTemplateProvider templateProvider) - { - _templateProvider = templateProvider; - } - - public Task> GetQueryTemplatesAsync() - { - var templates = _templateProvider.GetAvailableQueryTemplates() - .Select(t => new SqlTemplateDto - { - Type = t.Type, - Name = t.Name, - Description = t.Description, - Template = _templateProvider.GetQueryTemplate(t.Type) - }) - .ToList(); - - return Task.FromResult(templates); - } - - public Task GetStoredProcedureTemplateAsync(string procedureName, string schemaName = "dbo") - { - var template = _templateProvider.GetStoredProcedureTemplate(procedureName, schemaName); - return Task.FromResult(template); - } - - public Task GetViewTemplateAsync(string viewName, string schemaName = "dbo", bool withSchemaBinding = false) - { - var template = _templateProvider.GetViewTemplate(viewName, schemaName, withSchemaBinding); - return Task.FromResult(template); - } - - public Task GetFunctionTemplateAsync(string functionName, SqlFunctionType functionType, string schemaName = "dbo") - { - var template = _templateProvider.GetFunctionTemplate(functionName, functionType, schemaName); - return Task.FromResult(template); - } - - public Task GetQueryTemplateAsync(string templateType) - { - var template = _templateProvider.GetQueryTemplate(templateType); - return Task.FromResult(template); - } -} diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlViewAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlViewAppService.cs deleted file mode 100644 index 447aecca..00000000 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlViewAppService.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Threading.Tasks; -using Erp.SqlQueryManager.Application.Contracts; -using Erp.SqlQueryManager.Domain.Entities; -using Erp.SqlQueryManager.Domain.Services; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Erp.SqlQueryManager.Application; - -public class SqlViewAppService : CrudAppService< - SqlView, - SqlViewDto, - Guid, - PagedAndSortedResultRequestDto, - CreateSqlViewDto, - UpdateSqlViewDto>, ISqlViewAppService -{ - private readonly ISqlExecutorService _sqlExecutorService; - - public SqlViewAppService( - IRepository repository, - ISqlExecutorService sqlExecutorService) : base(repository) - { - _sqlExecutorService = sqlExecutorService; - } - - public override async Task CreateAsync(CreateSqlViewDto input) - { - var entity = new SqlView( - GuidGenerator.Create(), - input.ViewName, - input.SchemaName ?? "dbo", - input.DisplayName, - input.ViewDefinition, - input.DataSourceCode, - CurrentTenant.Id) - { - Description = input.Description, - Category = input.Category, - WithSchemaBinding = input.WithSchemaBinding - }; - - await Repository.InsertAsync(entity); - return ObjectMapper.Map(entity); - } - - public override async Task UpdateAsync(Guid id, UpdateSqlViewDto input) - { - var entity = await Repository.GetAsync(id); - - entity.DisplayName = input.DisplayName; - entity.Description = input.Description; - entity.UpdateDefinition(input.ViewDefinition); - entity.Category = input.Category; - entity.WithSchemaBinding = input.WithSchemaBinding; - - await Repository.UpdateAsync(entity); - return ObjectMapper.Map(entity); - } - - public async Task DeployAsync(DeployViewDto input) - { - var view = await Repository.GetAsync(input.Id); - - // Drop if exists and requested - if (input.DropIfExists) - { - await _sqlExecutorService.DropObjectAsync( - view.ViewName, - "VIEW", - view.DataSourceCode, - view.SchemaName); - } - - // Deploy the view - var result = await _sqlExecutorService.DeployViewAsync( - view.ViewDefinition, - view.DataSourceCode); - - if (result.Success) - { - view.MarkAsDeployed(); - await Repository.UpdateAsync(view); - } - - return MapExecutionResult(result); - } - - public async Task CheckExistsAsync(Guid id) - { - var view = await Repository.GetAsync(id); - - return await _sqlExecutorService.CheckObjectExistsAsync( - view.ViewName, - "VIEW", - view.DataSourceCode, - view.SchemaName); - } - - public async Task DropAsync(Guid id) - { - var view = await Repository.GetAsync(id); - - var result = await _sqlExecutorService.DropObjectAsync( - view.ViewName, - "VIEW", - view.DataSourceCode, - view.SchemaName); - - if (result.Success) - { - view.IsDeployed = false; - await Repository.UpdateAsync(view); - } - - return MapExecutionResult(result); - } - - 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 - }; - } -} diff --git a/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json index 01df8863..09708996 100644 --- a/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json @@ -10513,6 +10513,12 @@ "tr": "View", "en": "View" }, + { + "resourceName": "Platform", + "key": "App.Platform.Tables", + "tr": "Tablolar", + "en": "Tables" + }, { "resourceName": "Platform", "key": "App.Platform.Function", diff --git a/ui/src/proxy/sql-query-manager/models.ts b/ui/src/proxy/sql-query-manager/models.ts index 6754d0e3..2f237c04 100644 --- a/ui/src/proxy/sql-query-manager/models.ts +++ b/ui/src/proxy/sql-query-manager/models.ts @@ -244,3 +244,27 @@ export interface GetSqlViewsInput extends PagedAndSortedResultRequestDto { status?: SqlQueryStatus category?: string } + +// Database Metadata DTOs +export interface DatabaseTableDto { + schemaName: string + tableName: string + fullName: string +} + +export interface DatabaseColumnDto { + columnName: string + dataType: string + isNullable: boolean + maxLength?: number +} + +// Unified Object Explorer Response +export interface SqlObjectExplorerDto { + queries: SqlQueryDto[] + storedProcedures: SqlStoredProcedureDto[] + views: SqlViewDto[] + functions: SqlFunctionDto[] + tables: DatabaseTableDto[] + templates: SqlTemplateDto[] +} diff --git a/ui/src/services/sql-query-manager.service.ts b/ui/src/services/sql-query-manager.service.ts index 6753deac..961ad416 100644 --- a/ui/src/services/sql-query-manager.service.ts +++ b/ui/src/services/sql-query-manager.service.ts @@ -1,159 +1,66 @@ import apiService, { Config } from '@/services/api.service' -import type { PagedResultDto } from '@/proxy' import type { SqlFunctionDto, - CreateSqlFunctionDto, UpdateSqlFunctionDto, - DeployFunctionDto, SqlQueryDto, - CreateSqlQueryDto, UpdateSqlQueryDto, + CreateSqlQueryDto, ExecuteSqlQueryDto, - ExecuteSavedQueryDto, - ValidateQueryDto, SqlStoredProcedureDto, - CreateSqlStoredProcedureDto, UpdateSqlStoredProcedureDto, DeployStoredProcedureDto, SqlViewDto, - CreateSqlViewDto, UpdateSqlViewDto, DeployViewDto, - SqlTemplateDto, + DeployFunctionDto, SqlQueryExecutionResultDto, - GetSqlFunctionsInput, - GetSqlQueriesInput, - GetSqlStoredProceduresInput, - GetSqlViewsInput, + DatabaseColumnDto, + DatabaseTableDto, + SqlObjectExplorerDto, } from '@/proxy/sql-query-manager/models' -export class SqlFunctionService { +export class SqlObjectManagerService { apiName = 'Default' - create = (input: CreateSqlFunctionDto, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: '/api/app/sql-function', - data: input, - }, - { apiName: this.apiName, ...config }, - ) - - getList = (input: GetSqlFunctionsInput, config?: Partial) => - apiService.fetchData, GetSqlFunctionsInput>( + /** + * Get all SQL objects for Object Explorer in a single call + */ + getAllObjects = (dataSourceCode: string, config?: Partial) => + apiService.fetchData( { method: 'GET', - url: '/api/app/sql-function', - params: input, + url: '/api/app/sql-object-manager/objects', + params: { dataSourceCode }, }, { apiName: this.apiName, ...config }, ) - get = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'GET', - url: `/api/app/sql-function/${id}`, - }, - { apiName: this.apiName, ...config }, - ) - - update = (id: string, input: UpdateSqlFunctionDto, config?: Partial) => - apiService.fetchData( - { - method: 'PUT', - url: `/api/app/sql-function/${id}`, - data: input, - }, - { apiName: this.apiName, ...config }, - ) - - delete = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'DELETE', - url: `/api/app/sql-function/${id}`, - }, - { apiName: this.apiName, ...config }, - ) - - deploy = (input: DeployFunctionDto, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: '/api/app/sql-function/deploy', - data: input, - }, - { apiName: this.apiName, ...config }, - ) - - checkExists = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: `/api/app/sql-function/${id}/check-exists`, - }, - { apiName: this.apiName, ...config }, - ) - - drop = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: `/api/app/sql-function/${id}/drop`, - }, - { apiName: this.apiName, ...config }, - ) -} - -export class SqlQueryService { - apiName = 'Default' - - create = (input: CreateSqlQueryDto, config?: Partial) => + // Query Operations + createQuery = (input: CreateSqlQueryDto, config?: Partial) => apiService.fetchData( { method: 'POST', - url: '/api/app/sql-query', + url: '/api/app/sql-object-manager/query', data: input, }, { apiName: this.apiName, ...config }, ) - getList = (input: GetSqlQueriesInput, config?: Partial) => - apiService.fetchData, GetSqlQueriesInput>( - { - method: 'GET', - url: '/api/app/sql-query', - params: input, - }, - { apiName: this.apiName, ...config }, - ) - - get = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'GET', - url: `/api/app/sql-query/${id}`, - }, - { apiName: this.apiName, ...config }, - ) - - update = (id: string, input: UpdateSqlQueryDto, config?: Partial) => + updateQuery = (id: string, input: UpdateSqlQueryDto, config?: Partial) => apiService.fetchData( { method: 'PUT', - url: `/api/app/sql-query/${id}`, + url: `/api/app/sql-object-manager/query/${id}`, data: input, }, { apiName: this.apiName, ...config }, ) - delete = (id: string, config?: Partial) => + deleteQuery = (id: string, config?: Partial) => apiService.fetchData( { method: 'DELETE', - url: `/api/app/sql-query/${id}`, + url: `/api/app/sql-object-manager/query/${id}`, }, { apiName: this.apiName, ...config }, ) @@ -162,281 +69,122 @@ export class SqlQueryService { apiService.fetchData( { method: 'POST', - url: '/api/app/sql-query/execute-query', + url: '/api/app/sql-object-manager/execute-query', data: input, }, { apiName: this.apiName, ...config }, ) - executeSavedQuery = (id: string, parameters?: Record, config?: Partial) => - apiService.fetchData( + executeSavedQuery = (id: string, config?: Partial) => + apiService.fetchData( { method: 'POST', - url: `/api/app/sql-query/${id}/execute-saved-query`, - data: { id, parameters }, + url: `/api/app/sql-object-manager/execute-saved-query/${id}`, }, { apiName: this.apiName, ...config }, ) - validateQuery = (input: ValidateQueryDto, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: '/api/app/sql-query/validate-query', - data: input, - }, - { apiName: this.apiName, ...config }, - ) - - activate = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: `/api/app/sql-query/${id}/activate`, - }, - { apiName: this.apiName, ...config }, - ) - - archive = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: `/api/app/sql-query/${id}/archive`, - }, - { apiName: this.apiName, ...config }, - ) -} - -export class SqlStoredProcedureService { - apiName = 'Default' - - create = (input: CreateSqlStoredProcedureDto, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: '/api/app/sql-stored-procedure', - data: input, - }, - { apiName: this.apiName, ...config }, - ) - - getList = (input: GetSqlStoredProceduresInput, config?: Partial) => - apiService.fetchData, GetSqlStoredProceduresInput>( - { - method: 'GET', - url: '/api/app/sql-stored-procedure', - params: input, - }, - { apiName: this.apiName, ...config }, - ) - - get = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'GET', - url: `/api/app/sql-stored-procedure/${id}`, - }, - { apiName: this.apiName, ...config }, - ) - - update = (id: string, input: UpdateSqlStoredProcedureDto, config?: Partial) => + // Stored Procedure Operations + updateStoredProcedure = (id: string, input: UpdateSqlStoredProcedureDto, config?: Partial) => apiService.fetchData( { method: 'PUT', - url: `/api/app/sql-stored-procedure/${id}`, + url: `/api/app/sql-object-manager/stored-procedure/${id}`, data: input, }, { apiName: this.apiName, ...config }, ) - delete = (id: string, config?: Partial) => + deleteStoredProcedure = (id: string, config?: Partial) => apiService.fetchData( { method: 'DELETE', - url: `/api/app/sql-stored-procedure/${id}`, + url: `/api/app/sql-object-manager/stored-procedure/${id}`, }, { apiName: this.apiName, ...config }, ) - deploy = (input: DeployStoredProcedureDto, config?: Partial) => + deployStoredProcedure = (input: DeployStoredProcedureDto, config?: Partial) => apiService.fetchData( { method: 'POST', - url: '/api/app/sql-stored-procedure/deploy', + url: '/api/app/sql-object-manager/deploy-stored-procedure', data: input, }, { apiName: this.apiName, ...config }, ) - checkExists = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: `/api/app/sql-stored-procedure/${id}/check-exists`, - }, - { apiName: this.apiName, ...config }, - ) - - drop = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: `/api/app/sql-stored-procedure/${id}/drop`, - }, - { apiName: this.apiName, ...config }, - ) -} - -export class SqlViewService { - apiName = 'Default' - - create = (input: CreateSqlViewDto, config?: Partial) => - apiService.fetchData( - { - method: 'POST', - url: '/api/app/sql-view', - data: input, - }, - { apiName: this.apiName, ...config }, - ) - - getList = (input: GetSqlViewsInput, config?: Partial) => - apiService.fetchData, GetSqlViewsInput>( - { - method: 'GET', - url: '/api/app/sql-view', - params: input, - }, - { apiName: this.apiName, ...config }, - ) - - get = (id: string, config?: Partial) => - apiService.fetchData( - { - method: 'GET', - url: `/api/app/sql-view/${id}`, - }, - { apiName: this.apiName, ...config }, - ) - - update = (id: string, input: UpdateSqlViewDto, config?: Partial) => + // View Operations + updateView = (id: string, input: UpdateSqlViewDto, config?: Partial) => apiService.fetchData( { method: 'PUT', - url: `/api/app/sql-view/${id}`, + url: `/api/app/sql-object-manager/view/${id}`, data: input, }, { apiName: this.apiName, ...config }, ) - delete = (id: string, config?: Partial) => + deleteView = (id: string, config?: Partial) => apiService.fetchData( { method: 'DELETE', - url: `/api/app/sql-view/${id}`, + url: `/api/app/sql-object-manager/view/${id}`, }, { apiName: this.apiName, ...config }, ) - deploy = (input: DeployViewDto, config?: Partial) => + deployView = (input: DeployViewDto, config?: Partial) => apiService.fetchData( { method: 'POST', - url: '/api/app/sql-view/deploy', + url: '/api/app/sql-object-manager/deploy-view', data: input, }, { apiName: this.apiName, ...config }, ) - checkExists = (id: string, config?: Partial) => - apiService.fetchData( + // Function Operations + updateFunction = (id: string, input: UpdateSqlFunctionDto, config?: Partial) => + apiService.fetchData( { - method: 'POST', - url: `/api/app/sql-view/${id}/check-exists`, + method: 'PUT', + url: `/api/app/sql-object-manager/function/${id}`, + data: input, }, { apiName: this.apiName, ...config }, ) - drop = (id: string, config?: Partial) => - apiService.fetchData( + deleteFunction = (id: string, config?: Partial) => + apiService.fetchData( + { + method: 'DELETE', + url: `/api/app/sql-object-manager/function/${id}`, + }, + { apiName: this.apiName, ...config }, + ) + + deployFunction = (input: DeployFunctionDto, config?: Partial) => + apiService.fetchData( { method: 'POST', - url: `/api/app/sql-view/${id}/drop`, + url: '/api/app/sql-object-manager/deploy-function', + data: input, + }, + { apiName: this.apiName, ...config }, + ) + + // Database Metadata Operations + getTableColumns = (dataSourceCode: string, schemaName: string, tableName: string, config?: Partial) => + apiService.fetchData( + { + method: 'GET', + url: '/api/app/sql-object-manager/table-columns', + params: { dataSourceCode, schemaName, tableName }, }, { apiName: this.apiName, ...config }, ) } -export class SqlTemplateService { - apiName = 'Default' - - getQueryTemplates = (config?: Partial) => - apiService.fetchData( - { - method: 'GET', - url: '/api/app/sql-template/query-templates', - }, - { apiName: this.apiName, ...config }, - ) - - getStoredProcedureTemplate = ( - procedureName: string, - schemaName = 'dbo', - config?: Partial, - ) => - apiService.fetchData( - { - method: 'GET', - url: '/api/app/sql-template/stored-procedure-template', - params: { procedureName, schemaName }, - }, - { apiName: this.apiName, ...config }, - ) - - getViewTemplate = ( - viewName: string, - schemaName = 'dbo', - withSchemaBinding = false, - config?: Partial, - ) => - apiService.fetchData( - { - method: 'GET', - url: '/api/app/sql-template/view-template', - params: { viewName, schemaName, withSchemaBinding }, - }, - { apiName: this.apiName, ...config }, - ) - - getFunctionTemplate = ( - functionName: string, - functionType: number, - schemaName = 'dbo', - config?: Partial, - ) => - apiService.fetchData( - { - method: 'GET', - url: '/api/app/sql-template/function-template', - params: { functionName, functionType, schemaName }, - }, - { apiName: this.apiName, ...config }, - ) - - getQueryTemplate = (templateType: string, config?: Partial) => - apiService.fetchData( - { - method: 'GET', - url: '/api/app/sql-template/query-template', - params: { templateType }, - }, - { apiName: this.apiName, ...config }, - ) -} - -// Export service instances -export const sqlFunctionService = new SqlFunctionService() -export const sqlQueryService = new SqlQueryService() -export const sqlStoredProcedureService = new SqlStoredProcedureService() -export const sqlViewService = new SqlViewService() -export const sqlTemplateService = new SqlTemplateService() +// Export service instance +export const sqlObjectManagerService = new SqlObjectManagerService() diff --git a/ui/src/views/sqlQueryManager/SqlQueryManager.tsx b/ui/src/views/sqlQueryManager/SqlQueryManager.tsx index 961835d4..ce973c61 100644 --- a/ui/src/views/sqlQueryManager/SqlQueryManager.tsx +++ b/ui/src/views/sqlQueryManager/SqlQueryManager.tsx @@ -13,10 +13,7 @@ import type { SqlQueryExecutionResultDto, } from '@/proxy/sql-query-manager/models' import { - sqlFunctionService, - sqlQueryService, - sqlStoredProcedureService, - sqlViewService, + sqlObjectManagerService, } from '@/services/sql-query-manager.service' import { FaDatabase, FaPlay, FaSave, FaSyncAlt } from 'react-icons/fa' import { useLocalization } from '@/utils/hooks/useLocalization' @@ -48,8 +45,7 @@ const SqlQueryManager = () => { selectedDataSource: null, selectedObject: null, selectedObjectType: null, - editorContent: - '-- SQL Query Editor\n-- Write your SQL query here and press F5 or click Execute to run\n\nSELECT * FROM YourTable\nWHERE 1=1', + editorContent: '', isExecuting: false, executionResult: null, showProperties: false, @@ -279,7 +275,8 @@ GO` }, [translate]) const handleTemplateSelect = useCallback((template: string, templateType: string) => { - const templateContent = getTemplateContent(templateType) + // If template is already provided (e.g., from table click), use it + const templateContent = template || getTemplateContent(templateType) // Check if editor has content and it's not from a previous template const hasUserContent = state.editorContent.trim() && state.isDirty @@ -331,7 +328,7 @@ GO` setState((prev) => ({ ...prev, isExecuting: true, executionResult: null })) try { - const result = await sqlQueryService.executeQuery({ + const result = await sqlObjectManagerService.executeQuery({ queryText: state.editorContent, dataSourceCode: state.selectedDataSource.code || '', }) @@ -395,13 +392,13 @@ GO` switch (state.selectedObjectType) { case 1: // Query - await sqlQueryService.update(objectId, { + await sqlObjectManagerService.updateQuery(objectId, { ...(state.selectedObject as SqlQueryDto), queryText: state.editorContent, }) break case 2: // Stored Procedure - await sqlStoredProcedureService.update(objectId, { + await sqlObjectManagerService.updateStoredProcedure(objectId, { displayName: (state.selectedObject as SqlStoredProcedureDto).displayName, description: (state.selectedObject as SqlStoredProcedureDto).description, procedureBody: state.editorContent, @@ -410,7 +407,7 @@ GO` }) break case 3: // View - await sqlViewService.update(objectId, { + await sqlObjectManagerService.updateView(objectId, { displayName: (state.selectedObject as SqlViewDto).displayName, description: (state.selectedObject as SqlViewDto).description, viewDefinition: state.editorContent, @@ -419,7 +416,7 @@ GO` }) break case 4: // Function - await sqlFunctionService.update(objectId, { + await sqlObjectManagerService.updateFunction(objectId, { displayName: (state.selectedObject as SqlFunctionDto).displayName, description: (state.selectedObject as SqlFunctionDto).description, functionBody: state.editorContent, @@ -452,7 +449,7 @@ GO` if (!state.selectedDataSource || !saveDialogData.name) return try { - await sqlQueryService.create({ + await sqlObjectManagerService.createQuery({ code: saveDialogData.name.replace(/\s+/g, '_'), name: saveDialogData.name, description: saveDialogData.description, @@ -501,13 +498,13 @@ GO` switch (state.selectedObjectType) { case 2: // Stored Procedure - result = await sqlStoredProcedureService.deploy({ id: objectId, dropIfExists: true }) + result = await sqlObjectManagerService.deployStoredProcedure({ id: objectId, dropIfExists: true }) break case 3: // View - result = await sqlViewService.deploy({ id: objectId, dropIfExists: true }) + result = await sqlObjectManagerService.deployView({ id: objectId, dropIfExists: true }) break case 4: // Function - result = await sqlFunctionService.deploy({ id: objectId, dropIfExists: true }) + result = await sqlObjectManagerService.deployFunction({ id: objectId, dropIfExists: true }) break default: toast.push( @@ -537,9 +534,9 @@ GO` return ( -
+
{/* Toolbar */} - +
@@ -615,9 +612,9 @@ GO` {/* Main Content Area */} -
+
{/* Left Panel - Object Explorer */} -
+
@@ -625,7 +622,7 @@ GO` {translate('::App.Platform.ObjectExplorer')}
-
+
{/* Center Panel - Editor and Results */} -
+
{translate('::App.Platform.QueryEditor')}
diff --git a/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx b/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx index a8fc7460..54d29a7a 100644 --- a/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx +++ b/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx @@ -10,7 +10,9 @@ import { FaSyncAlt, FaEdit, FaTrash, + FaTable, } from 'react-icons/fa' +import { MdViewColumn } from 'react-icons/md' import type { DataSourceDto } from '@/proxy/data-source' import type { SqlFunctionDto, @@ -20,10 +22,7 @@ import type { SqlObjectType, } from '@/proxy/sql-query-manager/models' import { - sqlFunctionService, - sqlQueryService, - sqlStoredProcedureService, - sqlViewService, + sqlObjectManagerService, } from '@/services/sql-query-manager.service' import { useLocalization } from '@/utils/hooks/useLocalization' @@ -32,11 +31,13 @@ export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | S interface TreeNode { id: string label: string - type: 'root' | 'folder' | 'object' + type: 'root' | 'folder' | 'object' | 'column' objectType?: SqlObjectType - data?: SqlObject + data?: SqlObject | any children?: TreeNode[] expanded?: boolean + isColumn?: boolean + parentTable?: { schemaName: string; tableName: string } } interface SqlObjectExplorerProps { @@ -55,9 +56,10 @@ const SqlObjectExplorer = ({ const { translate } = useLocalization() const [treeData, setTreeData] = useState([]) const [expandedNodes, setExpandedNodes] = useState>( - new Set(['root', 'templates', 'queries', 'storedProcedures', 'views', 'functions']), + new Set(['root']), // Only root expanded by default ) const [loading, setLoading] = useState(false) + const [filterText, setFilterText] = useState('') const [contextMenu, setContextMenu] = useState<{ show: boolean x: number @@ -83,28 +85,10 @@ const SqlObjectExplorer = ({ setLoading(true) try { - const [queries, storedProcedures, views, functions] = await Promise.all([ - sqlQueryService.getList({ - skipCount: 0, - maxResultCount: 1000, - dataSourceCode: dataSource.code, - }), - sqlStoredProcedureService.getList({ - skipCount: 0, - maxResultCount: 1000, - dataSourceCode: dataSource.code, - }), - sqlViewService.getList({ - skipCount: 0, - maxResultCount: 1000, - dataSourceCode: dataSource.code, - }), - sqlFunctionService.getList({ - skipCount: 0, - maxResultCount: 1000, - dataSourceCode: dataSource.code, - }), - ]) + // Single API call to get all objects + const response = await sqlObjectManagerService.getAllObjects(dataSource.code || '') + const allObjects = response.data + const tree: TreeNode[] = [ { id: 'root', @@ -168,14 +152,27 @@ const SqlObjectExplorer = ({ }, ], }, + { + id: 'tables', + label: `${translate('::App.Platform.Tables')} (${allObjects.tables.length})`, + type: 'folder', + expanded: expandedNodes.has('tables'), + children: + allObjects.tables.map((t) => ({ + id: `table-${t.schemaName}-${t.tableName}`, + label: t.fullName, + type: 'object' as const, + data: t, + })) || [], + }, { id: 'queries', - label: `${translate('::App.Platform.Queries')} (${queries.data.totalCount})`, + label: `${translate('::App.Platform.Queries')} (${allObjects.queries.length})`, type: 'folder', objectType: 1, expanded: expandedNodes.has('queries'), children: - queries.data.items?.map((q) => ({ + allObjects.queries.map((q) => ({ id: q.id || '', label: q.name, type: 'object' as const, @@ -185,12 +182,12 @@ const SqlObjectExplorer = ({ }, { id: 'storedProcedures', - label: `${translate('::App.Platform.StoredProcedures')} (${storedProcedures.data.totalCount})`, + label: `${translate('::App.Platform.StoredProcedures')} (${allObjects.storedProcedures.length})`, type: 'folder', objectType: 2, expanded: expandedNodes.has('storedProcedures'), children: - storedProcedures.data.items?.map((sp) => ({ + allObjects.storedProcedures.map((sp) => ({ id: sp.id || '', label: sp.displayName || sp.procedureName, type: 'object' as const, @@ -200,12 +197,12 @@ const SqlObjectExplorer = ({ }, { id: 'views', - label: `${translate('::App.Platform.Views')} (${views.data.totalCount})`, + label: `${translate('::App.Platform.Views')} (${allObjects.views.length})`, type: 'folder', objectType: 3, expanded: expandedNodes.has('views'), children: - views.data.items?.map((v) => ({ + allObjects.views.map((v) => ({ id: v.id || '', label: v.displayName || v.viewName, type: 'object' as const, @@ -215,12 +212,12 @@ const SqlObjectExplorer = ({ }, { id: 'functions', - label: `${translate('::App.Platform.Functions')} (${functions.data.totalCount})`, + label: `${translate('::App.Platform.Functions')} (${allObjects.functions.length})`, type: 'folder', objectType: 4, expanded: expandedNodes.has('functions'), children: - functions.data.items?.map((f) => ({ + allObjects.functions.map((f) => ({ id: f.id || '', label: f.displayName || f.functionName, type: 'object' as const, @@ -245,24 +242,116 @@ const SqlObjectExplorer = ({ } } - const toggleNode = (nodeId: string) => { - setExpandedNodes((prev) => { - const newSet = new Set(prev) - if (newSet.has(nodeId)) newSet.delete(nodeId) - else newSet.add(nodeId) - return newSet + const loadTableColumns = async (schemaName: string, tableName: string): Promise => { + try { + const response = await sqlObjectManagerService.getTableColumns( + dataSource?.code || '', + schemaName, + tableName, + ) + return response.data.map((col) => ({ + id: `column-${schemaName}-${tableName}-${col.columnName}`, + label: `${col.columnName} (${col.dataType}${col.maxLength ? `(${col.maxLength})` : ''})${col.isNullable ? '' : ' NOT NULL'}`, + type: 'column' as const, + isColumn: true, + data: col, + parentTable: { schemaName, tableName }, + })) + } catch (error) { + return [] + } + } + + const toggleNode = async (nodeId: string) => { + const newSet = new Set(expandedNodes) + + if (newSet.has(nodeId)) { + newSet.delete(nodeId) + setExpandedNodes(newSet) + } else { + newSet.add(nodeId) + setExpandedNodes(newSet) + + // If it's a table node and hasn't loaded columns yet, load them + if (nodeId.startsWith('table-') && dataSource) { + const tableNode = findNodeById(treeData, nodeId) + if (tableNode && (!tableNode.children || tableNode.children.length === 0)) { + const tableData = tableNode.data as any + const columns = await loadTableColumns(tableData.schemaName, tableData.tableName) + + // Update tree data with columns + setTreeData((prevTree) => { + return updateNodeChildren(prevTree, nodeId, columns) + }) + } + } + } + } + + const findNodeById = (nodes: TreeNode[], id: string): TreeNode | null => { + for (const node of nodes) { + if (node.id === id) return node + if (node.children) { + const found = findNodeById(node.children, id) + if (found) return found + } + } + return null + } + + const updateNodeChildren = (nodes: TreeNode[], nodeId: string, children: TreeNode[]): TreeNode[] => { + return nodes.map((node) => { + if (node.id === nodeId) { + return { ...node, children } + } + if (node.children) { + return { ...node, children: updateNodeChildren(node.children, nodeId, children) } + } + return node }) } + const filterTree = (nodes: TreeNode[], searchText: string): TreeNode[] => { + if (!searchText.trim()) return nodes + + const search = searchText.toLowerCase() + const filtered = nodes + .map((node) => { + const matchesSearch = node.label.toLowerCase().includes(search) + const filteredChildren = node.children ? filterTree(node.children, searchText) : [] + + if (matchesSearch || filteredChildren.length > 0) { + return { + ...node, + children: filteredChildren.length > 0 ? filteredChildren : node.children, + } as TreeNode + } + return null + }) + .filter((node) => node !== null) as TreeNode[] + + return filtered + } + const handleNodeClick = (node: TreeNode) => { if (node.type === 'folder' || node.type === 'root') { toggleNode(node.id) + } else if (node.type === 'column') { + // Column clicked - do nothing or show info + return } else if (node.type === 'object' && node.data) { // Check if it's a template if ((node.data as any).templateType && onTemplateSelect) { const templateType = (node.data as any).templateType onTemplateSelect('', templateType) // Template content will be generated in parent - } else if (node.objectType) { + } + // Check if it's a table + else if (node.id.startsWith('table-') && onTemplateSelect) { + const table = node.data as any + const selectQuery = `-- SELECT from ${table.fullName || table.tableName}\nSELECT * \nFROM ${table.fullName || `[${table.schemaName}].[${table.tableName}]`}\nWHERE 1=1;` + onTemplateSelect(selectQuery, 'table-select') + } + else if (node.objectType) { onObjectSelect(node.data, node.objectType) } } @@ -270,6 +359,12 @@ const SqlObjectExplorer = ({ const handleContextMenu = (e: React.MouseEvent, node: TreeNode) => { e.preventDefault() + + // Don't show context menu for columns, templates, or tables + if (node.type === 'column' || node.id.startsWith('template-') || node.id.startsWith('table-')) { + return + } + setContextMenu({ show: true, x: e.clientX, @@ -286,16 +381,16 @@ const SqlObjectExplorer = ({ switch (type) { case 1: - await sqlQueryService.delete(object.id!) + await sqlObjectManagerService.deleteQuery(object.id!) break case 2: - await sqlStoredProcedureService.delete(object.id!) + await sqlObjectManagerService.deleteStoredProcedure(object.id!) break case 3: - await sqlViewService.delete(object.id!) + await sqlObjectManagerService.deleteView(object.id!) break case 4: - await sqlFunctionService.delete(object.id!) + await sqlObjectManagerService.deleteFunction(object.id!) break } @@ -337,6 +432,14 @@ const SqlObjectExplorer = ({ ) + // Tables folder + if (node.id === 'tables') + return isExpanded ? ( + + ) : ( + + ) + if (node.objectType === 1) return isExpanded ? ( @@ -372,31 +475,41 @@ const SqlObjectExplorer = ({ return } + // Check if it's a table + if (node.id.startsWith('table-')) { + return + } + if (node.objectType === 1) return if (node.objectType === 2) return if (node.objectType === 3) return if (node.objectType === 4) return } + if (node.type === 'column') { + return + } + return } const renderNode = (node: TreeNode, level = 0) => { const isExpanded = expandedNodes.has(node.id) const isSelected = node.type === 'object' && selectedObject?.id === node.id + const isColumn = node.type === 'column' return (
handleNodeClick(node)} - onContextMenu={(e) => handleContextMenu(e, node)} + onClick={() => !isColumn && handleNodeClick(node)} + onContextMenu={(e) => !isColumn && handleContextMenu(e, node)} > {getIcon(node)} - {node.label} + {node.label}
{isExpanded && node.children && ( @@ -406,17 +519,48 @@ const SqlObjectExplorer = ({ ) } + const filteredTree = filterTree(treeData, filterText) + return ( -
- {loading &&
{translate('::App.Platform.Loading')}
} - {!loading && treeData.length === 0 && ( -
- {translate('::App.Platform.NoDataSourceSelected')} +
+ {/* Filter and Refresh Controls */} +
+
+ setFilterText(e.target.value)} + className="flex-1 px-3 py-1.5 text-sm border rounded-md bg-white dark:bg-gray-700 dark:border-gray-600" + /> +
- )} - {!loading && treeData.length > 0 && ( -
{treeData.map((node) => renderNode(node))}
- )} +
+ + {/* Tree Content */} +
+ {loading &&
{translate('::App.Platform.Loading')}
} + {!loading && treeData.length === 0 && ( +
+ {translate('::App.Platform.NoDataSourceSelected')} +
+ )} + {!loading && filteredTree.length > 0 && ( +
{filteredTree.map((node) => renderNode(node))}
+ )} + {!loading && treeData.length > 0 && filteredTree.length === 0 && ( +
+ {translate('::App.Platform.NoResultsFound')} +
+ )} +
{contextMenu.show && ( <>