Sql Query Manager düzenlemesi

This commit is contained in:
Sedat Öztürk 2025-12-06 15:09:54 +03:00
parent 4a7f2ee853
commit 9b2c1c25ee
11 changed files with 417 additions and 83 deletions

View file

@ -42,6 +42,11 @@ public interface ISqlObjectManagerAppService : IApplicationService
// Database Metadata Operations
Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName);
/// <summary>
/// Gets the SQL definition/body of a native SQL Server object (Stored Procedure, View, or Function)
/// </summary>
Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName);
// Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy
Task<SmartSaveResultDto> SmartSaveAsync(SmartSaveInputDto input);
}

View file

@ -20,6 +20,7 @@ public class SqlFunctionDto : FullAuditedEntityDto<Guid>
public bool IsDeployed { get; set; }
public DateTime? LastDeployedAt { get; set; }
public string Parameters { get; set; }
public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object
}
public class CreateSqlFunctionDto

View file

@ -18,6 +18,7 @@ public class SqlStoredProcedureDto : FullAuditedEntityDto<Guid>
public bool IsDeployed { get; set; }
public DateTime? LastDeployedAt { get; set; }
public string Parameters { get; set; }
public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object
}
public class CreateSqlStoredProcedureDto

View file

@ -18,6 +18,7 @@ public class SqlViewDto : FullAuditedEntityDto<Guid>
public bool IsDeployed { get; set; }
public DateTime? LastDeployedAt { get; set; }
public bool WithSchemaBinding { get; set; }
public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object
}
public class CreateSqlViewDto

View file

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Erp.SqlQueryManager.Application.Contracts;
using Erp.SqlQueryManager.Domain.Entities;
@ -100,67 +102,14 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
})
.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 stored procedures for this data source (custom + native merged)
result.StoredProcedures = await GetMergedStoredProceduresAsync(dataSourceCode);
// 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 views for this data source (custom + native merged)
result.Views = await GetMergedViewsAsync(dataSourceCode);
// 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 functions for this data source (custom + native merged)
result.Functions = await GetMergedFunctionsAsync(dataSourceCode);
// Get all database tables
result.Tables = await GetTablesAsync(dataSourceCode);
@ -214,6 +163,244 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
return tables;
}
private async Task<List<SqlStoredProcedureDto>> GetMergedStoredProceduresAsync(string dataSourceCode)
{
// Get custom stored procedures from database
var customProcedures = await _procedureRepository.GetListAsync();
var customList = customProcedures
.Where(p => p.DataSourceCode == dataSourceCode)
.Select(p => new SqlStoredProcedureDto
{
Id = p.Id,
ProcedureName = p.ProcedureName,
SchemaName = p.SchemaName,
DisplayName = p.DisplayName,
Description = p.Description,
ProcedureBody = p.ProcedureBody,
DataSourceCode = p.DataSourceCode,
Category = p.Category,
Parameters = p.Parameters,
IsDeployed = p.IsDeployed,
LastDeployedAt = p.LastDeployedAt,
IsCustom = true
})
.ToList();
// Get native stored procedures from SQL Server
var nativeQuery = @"
SELECT
SCHEMA_NAME(p.schema_id) AS SchemaName,
p.name AS ProcedureName,
p.create_date AS CreatedDate,
p.modify_date AS ModifiedDate
FROM
sys.procedures p
WHERE
p.is_ms_shipped = 0
ORDER BY
SCHEMA_NAME(p.schema_id), p.name";
var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode);
var nativeList = new List<SqlStoredProcedureDto>();
if (result.Success && result.Data != null)
{
foreach (var row in result.Data)
{
var dict = row as System.Collections.Generic.IDictionary<string, object>;
if (dict != null)
{
var schemaName = dict["SchemaName"]?.ToString() ?? "dbo";
var procName = dict["ProcedureName"]?.ToString() ?? "";
// Skip if already exists in custom list
if (!customList.Any(c => c.SchemaName == schemaName && c.ProcedureName == procName))
{
// Generate deterministic GUID from schema and name to ensure uniqueness
var uniqueId = GenerateDeterministicGuid($"SP_{dataSourceCode}_{schemaName}_{procName}");
nativeList.Add(new SqlStoredProcedureDto
{
Id = uniqueId,
SchemaName = schemaName,
ProcedureName = procName,
DisplayName = $"[{schemaName}].[{procName}]",
DataSourceCode = dataSourceCode,
IsCustom = false,
IsDeployed = true // Native objects are already deployed
});
}
}
}
}
// Merge and return
return customList.Concat(nativeList).ToList();
}
private async Task<List<SqlViewDto>> GetMergedViewsAsync(string dataSourceCode)
{
// Get custom views from database
var customViews = await _viewRepository.GetListAsync();
var customList = customViews
.Where(v => v.DataSourceCode == dataSourceCode)
.Select(v => new SqlViewDto
{
Id = v.Id,
ViewName = v.ViewName,
SchemaName = v.SchemaName,
DisplayName = v.DisplayName,
Description = v.Description,
ViewDefinition = v.ViewDefinition,
DataSourceCode = v.DataSourceCode,
Category = v.Category,
WithSchemaBinding = v.WithSchemaBinding,
IsDeployed = v.IsDeployed,
LastDeployedAt = v.LastDeployedAt,
IsCustom = true
})
.ToList();
// Get native views from SQL Server
var nativeQuery = @"
SELECT
SCHEMA_NAME(v.schema_id) AS SchemaName,
v.name AS ViewName,
v.create_date AS CreatedDate,
v.modify_date AS ModifiedDate
FROM
sys.views v
WHERE
v.is_ms_shipped = 0
ORDER BY
SCHEMA_NAME(v.schema_id), v.name";
var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode);
var nativeList = new List<SqlViewDto>();
if (result.Success && result.Data != null)
{
foreach (var row in result.Data)
{
var dict = row as System.Collections.Generic.IDictionary<string, object>;
if (dict != null)
{
var schemaName = dict["SchemaName"]?.ToString() ?? "dbo";
var viewName = dict["ViewName"]?.ToString() ?? "";
// Skip if already exists in custom list
if (!customList.Any(c => c.SchemaName == schemaName && c.ViewName == viewName))
{
// Generate deterministic GUID from schema and name to ensure uniqueness
var uniqueId = GenerateDeterministicGuid($"VIEW_{dataSourceCode}_{schemaName}_{viewName}");
nativeList.Add(new SqlViewDto
{
Id = uniqueId,
SchemaName = schemaName,
ViewName = viewName,
DisplayName = $"[{schemaName}].[{viewName}]",
DataSourceCode = dataSourceCode,
IsCustom = false,
IsDeployed = true
});
}
}
}
}
return customList.Concat(nativeList).ToList();
}
private async Task<List<SqlFunctionDto>> GetMergedFunctionsAsync(string dataSourceCode)
{
// Get custom functions from database
var customFunctions = await _functionRepository.GetListAsync();
var customList = customFunctions
.Where(f => f.DataSourceCode == dataSourceCode)
.Select(f => new SqlFunctionDto
{
Id = f.Id,
FunctionName = f.FunctionName,
SchemaName = f.SchemaName,
DisplayName = f.DisplayName,
Description = f.Description,
FunctionType = f.FunctionType,
FunctionBody = f.FunctionBody,
ReturnType = f.ReturnType,
DataSourceCode = f.DataSourceCode,
Category = f.Category,
Parameters = f.Parameters,
IsDeployed = f.IsDeployed,
LastDeployedAt = f.LastDeployedAt,
IsCustom = true
})
.ToList();
// Get native functions from SQL Server
var nativeQuery = @"
SELECT
SCHEMA_NAME(o.schema_id) AS SchemaName,
o.name AS FunctionName,
o.create_date AS CreatedDate,
o.modify_date AS ModifiedDate,
CASE o.type
WHEN 'FN' THEN 'Scalar'
WHEN 'IF' THEN 'InlineTableValued'
WHEN 'TF' THEN 'TableValued'
ELSE 'Unknown'
END AS FunctionType
FROM
sys.objects o
WHERE
o.type IN ('FN', 'IF', 'TF')
AND o.is_ms_shipped = 0
ORDER BY
SCHEMA_NAME(o.schema_id), o.name";
var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode);
var nativeList = new List<SqlFunctionDto>();
if (result.Success && result.Data != null)
{
foreach (var row in result.Data)
{
var dict = row as System.Collections.Generic.IDictionary<string, object>;
if (dict != null)
{
var schemaName = dict["SchemaName"]?.ToString() ?? "dbo";
var funcName = dict["FunctionName"]?.ToString() ?? "";
// Skip if already exists in custom list
if (!customList.Any(c => c.SchemaName == schemaName && c.FunctionName == funcName))
{
var funcTypeStr = dict["FunctionType"]?.ToString() ?? "Scalar";
var funcType = funcTypeStr == "Scalar" ? SqlFunctionType.ScalarFunction :
funcTypeStr == "InlineTableValued" ? SqlFunctionType.InlineTableValuedFunction :
SqlFunctionType.TableValuedFunction;
// Generate deterministic GUID from schema and name to ensure uniqueness
var uniqueId = GenerateDeterministicGuid($"FUNC_{dataSourceCode}_{schemaName}_{funcName}");
nativeList.Add(new SqlFunctionDto
{
Id = uniqueId,
SchemaName = schemaName,
FunctionName = funcName,
DisplayName = $"[{schemaName}].[{funcName}]",
DataSourceCode = dataSourceCode,
FunctionType = funcType,
IsCustom = false,
IsDeployed = true
});
}
}
}
}
return customList.Concat(nativeList).ToList();
}
#region Query Operations
public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input)
@ -551,6 +738,34 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
#region Database Metadata Operations
public async Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName)
{
ValidateTenantAccess();
var query = @"
SELECT OBJECT_DEFINITION(OBJECT_ID(@ObjectName)) AS Definition";
var fullObjectName = $"[{schemaName}].[{objectName}]";
var result = await _sqlExecutorService.ExecuteQueryAsync(
query.Replace("@ObjectName", $"'{fullObjectName}'"),
dataSourceCode
);
if (result.Success && result.Data != null)
{
var dataList = result.Data.ToList();
if (dataList.Count > 0)
{
var row = dataList[0] as System.Collections.Generic.IDictionary<string, object>;
if (row != null && row.ContainsKey("Definition"))
{
return row["Definition"]?.ToString() ?? string.Empty;
}
}
}
return string.Empty;
}
public async Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName)
{
ValidateTenantAccess();
@ -838,4 +1053,21 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
}
#endregion
#region Helper Methods
/// <summary>
/// Generates a deterministic GUID from a string input using MD5 hash
/// This ensures same input always produces same GUID
/// </summary>
private static Guid GenerateDeterministicGuid(string input)
{
using (var md5 = MD5.Create())
{
var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
return new Guid(hash);
}
}
#endregion
}

View file

@ -26,7 +26,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
.Ignore(x => x.LastExecutedAt)
.Ignore(x => x.ExecutionCount);
CreateMap<SqlStoredProcedure, SqlStoredProcedureDto>();
CreateMap<SqlStoredProcedure, SqlStoredProcedureDto>().Ignore(x => x.IsCustom);
CreateMap<CreateSqlStoredProcedureDto, SqlStoredProcedure>()
.IgnoreFullAuditedObjectProperties()
.Ignore(x => x.Id)
@ -34,6 +34,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
.Ignore(x => x.Status)
.Ignore(x => x.IsDeployed)
.Ignore(x => x.LastDeployedAt);
CreateMap<UpdateSqlStoredProcedureDto, SqlStoredProcedure>()
.IgnoreFullAuditedObjectProperties()
.Ignore(x => x.Id)
@ -45,7 +46,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
.Ignore(x => x.IsDeployed)
.Ignore(x => x.LastDeployedAt);
CreateMap<SqlView, SqlViewDto>();
CreateMap<SqlView, SqlViewDto>().Ignore(x => x.IsCustom);
CreateMap<CreateSqlViewDto, SqlView>()
.IgnoreFullAuditedObjectProperties()
.Ignore(x => x.Id)
@ -64,7 +65,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile
.Ignore(x => x.IsDeployed)
.Ignore(x => x.LastDeployedAt);
CreateMap<SqlFunction, SqlFunctionDto>();
CreateMap<SqlFunction, SqlFunctionDto>().Ignore(x => x.IsCustom);
CreateMap<CreateSqlFunctionDto, SqlFunction>()
.IgnoreFullAuditedObjectProperties()
.Ignore(x => x.Id)

View file

@ -10327,6 +10327,12 @@
"tr": "Şablon Kullan",
"en": "Use Template"
},
{
"resourceName": "Platform",
"key": "App.Platform.NativeObjectViewOnly",
"tr": "Yerel Nesne Sadece Görüntüle",
"en": "Native Object View Only"
},
{
"resourceName": "Platform",
"key": "App.Platform.Error",

View file

@ -34,6 +34,7 @@ export interface SqlFunctionDto extends FullAuditedEntityDto<string> {
isDeployed: boolean
lastDeployedAt?: string
parameters: string
isCustom: boolean // true = stored in database, false = native SQL Server object
}
export interface CreateSqlFunctionDto {
@ -132,6 +133,7 @@ export interface SqlStoredProcedureDto extends FullAuditedEntityDto<string> {
isDeployed: boolean
lastDeployedAt?: string
parameters: string
isCustom: boolean // true = stored in database, false = native SQL Server object
}
export interface CreateSqlStoredProcedureDto {
@ -171,6 +173,7 @@ export interface SqlViewDto extends FullAuditedEntityDto<string> {
isDeployed: boolean
lastDeployedAt?: string
withSchemaBinding: boolean
isCustom: boolean // true = stored in database, false = native SQL Server object
}
export interface CreateSqlViewDto {

View file

@ -185,6 +185,16 @@ export class SqlObjectManagerService {
{ apiName: this.apiName, ...config },
)
getNativeObjectDefinition = (dataSourceCode: string, schemaName: string, objectName: string, config?: Partial<Config>) =>
apiService.fetchData<string, void>(
{
method: 'GET',
url: '/api/app/sql-object-manager/native-object-definition',
params: { dataSourceCode, schemaName, objectName },
},
{ apiName: this.apiName, ...config },
)
// Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy
smartSave = (input: { sqlText: string; dataSourceCode: string; name?: string; description?: string }, config?: Partial<Config>) =>
apiService.fetchData<{ objectType: string; objectId: string; deployed: boolean; message: string }, typeof input>(

View file

@ -113,7 +113,7 @@ const SqlQueryManager = () => {
}, [])
const handleObjectSelect = useCallback(
(object: SqlObject | null, objectType: SqlObjectType | null) => {
async (object: SqlObject | null, objectType: SqlObjectType | null) => {
if (state.isDirty) {
if (!confirm(translate('::App.Platform.UnsavedChangesConfirmation'))) {
return
@ -124,16 +124,76 @@ const SqlQueryManager = () => {
if (object) {
if (objectType === 1) {
// Query
content = (object as SqlQueryDto).queryText
content = (object as SqlQueryDto).queryText || ''
} else if (objectType === 2) {
// Stored Procedure
content = (object as SqlStoredProcedureDto).procedureBody
const sp = object as SqlStoredProcedureDto
if (sp.isCustom) {
content = sp.procedureBody || ''
} else {
// Native SP - fetch definition from SQL Server
try {
const result = await sqlObjectManagerService.getNativeObjectDefinition(
state.selectedDataSource || '',
sp.schemaName,
sp.procedureName
)
content = result.data || ''
} catch (error) {
toast.push(
<Notification type="danger" title={translate('::App.Platform.Error')}>
{translate('::App.Platform.FailedToLoadDefinition')}
</Notification>,
{ placement: 'top-center' },
)
}
}
} else if (objectType === 3) {
// View
content = (object as SqlViewDto).viewDefinition
const view = object as SqlViewDto
if (view.isCustom) {
content = view.viewDefinition || ''
} else {
// Native View - fetch definition from SQL Server
try {
const result = await sqlObjectManagerService.getNativeObjectDefinition(
state.selectedDataSource || '',
view.schemaName,
view.viewName
)
content = result.data || ''
} catch (error) {
toast.push(
<Notification type="danger" title={translate('::App.Platform.Error')}>
{translate('::App.Platform.FailedToLoadDefinition')}
</Notification>,
{ placement: 'top-center' },
)
}
}
} else if (objectType === 4) {
// Function
content = (object as SqlFunctionDto).functionBody
const func = object as SqlFunctionDto
if (func.isCustom) {
content = func.functionBody || ''
} else {
// Native Function - fetch definition from SQL Server
try {
const result = await sqlObjectManagerService.getNativeObjectDefinition(
state.selectedDataSource || '',
func.schemaName,
func.functionName
)
content = result.data || ''
} catch (error) {
toast.push(
<Notification type="danger" title={translate('::App.Platform.Error')}>
{translate('::App.Platform.FailedToLoadDefinition')}
</Notification>,
{ placement: 'top-center' },
)
}
}
}
}
@ -148,7 +208,7 @@ const SqlQueryManager = () => {
isSaved: false,
}))
},
[state.isDirty, translate],
[state.isDirty, state.selectedDataSource, translate],
)
const handleEditorChange = useCallback((value: string | undefined) => {
@ -345,7 +405,7 @@ GO`,
const handleUseTemplateFromDialog = useCallback(
(templateContent: string, templateType: string) => {
// Check if editor has content
const hasUserContent = state.editorContent.trim() && state.isDirty
const hasUserContent = state.editorContent?.trim() && state.isDirty
if (hasUserContent) {
// Ask for confirmation
@ -365,7 +425,7 @@ GO`,
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
const hasUserContent = state.editorContent?.trim() && state.isDirty
if (hasUserContent) {
// Ask for confirmation
@ -405,7 +465,7 @@ GO`,
// Seçili text varsa onu, yoksa tüm editor içeriğini kullan
const selectedText = editorRef.current?.getSelectedText() || ''
const queryToExecute = selectedText.trim() || state.editorContent.trim()
const queryToExecute = selectedText.trim() || state.editorContent?.trim() || ''
if (!queryToExecute) {
toast.push(
@ -471,7 +531,7 @@ GO`,
return
}
if (!state.editorContent.trim()) {
if (!state.editorContent?.trim()) {
toast.push(
<Notification type="warning" title={translate('::App.Platform.Warning')}>
{translate('::App.Platform.PleaseEnterContentToSave')}
@ -771,7 +831,7 @@ GO`,
onClick={handleSave}
disabled={
!state.selectedDataSource ||
!state.editorContent.trim() ||
!state.editorContent?.trim() ||
(state.isSaved && !state.isDirty) ||
!state.executionResult?.success
}

View file

@ -120,9 +120,10 @@ const SqlObjectExplorer = ({
const deployInfo = p.isDeployed && p.lastDeployedAt
? ` (${new Date(p.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
: '';
const customBadge = p.isCustom ? ' 📝' : ' 🗄️';
return {
id: p.id || '',
label: `${p.displayName || p.procedureName}${p.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
id: p.id!,
label: `${p.displayName || p.procedureName}${customBadge}${p.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
type: 'object' as const,
objectType: 2 as SqlObjectType,
data: p,
@ -140,9 +141,10 @@ const SqlObjectExplorer = ({
const deployInfo = v.isDeployed && v.lastDeployedAt
? ` (${new Date(v.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
: '';
const customBadge = v.isCustom ? ' 📝' : ' 🗄️';
return {
id: v.id || '',
label: `${v.displayName || v.viewName}${v.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
id: v.id!,
label: `${v.displayName || v.viewName}${customBadge}${v.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
type: 'object' as const,
objectType: 3 as SqlObjectType,
data: v,
@ -160,9 +162,10 @@ const SqlObjectExplorer = ({
const deployInfo = f.isDeployed && f.lastDeployedAt
? ` (${new Date(f.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})`
: '';
const customBadge = f.isCustom ? ' 📝' : ' 🗄️';
return {
id: f.id || '',
label: `${f.displayName || f.functionName}${f.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
id: f.id!,
label: `${f.displayName || f.functionName}${customBadge}${f.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`,
type: 'object' as const,
objectType: 4 as SqlObjectType,
data: f,
@ -289,7 +292,12 @@ const SqlObjectExplorer = ({
// Column clicked - do nothing or show info
return
} else if (node.type === 'object' && node.data) {
if (node.objectType) {
// Check if it's a table - generate SELECT template
if (node.id.startsWith('table-') && onTemplateSelect) {
const table = node.data as any
const selectQuery = `SELECT TOP 10 * \nFROM ${table.fullName || `[${table.schemaName}].[${table.tableName}]`};`
onTemplateSelect(selectQuery, 'table-select')
} else if (node.objectType) {
onObjectSelect(node.data, node.objectType)
}
}
@ -498,7 +506,7 @@ const SqlObjectExplorer = ({
className="fixed z-50 bg-white dark:bg-gray-800 shadow-lg rounded border border-gray-200 dark:border-gray-700 py-1"
style={{ top: contextMenu.y, left: contextMenu.x }}
>
{contextMenu.node?.type === 'object' && contextMenu.node?.objectType && (
{contextMenu.node?.type === 'object' && contextMenu.node?.objectType && contextMenu.node?.data?.isCustom && (
<>
<button
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
@ -532,6 +540,12 @@ const SqlObjectExplorer = ({
</>
)}
{contextMenu.node?.type === 'object' && contextMenu.node?.objectType && !contextMenu.node?.data?.isCustom && (
<div className="px-4 py-2 text-xs text-gray-500 italic">
{translate('::App.Platform.NativeObjectViewOnly')}
</div>
)}
{contextMenu.node?.type === 'folder' && (
<button
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"