Sql Object AppServis başlığında tüm appservisler birleştirildi.
This commit is contained in:
parent
cb2e007302
commit
932ee406b1
19 changed files with 840 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,417 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Erp.SqlQueryManager.Application.Contracts;
|
||||
using Erp.SqlQueryManager.Domain.Entities;
|
||||
using Erp.SqlQueryManager.Domain.Services;
|
||||
using Erp.SqlQueryManager.Domain.Shared;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
namespace Erp.SqlQueryManager.Application;
|
||||
|
||||
/// <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",
|
||||
"en": "View"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Tables",
|
||||
"tr": "Tablolar",
|
||||
"en": "Tables"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Function",
|
||||
|
|
|
|||
|
|
@ -244,3 +244,27 @@ export interface GetSqlViewsInput extends PagedAndSortedResultRequestDto {
|
|||
status?: SqlQueryStatus
|
||||
category?: string
|
||||
}
|
||||
|
||||
// Database Metadata DTOs
|
||||
export interface DatabaseTableDto {
|
||||
schemaName: string
|
||||
tableName: string
|
||||
fullName: string
|
||||
}
|
||||
|
||||
export interface DatabaseColumnDto {
|
||||
columnName: string
|
||||
dataType: string
|
||||
isNullable: boolean
|
||||
maxLength?: number
|
||||
}
|
||||
|
||||
// Unified Object Explorer Response
|
||||
export interface SqlObjectExplorerDto {
|
||||
queries: SqlQueryDto[]
|
||||
storedProcedures: SqlStoredProcedureDto[]
|
||||
views: SqlViewDto[]
|
||||
functions: SqlFunctionDto[]
|
||||
tables: DatabaseTableDto[]
|
||||
templates: SqlTemplateDto[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,159 +1,66 @@
|
|||
import apiService, { Config } from '@/services/api.service'
|
||||
import type { PagedResultDto } from '@/proxy'
|
||||
import type {
|
||||
SqlFunctionDto,
|
||||
CreateSqlFunctionDto,
|
||||
UpdateSqlFunctionDto,
|
||||
DeployFunctionDto,
|
||||
SqlQueryDto,
|
||||
CreateSqlQueryDto,
|
||||
UpdateSqlQueryDto,
|
||||
CreateSqlQueryDto,
|
||||
ExecuteSqlQueryDto,
|
||||
ExecuteSavedQueryDto,
|
||||
ValidateQueryDto,
|
||||
SqlStoredProcedureDto,
|
||||
CreateSqlStoredProcedureDto,
|
||||
UpdateSqlStoredProcedureDto,
|
||||
DeployStoredProcedureDto,
|
||||
SqlViewDto,
|
||||
CreateSqlViewDto,
|
||||
UpdateSqlViewDto,
|
||||
DeployViewDto,
|
||||
SqlTemplateDto,
|
||||
DeployFunctionDto,
|
||||
SqlQueryExecutionResultDto,
|
||||
GetSqlFunctionsInput,
|
||||
GetSqlQueriesInput,
|
||||
GetSqlStoredProceduresInput,
|
||||
GetSqlViewsInput,
|
||||
DatabaseColumnDto,
|
||||
DatabaseTableDto,
|
||||
SqlObjectExplorerDto,
|
||||
} from '@/proxy/sql-query-manager/models'
|
||||
|
||||
export class SqlFunctionService {
|
||||
export class SqlObjectManagerService {
|
||||
apiName = 'Default'
|
||||
|
||||
create = (input: CreateSqlFunctionDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlFunctionDto, CreateSqlFunctionDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-function',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getList = (input: GetSqlFunctionsInput, config?: Partial<Config>) =>
|
||||
apiService.fetchData<PagedResultDto<SqlFunctionDto>, GetSqlFunctionsInput>(
|
||||
/**
|
||||
* Get all SQL objects for Object Explorer in a single call
|
||||
*/
|
||||
getAllObjects = (dataSourceCode: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlObjectExplorerDto, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-function',
|
||||
params: input,
|
||||
url: '/api/app/sql-object-manager/objects',
|
||||
params: { dataSourceCode },
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
get = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlFunctionDto, void>(
|
||||
{
|
||||
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>) =>
|
||||
// Query Operations
|
||||
createQuery = (input: CreateSqlQueryDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryDto, CreateSqlQueryDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-query',
|
||||
url: '/api/app/sql-object-manager/query',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getList = (input: GetSqlQueriesInput, config?: Partial<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>) =>
|
||||
updateQuery = (id: string, input: UpdateSqlQueryDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryDto, UpdateSqlQueryDto>(
|
||||
{
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-query/${id}`,
|
||||
url: `/api/app/sql-object-manager/query/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
delete = (id: string, config?: Partial<Config>) =>
|
||||
deleteQuery = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/api/app/sql-query/${id}`,
|
||||
url: `/api/app/sql-object-manager/query/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
|
@ -162,281 +69,122 @@ export class SqlQueryService {
|
|||
apiService.fetchData<SqlQueryExecutionResultDto, ExecuteSqlQueryDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-query/execute-query',
|
||||
url: '/api/app/sql-object-manager/execute-query',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
executeSavedQuery = (id: string, parameters?: Record<string, any>, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, ExecuteSavedQueryDto>(
|
||||
executeSavedQuery = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-query/${id}/execute-saved-query`,
|
||||
data: { id, parameters },
|
||||
url: `/api/app/sql-object-manager/execute-saved-query/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
validateQuery = (input: ValidateQueryDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, ValidateQueryDto>(
|
||||
{
|
||||
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>) =>
|
||||
// Stored Procedure Operations
|
||||
updateStoredProcedure = (id: string, input: UpdateSqlStoredProcedureDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlStoredProcedureDto, UpdateSqlStoredProcedureDto>(
|
||||
{
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-stored-procedure/${id}`,
|
||||
url: `/api/app/sql-object-manager/stored-procedure/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
delete = (id: string, config?: Partial<Config>) =>
|
||||
deleteStoredProcedure = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/api/app/sql-stored-procedure/${id}`,
|
||||
url: `/api/app/sql-object-manager/stored-procedure/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
deploy = (input: DeployStoredProcedureDto, config?: Partial<Config>) =>
|
||||
deployStoredProcedure = (input: DeployStoredProcedureDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployStoredProcedureDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-stored-procedure/deploy',
|
||||
url: '/api/app/sql-object-manager/deploy-stored-procedure',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
checkExists = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<boolean, void>(
|
||||
{
|
||||
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>) =>
|
||||
// View Operations
|
||||
updateView = (id: string, input: UpdateSqlViewDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlViewDto, UpdateSqlViewDto>(
|
||||
{
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-view/${id}`,
|
||||
url: `/api/app/sql-object-manager/view/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
delete = (id: string, config?: Partial<Config>) =>
|
||||
deleteView = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/api/app/sql-view/${id}`,
|
||||
url: `/api/app/sql-object-manager/view/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
deploy = (input: DeployViewDto, config?: Partial<Config>) =>
|
||||
deployView = (input: DeployViewDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployViewDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-view/deploy',
|
||||
url: '/api/app/sql-object-manager/deploy-view',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
checkExists = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<boolean, void>(
|
||||
// Function Operations
|
||||
updateFunction = (id: string, input: UpdateSqlFunctionDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlFunctionDto, UpdateSqlFunctionDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-view/${id}/check-exists`,
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-object-manager/function/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
drop = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
||||
deleteFunction = (id: string, config?: Partial<Config>) =>
|
||||
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',
|
||||
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 },
|
||||
)
|
||||
}
|
||||
|
||||
export class SqlTemplateService {
|
||||
apiName = 'Default'
|
||||
|
||||
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()
|
||||
// Export service instance
|
||||
export const sqlObjectManagerService = new SqlObjectManagerService()
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ import type {
|
|||
SqlQueryExecutionResultDto,
|
||||
} from '@/proxy/sql-query-manager/models'
|
||||
import {
|
||||
sqlFunctionService,
|
||||
sqlQueryService,
|
||||
sqlStoredProcedureService,
|
||||
sqlViewService,
|
||||
sqlObjectManagerService,
|
||||
} from '@/services/sql-query-manager.service'
|
||||
import { FaDatabase, FaPlay, FaSave, FaSyncAlt } from 'react-icons/fa'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
|
@ -48,8 +45,7 @@ const SqlQueryManager = () => {
|
|||
selectedDataSource: null,
|
||||
selectedObject: null,
|
||||
selectedObjectType: null,
|
||||
editorContent:
|
||||
'-- SQL Query Editor\n-- Write your SQL query here and press F5 or click Execute to run\n\nSELECT * FROM YourTable\nWHERE 1=1',
|
||||
editorContent: '',
|
||||
isExecuting: false,
|
||||
executionResult: null,
|
||||
showProperties: false,
|
||||
|
|
@ -279,7 +275,8 @@ GO`
|
|||
}, [translate])
|
||||
|
||||
const handleTemplateSelect = useCallback((template: string, templateType: string) => {
|
||||
const templateContent = getTemplateContent(templateType)
|
||||
// If template is already provided (e.g., from table click), use it
|
||||
const templateContent = template || getTemplateContent(templateType)
|
||||
|
||||
// Check if editor has content and it's not from a previous template
|
||||
const hasUserContent = state.editorContent.trim() && state.isDirty
|
||||
|
|
@ -331,7 +328,7 @@ GO`
|
|||
setState((prev) => ({ ...prev, isExecuting: true, executionResult: null }))
|
||||
|
||||
try {
|
||||
const result = await sqlQueryService.executeQuery({
|
||||
const result = await sqlObjectManagerService.executeQuery({
|
||||
queryText: state.editorContent,
|
||||
dataSourceCode: state.selectedDataSource.code || '',
|
||||
})
|
||||
|
|
@ -395,13 +392,13 @@ GO`
|
|||
|
||||
switch (state.selectedObjectType) {
|
||||
case 1: // Query
|
||||
await sqlQueryService.update(objectId, {
|
||||
await sqlObjectManagerService.updateQuery(objectId, {
|
||||
...(state.selectedObject as SqlQueryDto),
|
||||
queryText: state.editorContent,
|
||||
})
|
||||
break
|
||||
case 2: // Stored Procedure
|
||||
await sqlStoredProcedureService.update(objectId, {
|
||||
await sqlObjectManagerService.updateStoredProcedure(objectId, {
|
||||
displayName: (state.selectedObject as SqlStoredProcedureDto).displayName,
|
||||
description: (state.selectedObject as SqlStoredProcedureDto).description,
|
||||
procedureBody: state.editorContent,
|
||||
|
|
@ -410,7 +407,7 @@ GO`
|
|||
})
|
||||
break
|
||||
case 3: // View
|
||||
await sqlViewService.update(objectId, {
|
||||
await sqlObjectManagerService.updateView(objectId, {
|
||||
displayName: (state.selectedObject as SqlViewDto).displayName,
|
||||
description: (state.selectedObject as SqlViewDto).description,
|
||||
viewDefinition: state.editorContent,
|
||||
|
|
@ -419,7 +416,7 @@ GO`
|
|||
})
|
||||
break
|
||||
case 4: // Function
|
||||
await sqlFunctionService.update(objectId, {
|
||||
await sqlObjectManagerService.updateFunction(objectId, {
|
||||
displayName: (state.selectedObject as SqlFunctionDto).displayName,
|
||||
description: (state.selectedObject as SqlFunctionDto).description,
|
||||
functionBody: state.editorContent,
|
||||
|
|
@ -452,7 +449,7 @@ GO`
|
|||
if (!state.selectedDataSource || !saveDialogData.name) return
|
||||
|
||||
try {
|
||||
await sqlQueryService.create({
|
||||
await sqlObjectManagerService.createQuery({
|
||||
code: saveDialogData.name.replace(/\s+/g, '_'),
|
||||
name: saveDialogData.name,
|
||||
description: saveDialogData.description,
|
||||
|
|
@ -501,13 +498,13 @@ GO`
|
|||
|
||||
switch (state.selectedObjectType) {
|
||||
case 2: // Stored Procedure
|
||||
result = await sqlStoredProcedureService.deploy({ id: objectId, dropIfExists: true })
|
||||
result = await sqlObjectManagerService.deployStoredProcedure({ id: objectId, dropIfExists: true })
|
||||
break
|
||||
case 3: // View
|
||||
result = await sqlViewService.deploy({ id: objectId, dropIfExists: true })
|
||||
result = await sqlObjectManagerService.deployView({ id: objectId, dropIfExists: true })
|
||||
break
|
||||
case 4: // Function
|
||||
result = await sqlFunctionService.deploy({ id: objectId, dropIfExists: true })
|
||||
result = await sqlObjectManagerService.deployFunction({ id: objectId, dropIfExists: true })
|
||||
break
|
||||
default:
|
||||
toast.push(
|
||||
|
|
@ -537,9 +534,9 @@ GO`
|
|||
|
||||
return (
|
||||
<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 */}
|
||||
<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 gap-3">
|
||||
<FaDatabase className="text-lg text-blue-500" />
|
||||
|
|
@ -615,9 +612,9 @@ GO`
|
|||
</AdaptableCard>
|
||||
|
||||
{/* 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 */}
|
||||
<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">
|
||||
<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">
|
||||
|
|
@ -625,7 +622,7 @@ GO`
|
|||
{translate('::App.Platform.ObjectExplorer')}
|
||||
</h6>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto p-2 min-h-0">
|
||||
<div className="flex-1 min-h-0 overflow-auto">
|
||||
<SqlObjectExplorer
|
||||
dataSource={state.selectedDataSource}
|
||||
onObjectSelect={handleObjectSelect}
|
||||
|
|
@ -638,7 +635,7 @@ GO`
|
|||
</div>
|
||||
|
||||
{/* 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="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>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import {
|
|||
FaSyncAlt,
|
||||
FaEdit,
|
||||
FaTrash,
|
||||
FaTable,
|
||||
} from 'react-icons/fa'
|
||||
import { MdViewColumn } from 'react-icons/md'
|
||||
import type { DataSourceDto } from '@/proxy/data-source'
|
||||
import type {
|
||||
SqlFunctionDto,
|
||||
|
|
@ -20,10 +22,7 @@ import type {
|
|||
SqlObjectType,
|
||||
} from '@/proxy/sql-query-manager/models'
|
||||
import {
|
||||
sqlFunctionService,
|
||||
sqlQueryService,
|
||||
sqlStoredProcedureService,
|
||||
sqlViewService,
|
||||
sqlObjectManagerService,
|
||||
} from '@/services/sql-query-manager.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
|
|
@ -32,11 +31,13 @@ export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | S
|
|||
interface TreeNode {
|
||||
id: string
|
||||
label: string
|
||||
type: 'root' | 'folder' | 'object'
|
||||
type: 'root' | 'folder' | 'object' | 'column'
|
||||
objectType?: SqlObjectType
|
||||
data?: SqlObject
|
||||
data?: SqlObject | any
|
||||
children?: TreeNode[]
|
||||
expanded?: boolean
|
||||
isColumn?: boolean
|
||||
parentTable?: { schemaName: string; tableName: string }
|
||||
}
|
||||
|
||||
interface SqlObjectExplorerProps {
|
||||
|
|
@ -55,9 +56,10 @@ const SqlObjectExplorer = ({
|
|||
const { translate } = useLocalization()
|
||||
const [treeData, setTreeData] = useState<TreeNode[]>([])
|
||||
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 [filterText, setFilterText] = useState('')
|
||||
const [contextMenu, setContextMenu] = useState<{
|
||||
show: boolean
|
||||
x: number
|
||||
|
|
@ -83,28 +85,10 @@ const SqlObjectExplorer = ({
|
|||
|
||||
setLoading(true)
|
||||
try {
|
||||
const [queries, storedProcedures, views, functions] = await Promise.all([
|
||||
sqlQueryService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
sqlStoredProcedureService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
sqlViewService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
sqlFunctionService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
])
|
||||
// Single API call to get all objects
|
||||
const response = await sqlObjectManagerService.getAllObjects(dataSource.code || '')
|
||||
const allObjects = response.data
|
||||
|
||||
const tree: TreeNode[] = [
|
||||
{
|
||||
id: 'root',
|
||||
|
|
@ -168,14 +152,27 @@ const SqlObjectExplorer = ({
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'tables',
|
||||
label: `${translate('::App.Platform.Tables')} (${allObjects.tables.length})`,
|
||||
type: 'folder',
|
||||
expanded: expandedNodes.has('tables'),
|
||||
children:
|
||||
allObjects.tables.map((t) => ({
|
||||
id: `table-${t.schemaName}-${t.tableName}`,
|
||||
label: t.fullName,
|
||||
type: 'object' as const,
|
||||
data: t,
|
||||
})) || [],
|
||||
},
|
||||
{
|
||||
id: 'queries',
|
||||
label: `${translate('::App.Platform.Queries')} (${queries.data.totalCount})`,
|
||||
label: `${translate('::App.Platform.Queries')} (${allObjects.queries.length})`,
|
||||
type: 'folder',
|
||||
objectType: 1,
|
||||
expanded: expandedNodes.has('queries'),
|
||||
children:
|
||||
queries.data.items?.map((q) => ({
|
||||
allObjects.queries.map((q) => ({
|
||||
id: q.id || '',
|
||||
label: q.name,
|
||||
type: 'object' as const,
|
||||
|
|
@ -185,12 +182,12 @@ const SqlObjectExplorer = ({
|
|||
},
|
||||
{
|
||||
id: 'storedProcedures',
|
||||
label: `${translate('::App.Platform.StoredProcedures')} (${storedProcedures.data.totalCount})`,
|
||||
label: `${translate('::App.Platform.StoredProcedures')} (${allObjects.storedProcedures.length})`,
|
||||
type: 'folder',
|
||||
objectType: 2,
|
||||
expanded: expandedNodes.has('storedProcedures'),
|
||||
children:
|
||||
storedProcedures.data.items?.map((sp) => ({
|
||||
allObjects.storedProcedures.map((sp) => ({
|
||||
id: sp.id || '',
|
||||
label: sp.displayName || sp.procedureName,
|
||||
type: 'object' as const,
|
||||
|
|
@ -200,12 +197,12 @@ const SqlObjectExplorer = ({
|
|||
},
|
||||
{
|
||||
id: 'views',
|
||||
label: `${translate('::App.Platform.Views')} (${views.data.totalCount})`,
|
||||
label: `${translate('::App.Platform.Views')} (${allObjects.views.length})`,
|
||||
type: 'folder',
|
||||
objectType: 3,
|
||||
expanded: expandedNodes.has('views'),
|
||||
children:
|
||||
views.data.items?.map((v) => ({
|
||||
allObjects.views.map((v) => ({
|
||||
id: v.id || '',
|
||||
label: v.displayName || v.viewName,
|
||||
type: 'object' as const,
|
||||
|
|
@ -215,12 +212,12 @@ const SqlObjectExplorer = ({
|
|||
},
|
||||
{
|
||||
id: 'functions',
|
||||
label: `${translate('::App.Platform.Functions')} (${functions.data.totalCount})`,
|
||||
label: `${translate('::App.Platform.Functions')} (${allObjects.functions.length})`,
|
||||
type: 'folder',
|
||||
objectType: 4,
|
||||
expanded: expandedNodes.has('functions'),
|
||||
children:
|
||||
functions.data.items?.map((f) => ({
|
||||
allObjects.functions.map((f) => ({
|
||||
id: f.id || '',
|
||||
label: f.displayName || f.functionName,
|
||||
type: 'object' as const,
|
||||
|
|
@ -245,24 +242,116 @@ const SqlObjectExplorer = ({
|
|||
}
|
||||
}
|
||||
|
||||
const toggleNode = (nodeId: string) => {
|
||||
setExpandedNodes((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
if (newSet.has(nodeId)) newSet.delete(nodeId)
|
||||
else newSet.add(nodeId)
|
||||
return newSet
|
||||
const loadTableColumns = async (schemaName: string, tableName: string): Promise<TreeNode[]> => {
|
||||
try {
|
||||
const response = await sqlObjectManagerService.getTableColumns(
|
||||
dataSource?.code || '',
|
||||
schemaName,
|
||||
tableName,
|
||||
)
|
||||
return response.data.map((col) => ({
|
||||
id: `column-${schemaName}-${tableName}-${col.columnName}`,
|
||||
label: `${col.columnName} (${col.dataType}${col.maxLength ? `(${col.maxLength})` : ''})${col.isNullable ? '' : ' NOT NULL'}`,
|
||||
type: 'column' as const,
|
||||
isColumn: true,
|
||||
data: col,
|
||||
parentTable: { schemaName, tableName },
|
||||
}))
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const toggleNode = async (nodeId: string) => {
|
||||
const newSet = new Set(expandedNodes)
|
||||
|
||||
if (newSet.has(nodeId)) {
|
||||
newSet.delete(nodeId)
|
||||
setExpandedNodes(newSet)
|
||||
} else {
|
||||
newSet.add(nodeId)
|
||||
setExpandedNodes(newSet)
|
||||
|
||||
// If it's a table node and hasn't loaded columns yet, load them
|
||||
if (nodeId.startsWith('table-') && dataSource) {
|
||||
const tableNode = findNodeById(treeData, nodeId)
|
||||
if (tableNode && (!tableNode.children || tableNode.children.length === 0)) {
|
||||
const tableData = tableNode.data as any
|
||||
const columns = await loadTableColumns(tableData.schemaName, tableData.tableName)
|
||||
|
||||
// Update tree data with columns
|
||||
setTreeData((prevTree) => {
|
||||
return updateNodeChildren(prevTree, nodeId, columns)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const findNodeById = (nodes: TreeNode[], id: string): TreeNode | null => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node
|
||||
if (node.children) {
|
||||
const found = findNodeById(node.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const updateNodeChildren = (nodes: TreeNode[], nodeId: string, children: TreeNode[]): TreeNode[] => {
|
||||
return nodes.map((node) => {
|
||||
if (node.id === nodeId) {
|
||||
return { ...node, children }
|
||||
}
|
||||
if (node.children) {
|
||||
return { ...node, children: updateNodeChildren(node.children, nodeId, children) }
|
||||
}
|
||||
return node
|
||||
})
|
||||
}
|
||||
|
||||
const filterTree = (nodes: TreeNode[], searchText: string): TreeNode[] => {
|
||||
if (!searchText.trim()) return nodes
|
||||
|
||||
const search = searchText.toLowerCase()
|
||||
const filtered = nodes
|
||||
.map((node) => {
|
||||
const matchesSearch = node.label.toLowerCase().includes(search)
|
||||
const filteredChildren = node.children ? filterTree(node.children, searchText) : []
|
||||
|
||||
if (matchesSearch || filteredChildren.length > 0) {
|
||||
return {
|
||||
...node,
|
||||
children: filteredChildren.length > 0 ? filteredChildren : node.children,
|
||||
} as TreeNode
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter((node) => node !== null) as TreeNode[]
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
const handleNodeClick = (node: TreeNode) => {
|
||||
if (node.type === 'folder' || node.type === 'root') {
|
||||
toggleNode(node.id)
|
||||
} else if (node.type === 'column') {
|
||||
// Column clicked - do nothing or show info
|
||||
return
|
||||
} else if (node.type === 'object' && node.data) {
|
||||
// Check if it's a template
|
||||
if ((node.data as any).templateType && onTemplateSelect) {
|
||||
const templateType = (node.data as any).templateType
|
||||
onTemplateSelect('', templateType) // Template content will be generated in parent
|
||||
} else if (node.objectType) {
|
||||
}
|
||||
// Check if it's a table
|
||||
else if (node.id.startsWith('table-') && onTemplateSelect) {
|
||||
const table = node.data as any
|
||||
const selectQuery = `-- SELECT from ${table.fullName || table.tableName}\nSELECT * \nFROM ${table.fullName || `[${table.schemaName}].[${table.tableName}]`}\nWHERE 1=1;`
|
||||
onTemplateSelect(selectQuery, 'table-select')
|
||||
}
|
||||
else if (node.objectType) {
|
||||
onObjectSelect(node.data, node.objectType)
|
||||
}
|
||||
}
|
||||
|
|
@ -270,6 +359,12 @@ const SqlObjectExplorer = ({
|
|||
|
||||
const handleContextMenu = (e: React.MouseEvent, node: TreeNode) => {
|
||||
e.preventDefault()
|
||||
|
||||
// Don't show context menu for columns, templates, or tables
|
||||
if (node.type === 'column' || node.id.startsWith('template-') || node.id.startsWith('table-')) {
|
||||
return
|
||||
}
|
||||
|
||||
setContextMenu({
|
||||
show: true,
|
||||
x: e.clientX,
|
||||
|
|
@ -286,16 +381,16 @@ const SqlObjectExplorer = ({
|
|||
|
||||
switch (type) {
|
||||
case 1:
|
||||
await sqlQueryService.delete(object.id!)
|
||||
await sqlObjectManagerService.deleteQuery(object.id!)
|
||||
break
|
||||
case 2:
|
||||
await sqlStoredProcedureService.delete(object.id!)
|
||||
await sqlObjectManagerService.deleteStoredProcedure(object.id!)
|
||||
break
|
||||
case 3:
|
||||
await sqlViewService.delete(object.id!)
|
||||
await sqlObjectManagerService.deleteView(object.id!)
|
||||
break
|
||||
case 4:
|
||||
await sqlFunctionService.delete(object.id!)
|
||||
await sqlObjectManagerService.deleteFunction(object.id!)
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -337,6 +432,14 @@ const SqlObjectExplorer = ({
|
|||
<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)
|
||||
return isExpanded ? (
|
||||
<FaRegFolderOpen className="text-yellow-500" />
|
||||
|
|
@ -372,31 +475,41 @@ const SqlObjectExplorer = ({
|
|||
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 === 2) return <FaCog 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.type === 'column') {
|
||||
return <MdViewColumn className="text-gray-400 text-sm" />
|
||||
}
|
||||
|
||||
return <FaRegFolder />
|
||||
}
|
||||
|
||||
const renderNode = (node: TreeNode, level = 0) => {
|
||||
const isExpanded = expandedNodes.has(node.id)
|
||||
const isSelected = node.type === 'object' && selectedObject?.id === node.id
|
||||
const isColumn = node.type === 'column'
|
||||
|
||||
return (
|
||||
<div key={node.id}>
|
||||
<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' : ''
|
||||
}`}
|
||||
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
||||
onClick={() => handleNodeClick(node)}
|
||||
onContextMenu={(e) => handleContextMenu(e, node)}
|
||||
onClick={() => !isColumn && handleNodeClick(node)}
|
||||
onContextMenu={(e) => !isColumn && handleContextMenu(e, 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>
|
||||
|
||||
{isExpanded && node.children && (
|
||||
|
|
@ -406,17 +519,48 @@ const SqlObjectExplorer = ({
|
|||
)
|
||||
}
|
||||
|
||||
const filteredTree = filterTree(treeData, filterText)
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
{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 className="h-full flex flex-col">
|
||||
{/* Filter and Refresh Controls */}
|
||||
<div className="p-2 border-b space-y-2 flex-shrink-0">
|
||||
<div className="flex gap-2">
|
||||
<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>
|
||||
)}
|
||||
{!loading && treeData.length > 0 && (
|
||||
<div className="space-y-1">{treeData.map((node) => renderNode(node))}</div>
|
||||
)}
|
||||
</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 && (
|
||||
<>
|
||||
|
|
|
|||
Loading…
Reference in a new issue