Sql Object AppServis başlığında tüm appservisler birleştirildi.
This commit is contained in:
parent
cb2e007302
commit
60dd2a0ba7
19 changed files with 839 additions and 1154 deletions
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
|
@ -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>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Deploy function to database
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> DeployAsync(DeployFunctionDto input);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if function exists in database
|
|
||||||
/// </summary>
|
|
||||||
Task<bool> CheckExistsAsync(Guid id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Drop function from database
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> DropAsync(Guid id);
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Erp.SqlQueryManager.Application.Contracts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unified service for SQL Object Explorer and CRUD operations
|
||||||
|
/// </summary>
|
||||||
|
public interface ISqlObjectManagerAppService : IApplicationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get all SQL objects for Object Explorer (Queries, SPs, Views, Functions, Tables, Templates)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dataSourceCode">Data source code to filter objects</param>
|
||||||
|
/// <returns>Combined response with all object types</returns>
|
||||||
|
Task<SqlObjectExplorerDto> GetAllObjectsAsync(string dataSourceCode);
|
||||||
|
|
||||||
|
// Query Operations
|
||||||
|
Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input);
|
||||||
|
Task<SqlQueryDto> UpdateQueryAsync(Guid id, UpdateSqlQueryDto input);
|
||||||
|
Task DeleteQueryAsync(Guid id);
|
||||||
|
Task<SqlQueryExecutionResultDto> ExecuteQueryAsync(ExecuteSqlQueryDto input);
|
||||||
|
Task<SqlQueryExecutionResultDto> ExecuteSavedQueryAsync(Guid id);
|
||||||
|
|
||||||
|
// Stored Procedure Operations
|
||||||
|
Task<SqlStoredProcedureDto> UpdateStoredProcedureAsync(Guid id, UpdateSqlStoredProcedureDto input);
|
||||||
|
Task DeleteStoredProcedureAsync(Guid id);
|
||||||
|
Task<SqlQueryExecutionResultDto> DeployStoredProcedureAsync(DeployStoredProcedureDto input);
|
||||||
|
|
||||||
|
// View Operations
|
||||||
|
Task<SqlViewDto> UpdateViewAsync(Guid id, UpdateSqlViewDto input);
|
||||||
|
Task DeleteViewAsync(Guid id);
|
||||||
|
Task<SqlQueryExecutionResultDto> DeployViewAsync(DeployViewDto input);
|
||||||
|
|
||||||
|
// Function Operations
|
||||||
|
Task<SqlFunctionDto> UpdateFunctionAsync(Guid id, UpdateSqlFunctionDto input);
|
||||||
|
Task DeleteFunctionAsync(Guid id);
|
||||||
|
Task<SqlQueryExecutionResultDto> DeployFunctionAsync(DeployFunctionDto input);
|
||||||
|
|
||||||
|
// Database Metadata Operations
|
||||||
|
Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName);
|
||||||
|
}
|
||||||
|
|
@ -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>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Execute a SQL query
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> ExecuteQueryAsync(ExecuteSqlQueryDto input);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Execute a saved query by ID
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> ExecuteSavedQueryAsync(Guid id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate SQL query syntax
|
|
||||||
/// </summary>
|
|
||||||
Task<(bool IsValid, string ErrorMessage)> ValidateQueryAsync(string sql);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Activate query
|
|
||||||
/// </summary>
|
|
||||||
Task ActivateAsync(Guid id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Archive query
|
|
||||||
/// </summary>
|
|
||||||
Task ArchiveAsync(Guid id);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Deploy stored procedure to database
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> DeployAsync(DeployStoredProcedureDto input);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if procedure exists in database
|
|
||||||
/// </summary>
|
|
||||||
Task<bool> CheckExistsAsync(Guid id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Drop procedure from database
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> DropAsync(Guid id);
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get all available query templates
|
|
||||||
/// </summary>
|
|
||||||
Task<List<SqlTemplateDto>> GetQueryTemplatesAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get stored procedure template
|
|
||||||
/// </summary>
|
|
||||||
Task<string> GetStoredProcedureTemplateAsync(string procedureName, string schemaName = "dbo");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get view template
|
|
||||||
/// </summary>
|
|
||||||
Task<string> GetViewTemplateAsync(string viewName, string schemaName = "dbo", bool withSchemaBinding = false);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get function template
|
|
||||||
/// </summary>
|
|
||||||
Task<string> GetFunctionTemplateAsync(string functionName, SqlFunctionType functionType, string schemaName = "dbo");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get specific query template
|
|
||||||
/// </summary>
|
|
||||||
Task<string> GetQueryTemplateAsync(string templateType);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Deploy view to database
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> DeployAsync(DeployViewDto input);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if view exists in database
|
|
||||||
/// </summary>
|
|
||||||
Task<bool> CheckExistsAsync(Guid id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Drop view from database
|
|
||||||
/// </summary>
|
|
||||||
Task<SqlQueryExecutionResultDto> DropAsync(Guid id);
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Erp.SqlQueryManager.Application.Contracts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combined DTO for Object Explorer containing all SQL objects
|
||||||
|
/// </summary>
|
||||||
|
public class SqlObjectExplorerDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SQL Queries
|
||||||
|
/// </summary>
|
||||||
|
public List<SqlQueryDto> Queries { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stored Procedures
|
||||||
|
/// </summary>
|
||||||
|
public List<SqlStoredProcedureDto> StoredProcedures { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Views
|
||||||
|
/// </summary>
|
||||||
|
public List<SqlViewDto> Views { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Functions
|
||||||
|
/// </summary>
|
||||||
|
public List<SqlFunctionDto> Functions { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Database Tables
|
||||||
|
/// </summary>
|
||||||
|
public List<DatabaseTableDto> Tables { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query Templates
|
||||||
|
/// </summary>
|
||||||
|
public List<SqlTemplateDto> Templates { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
@ -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<SqlFunction, Guid> repository,
|
|
||||||
ISqlExecutorService sqlExecutorService) : base(repository)
|
|
||||||
{
|
|
||||||
_sqlExecutorService = sqlExecutorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlFunctionDto> 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<SqlFunction, SqlFunctionDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlFunctionDto> 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<SqlFunction, SqlFunctionDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SqlQueryExecutionResultDto> 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<bool> 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<SqlQueryExecutionResultDto> 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,416 @@
|
||||||
|
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 Volo.Abp.Application.Services;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace Erp.SqlQueryManager.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unified service for SQL Object Explorer
|
||||||
|
/// Combines all SQL objects into a single endpoint
|
||||||
|
/// </summary>
|
||||||
|
public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerAppService
|
||||||
|
{
|
||||||
|
private readonly IRepository<SqlQuery, Guid> _queryRepository;
|
||||||
|
private readonly IRepository<SqlStoredProcedure, Guid> _procedureRepository;
|
||||||
|
private readonly IRepository<SqlView, Guid> _viewRepository;
|
||||||
|
private readonly IRepository<SqlFunction, Guid> _functionRepository;
|
||||||
|
private readonly ISqlExecutorService _sqlExecutorService;
|
||||||
|
private readonly ISqlTemplateProvider _templateProvider;
|
||||||
|
|
||||||
|
public SqlObjectManagerAppService(
|
||||||
|
IRepository<SqlQuery, Guid> queryRepository,
|
||||||
|
IRepository<SqlStoredProcedure, Guid> procedureRepository,
|
||||||
|
IRepository<SqlView, Guid> viewRepository,
|
||||||
|
IRepository<SqlFunction, Guid> functionRepository,
|
||||||
|
ISqlExecutorService sqlExecutorService,
|
||||||
|
ISqlTemplateProvider templateProvider)
|
||||||
|
{
|
||||||
|
_queryRepository = queryRepository;
|
||||||
|
_procedureRepository = procedureRepository;
|
||||||
|
_viewRepository = viewRepository;
|
||||||
|
_functionRepository = functionRepository;
|
||||||
|
_sqlExecutorService = sqlExecutorService;
|
||||||
|
_templateProvider = templateProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SqlObjectExplorerDto> GetAllObjectsAsync(string dataSourceCode)
|
||||||
|
{
|
||||||
|
var result = new SqlObjectExplorerDto();
|
||||||
|
|
||||||
|
// Get all queries for this data source
|
||||||
|
var queries = await _queryRepository.GetListAsync();
|
||||||
|
result.Queries = queries
|
||||||
|
.Where(q => q.DataSourceCode == dataSourceCode)
|
||||||
|
.Select(q => new SqlQueryDto
|
||||||
|
{
|
||||||
|
Id = q.Id,
|
||||||
|
Code = q.Code,
|
||||||
|
Name = q.Name,
|
||||||
|
Description = q.Description,
|
||||||
|
QueryText = q.QueryText,
|
||||||
|
DataSourceCode = q.DataSourceCode,
|
||||||
|
Status = q.Status,
|
||||||
|
Category = q.Category,
|
||||||
|
Tags = q.Tags,
|
||||||
|
IsModifyingData = q.IsModifyingData,
|
||||||
|
Parameters = q.Parameters,
|
||||||
|
ExecutionCount = q.ExecutionCount,
|
||||||
|
LastExecutedAt = q.LastExecutedAt
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get all stored procedures for this data source
|
||||||
|
var procedures = await _procedureRepository.GetListAsync();
|
||||||
|
result.StoredProcedures = procedures
|
||||||
|
.Where(p => p.DataSourceCode == dataSourceCode)
|
||||||
|
.Select(p => new SqlStoredProcedureDto
|
||||||
|
{
|
||||||
|
Id = p.Id,
|
||||||
|
ProcedureName = p.ProcedureName,
|
||||||
|
SchemaName = p.SchemaName,
|
||||||
|
DisplayName = p.DisplayName,
|
||||||
|
Description = p.Description,
|
||||||
|
ProcedureBody = p.ProcedureBody,
|
||||||
|
DataSourceCode = p.DataSourceCode,
|
||||||
|
Category = p.Category,
|
||||||
|
Parameters = p.Parameters,
|
||||||
|
IsDeployed = p.IsDeployed,
|
||||||
|
LastDeployedAt = p.LastDeployedAt
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get all views for this data source
|
||||||
|
var views = await _viewRepository.GetListAsync();
|
||||||
|
result.Views = views
|
||||||
|
.Where(v => v.DataSourceCode == dataSourceCode)
|
||||||
|
.Select(v => new SqlViewDto
|
||||||
|
{
|
||||||
|
Id = v.Id,
|
||||||
|
ViewName = v.ViewName,
|
||||||
|
SchemaName = v.SchemaName,
|
||||||
|
DisplayName = v.DisplayName,
|
||||||
|
Description = v.Description,
|
||||||
|
ViewDefinition = v.ViewDefinition,
|
||||||
|
DataSourceCode = v.DataSourceCode,
|
||||||
|
Category = v.Category,
|
||||||
|
WithSchemaBinding = v.WithSchemaBinding,
|
||||||
|
IsDeployed = v.IsDeployed,
|
||||||
|
LastDeployedAt = v.LastDeployedAt
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get all functions for this data source
|
||||||
|
var functions = await _functionRepository.GetListAsync();
|
||||||
|
result.Functions = functions
|
||||||
|
.Where(f => f.DataSourceCode == dataSourceCode)
|
||||||
|
.Select(f => new SqlFunctionDto
|
||||||
|
{
|
||||||
|
Id = f.Id,
|
||||||
|
FunctionName = f.FunctionName,
|
||||||
|
SchemaName = f.SchemaName,
|
||||||
|
DisplayName = f.DisplayName,
|
||||||
|
Description = f.Description,
|
||||||
|
FunctionType = f.FunctionType,
|
||||||
|
FunctionBody = f.FunctionBody,
|
||||||
|
ReturnType = f.ReturnType,
|
||||||
|
DataSourceCode = f.DataSourceCode,
|
||||||
|
Category = f.Category,
|
||||||
|
Parameters = f.Parameters,
|
||||||
|
IsDeployed = f.IsDeployed,
|
||||||
|
LastDeployedAt = f.LastDeployedAt
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Get all database tables
|
||||||
|
result.Tables = await GetTablesAsync(dataSourceCode);
|
||||||
|
|
||||||
|
// Get all templates
|
||||||
|
result.Templates = _templateProvider.GetAvailableQueryTemplates()
|
||||||
|
.Select(t => new SqlTemplateDto
|
||||||
|
{
|
||||||
|
Type = t.Type,
|
||||||
|
Name = t.Name,
|
||||||
|
Description = t.Description,
|
||||||
|
Template = _templateProvider.GetQueryTemplate(t.Type)
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<DatabaseTableDto>> GetTablesAsync(string dataSourceCode)
|
||||||
|
{
|
||||||
|
var query = @"
|
||||||
|
SELECT
|
||||||
|
SCHEMA_NAME(t.schema_id) AS SchemaName,
|
||||||
|
t.name AS TableName
|
||||||
|
FROM
|
||||||
|
sys.tables t
|
||||||
|
WHERE
|
||||||
|
t.is_ms_shipped = 0
|
||||||
|
ORDER BY
|
||||||
|
SCHEMA_NAME(t.schema_id), t.name";
|
||||||
|
|
||||||
|
var result = await _sqlExecutorService.ExecuteQueryAsync(query, dataSourceCode);
|
||||||
|
|
||||||
|
var tables = new List<DatabaseTableDto>();
|
||||||
|
if (result.Success && result.Data != null)
|
||||||
|
{
|
||||||
|
foreach (var row in result.Data)
|
||||||
|
{
|
||||||
|
var dict = row as System.Collections.Generic.IDictionary<string, object>;
|
||||||
|
if (dict != null)
|
||||||
|
{
|
||||||
|
tables.Add(new DatabaseTableDto
|
||||||
|
{
|
||||||
|
SchemaName = dict["SchemaName"]?.ToString() ?? "dbo",
|
||||||
|
TableName = dict["TableName"]?.ToString() ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Query Operations
|
||||||
|
|
||||||
|
public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input)
|
||||||
|
{
|
||||||
|
var query = ObjectMapper.Map<CreateSqlQueryDto, SqlQuery>(input);
|
||||||
|
query.Status = SqlQueryStatus.Draft;
|
||||||
|
|
||||||
|
var created = await _queryRepository.InsertAsync(query, autoSave: true);
|
||||||
|
return ObjectMapper.Map<SqlQuery, SqlQueryDto>(created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SqlQueryDto> UpdateQueryAsync(Guid id, UpdateSqlQueryDto input)
|
||||||
|
{
|
||||||
|
var query = await _queryRepository.GetAsync(id);
|
||||||
|
|
||||||
|
query.Name = input.Name;
|
||||||
|
query.Description = input.Description;
|
||||||
|
query.QueryText = input.QueryText;
|
||||||
|
query.Category = input.Category;
|
||||||
|
query.Tags = input.Tags;
|
||||||
|
|
||||||
|
var updated = await _queryRepository.UpdateAsync(query, autoSave: true);
|
||||||
|
return ObjectMapper.Map<SqlQuery, SqlQueryDto>(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteQueryAsync(Guid id)
|
||||||
|
{
|
||||||
|
await _queryRepository.DeleteAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SqlQueryExecutionResultDto> ExecuteQueryAsync(ExecuteSqlQueryDto input)
|
||||||
|
{
|
||||||
|
var result = await _sqlExecutorService.ExecuteQueryAsync(input.QueryText, input.DataSourceCode);
|
||||||
|
return MapExecutionResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SqlQueryExecutionResultDto> ExecuteSavedQueryAsync(Guid id)
|
||||||
|
{
|
||||||
|
var query = await _queryRepository.GetAsync(id);
|
||||||
|
var result = await _sqlExecutorService.ExecuteQueryAsync(query.QueryText, query.DataSourceCode);
|
||||||
|
|
||||||
|
// Update execution statistics
|
||||||
|
query.ExecutionCount++;
|
||||||
|
query.LastExecutedAt = DateTime.UtcNow;
|
||||||
|
await _queryRepository.UpdateAsync(query, autoSave: true);
|
||||||
|
|
||||||
|
return MapExecutionResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Stored Procedure Operations
|
||||||
|
|
||||||
|
public async Task<SqlStoredProcedureDto> UpdateStoredProcedureAsync(Guid id, UpdateSqlStoredProcedureDto input)
|
||||||
|
{
|
||||||
|
var procedure = await _procedureRepository.GetAsync(id);
|
||||||
|
|
||||||
|
procedure.DisplayName = input.DisplayName;
|
||||||
|
procedure.Description = input.Description;
|
||||||
|
procedure.ProcedureBody = input.ProcedureBody;
|
||||||
|
procedure.Category = input.Category;
|
||||||
|
|
||||||
|
var updated = await _procedureRepository.UpdateAsync(procedure, autoSave: true);
|
||||||
|
return ObjectMapper.Map<SqlStoredProcedure, SqlStoredProcedureDto>(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteStoredProcedureAsync(Guid id)
|
||||||
|
{
|
||||||
|
await _procedureRepository.DeleteAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SqlQueryExecutionResultDto> 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<SqlViewDto> UpdateViewAsync(Guid id, UpdateSqlViewDto input)
|
||||||
|
{
|
||||||
|
var view = await _viewRepository.GetAsync(id);
|
||||||
|
|
||||||
|
view.DisplayName = input.DisplayName;
|
||||||
|
view.Description = input.Description;
|
||||||
|
view.ViewDefinition = input.ViewDefinition;
|
||||||
|
view.Category = input.Category;
|
||||||
|
|
||||||
|
var updated = await _viewRepository.UpdateAsync(view, autoSave: true);
|
||||||
|
return ObjectMapper.Map<SqlView, SqlViewDto>(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteViewAsync(Guid id)
|
||||||
|
{
|
||||||
|
await _viewRepository.DeleteAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SqlQueryExecutionResultDto> 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<SqlFunctionDto> UpdateFunctionAsync(Guid id, UpdateSqlFunctionDto input)
|
||||||
|
{
|
||||||
|
var function = await _functionRepository.GetAsync(id);
|
||||||
|
|
||||||
|
function.DisplayName = input.DisplayName;
|
||||||
|
function.Description = input.Description;
|
||||||
|
function.FunctionBody = input.FunctionBody;
|
||||||
|
function.Category = input.Category;
|
||||||
|
|
||||||
|
var updated = await _functionRepository.UpdateAsync(function, autoSave: true);
|
||||||
|
return ObjectMapper.Map<SqlFunction, SqlFunctionDto>(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteFunctionAsync(Guid id)
|
||||||
|
{
|
||||||
|
await _functionRepository.DeleteAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SqlQueryExecutionResultDto> 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<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName)
|
||||||
|
{
|
||||||
|
var query = $@"
|
||||||
|
SELECT
|
||||||
|
c.name AS ColumnName,
|
||||||
|
TYPE_NAME(c.user_type_id) AS DataType,
|
||||||
|
c.is_nullable AS IsNullable,
|
||||||
|
c.max_length AS MaxLength
|
||||||
|
FROM
|
||||||
|
sys.columns c
|
||||||
|
INNER JOIN sys.tables t ON c.object_id = t.object_id
|
||||||
|
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
||||||
|
WHERE
|
||||||
|
s.name = '{schemaName}'
|
||||||
|
AND t.name = '{tableName}'
|
||||||
|
ORDER BY
|
||||||
|
c.column_id";
|
||||||
|
|
||||||
|
var result = await _sqlExecutorService.ExecuteQueryAsync(query, dataSourceCode);
|
||||||
|
|
||||||
|
var columns = new List<DatabaseColumnDto>();
|
||||||
|
if (result.Success && result.Data != null)
|
||||||
|
{
|
||||||
|
foreach (var row in result.Data)
|
||||||
|
{
|
||||||
|
var dict = row as System.Collections.Generic.IDictionary<string, object>;
|
||||||
|
if (dict != null)
|
||||||
|
{
|
||||||
|
columns.Add(new DatabaseColumnDto
|
||||||
|
{
|
||||||
|
ColumnName = dict["ColumnName"]?.ToString() ?? "",
|
||||||
|
DataType = dict["DataType"]?.ToString() ?? "",
|
||||||
|
IsNullable = dict["IsNullable"] is bool b && b,
|
||||||
|
MaxLength = dict["MaxLength"] != null ? int.Parse(dict["MaxLength"].ToString()) : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
@ -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<SqlQuery, Guid> repository,
|
|
||||||
ISqlExecutorService sqlExecutorService) : base(repository)
|
|
||||||
{
|
|
||||||
_sqlExecutorService = sqlExecutorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlQueryDto> 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<SqlQuery, SqlQueryDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlQueryDto> 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<SqlQuery, SqlQueryDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SqlQueryExecutionResultDto> 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<SqlQueryExecutionResultDto> 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<SqlStoredProcedure, Guid> repository,
|
|
||||||
ISqlExecutorService sqlExecutorService) : base(repository)
|
|
||||||
{
|
|
||||||
_sqlExecutorService = sqlExecutorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlStoredProcedureDto> 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<SqlStoredProcedure, SqlStoredProcedureDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlStoredProcedureDto> 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<SqlStoredProcedure, SqlStoredProcedureDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SqlQueryExecutionResultDto> 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<bool> 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<SqlQueryExecutionResultDto> 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<List<SqlTemplateDto>> 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<string> GetStoredProcedureTemplateAsync(string procedureName, string schemaName = "dbo")
|
|
||||||
{
|
|
||||||
var template = _templateProvider.GetStoredProcedureTemplate(procedureName, schemaName);
|
|
||||||
return Task.FromResult(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string> GetViewTemplateAsync(string viewName, string schemaName = "dbo", bool withSchemaBinding = false)
|
|
||||||
{
|
|
||||||
var template = _templateProvider.GetViewTemplate(viewName, schemaName, withSchemaBinding);
|
|
||||||
return Task.FromResult(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string> GetFunctionTemplateAsync(string functionName, SqlFunctionType functionType, string schemaName = "dbo")
|
|
||||||
{
|
|
||||||
var template = _templateProvider.GetFunctionTemplate(functionName, functionType, schemaName);
|
|
||||||
return Task.FromResult(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string> GetQueryTemplateAsync(string templateType)
|
|
||||||
{
|
|
||||||
var template = _templateProvider.GetQueryTemplate(templateType);
|
|
||||||
return Task.FromResult(template);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<SqlView, Guid> repository,
|
|
||||||
ISqlExecutorService sqlExecutorService) : base(repository)
|
|
||||||
{
|
|
||||||
_sqlExecutorService = sqlExecutorService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlViewDto> 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<SqlView, SqlViewDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<SqlViewDto> 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<SqlView, SqlViewDto>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SqlQueryExecutionResultDto> 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<bool> CheckExistsAsync(Guid id)
|
|
||||||
{
|
|
||||||
var view = await Repository.GetAsync(id);
|
|
||||||
|
|
||||||
return await _sqlExecutorService.CheckObjectExistsAsync(
|
|
||||||
view.ViewName,
|
|
||||||
"VIEW",
|
|
||||||
view.DataSourceCode,
|
|
||||||
view.SchemaName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SqlQueryExecutionResultDto> 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10513,6 +10513,12 @@
|
||||||
"tr": "View",
|
"tr": "View",
|
||||||
"en": "View"
|
"en": "View"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Platform.Tables",
|
||||||
|
"tr": "Tablolar",
|
||||||
|
"en": "Tables"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Platform.Function",
|
"key": "App.Platform.Function",
|
||||||
|
|
|
||||||
|
|
@ -244,3 +244,27 @@ export interface GetSqlViewsInput extends PagedAndSortedResultRequestDto {
|
||||||
status?: SqlQueryStatus
|
status?: SqlQueryStatus
|
||||||
category?: string
|
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[]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,159 +1,66 @@
|
||||||
import apiService, { Config } from '@/services/api.service'
|
import apiService, { Config } from '@/services/api.service'
|
||||||
import type { PagedResultDto } from '@/proxy'
|
|
||||||
import type {
|
import type {
|
||||||
SqlFunctionDto,
|
SqlFunctionDto,
|
||||||
CreateSqlFunctionDto,
|
|
||||||
UpdateSqlFunctionDto,
|
UpdateSqlFunctionDto,
|
||||||
DeployFunctionDto,
|
|
||||||
SqlQueryDto,
|
SqlQueryDto,
|
||||||
CreateSqlQueryDto,
|
|
||||||
UpdateSqlQueryDto,
|
UpdateSqlQueryDto,
|
||||||
|
CreateSqlQueryDto,
|
||||||
ExecuteSqlQueryDto,
|
ExecuteSqlQueryDto,
|
||||||
ExecuteSavedQueryDto,
|
|
||||||
ValidateQueryDto,
|
|
||||||
SqlStoredProcedureDto,
|
SqlStoredProcedureDto,
|
||||||
CreateSqlStoredProcedureDto,
|
|
||||||
UpdateSqlStoredProcedureDto,
|
UpdateSqlStoredProcedureDto,
|
||||||
DeployStoredProcedureDto,
|
DeployStoredProcedureDto,
|
||||||
SqlViewDto,
|
SqlViewDto,
|
||||||
CreateSqlViewDto,
|
|
||||||
UpdateSqlViewDto,
|
UpdateSqlViewDto,
|
||||||
DeployViewDto,
|
DeployViewDto,
|
||||||
SqlTemplateDto,
|
DeployFunctionDto,
|
||||||
SqlQueryExecutionResultDto,
|
SqlQueryExecutionResultDto,
|
||||||
GetSqlFunctionsInput,
|
DatabaseColumnDto,
|
||||||
GetSqlQueriesInput,
|
DatabaseTableDto,
|
||||||
GetSqlStoredProceduresInput,
|
SqlObjectExplorerDto,
|
||||||
GetSqlViewsInput,
|
|
||||||
} from '@/proxy/sql-query-manager/models'
|
} from '@/proxy/sql-query-manager/models'
|
||||||
|
|
||||||
export class SqlFunctionService {
|
export class SqlObjectManagerService {
|
||||||
apiName = 'Default'
|
apiName = 'Default'
|
||||||
|
|
||||||
create = (input: CreateSqlFunctionDto, config?: Partial<Config>) =>
|
/**
|
||||||
apiService.fetchData<SqlFunctionDto, CreateSqlFunctionDto>(
|
* Get all SQL objects for Object Explorer in a single call
|
||||||
{
|
*/
|
||||||
method: 'POST',
|
getAllObjects = (dataSourceCode: string, config?: Partial<Config>) =>
|
||||||
url: '/api/app/sql-function',
|
apiService.fetchData<SqlObjectExplorerDto, void>(
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
getList = (input: GetSqlFunctionsInput, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<PagedResultDto<SqlFunctionDto>, GetSqlFunctionsInput>(
|
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/api/app/sql-function',
|
url: '/api/app/sql-object-manager/all-objects',
|
||||||
params: input,
|
params: { dataSourceCode },
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
get = (id: string, config?: Partial<Config>) =>
|
// Query Operations
|
||||||
apiService.fetchData<SqlFunctionDto, void>(
|
createQuery = (input: CreateSqlQueryDto, config?: Partial<Config>) =>
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: `/api/app/sql-function/${id}`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
update = (id: string, input: UpdateSqlFunctionDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlFunctionDto, UpdateSqlFunctionDto>(
|
|
||||||
{
|
|
||||||
method: 'PUT',
|
|
||||||
url: `/api/app/sql-function/${id}`,
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
delete = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<void, void>(
|
|
||||||
{
|
|
||||||
method: 'DELETE',
|
|
||||||
url: `/api/app/sql-function/${id}`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
deploy = (input: DeployFunctionDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployFunctionDto>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/sql-function/deploy',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
checkExists = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<boolean, void>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/sql-function/${id}/check-exists`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
drop = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/sql-function/${id}/drop`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SqlQueryService {
|
|
||||||
apiName = 'Default'
|
|
||||||
|
|
||||||
create = (input: CreateSqlQueryDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlQueryDto, CreateSqlQueryDto>(
|
apiService.fetchData<SqlQueryDto, CreateSqlQueryDto>(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/app/sql-query',
|
url: '/api/app/sql-object-manager/query',
|
||||||
data: input,
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
getList = (input: GetSqlQueriesInput, config?: Partial<Config>) =>
|
updateQuery = (id: string, input: UpdateSqlQueryDto, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<PagedResultDto<SqlQueryDto>, GetSqlQueriesInput>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/sql-query',
|
|
||||||
params: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
get = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlQueryDto, void>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: `/api/app/sql-query/${id}`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
update = (id: string, input: UpdateSqlQueryDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlQueryDto, UpdateSqlQueryDto>(
|
apiService.fetchData<SqlQueryDto, UpdateSqlQueryDto>(
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url: `/api/app/sql-query/${id}`,
|
url: `/api/app/sql-object-manager/query/${id}`,
|
||||||
data: input,
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
delete = (id: string, config?: Partial<Config>) =>
|
deleteQuery = (id: string, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<void, void>(
|
apiService.fetchData<void, void>(
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `/api/app/sql-query/${id}`,
|
url: `/api/app/sql-object-manager/query/${id}`,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
@ -162,281 +69,122 @@ export class SqlQueryService {
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, ExecuteSqlQueryDto>(
|
apiService.fetchData<SqlQueryExecutionResultDto, ExecuteSqlQueryDto>(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/app/sql-query/execute-query',
|
url: '/api/app/sql-object-manager/execute-query',
|
||||||
data: input,
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
executeSavedQuery = (id: string, parameters?: Record<string, any>, config?: Partial<Config>) =>
|
executeSavedQuery = (id: string, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, ExecuteSavedQueryDto>(
|
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `/api/app/sql-query/${id}/execute-saved-query`,
|
url: `/api/app/sql-object-manager/execute-saved-query/${id}`,
|
||||||
data: { id, parameters },
|
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
validateQuery = (input: ValidateQueryDto, config?: Partial<Config>) =>
|
// Stored Procedure Operations
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, ValidateQueryDto>(
|
updateStoredProcedure = (id: string, input: UpdateSqlStoredProcedureDto, config?: Partial<Config>) =>
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/sql-query/validate-query',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
activate = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<void, void>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/sql-query/${id}/activate`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
archive = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<void, void>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/sql-query/${id}/archive`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SqlStoredProcedureService {
|
|
||||||
apiName = 'Default'
|
|
||||||
|
|
||||||
create = (input: CreateSqlStoredProcedureDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlStoredProcedureDto, CreateSqlStoredProcedureDto>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/sql-stored-procedure',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
getList = (input: GetSqlStoredProceduresInput, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<PagedResultDto<SqlStoredProcedureDto>, GetSqlStoredProceduresInput>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/sql-stored-procedure',
|
|
||||||
params: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
get = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlStoredProcedureDto, void>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: `/api/app/sql-stored-procedure/${id}`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
update = (id: string, input: UpdateSqlStoredProcedureDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlStoredProcedureDto, UpdateSqlStoredProcedureDto>(
|
apiService.fetchData<SqlStoredProcedureDto, UpdateSqlStoredProcedureDto>(
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url: `/api/app/sql-stored-procedure/${id}`,
|
url: `/api/app/sql-object-manager/stored-procedure/${id}`,
|
||||||
data: input,
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
delete = (id: string, config?: Partial<Config>) =>
|
deleteStoredProcedure = (id: string, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<void, void>(
|
apiService.fetchData<void, void>(
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `/api/app/sql-stored-procedure/${id}`,
|
url: `/api/app/sql-object-manager/stored-procedure/${id}`,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
deploy = (input: DeployStoredProcedureDto, config?: Partial<Config>) =>
|
deployStoredProcedure = (input: DeployStoredProcedureDto, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployStoredProcedureDto>(
|
apiService.fetchData<SqlQueryExecutionResultDto, DeployStoredProcedureDto>(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/app/sql-stored-procedure/deploy',
|
url: '/api/app/sql-object-manager/deploy-stored-procedure',
|
||||||
data: input,
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
checkExists = (id: string, config?: Partial<Config>) =>
|
// View Operations
|
||||||
apiService.fetchData<boolean, void>(
|
updateView = (id: string, input: UpdateSqlViewDto, config?: Partial<Config>) =>
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/sql-stored-procedure/${id}/check-exists`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
drop = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/app/sql-stored-procedure/${id}/drop`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SqlViewService {
|
|
||||||
apiName = 'Default'
|
|
||||||
|
|
||||||
create = (input: CreateSqlViewDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlViewDto, CreateSqlViewDto>(
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/app/sql-view',
|
|
||||||
data: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
getList = (input: GetSqlViewsInput, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<PagedResultDto<SqlViewDto>, GetSqlViewsInput>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/sql-view',
|
|
||||||
params: input,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
get = (id: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlViewDto, void>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: `/api/app/sql-view/${id}`,
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
update = (id: string, input: UpdateSqlViewDto, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlViewDto, UpdateSqlViewDto>(
|
apiService.fetchData<SqlViewDto, UpdateSqlViewDto>(
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url: `/api/app/sql-view/${id}`,
|
url: `/api/app/sql-object-manager/view/${id}`,
|
||||||
data: input,
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
delete = (id: string, config?: Partial<Config>) =>
|
deleteView = (id: string, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<void, void>(
|
apiService.fetchData<void, void>(
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `/api/app/sql-view/${id}`,
|
url: `/api/app/sql-object-manager/view/${id}`,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
deploy = (input: DeployViewDto, config?: Partial<Config>) =>
|
deployView = (input: DeployViewDto, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployViewDto>(
|
apiService.fetchData<SqlQueryExecutionResultDto, DeployViewDto>(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/app/sql-view/deploy',
|
url: '/api/app/sql-object-manager/deploy-view',
|
||||||
data: input,
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
checkExists = (id: string, config?: Partial<Config>) =>
|
// Function Operations
|
||||||
apiService.fetchData<boolean, void>(
|
updateFunction = (id: string, input: UpdateSqlFunctionDto, config?: Partial<Config>) =>
|
||||||
|
apiService.fetchData<SqlFunctionDto, UpdateSqlFunctionDto>(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'PUT',
|
||||||
url: `/api/app/sql-view/${id}/check-exists`,
|
url: `/api/app/sql-object-manager/function/${id}`,
|
||||||
|
data: input,
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
|
|
||||||
drop = (id: string, config?: Partial<Config>) =>
|
deleteFunction = (id: string, config?: Partial<Config>) =>
|
||||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
apiService.fetchData<void, void>(
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `/api/app/sql-object-manager/function/${id}`,
|
||||||
|
},
|
||||||
|
{ apiName: this.apiName, ...config },
|
||||||
|
)
|
||||||
|
|
||||||
|
deployFunction = (input: DeployFunctionDto, config?: Partial<Config>) =>
|
||||||
|
apiService.fetchData<SqlQueryExecutionResultDto, DeployFunctionDto>(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
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<Config>) =>
|
||||||
|
apiService.fetchData<DatabaseColumnDto[], void>(
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/app/sql-object-manager/table-columns',
|
||||||
|
params: { dataSourceCode, schemaName, tableName },
|
||||||
},
|
},
|
||||||
{ apiName: this.apiName, ...config },
|
{ apiName: this.apiName, ...config },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SqlTemplateService {
|
// Export service instance
|
||||||
apiName = 'Default'
|
export const sqlObjectManagerService = new SqlObjectManagerService()
|
||||||
|
|
||||||
getQueryTemplates = (config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<SqlTemplateDto[], void>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/sql-template/query-templates',
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
getStoredProcedureTemplate = (
|
|
||||||
procedureName: string,
|
|
||||||
schemaName = 'dbo',
|
|
||||||
config?: Partial<Config>,
|
|
||||||
) =>
|
|
||||||
apiService.fetchData<string, void>(
|
|
||||||
{
|
|
||||||
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<Config>,
|
|
||||||
) =>
|
|
||||||
apiService.fetchData<string, void>(
|
|
||||||
{
|
|
||||||
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<Config>,
|
|
||||||
) =>
|
|
||||||
apiService.fetchData<string, void>(
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/api/app/sql-template/function-template',
|
|
||||||
params: { functionName, functionType, schemaName },
|
|
||||||
},
|
|
||||||
{ apiName: this.apiName, ...config },
|
|
||||||
)
|
|
||||||
|
|
||||||
getQueryTemplate = (templateType: string, config?: Partial<Config>) =>
|
|
||||||
apiService.fetchData<string, void>(
|
|
||||||
{
|
|
||||||
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()
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,7 @@ import type {
|
||||||
SqlQueryExecutionResultDto,
|
SqlQueryExecutionResultDto,
|
||||||
} from '@/proxy/sql-query-manager/models'
|
} from '@/proxy/sql-query-manager/models'
|
||||||
import {
|
import {
|
||||||
sqlFunctionService,
|
sqlObjectManagerService,
|
||||||
sqlQueryService,
|
|
||||||
sqlStoredProcedureService,
|
|
||||||
sqlViewService,
|
|
||||||
} from '@/services/sql-query-manager.service'
|
} from '@/services/sql-query-manager.service'
|
||||||
import { FaDatabase, FaPlay, FaSave, FaSyncAlt } from 'react-icons/fa'
|
import { FaDatabase, FaPlay, FaSave, FaSyncAlt } from 'react-icons/fa'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -48,8 +45,7 @@ const SqlQueryManager = () => {
|
||||||
selectedDataSource: null,
|
selectedDataSource: null,
|
||||||
selectedObject: null,
|
selectedObject: null,
|
||||||
selectedObjectType: null,
|
selectedObjectType: null,
|
||||||
editorContent:
|
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',
|
|
||||||
isExecuting: false,
|
isExecuting: false,
|
||||||
executionResult: null,
|
executionResult: null,
|
||||||
showProperties: false,
|
showProperties: false,
|
||||||
|
|
@ -279,7 +275,8 @@ GO`
|
||||||
}, [translate])
|
}, [translate])
|
||||||
|
|
||||||
const handleTemplateSelect = useCallback((template: string, templateType: string) => {
|
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
|
// 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
|
||||||
|
|
@ -331,7 +328,7 @@ GO`
|
||||||
setState((prev) => ({ ...prev, isExecuting: true, executionResult: null }))
|
setState((prev) => ({ ...prev, isExecuting: true, executionResult: null }))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await sqlQueryService.executeQuery({
|
const result = await sqlObjectManagerService.executeQuery({
|
||||||
queryText: state.editorContent,
|
queryText: state.editorContent,
|
||||||
dataSourceCode: state.selectedDataSource.code || '',
|
dataSourceCode: state.selectedDataSource.code || '',
|
||||||
})
|
})
|
||||||
|
|
@ -395,13 +392,13 @@ GO`
|
||||||
|
|
||||||
switch (state.selectedObjectType) {
|
switch (state.selectedObjectType) {
|
||||||
case 1: // Query
|
case 1: // Query
|
||||||
await sqlQueryService.update(objectId, {
|
await sqlObjectManagerService.updateQuery(objectId, {
|
||||||
...(state.selectedObject as SqlQueryDto),
|
...(state.selectedObject as SqlQueryDto),
|
||||||
queryText: state.editorContent,
|
queryText: state.editorContent,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 2: // Stored Procedure
|
case 2: // Stored Procedure
|
||||||
await sqlStoredProcedureService.update(objectId, {
|
await sqlObjectManagerService.updateStoredProcedure(objectId, {
|
||||||
displayName: (state.selectedObject as SqlStoredProcedureDto).displayName,
|
displayName: (state.selectedObject as SqlStoredProcedureDto).displayName,
|
||||||
description: (state.selectedObject as SqlStoredProcedureDto).description,
|
description: (state.selectedObject as SqlStoredProcedureDto).description,
|
||||||
procedureBody: state.editorContent,
|
procedureBody: state.editorContent,
|
||||||
|
|
@ -410,7 +407,7 @@ GO`
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 3: // View
|
case 3: // View
|
||||||
await sqlViewService.update(objectId, {
|
await sqlObjectManagerService.updateView(objectId, {
|
||||||
displayName: (state.selectedObject as SqlViewDto).displayName,
|
displayName: (state.selectedObject as SqlViewDto).displayName,
|
||||||
description: (state.selectedObject as SqlViewDto).description,
|
description: (state.selectedObject as SqlViewDto).description,
|
||||||
viewDefinition: state.editorContent,
|
viewDefinition: state.editorContent,
|
||||||
|
|
@ -419,7 +416,7 @@ GO`
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 4: // Function
|
case 4: // Function
|
||||||
await sqlFunctionService.update(objectId, {
|
await sqlObjectManagerService.updateFunction(objectId, {
|
||||||
displayName: (state.selectedObject as SqlFunctionDto).displayName,
|
displayName: (state.selectedObject as SqlFunctionDto).displayName,
|
||||||
description: (state.selectedObject as SqlFunctionDto).description,
|
description: (state.selectedObject as SqlFunctionDto).description,
|
||||||
functionBody: state.editorContent,
|
functionBody: state.editorContent,
|
||||||
|
|
@ -452,7 +449,7 @@ GO`
|
||||||
if (!state.selectedDataSource || !saveDialogData.name) return
|
if (!state.selectedDataSource || !saveDialogData.name) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sqlQueryService.create({
|
await sqlObjectManagerService.createQuery({
|
||||||
code: saveDialogData.name.replace(/\s+/g, '_'),
|
code: saveDialogData.name.replace(/\s+/g, '_'),
|
||||||
name: saveDialogData.name,
|
name: saveDialogData.name,
|
||||||
description: saveDialogData.description,
|
description: saveDialogData.description,
|
||||||
|
|
@ -501,13 +498,13 @@ GO`
|
||||||
|
|
||||||
switch (state.selectedObjectType) {
|
switch (state.selectedObjectType) {
|
||||||
case 2: // Stored Procedure
|
case 2: // Stored Procedure
|
||||||
result = await sqlStoredProcedureService.deploy({ id: objectId, dropIfExists: true })
|
result = await sqlObjectManagerService.deployStoredProcedure({ id: objectId, dropIfExists: true })
|
||||||
break
|
break
|
||||||
case 3: // View
|
case 3: // View
|
||||||
result = await sqlViewService.deploy({ id: objectId, dropIfExists: true })
|
result = await sqlObjectManagerService.deployView({ id: objectId, dropIfExists: true })
|
||||||
break
|
break
|
||||||
case 4: // Function
|
case 4: // Function
|
||||||
result = await sqlFunctionService.deploy({ id: objectId, dropIfExists: true })
|
result = await sqlObjectManagerService.deployFunction({ id: objectId, dropIfExists: true })
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -537,9 +534,9 @@ GO`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="h-full overflow-hidden">
|
<Container className="h-full overflow-hidden">
|
||||||
<div className="flex flex-col h-full gap-4 p-1">
|
<div className="flex flex-col h-full p-1">
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<AdaptableCard className="flex-shrink-0 shadow-sm">
|
<AdaptableCard className="flex-shrink-0 shadow-sm mb-4">
|
||||||
<div className="flex items-center justify-between px-1 py-1">
|
<div className="flex items-center justify-between px-1 py-1">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<FaDatabase className="text-lg text-blue-500" />
|
<FaDatabase className="text-lg text-blue-500" />
|
||||||
|
|
@ -615,9 +612,9 @@ GO`
|
||||||
</AdaptableCard>
|
</AdaptableCard>
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
<div className="flex-1 flex gap-4 min-h-0">
|
<div className="flex-1 flex min-h-0">
|
||||||
{/* Left Panel - Object Explorer */}
|
{/* Left Panel - Object Explorer */}
|
||||||
<div className="w-80 flex-shrink-0 flex flex-col min-h-0">
|
<div className="w-80 flex-shrink-0 flex flex-col min-h-0 mr-4">
|
||||||
<AdaptableCard className="h-full" bodyClass="p-0">
|
<AdaptableCard className="h-full" bodyClass="p-0">
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="border-b px-4 py-3 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
<div className="border-b px-4 py-3 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
||||||
|
|
@ -625,7 +622,7 @@ GO`
|
||||||
{translate('::App.Platform.ObjectExplorer')}
|
{translate('::App.Platform.ObjectExplorer')}
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-auto p-2 min-h-0">
|
<div className="flex-1 min-h-0 overflow-auto">
|
||||||
<SqlObjectExplorer
|
<SqlObjectExplorer
|
||||||
dataSource={state.selectedDataSource}
|
dataSource={state.selectedDataSource}
|
||||||
onObjectSelect={handleObjectSelect}
|
onObjectSelect={handleObjectSelect}
|
||||||
|
|
@ -638,7 +635,7 @@ GO`
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Center Panel - Editor and Results */}
|
{/* Center Panel - Editor and Results */}
|
||||||
<div className="flex-1 flex flex-col min-h-0">
|
<div className="flex-1 flex flex-col min-h-0 mr-4">
|
||||||
<div className="flex-1 border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col overflow-hidden">
|
<div className="flex-1 border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col overflow-hidden">
|
||||||
<div className="border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
<div className="border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
||||||
<h6 className="font-semibold text-sm">{translate('::App.Platform.QueryEditor')}</h6>
|
<h6 className="font-semibold text-sm">{translate('::App.Platform.QueryEditor')}</h6>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ import {
|
||||||
FaSyncAlt,
|
FaSyncAlt,
|
||||||
FaEdit,
|
FaEdit,
|
||||||
FaTrash,
|
FaTrash,
|
||||||
|
FaTable,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
|
import { MdViewColumn } from 'react-icons/md'
|
||||||
import type { DataSourceDto } from '@/proxy/data-source'
|
import type { DataSourceDto } from '@/proxy/data-source'
|
||||||
import type {
|
import type {
|
||||||
SqlFunctionDto,
|
SqlFunctionDto,
|
||||||
|
|
@ -20,10 +22,7 @@ import type {
|
||||||
SqlObjectType,
|
SqlObjectType,
|
||||||
} from '@/proxy/sql-query-manager/models'
|
} from '@/proxy/sql-query-manager/models'
|
||||||
import {
|
import {
|
||||||
sqlFunctionService,
|
sqlObjectManagerService,
|
||||||
sqlQueryService,
|
|
||||||
sqlStoredProcedureService,
|
|
||||||
sqlViewService,
|
|
||||||
} from '@/services/sql-query-manager.service'
|
} from '@/services/sql-query-manager.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
||||||
|
|
@ -32,11 +31,13 @@ export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | S
|
||||||
interface TreeNode {
|
interface TreeNode {
|
||||||
id: string
|
id: string
|
||||||
label: string
|
label: string
|
||||||
type: 'root' | 'folder' | 'object'
|
type: 'root' | 'folder' | 'object' | 'column'
|
||||||
objectType?: SqlObjectType
|
objectType?: SqlObjectType
|
||||||
data?: SqlObject
|
data?: SqlObject | any
|
||||||
children?: TreeNode[]
|
children?: TreeNode[]
|
||||||
expanded?: boolean
|
expanded?: boolean
|
||||||
|
isColumn?: boolean
|
||||||
|
parentTable?: { schemaName: string; tableName: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SqlObjectExplorerProps {
|
interface SqlObjectExplorerProps {
|
||||||
|
|
@ -55,9 +56,10 @@ const SqlObjectExplorer = ({
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const [treeData, setTreeData] = useState<TreeNode[]>([])
|
const [treeData, setTreeData] = useState<TreeNode[]>([])
|
||||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(
|
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(
|
||||||
new Set(['root', 'templates', 'queries', 'storedProcedures', 'views', 'functions']),
|
new Set(['root']), // Only root expanded by default
|
||||||
)
|
)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [filterText, setFilterText] = useState('')
|
||||||
const [contextMenu, setContextMenu] = useState<{
|
const [contextMenu, setContextMenu] = useState<{
|
||||||
show: boolean
|
show: boolean
|
||||||
x: number
|
x: number
|
||||||
|
|
@ -83,28 +85,10 @@ const SqlObjectExplorer = ({
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const [queries, storedProcedures, views, functions] = await Promise.all([
|
// Single API call to get all objects
|
||||||
sqlQueryService.getList({
|
const response = await sqlObjectManagerService.getAllObjects(dataSource.code || '')
|
||||||
skipCount: 0,
|
const allObjects = response.data
|
||||||
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,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
const tree: TreeNode[] = [
|
const tree: TreeNode[] = [
|
||||||
{
|
{
|
||||||
id: 'root',
|
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',
|
id: 'queries',
|
||||||
label: `${translate('::App.Platform.Queries')} (${queries.data.totalCount})`,
|
label: `${translate('::App.Platform.Queries')} (${allObjects.queries.length})`,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
objectType: 1,
|
objectType: 1,
|
||||||
expanded: expandedNodes.has('queries'),
|
expanded: expandedNodes.has('queries'),
|
||||||
children:
|
children:
|
||||||
queries.data.items?.map((q) => ({
|
allObjects.queries.map((q) => ({
|
||||||
id: q.id || '',
|
id: q.id || '',
|
||||||
label: q.name,
|
label: q.name,
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
|
|
@ -185,12 +182,12 @@ const SqlObjectExplorer = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'storedProcedures',
|
id: 'storedProcedures',
|
||||||
label: `${translate('::App.Platform.StoredProcedures')} (${storedProcedures.data.totalCount})`,
|
label: `${translate('::App.Platform.StoredProcedures')} (${allObjects.storedProcedures.length})`,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
objectType: 2,
|
objectType: 2,
|
||||||
expanded: expandedNodes.has('storedProcedures'),
|
expanded: expandedNodes.has('storedProcedures'),
|
||||||
children:
|
children:
|
||||||
storedProcedures.data.items?.map((sp) => ({
|
allObjects.storedProcedures.map((sp) => ({
|
||||||
id: sp.id || '',
|
id: sp.id || '',
|
||||||
label: sp.displayName || sp.procedureName,
|
label: sp.displayName || sp.procedureName,
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
|
|
@ -200,12 +197,12 @@ const SqlObjectExplorer = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'views',
|
id: 'views',
|
||||||
label: `${translate('::App.Platform.Views')} (${views.data.totalCount})`,
|
label: `${translate('::App.Platform.Views')} (${allObjects.views.length})`,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
objectType: 3,
|
objectType: 3,
|
||||||
expanded: expandedNodes.has('views'),
|
expanded: expandedNodes.has('views'),
|
||||||
children:
|
children:
|
||||||
views.data.items?.map((v) => ({
|
allObjects.views.map((v) => ({
|
||||||
id: v.id || '',
|
id: v.id || '',
|
||||||
label: v.displayName || v.viewName,
|
label: v.displayName || v.viewName,
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
|
|
@ -215,12 +212,12 @@ const SqlObjectExplorer = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'functions',
|
id: 'functions',
|
||||||
label: `${translate('::App.Platform.Functions')} (${functions.data.totalCount})`,
|
label: `${translate('::App.Platform.Functions')} (${allObjects.functions.length})`,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
objectType: 4,
|
objectType: 4,
|
||||||
expanded: expandedNodes.has('functions'),
|
expanded: expandedNodes.has('functions'),
|
||||||
children:
|
children:
|
||||||
functions.data.items?.map((f) => ({
|
allObjects.functions.map((f) => ({
|
||||||
id: f.id || '',
|
id: f.id || '',
|
||||||
label: f.displayName || f.functionName,
|
label: f.displayName || f.functionName,
|
||||||
type: 'object' as const,
|
type: 'object' as const,
|
||||||
|
|
@ -245,24 +242,116 @@ const SqlObjectExplorer = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleNode = (nodeId: string) => {
|
const loadTableColumns = async (schemaName: string, tableName: string): Promise<TreeNode[]> => {
|
||||||
setExpandedNodes((prev) => {
|
try {
|
||||||
const newSet = new Set(prev)
|
const response = await sqlObjectManagerService.getTableColumns(
|
||||||
if (newSet.has(nodeId)) newSet.delete(nodeId)
|
dataSource?.code || '',
|
||||||
else newSet.add(nodeId)
|
schemaName,
|
||||||
return newSet
|
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) => {
|
const handleNodeClick = (node: TreeNode) => {
|
||||||
if (node.type === 'folder' || node.type === 'root') {
|
if (node.type === 'folder' || node.type === 'root') {
|
||||||
toggleNode(node.id)
|
toggleNode(node.id)
|
||||||
|
} else if (node.type === 'column') {
|
||||||
|
// Column clicked - do nothing or show info
|
||||||
|
return
|
||||||
} else if (node.type === 'object' && node.data) {
|
} else if (node.type === 'object' && node.data) {
|
||||||
// Check if it's a template
|
// Check if it's a template
|
||||||
if ((node.data as any).templateType && onTemplateSelect) {
|
if ((node.data as any).templateType && onTemplateSelect) {
|
||||||
const templateType = (node.data as any).templateType
|
const templateType = (node.data as any).templateType
|
||||||
onTemplateSelect('', templateType) // Template content will be generated in parent
|
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)
|
onObjectSelect(node.data, node.objectType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -270,6 +359,12 @@ const SqlObjectExplorer = ({
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent, node: TreeNode) => {
|
const handleContextMenu = (e: React.MouseEvent, node: TreeNode) => {
|
||||||
e.preventDefault()
|
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({
|
setContextMenu({
|
||||||
show: true,
|
show: true,
|
||||||
x: e.clientX,
|
x: e.clientX,
|
||||||
|
|
@ -286,16 +381,16 @@ const SqlObjectExplorer = ({
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1:
|
case 1:
|
||||||
await sqlQueryService.delete(object.id!)
|
await sqlObjectManagerService.deleteQuery(object.id!)
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
await sqlStoredProcedureService.delete(object.id!)
|
await sqlObjectManagerService.deleteStoredProcedure(object.id!)
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
await sqlViewService.delete(object.id!)
|
await sqlObjectManagerService.deleteView(object.id!)
|
||||||
break
|
break
|
||||||
case 4:
|
case 4:
|
||||||
await sqlFunctionService.delete(object.id!)
|
await sqlObjectManagerService.deleteFunction(object.id!)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,6 +432,14 @@ const SqlObjectExplorer = ({
|
||||||
<FaRegFolder className="text-orange-500" />
|
<FaRegFolder className="text-orange-500" />
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Tables folder
|
||||||
|
if (node.id === 'tables')
|
||||||
|
return isExpanded ? (
|
||||||
|
<FaRegFolderOpen className="text-blue-500" />
|
||||||
|
) : (
|
||||||
|
<FaRegFolder className="text-blue-500" />
|
||||||
|
)
|
||||||
|
|
||||||
if (node.objectType === 1)
|
if (node.objectType === 1)
|
||||||
return isExpanded ? (
|
return isExpanded ? (
|
||||||
<FaRegFolderOpen className="text-yellow-500" />
|
<FaRegFolderOpen className="text-yellow-500" />
|
||||||
|
|
@ -372,31 +475,41 @@ const SqlObjectExplorer = ({
|
||||||
return <FaCode className="text-orange-500" />
|
return <FaCode className="text-orange-500" />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if it's a table
|
||||||
|
if (node.id.startsWith('table-')) {
|
||||||
|
return <FaTable className="text-blue-500" />
|
||||||
|
}
|
||||||
|
|
||||||
if (node.objectType === 1) return <FaRegFileAlt className="text-gray-500" />
|
if (node.objectType === 1) return <FaRegFileAlt className="text-gray-500" />
|
||||||
if (node.objectType === 2) return <FaCog className="text-gray-500" />
|
if (node.objectType === 2) return <FaCog className="text-gray-500" />
|
||||||
if (node.objectType === 3) return <FaColumns className="text-gray-500" />
|
if (node.objectType === 3) return <FaColumns className="text-gray-500" />
|
||||||
if (node.objectType === 4) return <FaCode className="text-gray-500" />
|
if (node.objectType === 4) return <FaCode className="text-gray-500" />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.type === 'column') {
|
||||||
|
return <MdViewColumn className="text-gray-400 text-sm" />
|
||||||
|
}
|
||||||
|
|
||||||
return <FaRegFolder />
|
return <FaRegFolder />
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderNode = (node: TreeNode, level = 0) => {
|
const renderNode = (node: TreeNode, level = 0) => {
|
||||||
const isExpanded = expandedNodes.has(node.id)
|
const isExpanded = expandedNodes.has(node.id)
|
||||||
const isSelected = node.type === 'object' && selectedObject?.id === node.id
|
const isSelected = node.type === 'object' && selectedObject?.id === node.id
|
||||||
|
const isColumn = node.type === 'column'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={node.id}>
|
<div key={node.id}>
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded ${
|
className={`flex items-center gap-2 py-1 px-2 ${isColumn ? 'cursor-default' : 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700'} rounded ${
|
||||||
isSelected ? 'bg-blue-100 dark:bg-blue-900' : ''
|
isSelected ? 'bg-blue-100 dark:bg-blue-900' : ''
|
||||||
}`}
|
}`}
|
||||||
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
||||||
onClick={() => handleNodeClick(node)}
|
onClick={() => !isColumn && handleNodeClick(node)}
|
||||||
onContextMenu={(e) => handleContextMenu(e, node)}
|
onContextMenu={(e) => !isColumn && handleContextMenu(e, node)}
|
||||||
>
|
>
|
||||||
{getIcon(node)}
|
{getIcon(node)}
|
||||||
<span className="text-sm flex-1">{node.label}</span>
|
<span className={`text-sm flex-1 ${isColumn ? 'text-gray-600 dark:text-gray-400' : ''}`}>{node.label}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isExpanded && node.children && (
|
{isExpanded && node.children && (
|
||||||
|
|
@ -406,17 +519,48 @@ const SqlObjectExplorer = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredTree = filterTree(treeData, filterText)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full flex flex-col">
|
||||||
{loading && <div className="text-center py-8 text-gray-500">{translate('::App.Platform.Loading')}</div>}
|
{/* Filter and Refresh Controls */}
|
||||||
{!loading && treeData.length === 0 && (
|
<div className="p-2 border-b space-y-2 flex-shrink-0">
|
||||||
<div className="text-center py-8 text-gray-500">
|
<div className="flex gap-2">
|
||||||
{translate('::App.Platform.NoDataSourceSelected')}
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={translate('::App.Platform.Search')}
|
||||||
|
value={filterText}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={loadObjects}
|
||||||
|
disabled={loading || !dataSource}
|
||||||
|
className="px-3 py-1.5 text-sm border rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50"
|
||||||
|
title={translate('::App.Platform.Refresh')}
|
||||||
|
>
|
||||||
|
<FaSyncAlt className={loading ? 'animate-spin' : ''} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
{!loading && treeData.length > 0 && (
|
|
||||||
<div className="space-y-1">{treeData.map((node) => renderNode(node))}</div>
|
{/* Tree Content */}
|
||||||
)}
|
<div className="flex-1 overflow-auto">
|
||||||
|
{loading && <div className="text-center py-8 text-gray-500">{translate('::App.Platform.Loading')}</div>}
|
||||||
|
{!loading && treeData.length === 0 && (
|
||||||
|
<div className="text-center py-8 text-gray-500">
|
||||||
|
{translate('::App.Platform.NoDataSourceSelected')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!loading && filteredTree.length > 0 && (
|
||||||
|
<div className="space-y-1 p-2">{filteredTree.map((node) => renderNode(node))}</div>
|
||||||
|
)}
|
||||||
|
{!loading && treeData.length > 0 && filteredTree.length === 0 && (
|
||||||
|
<div className="text-center py-8 text-gray-500">
|
||||||
|
{translate('::App.Platform.NoResultsFound')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{contextMenu.show && (
|
{contextMenu.show && (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue